问题
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?
回答1:
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.
回答2:
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;
来源:https://stackoverflow.com/questions/64205811/how-to-make-atmega328-timmer-interrupt-tick-every-1-second