You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何需在主循环中调用millis()才能触发TIMER0_COMPA_vect中断服务程序?

定时器中断依赖millis()调用才能触发的问题

我正在复刻耳机的多功能按键功能——在一段时间内按下按键指定次数,耳机就会根据按压次数执行对应操作。但遇到一个奇怪的问题:必须在主循环里调用millis()函数,用于递增毫秒变量的TIMER0_COMPA_vect中断服务程序(ISR)才会触发,完全搞不懂为什么会这样。

以下是相关代码:

主代码

#include <mcu_init.h>
#include <millis.h>
#include <button.h>

int main() {

    GPIO_init();

    cli();

    INT0_init();
    TMR0_init();

    sei();

    while (1) {

        // 如果在这里调用millis(),TIMER0_COMPA_vect中断才会触发
        handleButton();
        
    }

    return 0;

}

millis.c 实现

#include <millis.h>

static volatile uint32_t miliseconds = 0;

ISR (TIMER0_COMPA_vect) {
    miliseconds++;
    PORTA |= (1 << 1);

}

uint32_t millis() {

    uint32_t time;
    uint8_t oldSREG = SREG;

    cli();

    time = miliseconds;
    SREG = oldSREG;

    return time;

}

button.c 实现

#include <button.h>

volatile uint8_t count = 0;
volatile uint32_t lastPressTime = 0;

ISR (INT0_vect) {

    if (millis() - lastPressTime >= 200) {
        count++;
        lastPressTime = millis();

    }

}

void handleButton() {
  
    if (millis() - lastPressTime >= 1500) {
        lastPressTime = millis();

        switch (count) {

            case 2:
                PORTA ^= (1 << 1);
                count = 0;
                break;

            case 3:
                PORTA ^= (1 << 3);
                count = 0;
                break;

            default:
                count = 0;

        }

    }

    return;

}

millis.h 头文件

#ifndef MILLIS_H_
#define MILLIS_H_

#include <avr/interrupt.h>

uint32_t millis();

#endif

button.h 头文件

#ifndef BUTTON_H_
#define BUTTON_H_

#include <millis.h>

void handleButton();

#endif

问题根源分析

这个现象的核心原因是编译器优化:

  • 你的miliseconds变量是static volatile,但如果主循环中没有任何代码读取它(或通过millis()间接读取),编译器会认为定时器中断对miliseconds的修改没有实际意义,可能会优化掉定时器中断的触发逻辑,甚至忽略定时器初始化的部分代码。
  • 虽然handleButton()中调用了millis(),但如果编译器判断该函数的执行没有产生明确的“可见副作用”(比如某些分支下PORTA的修改被判定为非必要),可能会优化掉handleButton()的调用,间接导致millis()没被执行,最终让中断看似“不触发”。

解决方法

  1. 强制保留读取操作:在主循环中添加一行无意义但会触发millis()调用的代码,阻止编译器优化:
    (void)millis(); // 强制读取miliseconds,确保编译器不会丢弃中断逻辑
    
  2. 明确保留副作用:确保中断和主循环中的操作有明确的硬件副作用,比如保留中断里的PORTA |= (1 << 1);调试代码,让编译器认为这段逻辑必须执行。
  3. 检查定时器初始化:确认TMR0_init()中正确开启了定时器比较匹配中断(设置OCIE0A位),并且时钟源配置正确。如果初始化代码有误,也可能导致中断仅在特定条件下触发。

额外注意事项

  • INT0_vect中断中调用millis()是安全的,但要确保临界区的处理正确(你当前的millis()实现已经通过保存和恢复SREG做到了这一点)。
  • lastPressTime是32位volatile变量,在handleButton()中读取时,建议同样用临界区保护,避免因中断打断导致读取到不完整的数值。

内容的提问来源于stack exchange,提问作者Carlos Leonel Rivas Pimentel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 18:23:13