1、问题描述
在使用基于 ARM CORTEX-M3、M4 或 M7 系列的 STM32 芯片做产品开发时,可能有人遇到过单次事件会触发两次中断的情形。或许对异常现象的表述不尽相同,比方有的人会说怎么中断请求标志要清 2 次才行;由于中断里有些执行操作,有人会说代码里明明只执行一次,可实际运行时却是两次;有的人会说,有些执行动作放在中断外执行正常,放到中断服务程序里又异常了等等。记得有一次,有个 STM32 用户反映,他的 SPI 实际发送效果跟程序代码里设计的完全不一样,明明是 8 位发送,硬生生变成了 16 位发送,诡异的很。诸如此类。
2、问题分析
像上面提到的这些情况,他们的中断服务程序代码都有个共性。那就是他们把清外设中断请
求标志的那行代码放在中断程序的结尾处。类似像下面的样子:
问题的原因就在于清除外设中断请求标志的代码执行完毕后,该标志位的清零相对于程序的
执行有点延迟,即标志位并没有跟程序执行指令同步完成,导致此时又产生了新的中断请求,让
CPU 又跑了一次。因为又跑了一次,给之前那个清除标志的执行动作以足够的时间来完成清零。
所以,一般来讲代码即使这样写,一次中断事件最多也就触发两次中断请求及响应。
显然,这样往往给人以中断标志清除不可靠、要清 2 次之类的感觉。如果说中断服务程序里
有放些执行代码,会感觉每次中断都快速执行了两次,让人迷惑不已。比方,下图展示的就是单
次定时器更新事件进入中断 ISR 的场景。【下面基于 ARM Cortex-M4 核 STM32G4 芯片开展
测试】
当然,知道这个原因后,我们只需将中断服务程序代码里的那句清除外设中断请求标志的代
码放前面点,或者在其后面加几句空延时也行,保证中断程序执行完毕时该标志也妥妥的被清零
了。
不过,有人通过在中断请求标志清零代码后面加空延时后,反而多了些疑问。还是以上面
STM32G4 系列的 TIMER1 更新事件的中断为例。我们会发现只需在清标志位的代码后面加上 4
句 NOP 操作就可以避免单次事件触发两次中断的问题。
以 M4 内核为例,这时有人会想,中断出栈需要 12 个 CLK,而这里只需加个 4 个 CLK 的空
延时就可以让中断标志位完成清零。保守点,为了让清零更可靠,再多两个 CLK 延时也无妨,即
使这样也不需要 12CLK。这也就意味着中断退栈后中断请求标志肯定是被清零了的,怎么又来了
一次中断请求呢?
这个问法听起来似乎无懈可击,是啊,出栈时间 12CLK,出栈后标志位早已清零,哪里来的
中断请求?
3、问题验证
其实,当执行完那句清标志的代码后,按理 CPU 该做出栈操作了。但是,由于此时硬件检
测到刚才没有实际清零的有效中断请求标志,立即做出了一个决定----不做出栈操作,而是马上
准备响应该中断请求。这就是 ARM 为 Cortex-M 内核中断设计的咬尾机制。新的中断响应基于
刚完成的中断服务程序不做压栈只稍作准备后立即运行新的中断服务程序,当然这里的“新”理
解为新的一次比较合适。说到这里,既然决定不做出栈操作了,至于出栈所需时间是多少个 CLK
已经不重要了。下图是基于同一中断请求咬尾执行的时序示意。
基于当前的应用场景,在本来可以出栈的地方结果没有出栈,而是紧接着开启新一轮中断服
务程序。上图箭头 B 所指的灰色区域是为执行新中断服务程序的准备时间,最快是 6 个系统时
钟。显然,这样可以节省出栈和再次压栈的时间。
看到这里,有人或许会想,是否可以比较直观地看到此时中断响应基于咬尾机制的实现呢。
其实也是可以的。刚才说了,基于咬尾机制的中断响应是没有出栈、压栈动作的。比方,我们还
是基于上面 STM32G4 TIM1 的更新中断,只给它一次更新事件,在中断里有意不清标志位,
TIM1 更新中断会被反复执行。借助于断点,我们只能见到一次压栈动作。
我们一起来看下。全局变量用来记录中断服务程序的运行次数。
上图表示第一次进中断的情形,堆栈里有压栈,注意,此时的栈顶位置 0x20000570。
然后不停地点击运行,因为没有做中断标志的清除,CPU 会反复运行该中断服务程序。
尽管该中断服务程序被反复运行,那个计数变量也在不停累加,但我们发现栈帧内容没有丝
毫动静,那个栈顶地址始终是 0x20000570。即虽然该中断程序被反复运行,但并没有压栈出栈
操作,一直在自己咬尾自己运行。运行过程如下图示意:
前面的测试场景中的 TIM1 中断运行就如上图示意的那样,只有第一次中断响应时有压栈,
后来就在无压栈、无出栈的情况下往复运行。当然,ARM 这么精妙的设计自然不是为了自己咬
尾自己好玩,而是为了快速高效地响应实际应用中各种竞争性的中断请求。此刻模拟的场景只是
为了分析问题的需要。
4、问题小结
这里基于多个客户共性话题,由浅入深地做了相应分析和释疑。抛砖引玉地就 ARM
Cortex-M4 内核的咬尾中断机制做了相关介绍,并基于具体测试代码做了些直观的体验。希望
能对大家在未来的 STM32 应用开发有所帮助。
本文档参考ST官方的《【应用笔记】LAT1363+浅析单次事件进入两次中断问题》文档。