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

Atmega328P中C语言ISR内全局变量old值未更新问题

问题分析与解决方案(Atmega328P定时器捕获异常)

核心问题定位

你遇到的输出异常(00→40→80→C0)以及全局变量old始终为0的问题,根源是Timer0溢出中断未实现导致程序跑飞,另外还有UART发送的细节问题。

具体错误与修复步骤

1. 未处理Timer0溢出中断(最关键的bug)

你的timer0Init()函数里开启了TOIE0(Timer0溢出中断),但代码中完全没有对应的中断服务程序(ISR)。当Timer0溢出触发中断时,CPU会跳转到默认的空中断向量,直接导致程序跑飞——要么无限循环,要么复位,这就使得ISR(TIMER1_CAPT_vect)里的old = temp根本没法持久生效,每次复位后old都会回到初始值0。

修复方式二选一:

  • 如果你不需要Timer0的溢出中断,直接关掉:
    // 把timer0Init里的TIMSK0设置改成这样
    TIMSK0 = 0;
    
  • 如果确实要用这个中断,补个空的ISR(哪怕啥都不做):
    ISR(TIMER0_OVF_vect) {
        // 这里加你需要的逻辑,不需要就留空
    }
    

2. UART只发了16位变量的低8位

delta是uint16_t类型,但UDR0是8位寄存器,直接赋值只会发送低8位。如果要完整传输脉冲间隔的16位数值,得拆分高、低字节分别发送:

修复代码:
把ISR里的UART发送部分改成:

// 先发高字节,等发送寄存器空了再发
while (!(UCSR0A & (1 << UDRE0)));
UDR0 = (delta >> 8) & 0xFF;
// 再发低字节
while (!(UCSR0A & (1 << UDRE0)));
UDR0 = delta & 0xFF;

3. 脉冲间隔计算可以简化

你原来的溢出判断分支完全没必要——AVR的无符号16位整数减法会自动处理溢出情况,temp - old在temp < old时,结果就是65536 - old + temp,和你手动写的分支逻辑完全一致,代码可以简化成:

uint16_t delta = temp - old; // 自动处理溢出,去掉冗余分支

4. 可选:关闭捕获噪声滤波(提升精度)

TCCR1B里的ICNC1是输入捕获噪声滤波,会引入4个时钟周期的延迟。如果你的PWM信号干净,完全可以关掉它来提高捕获精度:

// 修改timer1Init里的TCCR1B设置,移除(1 << ICNC1)
TCCR1B = (1 << ICES1) | (1 << CS12) | (1 << CS10);

修复后的完整代码

#include <avr/io.h>
#include <avr/interrupt.h>

void timer0Init()  // Timer0 setup
{
    TCCR0A = (1 << COM0A1) | (1 << WGM01) | (1 << WGM00);  // Fast PWM模式,非反相输出
    TCCR0B = (1 << CS02);  // 预分频器clk/256
    OCR0A = 128;  // 50%占空比
    TIMSK0 = 0;  // 关闭不需要的Timer0溢出中断
}

void timer1Init()  // Timer1 setup
{
    // 普通模式,上升沿捕获,预分频器clk/1024,关闭噪声滤波
    TCCR1B = (1 << ICES1) | (1 << CS12) | (1 << CS10);
    TIMSK1 = (1 << ICIE1);  // 开启捕获中断
}

void usartInit()  // UART setup
{
    UCSR0B = (1 << TXEN0);  // 开启发送
    UCSR0C = (0 << USBS0) | (1 << UCSZ01) | (1 << UCSZ00);  // 1停止位,8位数据
    UBRR0 = 103;  // 16MHz时钟下9600波特率
}

uint16_t old = 0;
ISR(TIMER1_CAPT_vect)  // Timer1捕获中断服务程序
{   
    uint16_t temp = ICR1;
    uint16_t delta = temp - old;  // 自动处理溢出的无符号减法
    
    // 发送16位delta的高字节
    while (!(UCSR0A & (1 << UDRE0)));
    UDR0 = (delta >> 8) & 0xFF;
    // 发送低字节
    while (!(UCSR0A & (1 << UDRE0)));
    UDR0 = delta & 0xFF;
    
    old = temp;
}

int main(void)
{
    DDRD = 0x40;  // 设置PD6(OC0A)为输出
    
    timer0Init();
    timer1Init();
    usartInit();
    
    sei();  // 开启全局中断
    while (1) {}
}

验证结果

修复后,Timer1每次捕获PWM的上升沿,计算出的delta应为64(对应4.096ms的周期,16MHz时钟下:4.096ms / (1/(16e6/1024)) = 64),UART会连续发送0x00 0x40(十进制64的16位表示),完全符合预期的PWM间隔值。

内容的提问来源于stack exchange,提问作者Elijah Sokol

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 17:28:11