I am trying to turn on a LED for 5sec every 12 hours.
I tried:
#include <mega16.h>
#include <delay.h>
void main(void)
{
DDRC.0 = 1;
PORTC.0 = 0;
while(1)
{
unsigned long i;
unsigned long j;
PORTC.0=1;
for(i=1; i<432; i++)
{
delay_ms(100);
}
PORTC.0 = 0;
for(j=1; j<100; j++)
{
delay_ms(50);
}
}
}
but the LED was on for about 2 hour every week.
Best Answer
I felt the need to give a non-blocking solution to the problem especially because we are talking about twelve hours of delay here.
The util/delay.h library and its _delay_ms() and _delay_us() functions are the software delay functions. They are convenient in small programs and for quick prototyping and experimentation. They are simple and the accuracy could be enough in many cases, but we have a disadvantage here. As they are software delays, they block the CPU. Actually we use the CPU as a counter/timer so we cannot do anything else in the meantime.
With hardware timers, it is possible to count in the background and handle events with interrupts, thus the CPU will be free to use while the timer runs independently.
The "disadvantage" is that we have to go through a little bit more documentation and we have to write a little bit more code, but in return we will get higher accuracy and a free CPU.
Set up a Timer with interrupts
I am going to use the Timer1 of Atmega16A which is a 16 bit counter. The clock frequency is \$ f = \small 8\,MHz \$ and I want the timer to overflow once in every seconds and I will count these seconds until 12 hours (43200 seconds) is reached.
Steps:
Execution:
We begin with: \$f_{SystemClock} = \small 8\,MHz \$, if we use this frequency with no prescale the timer will have a tick in every:
\$ \large T_{timer} = \frac{1}{f_{SystemClock}} = \frac{1}{8\,MHz} = 0.125\,\mu s \$
Unforunately it is too fast so we will prescale the clock frequency by 256.
\$ \large f_{timer} = \frac{f_{SystemClock}}{256} = 31250\,Hz \$
\$ \large T_{timer} = \frac{1}{f_{timer}} = \frac{1}{31.250\,kHz} = 32\,\mu s \$
For an 1 second overflow \$ \sum_{ticks} = \large \frac{1\,s}{32\,\mu s} = \small 31250 \$ ticks are needed, (In this 1 s case the frequency clearly gives this value.)
With a 16 bit timer the maximum possible value is: \$ 2^{16} - 1 = 65535 \$, so our value will fit, not like with \$8\,MHz\$ where \$ \small \sum_{ticks} \$ would be too high.
As we have determined the above mentioned values, it is time to configure the timer. We will use the CTC (Clear Timer on Compare match) mode of the timer, which allows to set a custom maximum value.
Next step is to set the prescaler to 256 by the Clock-select bits in the TCCR1B register. Note: the counting will begin as soon as the clock source is selected.
Next; enable the corresponding Timer1 interrupt in the TIMSK (Timer/Counter Interrupt Mask Register) register, which will be triggered upon reaching the maximum value.
This is only one way of writing the ISR, other could be using a flag indicating a 12 hours period, then handle the LED in the main function when this flag is set. For the 5 seconds delay either the
_delay_ms(5000)
(as it is not that long) or a separate flag could be used. The interrupt vectors (e.g:TIMER1_COMPA_vect
) are listed in the datasheet.Testing
I was toggling a pin inside the ISR to measure the 1 second timing of the interrupts and I have turned on the LED for 5 sec in every 30 sec. The result is in the image below:
A little error (~0.9%) could be observed on the 1 second value which is caused by the internal oscillator's inaccuracy. It is normal, according to the datasheet: