目前STM32芯片都是基于各种ARM Cortex-M内核的芯片,支持可编程中断优先级。支持中断抢占的同时,还中断响应的晚到和咬尾机制。中断抢占不难理解,就是某中断在运行时产生了另外的更高优先级的中断事件,低优先级的中断服务程序被暂停而去执行优先级更高的中断服务程序【后面中断服务程序用ISR表示】。所谓晚到机制,就是中断响应还在压栈阶段但没有正式进入ISR时又来了更高优先级的中断请求,等压栈操作完成后,则立刻执行高优先级的ISR,此时高优先级中断省去压栈操作,它执行完后再返回执行刚才申请压栈的低优先级ISR。而咬尾机制则是指某ISR正在执行过程中产生了新的不高于当前中断优先级的中断事件,一直等到当前中断ISR执行完毕,在退栈之前立即响应处于等待执行的ISR,此时该中断也无须再做压栈。不难理解,不论晚到机制还是咬尾机制都是为了提高中断响应速度,提高芯片性能。 下面是三幅分别代表中断抢占、中断晚到、中断咬尾的示意图。【注:从时间上讲,虽然发生中断晚到、中断咬尾可以显著节省时间,但也不等于从中断申请到进入ISR完全不要时间,下面图中没有体现出来。】 ! b: y$ S p' t/ {' G, h对于这几个中断响应的概念,我们是否可以比较直观地感受下呢? 或许有人想过一些诸如时间记录、波形输出等方法来感受之。这里,我想从中断响应先后顺序加上栈帧内容变化来体会中断抢占、中断晚到和中断咬尾。 对于Cortex M系列芯片【这里暂时将M33除外,我不知这个地方加上Security是否有很大差异。印象中只是有两套而已】,发生中断需要压栈的话,压栈内容根据是否启用浮点存储来进行。 上图表述了两种压栈格式。这里我们只关注右边的压栈格式,后面试验不使用浮点存储。 4 y( S# g! x" {# {/ \( ? 我这里借助Cortex-M4内核的STM32G4芯片的TIM1/TIM2的更新中断来完成试验。二者建立主从同步启动关系,TIM1的抢占优先级高于TIM2的,均工作在单脉冲模式,即每次启动后都能且只能产生1次更新中断。通过给二者设置相应的溢出时间参数,来模拟实现上面的三种情形。我们从中关注栈帧和个别特定寄存器的内容。【注:本试验过程中仅开启了TIM1/TIM2更新事件中断,再无其它。】 ! [4 J4 M9 N' X/ c( y对于发生抢占情形,我们通过在代码里设置断点,一方面可以看到2个中断的响应先后,另一方面可以看到因抢占动作导致栈帧内容的变动。 4 P6 r4 G; |" _+ m 对于晚到情形和晚到情形,同样也会通过在代码里设置断点查看中断执行先后顺序。 对于晚到情形,申请压栈的是低优先级中断事件,先得到执行的则是高优先级中断,同时会发现虽然响应了2次中断,却只看到1次压栈,两次ISR运行时维持同一栈内容。对于咬尾情形在栈帧内容变化上跟晚到情形类似,但实现机理不同,它申请压栈的是高优先级或同级中断事件,做咬尾操作的是低优先级或晚发生中断请求的同级中断。 先介绍两个跟中断返回有关的寄存器LR(R14)和EXC_RETURN。纠正下,EXC_RETURN不是寄存器,是微处理器动态生成的跟中断返回有关的一个值。这个值有点神秘,也很重要。神秘的就是这个值怎么产生的、放在哪里的,似乎在ARM相关手册找不到具体说明。另外,这个值本身很特别,大大区别于通用程序运行地址。如果程序里不启用浮点存储,它的值可能是下面三个。 这里我们重点关注图中的前2个,后面试验过程中会见到这两个值。结合图中信息,如果该值等于0xfffffff1,ISR执行完毕后要返回Handle Mode和Main Stack,极大可能地发生了中断嵌套;如果该值等于0xfffffff9,ISR执行完毕后要返回Thread Mode和Main Stack,意味着当前中断是在线程中产生的,不用OS的话,即Main程序被打断。 3 v2 B8 j$ g/ g- ~ 它很重要,中断返回得仰仗它。没有它,中断返回可能就乱套了。 每当中断压栈申请完成后,这个EXC_RETURN值就被硬件根据中断发生时CPU运行状态、运行模式、所用栈帧模式给生成好了,并在开始运行ISR之前将该值主动赋给LR寄存器。硬件在ISR执行完毕即将退栈返回时又自动将LR的内容提供给PC寄存器。当PC寄存器发现这个特殊的值后会不会一脸懵逼,啥玩意?地址不像地址。我们可以把这个值理解成中断返回告知书,并非程序地址。EXC_RETURN值通过LR寄存器做中间人传达给PC,主要传达下面几个信息: 1、恭喜我们完美地处理了刚才的突发事件,要归队返回了; 2、我们清楚刚才处理事情时的状态和待遇,但更要清楚返回后的状态、模式,不得以刚才的模式或状态来套返回后的模式或状态,不能因出了趟差就不知回家后的姿态和责任; 3、记住上面提到的,具体返回路线会专人提供【即之前压栈的PC值经出栈提供】; ! q6 `$ @2 b* W: c; m( [( o2 R 戏说下,知道大意即可,更多细节可以查看相关手册。退一步讲,个中细节我们旁人也真的难以知晓。 铺垫性的话题就聊到这里。下面具体看看针对中断抢占、中断晚到、中断咬尾的试验。 + U. j( n# Q* o6 q2 ^5 l$ J" [ 先看中断抢占的情形。下面截图是有关TIM1/TIM2时基参数的配置。 在前面提到的固定配置前提下,我将TIM2溢出周期比TIM1少21个脉冲【这个地方不是固定的,14~24应该都可以,具体自行验证】,二者同步启动。这样配置的目的就是确保TIM2一定是先进中断但又不至于它执行完毕了TIM1中断还没来,否则就没法看到抢占情形了。下图是TIM2首先进入中断时的情形: ' S4 P# s% }4 M, u% K! x" \! w从上图可以看出,TIM2首先进入中断,栈帧有新内容放入。LR寄存器为0xfffffff9,表示当前中断ISR是从线程模式下发生的,这里就是main程序被打断了。 下图是TIM1中断抢占TIM2中断的情形。 从上图中,明显看到栈帧内容再次被添加了8个字的内容,内容变多。我们还可以从LR寄存器的内容看出,结尾是F1,说明当前中断是抢占了其它低优先级中断,即发生了中断嵌套,它执行完后返回的还是handle模式,这跟它抢占了TIM2 ISR相吻合。 下图是TIM1 ISR执行完毕CPU再回来执行刚才被打断的TIM2 ISR情形。 从上图可以看出,TIM1中断抢占TIM2中断并完成ISR后,在返回TIM2 ISR之前还做了出栈操作。在当前TIM2 ISR里可以看到栈帧恢复到TIM2中断刚被响应时的情形,内容变少了。同样,我们可以发现LR寄存器内容也恢复到刚被响应时的值。 显然,发生抢占时除了看到ISR执行的顺序外,明显地看到栈帧内容的变化。 接着看看中断晚到的情形。先看TIM1/TIM2基本时基配置。 这样配置的目的,就是让优先级低的TIM2提前一点点发生中断,让它在申请压栈完成附近发生TIM1中断,TIM2 ISR并不能立即执行反而是TIM1抢先【不是抢占】执行ISR,之后再来运行TIM2 ISR。整个过程,只发生1次压栈、出栈操作。TIM1中断事件虽然晚发生,由于其高优先级和卡着点发生而抢先执行其ISR。 % G ^! v9 Z6 O/ F: o; X 开始运行程序后,TIM1 ISR首先得到响应。【参加图中备注说明】 + b. S5 \) W1 Q8 N下图是TIM2 ISR得到执行的情形: ' D' v' Z( _' Y6 aTIM1中断执行完毕后,回头来继续执行TIM2 ISR时,栈帧内容无变化。两次中断得到执行,只看到1次压栈操作。执行顺序靠TIMER时间参数保证TIM2的中断事件先发生并由其申请压栈, TIM1事件虽晚到却因高优先级而被优先执行其ISR。 最后来看看中断咬尾的情形。TIM1/TIM2时基参数配置如下: 二者设置的时基参数一样,上面TIM1的溢出周期减个1不是必须的,这里主要是为了确保TIM1中断事件不要晚于TIM2的即可,因为TIM1优先级高。二者同时申请中断,自然先响应TIM1的。 下图是TIM1 中断首先得到响应的情形: 下图是TIM1 ISR执行完后运行TIM2 ISR的情形: 2 P. g2 C; @( r0 iTIM2 ISR基于TIM1申请压栈并完成ISR后接着执行,也省去了压栈过程,完成2次中断只见1次压栈。从栈帧内容结果上看,中断晚到和中断咬尾很类似。不过,中断晚到情形下,申请压栈的是低优先级的中断事件,而咬尾中断情形下,申请压栈的是高优先级或者是先申请压栈的同级中断事件。比方以现在讨论的中断咬尾情形为例,如果把TIM1/TIM2的抢占优先级设置一样,TIM2溢出时间参数稍微调短点,这时玩咬尾动作的就是TIM1中断了,因为二者优先级一样,TIM2先产生溢出中断自然先响应它的,TIM1的中断则等它执行完ISR基于咬尾机制而得以执行。 1 y5 |; R2 m! m1 k 不难看出,基于晚到机制和咬尾机制而得以执行中断的行为不属于中断抢占。顺便提醒下,如果通过上面方式体验中断响应的话,测试代码尽量简单,尤其中断服务程序,否则若栈帧里压入太多其它信息,观察分析起来可能就不太方便了。 % s2 R1 P# K/ e: d; }; a$ t/ P7 L' T* p1 Q 转载自: 茶话MCU + U9 b( w, {. t. g$ ~% |* j. E$ Y 完整版请查看:附件 2 P0 z% f5 F+ _ |