I've noticed that several AVR users are asking this question so I thought I might whip something up to help understand the basics. This tutorial will issustrate how to implement an RTC using an ATTiny13, however it can easily be modified to create an RTC in any AVR.
For whatever reason you may find yourself at some point needing to create an RTC (real-time clock) for your AVR project. Before we get started with the code, let's understand some basics.... unfortunately, it's all math. :D
In order to generate an RTC, we need to have something that records a sence of time. For this purpose, we can use one of the internal Timer/Counters found in the AVR. In the tiny13, there's only one: Timer/Counter0.
This counter will increment relative to the clock source. Not only this, but we are able to play with some features (like the counter pre-scaler for example) that will allow us to control just how fast or slow the counter increments. The counter pre-scaler is simply a clock divider for the clock that drives the timer/counter (there is a separate clock prescaler for the system clock, but we'll get to that later). So by changing the counter pre-scaler value, we can have the counter increment at every system clock cycle, or every 8th cycle, or every 64th cycle, etc etc all the way to every 1024th clock cycle.
Now the system clock that the AVR uses also has a pre-scaler.that behaves in exactly the same way. By setting this value you can speed up, up or slow down the system clock which will also change the rate in which the timer/counter increments.
The timer/counter also has a lot of operating modes that one can use, but the one that interests us is the Normal mode. In this mode, all the counter does is count up. When it reaches 0xFFFF (255) and rolls over to 0x0000, an interrupt is sent. So in essence, an interrupt is sent every time the counter increments 256 times..... this is key.
So how can we use all this stuff? Well let's do some math....
The counter increments off the the timer/counter clock (which is divided by the timer/counter prescaler) and this clock is initially generated by the system clock (which is divided by the system clock prescaler). So we end up with:
TimerClkFreq = (SysClkFreq/SysClkPre) / TimClkPre
SysClkFrq = System Clock Frequency
SysClkPre = System Clock Prescaler
TimClkFrq = Timer/Counter Clock Frequency
TimClkPre = Timer/Counter Clock Prescaler
Now keep in mind that freqencies are given in Hertz which is cycles per seconds. So if we wanted to know how many Timer/Counter clock cycles would pass in say 2 seconds, we just need to multiply the above equation by 2. So for an arbitrary time value T, given in seconds, we have the following equation:
# of cycles in T seconds = (SysClkFreq/SysClkPre)* (T/ TimClkPre)
But remember, our timer/counter interrupt only fires when the counter rolls over, and this is every 256 timer/counter cycles. So eventually we have:
# of interrupts = (SysClkFreq/SysClkPre) * (T/ TimClkPre) * (1/256)
So what the heck do you do with all this???? Well all you have to do is toss in the values for your prescalers and the # of seconds you're interested in and you'll know how many times the interrupt will fire before you have passed that time. In your interrupt handler, all you need is a variable that increments every time the interrupt handler is called. When this variable is >= the # of interrupts you're looking for, you know the timer interval has passed. And keep in mind that this formula will work for any AVR that you have.
Let's see an example....
Suppose you want to create an RTC that will count seconds. Lets use a timer/counter prescaler of 8, the internal 9.6Mhz system clock and the default system clock prescaler of 8.
# of interrupts = (9.6Mhz/8 ) * (1/8 ) * (1/256)
# of interrupts = 585.938 or about 586
Not bad.. we can put a variable in the timer interrupt and when it reaches 586 we know a second has passed. But notice this, 586 will not fit in a single byte. (2^8 = 256). Not that it's a big deal... 16 bit variables are pretty common. But just keep in mind that if you were to bump up the prescaler to say 1024, it would definitely fit:
# of interrupts = (9.6Mhz/8 ) * (1/1024) * (1/256)
# of interrupts = 4.57764 or about about 5
What's the difference? You're trading variable space for accuracy.
The smaller the prescaler, the more times the interrupt gets called and the more accurate the RTC is. And keep in mind though if you trying to track large intervals of time, you may be forced into using larger prescalers, or have multiple variables tracking different intervals.
(I.E. seconds, minutes, hours, days, etc etc)
Here's an RTC implemented in the ATTiny13 that tracks how many minutes have gone by:
/////////////////////////////////////////////////////////////////////////////// // Includes /////////////////////////////////////////////////////////////////////////////// // Include avr-libc stuff #include#include #include "types.h" /////////////////////////////////////////////////////////////////////////////// // Defines /////////////////////////////////////////////////////////////////////////////// #define STOP_TIMER TCCR0B &= 0xF8 #define START_TIMER TCCR0B |= 0x05 /////////////////////////////////////////////////////////////////////////////// // Global Varibles /////////////////////////////////////////////////////////////////////////////// BYTE minutes; BYTE timeout; WORD Ticks256; /////////////////////////////////////////////////////////////////////////////// // Timer 0 Overflow Interrupt handler /////////////////////////////////////////////////////////////////////////////// ISR(TIM0_OVF_vect) { // 256 ticks have gone by Ticks256++; // If you do the math you'll find that if the interrupt goes off 275 times, a minute has passed if (Ticks256 == 275) { // a minute has passed minutes++; Ticks256 = 0; } // do something useful here if you wish; // like checking to see if minutes = 60 perhaps? if (minutes >= 60) { // do whatever you want.. you'll probably want to reset the minutes variable // so you can start counting all over again } } /////////////////////////////////////////////////////////////////////////////// // Configure device configures the micro's setting per our requirements /////////////////////////////////////////////////////////////////////////////// void ConfigureDevice(void) { cli(); // disable interrupts just in case // configure PORTB... and other settings TCNT0 = 0x00; // clear Timer/Counter START_TIMER; // enable interrupts on timer 0 overflow TIMSK0 |= _BV(TOIE0); sei(); // enable interrupts } /////////////////////////////////////////////////////////////////////////////// // Main function /////////////////////////////////////////////////////////////////////////////// void main(void) { // Configure device ConfigureDevice(); // clear minutes and Ticks minutes = 0; Ticks256 = 0; // Loop forever; the interrupts will take it from here while(1) { } }
That's about it... I'm no AVR guru but it works pretty nicely in my experience.