扫码加入

  • 正文
  • 相关推荐
申请入驻 产业图谱

单次事件竟进两次中断?STM32 咬尾机制导致的 “重复进中断” 详解

8小时前
195
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

在 STM32(Cortex‑M3/M4/M7)开发中,很多工程师都遇到过一个触发源只产生一次事件,却进了两次中断的诡异问题:

  • 发送 8 位数据,结果发成 16 位
  • 中断里计数每次都 +2
  • 清一次标志位不够,必须清两次
  • 动作在中断外正常,进中断就异常

这些坑 90% 都不是代码写错,而是中断标志清太晚 + Cortex‑M 咬尾机制共同导致。ST 官方 LAT1363 笔记把根因讲得非常透彻,本文用最直白、最工程化的方式给你讲清楚、给方案、给正确写法。

资料获取:【应用笔记】LAT1363 浅析单次事件进入两次中断问题

1. 问题根因:一句话说透

中断标志位清零有硬件延迟。

如果把清标志放在中断服务程序最后,CPU 准备退出、出栈时,标志位还没来得及真正变 0,内核就会认为 “中断仍在挂起”,然后触发咬尾机制(Tail-chaining),直接再进一次中断,不重新压栈、快速执行。

结果就是:一次事件 → 两次 ISR 执行。

 

2. 关键机制:什么是中断咬尾?

Cortex‑M 为了提高中断响应速度,设计了咬尾机制:

  • 中断 A 刚执行完
  • 中断 A 标志仍为 1
  • 内核不执行出栈 / 压栈
  • 直接再次进入 ISR,只需要 6 个周期

正常流程:入栈 → 运行 → 出栈

咬尾流程:运行 → 直接再次运行(无栈操作)

这就导致:你以为只进了一次,实际跑了两遍。

3. 错误写法(90% 的人都这样写过)

void TIM1_UP_TIM16_IRQHandler(void)
{
    // 先干活
    INTCounter++;
    DoSomething();

    // 最后才清中断标志
    __HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_UPDATE);
}

现象:

  • 单次更新事件
  • INTCounter 变成 +2
  • 逻辑执行两遍
  • 清标志像 “不灵”

4. 正确写法(LAT1363 官方推荐)

方案 1:进中断第一时间清标志(最稳)

void TIM1_UP_TIM16_IRQHandler(void)
{
    // 第一步:清标志!
    __HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_UPDATE);

    // 再干别的
    INTCounter++;
    DoSomething();
}

方案 2:清标志后加 4~6 个 NOP 等待硬件同步

void TIM1_UP_TIM16_IRQHandler(void)
{
    INTCounter++;

    __HAL_TIM_CLEAR_FLAG(&htim1, TIM_FLAG_UPDATE);

    // 保证标志硬件写完
    __NOP();
    __NOP();
    __NOP();
    __NOP();
}

实测:4 个 NOP 足够解决问题。

5. 为什么清在最后不行,清在最前就行?

  • 清标志放最后:执行到退出时,标志还没同步完成 → 咬尾 → 再进一次
  • 清标志放最前:有足够时间让硬件完成清零 → 退出时标志已经是 0 → 不咬尾、只进一次

6. 典型坑场景(你一定遇到过)

  • 定时器更新中断 → 计数翻倍
  • SPI 发送中断 → 多发一次
  • 外部中断下降沿 → 触发两次
  • UART 接收中断 → 读两次数据
  • PWM 刹车中断 → 误动作

全部都能用“进中断先清标志”统一解决。

7. LAT1363 最终结论(工程师记忆版)

  1. 单次事件进两次中断 = 标志清太晚 + 咬尾机制
  2. 外设中断标志硬件清零有延迟
  3. 解决办法:
    • 进中断第一件事清标志
    • 或清标志后加 4~6 个 NOP
  4. 不要把清标志放在 ISR 末尾

按这个规则写中断,99% 的 “重复进中断” 问题直接消失。

相关推荐