Saturday, 30 March 2019

microcontroller - 12 hours delay with ATmega16A


I am trying to turn on a LED for 5sec every 12 hours. I tried:


#include 
#include

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.



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:



  1. Determine the clock-speed and the maximum value of the timer to get an overflow in every seconds.

  2. Set the corresponding registers (Timer mode, Clock select, Enable interrupt, Compare value (max value))

  3. Write our Interrupt Service Routine (ISR) to count the seconds and in this case to control the LED.


Execution:





  1. 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.





  2. 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.



    In CTC mode the counter is cleared to zero when the counter value (TCNT1) matches the OCR1A (WGM13:0 = 4).




    • OCR1A =\$ \small \sum_{ticks} - 1\$ = 31250 - 1 = 31249, as the counter starts from 0.

    • WGM13:0 = 4,\$ \Rightarrow\$ so only WGM12 bit has to be set in the TCCR1B control register


    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.




    • CS12:0 = 4, \$ \Rightarrow\$ so only CS12 bit has to be set.


    Next; enable the corresponding Timer1 interrupt in the TIMSK (Timer/Counter Interrupt Mask Register) register, which will be triggered upon reaching the maximum value.


    enter image description here



    • We need OCIE1A bit (Timer/Counter1, Output Compare A Match Interrupt Enable), which is when written to 1 the Timer/Counter1 Output Compare A match interrupt is enabled. (In normal mode TOIE1 bit ( Timer/Counter1, Overflow Interrupt Enable) should be used)



  3. Last step is to write the code of the ISR and the timer initialization.





#include 
#include

#define LED PC0

// global variable to count the seconds
volatile uint16_t sec_cnt = 0;

// Timer1 initializtion

void init_timer1()
{
// Timer Mode 4: Clear Timer on Compare match (CTC)
TCCR1B |= (1< // Initialize Timer staring value
TCNT1 = 0;
// Set Compare value for 1s overflow
OCR1A = 31249;
// Enable Timer Compare A Match interrupt
TIMSK |= (1<
// Start Timer & Clock Select: Prescale I/O clock by 256
TCCR1B |= (1<}

// Timer1 output compare match A interrupt rutine
ISR(TIMER1_COMPA_vect)
{
if(sec_cnt < 5)
{
PORTC |= (1<
}
else
{
PORTC &= ~(1< }

sec_cnt++;

if(sec_cnt == 43200) // 12*60*60 = 43200
{

sec_cnt = 0; // restart counting
}
}

void main(void)
{
// init Timer1
init_timer1();
// enable global interrupts
sei();


while(1)
{
// Let the ISR handle the LED ...
// and do other stuff
}
}

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:


enter image description here


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:



At 5V, 25°C and 1.0, 2.0, 4.0 or 8.0MHz Oscillator frequency selected, this calibration gives a frequency within ± 3% of the nominal frequency.




No comments:

Post a Comment

arduino - Can I use TI&#39;s cc2541 BLE as micro controller to perform operations/ processing instead of ATmega328P AU to save cost?

I am using arduino pro mini (which contains Atmega328p AU ) along with cc2541(HM-10) to process and transfer data over BLE to smartphone. I...