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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 06:01:01