[TUT] [C] Creating an RTC using an internal counter/timer

Go To Last Post
65 posts / 0 new

Pages

Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

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.

-Adam
"Please don't judge my God by my inability to follow him" - Chris Mollins
================
www.onecircuit.com
================

Last Edited: Sat. Apr 1, 2006 - 12:37 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just to be a pedant but as Ticks256 is shared between the main code (after interrupt source started) and the ISR then shouldn't it be 'volatile' and, as it's wider than an atomically accessible unsigned char, the access in main() maybe should be wrapped in cli()/sei()

Though I guess it doesn't really matter in this context - but it's just good practice if this is being offered as an example for beginners.

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

True.. a better solution would to have Ticks256 be local to the ISR:

ISR(TIM0_OVF_vect)
{ 
 BYTE Ticks256 = 0;
 ...
 ...

-Adam
"Please don't judge my God by my inability to follow him" - Chris Mollins
================
www.onecircuit.com
================

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think you might just want to make that 'static' ! (unless 'BYTE' in types.h already does this?)

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Correct again :D Otherwise every time the ISR got called I believe the variable would get cleared out. Thanks!

-Adam
"Please don't judge my God by my inability to follow him" - Chris Mollins
================
www.onecircuit.com
================

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

I'm sure this tutorial will help a lot of people when trying to understand the use of timers, prescalers, interrupts and such. But for the benefit of the beginner, maybe it would be a good idea to say something about the accuracy that could be expected? The internal oscillators are not too accurate, and if the oscillator frequency is only 2% off , the error would be something like half an hour pr 24 hours. In the Tiny13 datasheet, Atmel states that the clock frequency should be within +/- 10% at a specified temperature and supply voltage.

If you just need a rough time estimate, the internal oscillator may be perfectly OK, but if you need more accurate timekeeping (let's say for building an alarm clock or something like that) a crystal oscillator should definitely be used (which in the case of the Tiny13 requires an external clock since it's not possible to connect a crystal directly the the chip. Most other AVR's can be connected to a crystal though).

By all means, your code works the same and will be equally usefull, independant of the clock source being used. Just thought it might be a good idea to throw in a few comments about accuracy ('..hey, I built this !!***##!! fancy alarm clock, and today I was an hour late for work..') :?

Knut

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thats a fantastic tutorial, I'm sure it will help alot of people understand setting up a timer in C.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi,
Beause this is a tutorial I am supposing I may ask something stupide:
What is in types.h ? I think that there are typedef definitions in this file.
Where can I download this file ?
After installing Gcc (Winavr), is libc installed or is libc an option to install after the basic installation of Winavr ?
Thanks, I am a beginner

Last Edited: Thu. Apr 6, 2006 - 01:52 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Not sure what it contains but I think the only things you need from it will be:

typedef volatile unsigned char BYTE;
typedef volatile unsigned int WORD;

so you can probably just remove the #include and add those two lines near the top of the file.

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Not sure what it contains but I think the only things you need from it will be:

typedef volatile unsigned char BYTE;
typedef volatile unsigned int WORD;

so you can probably just remove the #include and add those two lines near the top of the file.

Cliff

looks better when I add these lines in a types.h file, I am trying to compile the above tutorial file and got this error now:

timer2.c:26: warning: `TIM0_OVF_vect' appears to be a misspelled signal handler

EDIT: Tim0 must be TIMER0
Last Edited: Thu. Apr 6, 2006 - 02:29 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Go4it wrote:
looks better when I add these lines in a types.h file, I am trying to compile the above tutorial file and got this error now:

timer2.c:26: warning: `TIM0_OVF_vect' appears to be a misspelled signal handler


Which AVR are you building for - the code above is only guaranteed for the ATTiny13. On other AVRs with more/less timers the name of the registers, bits and interrupt vectors may be different so you'll need to check the io???.h file for the part you are using to find out what they are called on your AVR

Doing a grep for "OVF_vect" on the .h files in \winavr\avr\include\avr it seems that quite a few of the devices call the timer 0 overflow vector "TIMER0_OVF_vect" rather than just "TIM0_OVF_vect" - this may be all you require.

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I change the MCU type in the makefile and it works now,
I will remember that names are Device dependent. as I learned today, Thanks a lot !

Last Edited: Thu. Apr 6, 2006 - 02:51 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Go4it wrote:
Ok I will change the makefile, Thanks

Which device are you planning to run this tutorial on? That's the device you should configure the makefile for.

Then, modify the tutorial to use the correct register, bit, and vector names for that device.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson and ifmorrison, thanks I understand it now.
I have to take the datasheet with me and compare the register names.
But is there no table were all this device specific register-, vector - en bit names are mentioned.
Is it possible to define these names in the beginning of the program
to make programs more portable ?

.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, there is a register summary table near the end of every AVR datasheet. That table lists every register in the AVR, shows bitnames for every bit in each register, and gives a page reference for where to look for a detailed description of the register.

Start by looking at the ATtiny15 datasheet on the pages related to Timer0. Figure out what the TIMER_START and TIMER_STOP macros do to the bits in Timer0's registers, and figure out what mode of operation that configuration puts the Timer in.

Then look at the datasheet for the AVR you're working with. Figure out whether it's possible to put that AVR's Timer0 in the same mode of operation as the Tiny15 was set to. (The basic "interrupt on overflow" functionality of Timer0 is identical on the vast majority of all AVRs that have ever been built. However, some timers have extra functionality such as PWM and CTC that forces some of the configuration bits to be shifted around or split across more than one register.) Then figure out which bits you'll have to toggle to get that configuration.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

ajcrm125 wrote:
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.

Many AVR's do not have a System Clock pre-scaler (i.e. the ATMega8 or ATMega16). As such, your sample code is limited to those that do.

Also note that using the System Clock pre-scaler will affect the clock frequency of the CPU as well as all synchronous peripherals such as clkIO, clkADC, clkCPU, and clkFLASH. This is important for beginners that may not realize the implications of using the System Clock pre-scaler.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

as an exercise I just managed to adapt the tutor for a AT90S2313 >


      TCCR0B &= 0xF8
>    TCCR0  &= 0xF8  ( bits to set are the same)

      TCCR0B |= 0x05
>    TCCR0  |= 0x05

       ISR(TIM0_OVF_vect) 
>     ISR(TIMER0_OVF0_vect) 

       TIMSK0  |= _BV(TOIE0);
>     TIMSK  |= _BV(TOIE0);
       

Just for the syntax only, prescaler and clock must be recalculated

Last Edited: Fri. Apr 7, 2006 - 07:19 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

rjbishop wrote:

Many AVR's do not have a System Clock pre-scaler (i.e. the ATMega8 or ATMega16). As such, your sample code is limited to those that do.

Checked the datasheet of the ATmega16 and I think that
TCCR1B allows to select a prescaler of 1024
CS12/CS11/CS10 = 101 or do I am wrong ?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Go4it wrote:
rjbishop wrote:

Many AVR's do not have a System Clock pre-scaler (i.e. the ATMega8 or ATMega16). As such, your sample code is limited to those that do.

Checked the datasheet of the ATmega16 and I think that
TCCR1B allows to select a prescaler of 1024
CS12/CS11/CS10 = 101 or do I am wrong ?

That would be the Timer1 prescalar- but that is different from the System Clock pre-scalar that I was refering to.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The tutorial will have other fundamental differences as you move from the ATtiny13 with internal RC oscillator to most other machines:
The internal RC oscillators on most AVR's are standardized to 1, 2, 4, or 8 MHz. The ATtiny13 in this tutorial is running its internal oscillator at 9.6 MHz.

So the timer thresholds will need to be modified. Most likely you'll have to change the "Ticks256" threshold, and you may have to work with one of the CTC modes instead of allowing simple overflows, in order to get "normal" intervals such as 1 second or 1 minute.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Can't say I'm a big fan of "magic numbers" like 0xF8 and 0x05 myself. For one thing they give you no idea of what bits are being set in the registers and for another, when you port the code to another AVR, you will have to work them back to the bit fields being set, then try to find if the bits have moved in the target AVR's registers.

Personally I'd vote for:

#define STOP_TIMER TCCR0B &= ~((1<<CS02) | (1<<CS01) | (1<<CS00)) 
#define START_TIMER TCCR0B |= ((1<<CS02) | (1<<CS00)) 

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'm sorry I'm a slow learner. Could any body please translate ajcrm125's C codes to ASM. I don't know which ports and registers to properly setup parameters for my RTC

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

anhluulinh wrote:
I'm sorry I'm a slow learner. Could any body please translate ajcrm125's C codes to ASM. I don't know which ports and registers to properly setup parameters for my RTC

Once again, I claim RetroDan and anhluulinh are the same person, I could be wrong, but I believe you will be wasting your time if you help "anhluulinh" out.

Some tired Guy

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Someguy, need I remind you that I can (and have) checked your real account name? I have also checked the other account in question and unless Retro has two internet connections or is posting via a public internet cafe or the like, it's not him.

Please people. Harasment of the users here (this works BOTH ways) is not and should not be tollerated. No need for petty accusations here.

- Dean :twisted:

(PS: Too harsh?)

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

abcminiuser wrote:
Someguy, need I remind you that I can (and have) checked your real account name? I have also checked the other account in question and unless Retro has two internet connections or is posting via a public internet cafe or the like, it's not him.

Please people. Harasment of the users here (this works BOTH ways) is not and should not be tollerated. No need for petty accusations here.

- Dean :twisted:

(PS: Too harsh?)

Well Dean,

I can't really respond to your "need I remind you" comment, which assumes we have had previous comunication, which we have not, but I will say that I don't generally make accusations without reason or proof. Hey, just delete the SomeGuy22 account. Do us all that favor.

-SomeGuy

PS : I can walk to a local library too.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

anhluulinh wrote:
I'm sorry I'm a slow learner. Could any body please translate ajcrm125's C codes to ASM. I don't know which ports and registers to properly setup parameters for my RTC

What chip are you trying to do this for and what is the clock speed?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Someguy22 wrote:
I can't really respond to your "need I remind you" comment, which assumes we have had previous comunication, which we have not, but I will say that I don't generally make accusations without reason or proof. Hey, just delete the SomeGuy22 account. Do us all that favor.

PS : I can walk to a local library too.

Sorry about the pretentious "need I remind you", that was unnessesary. Still, I did a reverse-lookup on the IPs, and either Retro's using a proxy to post from the other account, or he flies over to to the US from Canada each time he wants to post :). I'd hope Retro would not waste our time like that, however...

- Dean :twisted:

PS: Please PM me if you wish to discuss this further so we don't totally derail the thread.

Make Atmel Studio better with my free extensions. Open source and feedback welcome!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
I'm sorry I'm a slow learner. Could any body please translate ajcrm125's C codes to ASM. I don't know which ports and registers to properly setup parameters for my RTC

Well, the registers are the exact ones that ajcrm125 used. That is, TCNT0, TCCR0B and TIMSK0. There are no ports involved in his code. Here is my translation to assembly.

.include "tn13def.inc"

.def temp = r16		;scratch register
.def minutes = r17		;minute count
.def Ticks256L = r18	;ticks per minute is greater than 255, so we need two bytes
.def Ticks256H = r19

.org 0x0000
	rjmp main

.org OVF0addr
	rjmp Tim0_Ovf

main:
	ldi temp, RAMEND	;set the stack pointer
	out SPL, temp
	clr minutes		;clear the minute count
	clr Ticks256L	;clear the tick count
	clr Ticks256H
	rcall ConfigureDevice	;set up the timer
loop_forever:
	rjmp loop_forever

ConfigureDevice:
	cli			;disable interrupts
	clr temp
	out TCNT0, temp	;clear the timer
	ldi temp, 0x01
	out TCCR0B, temp	;set the timer to a prescaler of 1024 (also starts timer)
	ldi temp, 1<<TOIE0
	out TIMSK0, temp	;enable the timer0 overflow interrupt
	sei			;enable interrupts
	ret 

Tim0_Ovf:			;Timer0 overflow interrupt routine
	in temp, SREG	;save the status register on the stack
	push temp
	inc Ticks256L	;increment the low byte of the tick count
	brne TO1
	inc Ticks256H	;increment the high byte if the low byte overflowed
TO1:
	cpi Ticks256L, 0x13	;compare the low byte with 19 (275 - 256)
	brne TO2
	cpi Ticks256H, 0x01	;compare the high byte with 1
	brne TO2
	inc minutes		;we reached 275, so increment the minute count
	clr Ticks256L	;clear the tick count
	clr Ticks256H
TO2:
	pop temp		;restore the status register
	out SREG, temp
	reti			;return from the inturrupt

I used the same names for variables and functions as he did to keep things straight.

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

anhluulinh wrote:
I'm sorry I'm a slow learner. Could any body please translate ajcrm125's C codes to ASM. I don't know which ports and registers to properly setup parameters for my RTC

PRELUDE:

In my latest Butterfly Bootloader (Cricket) I wanted to totally change the upload logic. I got tired of holding down the joystick in the middle postion and I think it's wearing out. I designed it mostly for my own personal use and find it very difficult to use anything else now that I am used to how much easier it is to use.

What I wanted was when the Cricket was RESET it would "chirp" once then calibrate the OSciallator then "chirp" again and automatically wait for you to either upload a new program or to move the joystick to launch a program. After a reasonable amount of time it would "chirp" one last time to let you know it was going to sleep to conserve battery.

I used a timer to do this, and perhaps this listing will help you to get started with your RTC in ASM. The only lines you really need to worry about are the first three and four near the end that read the clock and exit when high byte gets to 255.

THE MATH:

Here is how the Math works out:

The clock speed I chose to use for this part of program is 8Mhz and writing a 5 to TCCRB1 set the "prescaler" to 1024.

That mean that the timer will only "tick" after 1024 clock cycles.

Tick = PreScale / 8Mhz = 1,024/8,000,000 = 0.000128 Sec

Since I am using 2 bytes for the clock/timer the lower-byte is going to "roll-over" and increment the high-byte when it hits 256:

HighByteTick = 256 x 0.000128 = 0.032768 Sec

Since I Exit when HighByte gets to 255:

Exit=255 x 0.032768 = 8.35 Seconds 

When I initially worked this out, I figured the 8 seconds was way too short to start uploads, and was going to extend it, but it turns out that 8 seconds is ideal.

THE PROGRAM SEGMENT:

;----------------------------------;
; BOOT SENSORY LOOP                ;
; CHECK JOYSTICK & UART THEN SLEEP ;
;----------------------------------;
            STS     TCCR1B,FIVE  ;PRESCALE /1024
            STS     TCNT1L,ZERO  ;START AT ZERO
            STS     TCNT1H,ZERO

BOOT_LOOP:  CLR     TMP
            SBIS    PINB,6       ;JOYSTICK UP
             RJMP   START_8MHZ    	
            SBIS    PINB,7       ;JOYSTICK DOWN
             RJMP   START_2MHZ            
            SBIS    PINE,2       ;JOYSTICK LEFT
             RJMP   START_4MHZ
            SBIS    PINE,3       ;JOYSTICK RIGHT
             RJMP   START_1MHZ	

            LDS     TMP,TCNT1L   ;CHECK CLOCK
            LDS     TMP,TCNT1H
            CPI     TMP,255
             BREQ   GO_SLEEP

  	    LDS	    TMP1,UCSR0A	 ;CHECK UART
            SBRS    TMP1,RXC
             RJMP   BOOT_LOOP    ;NO UART THEN LOOP

IN CONCLUSION:

So I don't profess to be an expert on using timers in ASM, but I hope that you can work-out your own MATH using this as an example, and get you on-your-way to designing your own RTC.

ADDENDUM:

Another example of the Math using the same clock and prescaler:

1 Sec = Clock/PreScaler = 8,000,000/1024 = 7812.5 Ticks
1 Sec= 7812.5 / 256 = 30.5 HighByteTicks

So if you waited for HighByte of clock to reach 30 or 31 you would have approx. 1 second but this not going to be very accurate.

2 Seconds for 71 HighByteTicks in not too far off.

If you set the prescaller to 256 instead of 1024 by writing a 4 (instead of 5) to TCCR1B then:

1Sec= 8000000/256 = 31250 Ticks
1Sec= 31250 / 256 = 122.07 HighByteTicks

So by waiting for high byte to reach 122 in this example you are getting about a second also.

NOTE: For the Curious I have posted a download for the Cricket below:

Attachment(s): 

Last Edited: Fri. Apr 14, 2006 - 06:02 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thank you all for your help. I'm trying to learn these but I think I got it now.

To RetroDan: I'm using ATMEGA8515L and tiny2313

To Someguy22: I just joined this forum a while ago and I can feel there are some polictics going on. I'm enjoying my project and trying to learn, please exclude me out from the polictics - I got enough of that from work

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I found this tutorial very informative. I am a beginner and just happen to be playing with Tiny13's. I got the tutorial code all working OK but I am finding that I am counting about 63 seconds as a minute. It took me a while to get my head around the "do the maths" of the timing bit, but I finally did and can see why "if (Ticks256 == 275)" should be around 1 minute. With my Tiny13 I had to change it to "if (Ticks256 == 260)" to get as close as I can to a minute. Any ideas why this would be?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

What are you using as a clock source? If it is the internal oscillator, then the accuracy could very well change this much.

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Koshchi wrote:
What are you using as a clock source? If it is the internal oscillator, then the accuracy could very well change this much.

Yes I am using the internal oscillator. I didn't know it would vary that much. I don't need accurate timing anyway, I was just wondering. Thanks.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

From the datasheet:

Quote:
The calibrated internal RC Oscillator provides an 9.6 MHz or 4.8 MHz clock. The frequency is the nominal value at 3V and 25°C.

At 3V and 25°C, this calibration gives a frequency within ± 10% of the nominal frequency.


So if you are running at 5V, then I would think that the oscillator would run fast.

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Koshchi wrote:
From the datasheet:
Quote:
The calibrated internal RC Oscillator provides an 9.6 MHz or 4.8 MHz clock. The frequency is the nominal value at 3V and 25°C.

At 3V and 25°C, this calibration gives a frequency within ± 10% of the nominal frequency.


So if you are running at 5V, then I would think that the oscillator would run fast.

ah thanks, that explains it, I am running at 5V.

I have decided I may want a reasonably accurate way of measuring minutes with the Tiny13, what are my other options? I only have 1 free pin on it and probably not a lot of space for code.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

If that one free pin is CLKI (PB3), you can hook up an external crystal oscillator as the clock source.

Or if it is the T0 pin (PB2) you could hook up an external crystal oscillator to it. This would drive the timer directly. The cpu clock source would still be your internal oscillator. The disadvantage of this method is that there is no pre-scaling, so your interrupts would happen more often and your overflow counter would go much higher.

For both methods, if you choose the right frequency (i.e. some power of 2), the math becomes easy (and accurate). For instance, 3.6864MHz with a pre-scaler of 64 (and div8 fuse off), you get:

3686400 / (64 * 256) = 225

So the Ticks256 would be 225 per second.

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Koshchi wrote:
If that one free pin is CLKI (PB3), you can hook up an external crystal oscillator as the clock source.

Or if it is the T0 pin (PB2) you could hook up an external crystal oscillator to it. This would drive the timer directly. The cpu clock source would still be your internal oscillator. The disadvantage of this method is that there is no pre-scaling, so your interrupts would happen more often and your overflow counter would go much higher.

For both methods, if you choose the right frequency (i.e. some power of 2), the math becomes easy (and accurate). For instance, 3.6864MHz with a pre-scaler of 64 (and div8 fuse off), you get:

3686400 / (64 * 256) = 225

So the Ticks256 would be 225 per second.

Thanks for the advice Koshchi. As you can see I am a real beginner and I apologise for asking more questions. I keep reading the documents on the Tiny13 but a lot of it is over my head. I can free up any pin I want so it sounds like the easiest thing for me is to hook up an external crystal oscillator to CLKI. Are you suggesting I use a 3.6864MHz crystal to get more accuracy or would I just choose a 9.6MHz crystal?

Thanks again.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Not necessarily 3.6864MHz. Any thing that divides down to an integer would be fine. There are several to choose from. With 9.6MHz, it divides down to 274.66, but you would be counting to 275. This would make it gain nearly 2 minutes a day. If you want something near 9.6MHz, you could use 9.8304MHz, or 8.192MHz. Just look through what oscillators are available to you and do the math. Keep in mind that there are several pre-scaler values to choose from, as well as several clock dividers (look at the CLKPR register). Try different combinations until you find one that works for you.

Regards,
Steve A.

The Board helps those that help themselves.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

This is obviously basic stuff, but I can't figure it out. I am hoping someone can show me how to access the "minutes" variable from within the main loop. I have declared the variable like this

volatile unsigned char minutes = 0;

. I want to run a function once an hour. This function could take 10 minutes to complete. I was calling the function from inside the interrupt but found that it was stopping the interrupt while the function ran so my time count was not correct. So I thought I would put this inside main

	 	
  	// Loop forever; the interrupts will take it from here
	 while(1)
 	{
	 	if (minutes >= 60)
 		{
			cycle();
		}
 	}

But it isn't calling the function. Shouldn't the value of "minutes" be available in this loop? Any help will be greatly appreciated.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Taipan wrote:
But it isn't calling the function. Shouldn't the value of "minutes" be available in this loop? Any help will be greatly appreciated.

Yes it should. I can't see a reason why you wouldn't be able to test minutes in the main loop like that. So three suggestions -

1) is to add some debug UART output (or better still use a JTAG debugger) to see what's happening to minutes in the main loop

2) is to look at the .lss file that is generated when you compile and look at the code that accesses minutes in the main loop and make sure it is accessing the variable in its SRAM location as expected

3) post your code here so we can see if there's any obvious error in it

By the way, the program at the top of this thread seems a bit "odd" to me in that it declares 'minutes' as "BYTE" which has been established to be "volatile unsigned char minutes;". Well that only caters for counting 0..255 and will wrap back to 0 in the 256th minute. Personally I'd add 'hours' and 'days' and each time minutes reaches the 59->60 increment I'd reset it and increment hours then do the same to it at 23->24

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
Taipan wrote:
But it isn't calling the function. Shouldn't the value of "minutes" be available in this loop? Any help will be greatly appreciated.

Yes it should. I can't see a reason why you wouldn't be able to test minutes in the main loop like that. So three suggestions -

1) is to add some debug UART output (or better still use a JTAG debugger) to see what's happening to minutes in the main loop

2) is to look at the .lss file that is generated when you compile and look at the code that accesses minutes in the main loop and make sure it is accessing the variable in its SRAM location as expected

3) post your code here so we can see if there's any obvious error in it

By the way, the program at the top of this thread seems a bit "odd" to me in that it declares 'minutes' as "BYTE" which has been established to be "volatile unsigned char minutes;". Well that only caters for counting 0..255 and will wrap back to 0 in the 256th minute. Personally I'd add 'hours' and 'days' and each time minutes reaches the 59->60 increment I'd reset it and increment hours then do the same to it at 23->24

Cliff


Hi Cliff, I really appreciate the help.
1/ wish I knew how to do this. I was playing with a butterfly originally and being able to debug through a terminal made things much easier.

2/ I am writing my code straight into AVR-Studio and compiling it there, I don't see an .lss file.

3/. I have been experimenting more this morning and I think I have my whole approach wrong. I stripped a few unrelated things to try and get main to respond to "minutes" and I have succeeded .. sort of. Basically I have 2 pumps and 2 level sensors. Every hour I want to run a cycle that fills a container then empties it. I have been turning the pump on and then waiting in a loop for a response from the sensor. But I think this time waiting in the loop is not a good thing. Can I use interrupts to detect the state of 2 pins as well? One pin goes low when the container is full and the other pin goes high when the container is empty. If i do it this way I could run all the procedures from three interrupts? Does this sound like a better way to go? Thanks in advance.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Many hours of experimenting later and I have rewritten all my code and have interrupts handling it all. Finally got it working. :D

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't understand why generating more interrupts would increase the accuracy. It seems to me that the number of interrupts really doesn't matter. Could someone explain this?

Building my dreams!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The smaller the prescaler, the finer the granularity,

Quote:

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.

Take the prescaler of 8 example: we expect to see 585.938 interrupts occur for every second that goes by but we can't deal with floating point numbers so we have to round to 586. That means we are actually be signalled every time
586/585.938 = 1.00011 seconds go by.

If we jack the prescaler to 1024, we get:
5/4.57764 = 1.09227.

And thse errors add up when we start counting minutes,days, months, years....

-Adam
"Please don't judge my God by my inability to follow him" - Chris Mollins
================
www.onecircuit.com
================

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

@ajcrm125

Quote:
When it reaches 0xFFFF (255) and rolls over to 0x0000, an interrupt is sent.

Shouldn't it be 0xFF or 0x00FF?
0xFFFF = 65535

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hello,
I found this post when searching for how to use timers and interrupts, and I think it's an excellent post. Unfortunately for me I must be doing something fundamentally wrong and I can't get it working (I don't get any isr call).

My first attempt was to edit the code in the original post to fit my mcu's (ATMega88 and ATMega16), but when I couldn't get that working I switched over to the original code (with added typedefs for BYTE and WORD) and simulation of an ATTiny13. But.. Even that didn't solve my problem. The ISR never gets called (as far as I can see).

When I run the program (in AVR Studio 4.13, build 524) and set breakpoints (first line in isr and first line in main) the breakpoint in main gets called over and over again. Guess that means the simulated mcu restarts itself all the time? I never get to the breakpoing in the isr.

I get warnings when compiling:

Quote:

Build started 16.3.2007 at 00:45:01
avr-gcc.exe -mmcu=attiny13 -Wall -gdwarf-2 -O0 -fsigned-char -MD -MP -MT interrupt-test.o -MF dep/interrupt-test.o.d -c ../interrupt-test.c
../interrupt-test.c:28: warning: return type defaults to `int'
../interrupt-test.c: In function `ISR':
../interrupt-test.c:48: warning: control reaches end of non-void function
../interrupt-test.c: At top level:
../interrupt-test.c:73: warning: return type of 'main' is not `int'
avr-gcc.exe -mmcu=attiny13 interrupt-test.o -o interrupt-test.elf
avr-objcopy -O ihex -R .eeprom interrupt-test.elf interrupt-test.hex

AVR Memory Usage:
-----------------
Device: attiny13

Program: 236 bytes (23.0% Full)
(.text + .data + .bootloader)

Data: 4 bytes (6.2% Full)
(.data + .bss + .noinit)

Build succeeded with 3 Warnings...

I hope someone know what I do wrong!
Have a nice day!
/Daniel

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Repeated stops at the entry to main() usually means that an interrupt is occuring for which an ISR has not been provided (the default vector action is a jump to a routine that itself does a jump to 0 and effectively restarts the CPU). This may occur either because you have inadvertently set one of the ??IE bits in a control register somewhere or because you have built for the wrong CPU and while you've provided an ISR it's been entered into the wrong vector.

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I solved my problems by updating avr-libc and AVR Studio. As far as I can tell I use the exact same settings in AVR Studio (now using build 528) but after updating I don't get any warnings when compiling, and the program works as expected (using the exact same source as when it didn't work).

I don't know which one of the updates solved it, but now it works.

Thanks to clawson for the explanation about the ISR :)

/Daniel

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I:m trying to get this running on a Atmega162, but as soon as the code is flashed.. the result is a total lock up of the mcu. anyone knows what I;m doing wrong? The codes are in this thread http://www.avrfreaks.net/index.p...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Nice tutorial, just a few remarks:
As a rule you should initialize your variables and after that you can activate timers and interrupts. The C compiler doesn't initialize variables, this means that your interrupt function can be called with strange values inside your variables if the counting is faster then the program execution until the variables initialization.
About variables, you should prefer using local variables instead of global ones, so if Ticks256 is only used inside the interrupt function it should be a static variable:
static WORD Ticks256;

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

One last detail, it is a good idea to initialize Ticks256 on declaration:
static WORD Ticks256 = 0;
This will initialize the variable to zero ONLY the first tiem the function is executed and its value will be preserved in between calls to the function.

Pages