As mentioned, interrupts are necessary for an accurate RTC program. Interrupts make it possible to create an accurate time base with virtually unnoticable effects on the program as a whole. The issue with interrupts is the code overhead of saving important registers before getting to the programmer's interrupt code, such as reloading the timer and preparing for the next interrupt cycle. Timers rollover to zero which creates the interrupt but then continue running for several cycles before the programmer gets a chance to reload it. Often it is difficult to predict the length of time that the timer has continued to run before reloading it with a fixed time value. That value must be adjusted somehow. The examples below are intended to show how to compensate for this "lost" time with some clever software techniqes and methods.
About the methods. The first 2 programs utilize the Zero-Sum, or Zero Cumulative Error, method for maintaining accurate time. It's essentially a method of reloading a timer and automatically compensating for ISR code overhead by adding to the existing timer value. Remember that timers increment, count up, to rollover, or 0. If you add to their value you are actually shortening their trip to 0. So we can compensate for cycles unaccounted for by adding to the timer's value. It's too much to explain further here but is well explained by ROMAN BLACK on his webpage:
http://www.romanblack.com/one_sec.htm
The last 2 are period based free running timer methods using PR2 with Timer2 and Special Event Feature of Timer1 and the CCP1 module. Both methods automatically reset the timer when the values in PR2 and CCPR1 registers match their respective timer.
Each program was tested on a 18F452 @ 8MHz. The programs can also be used with PIC16's with a few changes. All source-code will compile with the free demo version of the compiler. I have added code to each program method to demonstrate a 24 Hour Time Clock output on a 2 line LCD.
Thanks to "janni" for his insight into timer applications. The first program is one of his.
TIMER1 - Zero-Sum with Timer Stop Method
Code: Select all
program RTC_Timer1_18F452
' 24 Hour Real Time Clock - Zero-Sum Timer Stop Method
' mikroBasic 5.02 ... 18F452 @ 8MHz Default Configuration
' Uses Timer1; No Prescaler; 2 counts per microsecond
' Add 6 cycles for Timer1 stop, reload, and restart in ISR
dim T1 as word absolute $FCE ' Timer1 Low and High
dim T1_Period as word ' Timer1 reload value + fudge factor
dim int_count as byte ' interrupt counter
dim update as boolean ' update output
dim secs, mins, hrs as byte ' tally variables
dim clk as string[9] absolute $20 ' output string for LCD
dim hstr as string[3] absolute $20
dim mstr as string[3] absolute $23
dim sstr as string[3] absolute $26
sub procedure interrupt
T1CON.TMR1ON = 0 ' stop Timer1
T1 = T1 + T1_Period ' reload Timer1 + zero-sum error
T1CON.TMR1ON = 1 ' start Timer1
If inc(int_count) = 40 Then ' 40 counts x 25000us = 1sec
int_count = 0 ' restart interrupt counter
update = true ' advance seconds & update output
End If
PIR1.TMR1IF = 0 ' clear interrupt flag
end sub
sub procedure Clock24
If inc(secs) = 60 Then ' check each for rollover
secs = 0
If inc(mins) = 60 Then
mins = 0
If inc(hrs) = 24 Then hrs = 0 End If
End If
End If
bytetostr(hrs, hstr) ' create clock output string for LCD
If hstr[1] = 32 Then hstr[1] = 48 End If
bytetostr(mins, mstr)
mstr[0] = ":"
If mstr[1] = 32 Then mstr[1] = 48 End If
bytetostr(secs, sstr)
sstr[0] = ":"
If sstr[1] = 32 Then sstr[1] = 48 End If
LCD_CMD(130)
LCD_OUT_CP("24-HOUR CLOCK")
LCD_CMD(195)
LCD_OUT_CP(clk)
update = false
end sub
sub procedure Initialize
ADCON1 = 15
int_count = 0
secs = 255
mins = 0
hrs = 0
update = true
LCD_INIT(PORTB)
LCD_CMD(LCD_CURSOR_OFF)
INTCON.GIE = 1 ' enable GIE
INTCON.PEIE = 1 ' enable PEIE
T1_Period = -50000 + 6 ' 25000us interrupt + 6 cycles Timer1 reload
T1CON = 0 ' no prescaler and Timer1 is Off
T1 = T1_Period ' load Timer1
PIE1.TMR1IE = 1 ' interrupt enabled
PIR1.TMR1IF = 0 ' interrupt flag cleared
T1CON.TMR1ON = 1 ' start Timer1
end sub
main:
Initialize
While true ' loop forever
If update <> false Then ' advance seconds?
Clock24 ' yes...then output
End If
Wend
end.
Code: Select all
program RTC_Timer0_FreeRun_18F452
' 24 Hour Real Time Clock - Zero-Sum Timer FreeRun Method
' mikroBasic 5.02 ... 18F452 @ 8MHz Default Configuration
' by Warren Schroeder on May 15, 2007
' Timer0 in 8-bit mode; No Prescaler; 2 counts per microsecond
'
' 1 second = 2000000 clock cycles - 2 for clock restart
const OneSecond as longint = 2000000 - 2
dim Cyc_Counter as longint 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 ' update output
dim secs, mins, hrs as byte ' tally variables
dim clk as string[9] absolute $20 ' output string for LCD
dim hstr as string[3] absolute $20
dim mstr as string[3] absolute $23
dim sstr as string[3] absolute $26
sub procedure interrupt
If dec(Countdown) = 0 Then
Cyc_Counter = OneSecond ' reload timer
TMR0L = TMR0L + T0Load ' include additional TMR0 counts
If Status.C = 0 Then
inc(Countdown) ' if no carry increment counter once
End If
update = true ' flag to update clock output
End If
INTCON.TMR0IF = 0 ' clear interrupt flag
end sub
sub procedure Clock24
If inc(secs) = 60 Then ' check each tally for rollover
secs = 0
If inc(mins) = 60 Then
mins = 0
If inc(hrs) = 24 Then hrs = 0 End If
End If
End If
bytetostr(hrs, hstr) ' create clock output string for LCD
If hstr[1] = 32 Then hstr[1] = 48 End If
bytetostr(mins, mstr)
mstr[0] = ":"
If mstr[1] = 32 Then mstr[1] = 48 End If
bytetostr(secs, sstr)
sstr[0] = ":"
If sstr[1] = 32 Then sstr[1] = 48 End If
LCD_CMD(130)
LCD_OUT_CP("24-HOUR CLOCK")
LCD_CMD(195)
LCD_OUT_CP(clk)
update = false
end sub
sub procedure Initialize
ADCON1 = 15
secs = 255
mins = 0
hrs = 0
update = true
Cyc_Counter = OneSecond
inc(CountDown)
LCD_INIT(PORTB)
LCD_CMD(LCD_CURSOR_OFF)
INTCON.GIE = 1 ' enable GIE
INTCON.PEIE = 1 ' enable PEIE
T0CON = 72 ' 8-bit; no prescaler; Timer0 off
TMR0L = 0 ' clear timer1
INTCON.TMR0IE = 1 ' interrupt enabled
INTCON.TMR0IF = 0 ' interrupt flag cleared
T0CON.TMR0ON = 1 ' start Timer0
end sub
main:
Initialize
While true ' loop forever
If update = true Then ' advance seconds?
Clock24 ' yes...then output
End If
Wend
end.
Code: Select all
program RTC_PR2_FreeRun_18F452
' 24 Hour Real Time Clock - Timer2 with PR2 Reset FreeRun Method
' mikroBasic 5.02 ... 18F452 @ 8MHz Default Configuration
' by Warren Schroeder on May 15, 2007
' Timer2; Prescaler=0; 2 clock every 1us; PR2=249
' Match and reset every 0.5us x 249 = 124.5us + 1 cycle for TMR2 restart
dim countdown as word ' interrupt counter
dim update as boolean ' update output
dim secs, mins, hrs as byte ' tally variables
dim clk as string[9] absolute $20 ' output string for LCD
dim hstr as string[3] absolute $20
dim mstr as string[3] absolute $23
dim sstr as string[3] absolute $26
sub procedure interrupt
If dec(countdown) = 0 Then
countdown = 8000 ' 8000 x 125us = 1 second
update = true ' flag to update clock output
End If
PIR1.TMR2IF = 0 ' clear interrupt flag
end sub
sub procedure Clock24
If inc(secs) = 60 Then ' check each tally for rollover
secs = 0
If inc(mins) = 60 Then
mins = 0
If inc(hrs) = 24 Then hrs = 0 End If
End If
End If
bytetostr(hrs, hstr) ' create clock output string for LCD
If hstr[1] = 32 Then hstr[1] = 48 End If
bytetostr(mins, mstr)
mstr[0] = ":"
If mstr[1] = 32 Then mstr[1] = 48 End If
bytetostr(secs, sstr)
sstr[0] = ":"
If sstr[1] = 32 Then sstr[1] = 48 End If
LCD_CMD(130)
LCD_OUT_CP("24-HOUR CLOCK")
LCD_CMD(195)
LCD_OUT_CP(clk)
update = false
end sub
sub procedure Initialize
ADCON1 = 15
secs = 255
mins = 0
hrs = 0
update = true
countdown = 8000
LCD_INIT(PORTB)
LCD_CMD(LCD_CURSOR_OFF)
INTCON.GIE = 1 ' enable GIE
INTCON.PEIE = 1 ' enable PEIE
T2CON = 0 ' prescaler=0; postscaler=0; Timer2=off
TMR2 = 0 ' clear timer2
PR2 = 249 ' TMR2 resets when matching PR2
PIE1.TMR2IE = 1 ' interrupt enabled
PIR1.TMR0IF = 0 ' interrupt flag cleared
T2CON.TMR2ON = 1 ' start Timer0
end sub
main:
Initialize
While true ' loop forever
If update = true Then ' advance seconds?
Clock24 ' yes...then output
End If
Wend
end.
Code: Select all
program RTC_CCP1_FreeRun_18F452
' 24 Hour Real Time Clock - Timer1 with CCP1 Reset FreeRun Method
' mikroBasic 5.02 ... 18F452 @ 8MHz Default Configuration
' by Warren Schroeder on May 15, 2007
' Timer1; Prescaler=0; 2 clock every 1us; Timer1 interrupt Disabled
' CCPR1L:CCPR1H compare value for TMR1; on match sets CCP1IF & resets TMR1
' Match and reset every 0.5us x (50000 - 1) = 24499.5us + 1 cycle for TMR1 restart
dim C1 as word absolute $FBE
dim countdown as byte ' interrupt counter
dim update as boolean ' update output
dim secs, mins, hrs as byte ' tally variables
dim clk as string[9] absolute $20 ' output string for LCD
dim hstr as string[3] absolute $20
dim mstr as string[3] absolute $23
dim sstr as string[3] absolute $26
sub procedure interrupt
If dec(countdown) = 0 Then
countdown = 40 ' 40 x 25000us = 1 second
update = true ' flag to update clock output
End If
PIR1.CCP1IF = 0 ' clear interrupt flag
end sub
sub procedure Clock24
If inc(secs) = 60 Then ' check each tally for rollover
secs = 0
If inc(mins) = 60 Then
mins = 0
If inc(hrs) = 24 Then hrs = 0 End If
End If
End If
bytetostr(hrs, hstr) ' create clock output string for LCD
If hstr[1] = 32 Then hstr[1] = 48 End If
bytetostr(mins, mstr)
mstr[0] = ":"
If mstr[1] = 32 Then mstr[1] = 48 End If
bytetostr(secs, sstr)
sstr[0] = ":"
If sstr[1] = 32 Then sstr[1] = 48 End If
LCD_CMD(130)
LCD_OUT_CP("24-HOUR CLOCK")
LCD_CMD(195)
LCD_OUT_CP(clk)
update = false
end sub
sub procedure Initialize
ADCON1 = 15
secs = 255
mins = 0
hrs = 0
update = true
countdown = 8000
LCD_INIT(PORTB)
LCD_CMD(LCD_CURSOR_OFF)
INTCON.GIE = 1 ' enable GIE
INTCON.PEIE = 1 ' enable PEIE
T1CON = 0 ' prescaler=0; timer1=off
TMR1L = 0 ' clear timer1
TMR1H = 0 '
CCP1CON = 11 ' special event trigger enabled
C1 = 49999 ' load match value = 24499.5us
PIE1.CCP1IE = 1 ' enable CCP1 interrupt
PIR1.CCP1IF = 0 ' clear CCP1 interrupt flag
T1CON.TMR1ON = 1 ' start Timer1
end sub
main:
Initialize
While true ' loop forever
If update = true Then ' advance seconds?
Clock24 ' yes...then output
End If
Wend
end.