在 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. 典型坑场景(你一定遇到过)
全部都能用“进中断先清标志”统一解决。
7. LAT1363 最终结论(工程师记忆版)
- 单次事件进两次中断 = 标志清太晚 + 咬尾机制
- 外设中断标志硬件清零有延迟
- 解决办法:
- 进中断第一件事清标志
- 或清标志后加 4~6 个 NOP
- 不要把清标志放在 ISR 末尾
按这个规则写中断,99% 的 “重复进中断” 问题直接消失。
阅读全文
195