如何使 Atmega328 定时器中断每 1 秒滴答一次?

How to make Atmega328 timmer interrupt tick every 1 second?

我有一个 Atmega328p,我已将 LED 连接到它的 D4 引脚,我希望它每隔一秒点亮 LED on/off。

我找到了 this tutorial,我已经根据一些在线 AVR 定时器计算器和我使用的 12MHZ 外部 crystal 将其更改为:

#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
}

无论我如何更改ICR1 值,LED 总是亮或灭。我怎样才能让它发挥作用?

计时器初始化看起来不错(参见其他答案),但看起来您没有正确切换 LED。

PORTD |= 0 << 4; 什么都不做。

假设您直接驱动 LED,使用 PORTD |= (1 << 4); 打开 LED,使用 PORTD &= ~(1 << 4); 关闭 LED,使用 PORTD ^= (1 << 4); 切换输出状态。

由于您只想切换 LED 状态,最后一个选项显然是最佳选择,因为您不必检查输出引脚的当前状态来决定是否需要打开或关闭。

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

注释正确:设置位 WGM12(而其他 WGM1x 位为零)将打开 CTC(清除定时器 将 match) 模式与 OCR1A.

定义的 TOP 值进行比较

但是!

ICR1 = 0xB71B;

你是把TOP值写入input-capture寄存器ICR1(也有这样的模式WGM12:WGM11:wGM11:WGM10设置为1110,但是需要用到另外一个中断) .

您想将该值写入 OCR1A

12 000 000 / 256(定时器预分频器)- 1 = 46874 ,即 0xB71A,而不是 0xb71B:您忘记减去 1.

由于定时器从零开始计数,所以TOP值比定时器的完整周期少1

而且在这种情况下,最好使用小数或公式来使代码更具可读性。

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

还有。正如 Rev1.0 所指出的,您需要在中断中切换输出。

您可以使用按位独占或 ^:

    PORTD ^= 1 << 4;

或者,在 Atmega328P 中,您只需将 1 写入 PINx 寄存器即可切换 PORTx:

中的位值
    PIND = 1 << 4;