View previous topic :: View next topic |
Author |
Message |
ckielstra
Joined: 18 Mar 2004 Posts: 3680 Location: The Netherlands
|
Timer based Real Time Clock (RTC) |
Posted: Tue Feb 28, 2006 6:18 pm |
|
|
Many times in the forums I have seen people asking for a real time clock based on the internal timers of the PIC. Several implementations of the version supplied by Neutone can be found in the discussion forum but not here in the code forum so I decided to add a new topic.
I like this algorithm because it is very accurate (zero drift) even when your clock frequency is not a multiple of 256Hz. Most algorithms configure the timer to generate an interrupt every 100ms, and then count the number of times the interrupt occurs. The problem in that approach is that with nice round clock frequencies like 16MHz you can't get an exact 100ms and the small error will count up to an error of several seconds a day. The algorithm presented here doesn't count the number of interrupts but counts the number of clock cycles which makes it very accurate in the long run.
The current implementation uses Timer1 but this can easily be changed to another internal timer.
Code: | ///////////////////////////////////////////////////////////
// Zero Drift Real Time Clock
// Original code supplied by Neutone.
// Some small optimizations by C.Kielstra.
///////////////////////////////////////////////////////////
#include <18F458.h>
#use delay(clock=20000000)
#fuses HS,NOWDT,NOLVP
//RTC variables
#define XTAL_FREQUENCY 20000000
#define TIMER1_FREQUENCY (XTAL_FREQUENCY / 4) // 1 clock tick = 1 instr. cycle = crystal frequency / 4
int32 Ticker;
int8 Seconds=0;
//optional:
// int8 Year=11, Month=1, Days=1, Hours=0, Minutes=0; // 2011-01-01 00:00
////////////////////////////////////////////////////////////////////////////////
// Test whether a given year is a leap year.
// This optimized version only works for the period 1901 - 2099
////////////////////////////////////////////////////////////////////////////////
#define IS_LEAP(year) (year%4 == 0)
////////////////////////////////////////////////////////////////////////////////
// Initialize RTC
////////////////////////////////////////////////////////////////////////////////
void Initialize_RTC(void)
{
Ticker = TIMER1_FREQUENCY; // initialize clock counter to number of clocks per second
setup_timer_1( T1_INTERNAL | T1_DIV_BY_1 ); // initialize 16-bit Timer1 to interrupt
// exactly every 65536 clock cycles
// (about 76 times per second)
enable_interrupts( INT_TIMER1 ); // Start RTC
}
////////////////////////////////////////////////////////////////////////////////
// -=Process Zero Drift Real Time Clock Information=-
//
// Most algorithms configure the timer to generate an interrupt every 100ms, and
// then count the number of interrupts. The problem in that approach is that most
// clock frequencies can't be divided by 256 and you don't get an exact 100ms.
// The small errors will add up to an error of several seconds a day.
//
// The algorithm presented here is exact in the long run because it doesn't
// count the number of interrupts but counts the number of clock cycles.
////////////////////////////////////////////////////////////////////////////////
#int_TIMER1
void TIMER1_isr()
{
Ticker -= 65536; // Decrement ticker by clocks per interrupt
if ( Ticker < 65536 ) // If second has expired
{ Ticker += TIMER1_FREQUENCY; // Increment ticker by clocks per second
seconds++; // Increment number of seconds
}
/* --- Optional part start ---
if (Seconds == 60) {Minutes++; Seconds=0;
if (Minutes == 60) {Hours++; Minutes=0;
if (Hours == 24) {Days++; Hours=0;
if ( (Days == 29 && Month==2 && !IS_LEAP(Year)) // February in leap year
|| (Days == 30 && Month==2) // February in normal years
|| (Days == 31 && (Month==4 || Month==6 || Month==9 || Month==11 )) // All months with 30 days
|| (Days == 32) // All months with 31 days
) {Month++; Days=1;}
if (Month == 13) {Year++; Month=1;}
}}}
--- Optional part end --- */
}
////////////////////////////////////////////////////////////////////////////////
// Example program for using the RTC
////////////////////////////////////////////////////////////////////////////////
void main()
{
int8 prev_second;
Initialize_RTC();
enable_interrupts( GLOBAL );
// loop forever
while(1)
{
// Toggle output every second
if (seconds != prev_second)
{
prev_second = seconds;
output_toggle(PIN_A1);
}
}
}
|
Edit 23 Mar 2006: Fixed the factor 4 error as indicated by Dorinm. Thanks.
Edit 13 Sep 2006: Fixed the leap year calculation (original version of IS_LEAP macro wasn't working at all).
Edit 18 Nov 2011: Fixed bug with day and month starting at zero instead of 1. Thanks ünloco.
Last edited by ckielstra on Thu Nov 17, 2011 6:58 pm; edited 7 times in total |
|
|
dorinm
Joined: 07 Jan 2006 Posts: 38
|
|
Posted: Sun Mar 05, 2006 10:41 am |
|
|
just tried your code on a 20mhz 458... a second seems to take ~4 seconds ...am I doing something wrong or is there a bug in your code!?
edit: just found out what's wrong; according to microchip documentation (18f458), "When TMR1CS is clear, Timer1 increments every
instruction cycle." , so that in your code timer1 actually counts the XTALFREQ/4 (a instruction cycle) and not crystal cycles ....
...for personal use i set XTAL_FREQUENCY @ realfreq/4 (5.000.000) and the code work well. |
|
|
Christophe
Joined: 10 May 2005 Posts: 323 Location: Belgium
|
|
Posted: Tue Jun 20, 2006 7:32 am |
|
|
Thanks for the implementation, works perfectly on my PIC16. |
|
|
jmann
Joined: 27 Dec 2004 Posts: 21
|
|
Posted: Sat Jun 24, 2006 10:01 pm |
|
|
That's cool!
The first assignment in my embedded system design class was just like this. |
|
|
akokyaw
Joined: 11 Feb 2005 Posts: 24
|
|
Posted: Mon Aug 06, 2007 7:54 am |
|
|
smart calculation that can be compensate decimal part too.
Thank for your code,
pak |
|
|
cybergm
Joined: 08 Aug 2007 Posts: 1
|
internal timer using WDT |
Posted: Wed Aug 08, 2007 11:42 am |
|
|
hi,
this timer works great, but can anyone tell me if I can use a WDT to do the same operation cos I want my PIC to be in sleep mode |
|
|
Neutone
Joined: 08 Sep 2003 Posts: 839 Location: Houston
|
Re: internal timer using WDT |
Posted: Wed Aug 08, 2007 3:15 pm |
|
|
cybergm wrote: | hi,
this timer works great, but can anyone tell me if I can use a WDT to do the same operation cos I want my PIC to be in sleep mode |
No because the processors must be running in order to keep time.
I have been thinking about using a watch crystal on timer 1 to keep time and instead of going into sleep mode simply switching oscilators. This will drop power by about 2 orders of magnitude if your using a fast crystal. This would get power down to the levels that a coin cell can provide while allowing for higher proformance when more power is available. |
|
|
jov_damo86
Joined: 29 Nov 2007 Posts: 9 Location: Sarrat, Ilocos Norte,Philippines
|
re:Timer based Real Time Clock (RTC) |
Posted: Fri Dec 14, 2007 11:29 pm |
|
|
can i used that code in PIC16f877A with an external crystal of 20Mhz?
what is that 65536 in the code?
void TIMER1_isr()
{
Ticker -= 65536; // Decrement ticker by clocks per interrupt
if ( Ticker < 65536 ) // If second has expired
{ Ticker += TIMER1_FREQUENCY; // Increment ticker by clocks per second
seconds++; // Increment number of seconds
}
please explain further.. thank you! |
|
|
Neutone
Joined: 08 Sep 2003 Posts: 839 Location: Houston
|
Re: re:Timer based Real Time Clock (RTC) |
Posted: Sun Dec 16, 2007 5:43 pm |
|
|
jov_damo86 wrote: | can i used that code in PIC16f877A with an external crystal of 20Mhz?
what is that 65536 in the code?
void TIMER1_isr()
{
Ticker -= 65536; // Decrement ticker by clocks per interrupt
if ( Ticker < 65536 ) // If second has expired
{ Ticker += TIMER1_FREQUENCY; // Increment ticker by clocks per second
seconds++; // Increment number of seconds
}
please explain further.. thank you! |
In the first spot it is the number of times the timer will increment per interrupt. In the second spot is checks to see if "ticker" is greater than the number of timer increments per interrupt.
These values assume that the timer increments per instruction cycles is 1:1. Actual ratio could be set to other values and the number used (65536) would have to be changed to the appropriate value.
Timer is 16 bit
2^16=65536 |
|
|
ThomasC
Joined: 09 Oct 2007 Posts: 62
|
|
Posted: Tue Dec 18, 2007 11:17 am |
|
|
Can this function run in the background while another function is being run? Thanks!
The program runs well, but it seemed to be blinking 4 times a second so I used:
#define XTAL_FREQUENCY (2000000)
and now it seems to blink once every second. How can I be sure that it is blinking exactly once a second? I'm comparing it to the windows clock second hand, and it's probably me but it seems off. |
|
|
Neutone
Joined: 08 Sep 2003 Posts: 839 Location: Houston
|
|
Posted: Tue Dec 18, 2007 12:34 pm |
|
|
ThomasC wrote: | Can this function run in the background while another function is being run? Thanks!
The program runs well, but it seemed to be blinking 4 times a second so I used:
#define XTAL_FREQUENCY (2000000)
and now it seems to blink once every second. How can I be sure that it is blinking exactly once a second? I'm comparing it to the windows clock second hand, and it's probably me but it seems off. |
If you can't do the math just let it run for 24 hours and see how much error you have. You would have to add a printF statement to show time. |
|
|
ThomasC
Joined: 09 Oct 2007 Posts: 62
|
|
Posted: Tue Dec 18, 2007 1:48 pm |
|
|
I was wrong. Its rights on the money. |
|
|
ThomasC
Joined: 09 Oct 2007 Posts: 62
|
|
Posted: Fri Dec 21, 2007 3:54 pm |
|
|
Is there a way to simultaneously run this clock program so it records the ON time of the pic while you are doing other tasks? Or does it only run in succession? Thanks. |
|
|
cnsnt
Joined: 29 Nov 2007 Posts: 6
|
|
Posted: Sun Dec 23, 2007 2:33 pm |
|
|
hi masters..
can I use these codes for pic16f628 with internal rc osc(4Mhz).I have to do it because I have a very small area.I can not use rtc hardware(ds1307 etc.)because of small area.
thanks |
|
|
ThomasC
Joined: 09 Oct 2007 Posts: 62
|
|
Posted: Wed Dec 26, 2007 8:12 am |
|
|
It should work.
I answered my own question, I figured out that it does run in the background. Sorry for the redundant question. |
|
|
|