PIC REAL TIME CLOCK - 4 Methods For A Precision Time Clock

General discussion on mikroBasic.
Author
Message
xor
Posts: 5465
Joined: 18 May 2005 00:59
Location: NYC
Contact:

PIC REAL TIME CLOCK - 4 Methods For A Precision Time Clock

#1 Post by xor » 16 May 2007 01:19

There are many requests for software based PIC Real Time Clocks but little is written or understood about how to do it. PIC timers and interrupts are the core of the RTC and can be setup to do the rudimentary timing functions, but achieving high accuracy is what makes the difference. The following programs create a precision time base using 4 different methods. The code is commented to help the programmer understand the functions and precision methods.

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.
TIMER0 - Zero-Sum Free-Running Timer Method

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.
TIMER2 - PR2 Compare Free-Running Timer Method

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.
TIMER1 - CCPR1 Compare Free-Running Timer Method

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.
Last edited by xor on 17 May 2007 04:45, edited 2 times in total.
[color=darkred][b]xor[/b][/color]
[url=http://circuit-ed.com]CircuitED -[/url]

janni
Posts: 5373
Joined: 18 Feb 2006 13:17
Contact:

#2 Post by janni » 16 May 2007 13:11

Nice work :D .

hansolo
Posts: 344
Joined: 28 Oct 2005 07:28

#3 Post by hansolo » 17 May 2007 00:16

Good work xor.

Should be transferred to projects page.

Hansolo
PIC Rules!!

xor
Posts: 5465
Joined: 18 May 2005 00:59
Location: NYC
Contact:

#4 Post by xor » 17 May 2007 18:28

The 4 examples have been zipped and are available on the Project Page:

http://www.mikroe.com/en/projects/
[color=darkred][b]xor[/b][/color]
[url=http://circuit-ed.com]CircuitED -[/url]

TonyC
Posts: 63
Joined: 12 Mar 2007 18:36
Location: Atlanta, GA

#5 Post by TonyC » 18 May 2007 17:56

Hey Warren, nice work! I've been doing some work with the Timers as well, but for a different application.

Is there a formula for making adjustments for different crystal frequencies that are compatible with your RTC methodology?

-Tony
Currently have EasyPIC4 & mikroBASIC

xor
Posts: 5465
Joined: 18 May 2005 00:59
Location: NYC
Contact:

#6 Post by xor » 18 May 2007 21:47

The rule is Fosc/4. Timer clock is the Oscillator divided by 4. All examples do not use any prescaler.
[color=darkred][b]xor[/b][/color]
[url=http://circuit-ed.com]CircuitED -[/url]

janni
Posts: 5373
Joined: 18 Feb 2006 13:17
Contact:

#7 Post by janni » 20 May 2007 21:22

Hi Warren,

I looked at the RTC examples a little closer and noticed two details you probably overlooked while preparing presentable version.

In the first program, the T1_Period variable should be declared at absolute address in the access RAM - otherwise it may take longer than assumed to update the counter in ISR.

In the example with Timer0, initalization of T0Load slipped-by somehow.

hansolo
Posts: 344
Joined: 28 Oct 2005 07:28

#8 Post by hansolo » 21 May 2007 01:47

mE,

Any chance of converting this program into a library?

I think it will be helpful to have a software RTC library.

Just a thought.

Hansolo.
PIC Rules!!

xor
Posts: 5465
Joined: 18 May 2005 00:59
Location: NYC
Contact:

#9 Post by xor » 21 May 2007 03:34

janni wrote:Hi Warren,

I looked at the RTC examples a little closer and noticed two details you probably overlooked while preparing presentable version.

In the first program, the T1_Period variable should be declared at absolute address in the access RAM - otherwise it may take longer than assumed to update the counter in ISR.
Good idea. I hadn't thought about that.
In the example with Timer0, initalization of T0Load slipped-by somehow.
It's there....in a sneaky way. When "Cyc_Counter" is loaded T0Load is the LSByte. Note the absolute address declarations.

Also note that Countdown is a word variable that is the High and Higher bytes of Cyc_Counter. I let the compiler do the calculating and distribution of values for the One_Second constant.
[color=darkred][b]xor[/b][/color]
[url=http://circuit-ed.com]CircuitED -[/url]

janni
Posts: 5373
Joined: 18 Feb 2006 13:17
Contact:

#10 Post by janni » 21 May 2007 12:07

xor wrote:It's there....in a sneaky way.
Yeah, I see it. It's not sneaky, just smart :D . I should have analysed the example, not just 'looked at' it :oops: .

p3t3rv
Posts: 268
Joined: 14 Apr 2005 09:33
Location: Doncaster UK

#11 Post by p3t3rv » 31 May 2007 12:58

cool thread i tryed the first example on a 16F819 runing on internal osc @ 8Mhz first test is was 3 mins 20 secs slow after running 15 hours
how would you advise the best way to tune it in, adjust the osc tuning reg or adjust the interupt timing cycle ?

i stumbled on this yesterday and it got me thinking
http://www.jewellerycatalogue.co.uk/time/index.php

if some one clever with visual basic could make a little application that connectes to a NTP Atomic time server and a pic via RS232 and calculates the time differnce and maybe offers a solution :)
there are 10 types of people Those that understand binary and those that dont

janni
Posts: 5373
Joined: 18 Feb 2006 13:17
Contact:

#12 Post by janni » 31 May 2007 13:34

p3t3rv wrote:cool thread i tryed the first example on a 16F819 runing on internal osc @ 8Mhz first test is was 3 mins 20 secs slow after running 15 hours
how would you advise the best way to tune it in, adjust the osc tuning reg or adjust the interupt timing cycle ?
You may tune it for a given supply voltage and temperature, but it will never be accurate (unless you put it in a thermostat :wink: ).

As for your other question, it's much easier/cheaper to use good stability quartz. Tuning once a month wouldn't hurt, naturally. Note, that Widows XP does synchronize with atomic clock, so you do not need direct access to it.

By the way, the page you've given shows the time one second late according to my, atomic clock tuned, hand watch :P .

xor
Posts: 5465
Joined: 18 May 2005 00:59
Location: NYC
Contact:

#13 Post by xor » 31 May 2007 13:41

Do a PING and it may explain the delay.
[color=darkred][b]xor[/b][/color]
[url=http://circuit-ed.com]CircuitED -[/url]

janni
Posts: 5373
Joined: 18 Feb 2006 13:17
Contact:

#14 Post by janni » 31 May 2007 13:51

Yes, it sure does and that was the point (apart from showing-off with my watch :D ).

xor
Posts: 5465
Joined: 18 May 2005 00:59
Location: NYC
Contact:

#15 Post by xor » 31 May 2007 13:56

I presume the propagation delay of a wireless RF signal to your watch is much less ... :D
[color=darkred][b]xor[/b][/color]
[url=http://circuit-ed.com]CircuitED -[/url]

Post Reply

Return to “mikroBasic General”