ESP32 Arduino定时器中断回调不触发 报duplicate func错误排查
问题原因
- 核心问题是重复占用了系统已使用的硬件定时器0。ESP32 Arduino核心默认会占用硬件定时器0实现系统基础功能(比如
millis()计时、外设时钟调度),你调用timerBegin(0, 80, true)重复初始化0号定时器时,会触发APB时钟变更回调重复注册的错误,也就是日志里打印的[E][esp32-hal-cpu.c:93] addApbChangeCallback(): duplicate func=400811F8 arg=3FFBDC54。这个错误出现后,后续的中断绑定、定时器闹钟配置流程都没有正常执行——从你的日志里看不到timerAttachInterrupt ran的输出,就能印证这点,最终导致定时回调永远不会触发。 - 代码存在两个潜在隐患:
- 你在中断回调函数里调用了
Serial.println(),串口打印属于耗时操作,且依赖系统调度,不允许在中断上下文执行,就算定时器正常触发,也大概率会导致程序崩溃。 - 中断服务函数没有加
IRAM_ATTR修饰,ESP32的中断函数需要放在内部RAM运行,否则当Flash处于缓存未加载、擦写状态时触发中断,会直接导致系统重启。
- 你在中断回调函数里调用了
修复方案
按以下步骤修改代码即可:
- 更换未被系统占用的硬件定时器:ESP32共4个硬件定时器(编号0~3),优先选用编号2或3,避开被系统默认占用的0、1号定时器。
- 精简中断服务函数:移除中断里的串口打印逻辑,中断函数只保留最核心的IO控制操作,保证执行时间极短。
- 给中断服务函数添加
IRAM_ATTR修饰,将函数加载到IRAM中运行,避免Flash访问冲突。 - 修正代码里的注释错误:现有注释写的是"Converts given seconds from us to seconds",实际逻辑是把输入的秒数转为微秒写入定时器配置,避免后续维护时产生误解。
修复后的可运行代码示例:
// 全局类实例指针 Screen* c_screen_Instance = nullptr; void Screen::turn_off_screen(){ digitalWrite(SCREEN_ENABLE, LOW); } // 中断服务函数,添加IRAM_ATTR修饰,移除串口打印 void IRAM_ATTR turn_off_screen_wrapper() { if (c_screen_Instance != nullptr) { c_screen_Instance->turn_off_screen(); } } void Screen::auto_display_power_off(int timeout){ Serial.println("auto_display_power_off called"); c_screen_Instance = this; // 将定时器编号从0修改为3,避开系统占用的定时器 auto_off_timer = timerBegin(3, 80, true); Serial.println("auto_off_timer ran"); timerAttachInterrupt(auto_off_timer, &turn_off_screen_wrapper, true); Serial.println("timerAttachInterrupt ran"); // 将输入的秒数转换为微秒,配置定时器单次触发 timerAlarmWrite(auto_off_timer, timeout * 1000000, false); timerAlarmEnable(auto_off_timer); }
额外提示:如果后续需要重复触发自动关屏逻辑,每次重新配置定时器前先调用
timerEnd(auto_off_timer)释放之前的定时器资源,避免重复初始化触发同类错误。
内容的提问来源于stack exchange,提问作者Sarge324
相关产品推荐
相关产品推荐

