Atmega328P中C语言ISR内全局变量old值未更新问题
核心问题定位
你遇到的输出异常(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

