How to make Atmega328 timmer interrupt tick every 1 second?

1.3k Views Asked by At

I've an Atmega328p and I've connected an LED to it's D4 pin and I want it to turn LED on/off every one second.

I found this tutorial and I've change it base on some online AVR timmer calculator and the 12MHZ external crystal which I've used, to this :

#define F_CPU 12000000UL
#include <avr/io.h>
#include <avr/interrupt.h>


int main(void)
{

    DDRD |= 1 << 4;
    PORTD |= 1 << 4;

    ICR1 = 0xB71B;

    TCCR1B |= (1 << WGM12);
    // Mode 4, CTC on OCR1A

    TIMSK1 |= (1 << ICIE1);
    //Set interrupt on compare match

    TCCR1B |= (1 << CS12);
    // set prescaler to 256 and starts the timer


    sei();
    // enable interrupts


    while (1)
    {
        // we have a working Timer
    }
}

ISR (TIMER1_COMPA_vect)
{
    PORTD |= 0 << 4;
    // action to be done every 200ms
}

The LED is always on or off no mater how I change ICR1 value. How can I make it work?

2

There are 2 best solutions below

0
On BEST ANSWER

The timer initialization looks fine (see other answer), but it looks like you are not toggling the LED correctly.

PORTD |= 0 << 4; will do nothing.

Assuming you drive the LED directly, use PORTD |= (1 << 4); to turn LED on, PORTD &= ~(1 << 4); to turn LED off and PORTD ^= (1 << 4); to toggle the output state.

Since you just want to toggle the LED state, the last option is obviously the best choice, because you do not have to check the current state of the output pin to decide if you need to switch on or off.

1
On
TCCR1B |= (1 << WGM12);
    // Mode 4, CTC on OCR1A

the comment is correct: Setting the bit WGM12 (while other WGM1x bits are zeroes) will turn on the CTC (Clear Timer on Compare match) mode with TOP value defined by OCR1A.

But!

ICR1 = 0xB71B;

you're writing the TOP value into the input-capture register ICR1 (there is also such a mode with WGM12:WGM11:wGM11:WGM10 set to 1110, but it needs to use another interrupt).

You want to write the value into OCR1A instead.

12 000 000 / 256 (timer prescaller) - 1 = 46874 , which is 0xB71A, not 0xb71B: you forgot to subtract 1.

Since the timer counts from zero, then the TOP value is 1 less than full period of the timer

And in this case it is better to use decimal or a formula to make the code more readable.

OCR1A = (F_CPU / 256) - 1; // 46874 

Also. As it noted by Rev1.0 you need to toggle the output in the interrupt.

You can do it by use bitwise exclusive or ^:

    PORTD ^= 1 << 4;

or, in Atmega328P you can just write 1 into PINx register to toggle the value of bits in PORTx:

    PIND = 1 << 4;