ATmega328P基于TIMER1 ISR实现10秒延时的异常排查
ATmega328P 10秒定时+TIMER0 PWM输出功能异常排查
现存核心错误(直接对应你遇到的“延时偏长、PWM无输出”现象)
- 定时器初值重载逻辑缺失,是导致延时大幅偏长的核心原因
TIMER1运行在普通模式下,计数到0xFFFF触发溢出中断后,硬件会自动将TCNT1清0,从0开始重新计数。你只在counter累计满10次的分支里才给TCNT1重载初值0xC2F8,前面9次溢出中断触发时完全没有重载初值——意味着第一次溢出(按初值算耗时1秒)之后,TCNT1从0开始跑满整个16位量程,16MHz晶振+1024分频下每次溢出耗时约4.2秒,9次累计就需要30多秒,和你预期的“1秒一次溢出”完全不符,最终实测延时远高于10秒。正确做法是每次进入溢出中断时,第一时间重载TCNT1初值,保证每次溢出间隔都是1秒。 - 主循环内反复调用
timer_on(),进一步拉长延时timer_on()的功能是置位TIMER1的溢出中断使能位。你把这个函数放在while(1)循环的第一行,哪怕ISR里在10秒计数完成后关闭了TIMER1中断,主循环运行一圈立刻又把中断重新打开,计数逻辑被反复重启,进一步拉长整体延时。 - 中断使能寄存器操作写错,存在寄存器误写风险
TIMER1的溢出中断使能位属于TIMSK1寄存器,你的中断服务程序里写的是通用TIMSK寄存器操作,要么编译时因为未定义标识报错,要么误操作其他定时器的中断位,完全无法正常关闭TIMER1的溢出中断。正确操作应该是TIMSK1 &= ~(1 << TOIE1); - PWM波形无输出的根因是触发逻辑永远无法触发
你设置50%占空比的触发条件是FLAG_TIMER.BIT == 1,也就是累计10次1秒溢出后置位标志。但因为前面初值不重载、中断反复重启的问题,1秒的累计计数永远到不了10次,标志位永远不会被置1;同时PWM初始化阶段没有给OCR0A设置初始值,上电后OCR0A默认值为0,输出始终是0%占空比的低电平,示波器自然测不到正常PWM波形。 - 标志位没有加volatile修饰,会被编译器优化导致主循环永远读不到置位值
你定义的位域标志FLAG_TIMER没有加volatile关键字,编译器开优化时会认为主循环里没有修改这个变量,会把对标志位的读取操作直接优化成固定读初始值0,哪怕ISR里修改了标志位,主循环也完全感知不到。 - 定时初值需要和实际晶振频率匹配验证
你当前计算的TCNT1初值0xC2F8是基于16MHz晶振、1024预分频下的1秒定时值,如果你的板卡用的是8MHz内部RC时钟,那这个初值算出来的实际定时时长是2秒,10次累计就是20秒,也会导致实测延时偏长。
修正后的调整点
- 调整ISR逻辑,每次进入溢出中断时第一时间重载TCNT1初值,保证每次溢出间隔固定为1秒,不要等到累计满10次才重载。
- 把
timer_on()的调用从while(1)主循环中挪出,放到初始化阶段仅执行一次,避免反复重启中断计数。 - 修正ISR中的寄存器操作,把对错误的
TIMSK寄存器的操作替换为正确的TIMSK1寄存器操作。 - 给标志位定义加volatile修饰,避免编译器优化:
struct { volatile unsigned int BIT: 1; } volatile FLAG_TIMER; - 在PWM初始化函数中提前设置
OCR0A的初始值(比如0),避免上电时输出电平不确定。 - 去掉重复的
sei()调用,全局中断仅需要在所有初始化完成后开启一次即可。 - 核实板卡实际使用的晶振频率,重新匹配计算TCNT1的溢出初值,保证1秒基础定时的准确性。
注意:普通定时模式下的定时器初值必须在每次进入中断时手动重载,否则溢出间隔会和预期偏差极大。
内容的提问来源于stack exchange,提问作者saravanan
相关产品推荐
相关产品推荐

