In some of the forums there are several requests for creating a real time clock using a PIC. With some ensuing discussion and looking around I found a great webpage by Roman Black (http://www.romanblack.com/one_sec.htm) which discussed a theory and method to actually achieve a precision time clock using a PIC timer. There he implements a method of Zero Sum or Zero Cumulative Error to achieve a zero error 1 second time base. It's a great method and for the explanation I will defer to him on that webpage. I further discovered that more techniques can be used to also achieve a very accurate time base. Below are 4 sample programs that demonstrate using each of the methods.
The first 2 coding techniques are implementations of the Zero-Sum method. One uses Timer1 and the other Timer0 with no prescalers. The first stops the timer, adds the load value to the timer's present value, and then restarts the timer. The Stop-Load-Start takes exactly 6 cycles and is thus added to the load value to compensate for the timer's stoppage. The second uses Timer0 and is free-running and never stops.
The last 2 techniques do not use zero-sum but utilize the PR2 match/reset feature of Timer2, and the Special Event Trigger/Interrupt of the CCP1 module when Timer1 matches CCPR1. In both cases, the respective timer is reset and continues to run, and so creates a regular and precise time base.
Each of the following examples outputs a 24 Hour Clock on a character LCD. Enjoy.
TIMER1 - Zero-Sum With Timer Stop
Code: Select all
{
****************************************************************
* Name : Soft_RTC_TimerStop.BAS *
* Author : Warren Schroeder alias "xor" *
* Notice : Copyright (c) 2007 Warren Schroeder *
* : All Rights Reserved *
* Date : 5/23/2007 *
* Version : 1.0 *
* Notes : Real Time Clock - Timer Stop Method *
* Precision 24H Time Clock Example on LCD *
* 18F452 @ 8MHz *
* Uses Timer1 and No Prescaler *
* : *
****************************************************************
}
Device = 18F452
Clock = 8
#option LCD_RS = PORTB.2
#option LCD_EN = PORTB.3
#option LCD_DATA = PORTB.4
Include "lcd.bas"
Include "convert.bas"
{
For One Second Update:
8MHz Fosc = 2MHz internal clock = 0.5us per cycle (timer count)
Use 16-bit Timer1; No Prescaler
50000 counts = 50000 x 0.5us = 25000us
40 x 25000us = 1000000us = 1 second
}
Const T1Period As Word = 65536-50000+6 ' 25000us interrupt cycle + 6 cycles for TIMER1 stoppage
Const Int_Total As Byte = 40 ' 40 x 25000us = 1 second
Dim T1 As Word Absolute $0FCE ' TMR1L + TMR1H
Dim T1IE As PIE1.0
Dim T1IF As PIR1.0
Dim T1ON As T1CON.0
Dim update As Boolean
Dim int_counter As Byte
Dim secs,mins,hrs As Byte
interrupt RTC()
T1ON = 0 ' stop timer
T1 = T1 + T1Period ' reload timer + include cycles after jump to ISR
T1ON = 1 ' restart timer
Dec(int_counter)
If int_counter = 0 Then
int_counter = Int_Total ' count 40 interrupts @ 25000us = 1 second
update = true ' update LCD clock
End If
T1IF = 0 ' clear int flag
End interrupt
Sub Clock24()
Dim clk As String
Inc(secs)
If secs = 60 Then ' check each tally for rollover
secs = 0
Inc(mins)
If mins = 60 Then
mins = 0
Inc(hrs)
If hrs = 24 Then
hrs = 0
End If
End If
End If
clk = DecToStr(hrs,2) ' output to LCD
LCD.WriteAt(2,5,clk)
clk = DecToStr(mins,2)
LCD.WriteAt(2,8,clk)
clk = DecToStr(secs,2)
LCD.WriteAt(2,11,clk)
update = false
End Sub
Sub Initialize()
ADCON1 = 15
secs = 0
mins = 0
hrs = 0
int_counter = Int_Total
update = false
LCD.Command(130)
LCD.Write("24-HOUR CLOCK")
LCD.Command(196)
LCD.Write("00:00:00")
INTCON = 192 ' enable GIE & PEIE
T1CON = 0 ' no prescaler and Timer1 is Off
T1 = 65536-50000 ' load Timer1
T1IE = 1 ' enable Timer1 Interrupt
T1IF = 0 ' clear Timer1 Interrupt Flag
T1ON = 1 ' start Timer1
Enable(RTC) ' enable jump to RTC ISR
End Sub
Initialize
While 1=1
If update = true Then
Clock24 ' update 24H Clock output
End If
Wend
Code: Select all
{
****************************************************************
* Name : Soft_RTC_FreeRunTimer.BAS *
* Author : Warren Schroeder alias "xor" *
* Notice : Copyright (c) 2007 Warren Schroeder *
* : All Rights Reserved *
* Date : 5/23/2007 *
* Version : 1.0 *
* Notes : Real Time Clock - Free Running Timer *
* Precision 24H Time Clock Example on LCD *
* 18F452 @ 8MHz *
* Timer0 and No Prescaler *
* Program implements Zero-Sum/Zero Cumulative Error Method *
* by Roman Black .. http://www.romanblack.com/one_sec.htm *:
****************************************************************
}
Device = 18F452
Clock = 8
#option LCD_RS = PORTB.2
#option LCD_EN = PORTB.3
#option LCD_DATA = PORTB.4
Include "lcd.bas"
Include "convert.bas"
{
For One Second Update:
8MHz Fosc = 2MHz internal clock = 0.5us per cycle (timer count)
Use 8-bit Timer0; No Prescaler
2000000 counts = 1 Second
Timer0 reload requires 2 cycle compensation = 2000000-2
}
Const OneSecond As LongWord = 2000000-2 ' 1 Second total clock cycles - 2 for timer relaod
Dim Cyc_Counter As LongWord Absolute $30 ' Timer0 cycle counter
Dim T0Load As Byte Absolute $30 ' Timer0 reload value
Dim Countdown As Word Absolute $31 ' # of 8-bit cycles
Dim update As Boolean
Dim secs,mins,hrs As Byte
interrupt RTC()
Dec(Countdown)
If Countdown = 0 Then
Cyc_Counter = OneSecond ' reload timer
TMR0L = TMR0L + T0Load ' include additional TMR0 counts
If STATUS.0 = 0 Then
Inc(Countdown) ' if no carry increment "countdown" counter once
End If
update = true ' flag to update clock output
End If
INTCON.2 = 0 ' clear interrupt flag
End interrupt
Sub Clock24()
Dim clk As String
Inc(secs)
If secs = 60 Then ' check each tally for rollover
secs = 0
Inc(mins)
If mins = 60 Then
mins = 0
Inc(hrs)
If hrs = 24 Then
hrs = 0
End If
End If
End If
clk = DecToStr(hrs,2) ' output to LCD
LCD.WriteAt(2,5,clk)
clk = DecToStr(mins,2)
LCD.WriteAt(2,8,clk)
clk = DecToStr(secs,2)
LCD.WriteAt(2,11,clk)
update = false
End Sub
Sub Initialize()
ADCON1 = 15
secs = 0
mins = 0
hrs = 0
Cyc_Counter = OneSecond
update = false
LCD.Command(130)
LCD.Write("24-HOUR CLOCK")
LCD.Command(196)
LCD.Write("00:00:00")
INTCON = 192 ' enable GIE & PEIE
T0CON = 72 ' 8-bit; no prescaler; Timer0 off
TMR0L = 0 ' clear timer0
INTCON.5 = 1 ' interrupt enabled
INTCON.2 = 0 ' interrupt flag cleared
T0CON.7 = 1 ' start Timer0
Enable(RTC) ' enable jump to RTC ISR
End Sub
Initialize
While 1=1
If update = true Then
Clock24 ' update 24H Clock output
End If
Wend
Code: Select all
{
****************************************************************
* Name : Soft_RTC_PR2_FreeRunTimer.BAS *
* Author : Warren Schroeder alias "xor" *
* Notice : Copyright (c) 2007 Warren Schroeder *
* : All Rights Reserved *
* Date : 5/23/2007 *
* Version : 1.0 *
* Notes : Real Time Clock - PR2 Free Running Timer *
* Precision 24H Time Clock Example on LCD *
* 18F452 @ 8MHz *
* Timer2, PR2, and No Prescaler *
* Implements PR2 match reset of Free Running Timer2 *
* *
****************************************************************
}
Device = 18F452
Clock = 8
#option LCD_RS = PORTB.2
#option LCD_EN = PORTB.3
#option LCD_DATA = PORTB.4
Include "lcd.bas"
Include "convert.bas"
{
For One Second Update:
8MHz Fosc = 2MHz internal clock = 0.5us per cycle (timer count)
Use 8-bit Timer2, No Prescaler
Set PR2 = 250; Timer2 resets on match every 250 counts = 125us
Each Timer2 reset requires 1 cycle compensation... so set PR2 = 249
8000 interrupts x 125us each = 1 second
}
Dim Int_Counter As Word
Dim update As Boolean
Dim secs,mins,hrs As Byte
interrupt RTC()
Dec(Int_Counter)
If Int_Counter = 0 Then
Int_Counter = 8000 ' each interrupt = 125us x 8000 int's = 1 second
update = true ' update LCD output
End If
PIR1.1 = 0 ' clear interrupt flag
End interrupt
Sub Clock24()
Dim clk As String
Inc(secs)
If secs = 60 Then ' check each tally for rollover
secs = 0
Inc(mins)
If mins = 60 Then
mins = 0
Inc(hrs)
If hrs = 24 Then
hrs = 0
End If
End If
End If
clk = DecToStr(hrs,2) ' output to LCD
LCD.WriteAt(2,5,clk)
clk = DecToStr(mins,2)
LCD.WriteAt(2,8,clk)
clk = DecToStr(secs,2)
LCD.WriteAt(2,11,clk)
update = false
End Sub
Sub Initialize()
ADCON1 = 15
secs = 0
mins = 0
hrs = 0
Int_Counter = 8000
update = false
LCD.Command(130)
LCD.Write("24-HOUR CLOCK")
LCD.Command(196)
LCD.Write("00:00:00")
INTCON = 192 ' enable GIE & PEIE
T2CON = 0 ' no prescaler or postscaler: timer2 OFF
TMR2 = 0 ' clear TMR2
PR2 = 249 ' set match value
PIE1.1 = 1 ' enable interrupt
PIR1.1 = 0 ' clear interrupt flag
T2CON.2 = 1 ' Timer2 ON
Enable(RTC) ' enable jump to RTC ISR
End Sub
Initialize
While 1=1
If update = true Then
Clock24 ' update 24H Clock output
End If
Wend
Code: Select all
{
****************************************************************
* Name : Soft_RTC_CCP1_FreeRunTimer.BAS *
* Author : Warren Schroeder alias "xor" *
* Notice : Copyright (c) 2007 Warren Schroeder *
* : All Rights Reserved *
* Date : 5/23/2007 *
* Version : 1.0 *
* Notes : Real Time Clock - PR2 Free Running Timer *
* Precision 24H Time Clock Example on LCD *
* 18F452 @ 8MHz *
* Timer1, CCPR1, and No Prescaler *
* Implements Special Event Trigger when Timer1 matches CCPR1 *
* *
****************************************************************
}
Device = 18F452
Clock = 8
#option LCD_RS = PORTB.2
#option LCD_EN = PORTB.3
#option LCD_DATA = PORTB.4
Include "lcd.bas"
Include "convert.bas"
{
For One Second Update:
8MHz Fosc = 2MHz internal clock = 0.5us per cycle (timer count)
Use 16-bit Timer1, No Prescaler
Set CCPR1 = 50000; Timer1 resets on match every 50000 counts = 25000us
Each Timer1 reset requires 1 cycle compensation... so set CCPR1 = 49999
40 interrupts x 25000us each = 1 second
}
Dim C1 As Word Absolute $0FBE ' CCPR1L + CCPR1H
Dim Int_Counter As Byte
Dim update As Boolean
Dim secs,mins,hrs As Byte
interrupt RTC()
Dec(Int_Counter)
If Int_Counter = 0 Then
Int_Counter = 40 ' each interrupt = 25000us x 40 int's = 1 second
update = true ' update LCD output
End If
PIR1.2 = 0 ' clear CCP1 interrupt flag
End interrupt
Sub Clock24()
Dim clk As String
Inc(secs)
If secs = 60 Then ' check each tally for rollover
secs = 0
Inc(mins)
If mins = 60 Then
mins = 0
Inc(hrs)
If hrs = 24 Then
hrs = 0
End If
End If
End If
clk = DecToStr(hrs,2) ' output to LCD
LCD.WriteAt(2,5,clk)
clk = DecToStr(mins,2)
LCD.WriteAt(2,8,clk)
clk = DecToStr(secs,2)
LCD.WriteAt(2,11,clk)
update = false
End Sub
Sub Initialize()
ADCON1 = 15
secs = 0
mins = 0
hrs = 0
Int_Counter = 40
update = false
LCD.Command(130)
LCD.Write("24-HOUR CLOCK")
LCD.Command(196)
LCD.Write("00:00:00")
INTCON = 192 ' enable GIE & PEIE
T1CON = 0 ' no prescaler timer OFF
TMR1H = 0 ' clear TMR1
TMR1L = 0
CCP1CON = 11 ' enable special trigger event
C1 = 49999 ' set match value
PIE1.2 = 1 ' enable CCP1 interrupt
PIR1.2 = 0 ' clear CCP1 interrupt flag
T1CON.0 = 1 ' Timer1 ON
Enable(RTC) ' enable jump to RTC ISR
End Sub
Initialize
While 1=1
If update = true Then
Clock24 ' update 24H Clock output
End If
Wend