实战技能分享,减小开关中断对系统实时性的影响,提升系统响应速度
一、背景知识:
(1)中断延迟:从中断触发到执行中断服务程序的第一条指令这段时间就是中断延迟时间。
对于Cortex-M内核芯片,典型的中断延迟时间是12-16个时钟周期
以Cortex-M3/M4内核为例,中断触发后,执行时序效果如下,其中xPSR,PC,R0,R1,R2,R3,R12和LR是硬件自动入栈的:
(2)那么问题来了,什么是零中断延迟:
零中断延迟并不是说中断延迟时间是0,而是中断触发后,延迟时间接近芯片特性的延迟时间。
这个里面影响中断延迟的关键就是开关中断,关的时间越长,中断延迟就越长,也是最影响系统实时性的。本帖就是针对这个问题,给大家提供一些实用的解决思路。
二、中断延迟的影响:
如果源码里面有较多的开关中断,且关闭时间较长,会给系统带来什么问题呢?比如某个任务正在调用系统API函数,此时中断正好也关闭了,如果有一个紧急的中断事件被触发,这个中断就不能得到及时执行,必须等到中断开启才可以得到执行,如果关中断时间超过了紧急中断能够容忍的限度,危害是可想而知的。
正是基于此,中断延迟时间也是衡量RTOS实时操作系统的一项重要指标。
三、实战应用场景一:尽量不要使用全局开关中断,使用局部中断
(1)一些外设驱动中,如果仅需开关自己的中断就能完成效果,建议仅开关自己,不要做全局中断的开关。以我们的8个串口FIFO驱动为例:
我们可以修改下,仅对相应串口做开关中断,这就大大降低了开关中断影响:
(2)使用__set_PRIMASK(操作PRIMASK寄存器)做全局开关中断的地方,改用__set_BASEPRI(操作BASEPRI寄存器)
Basepri寄存器仅对指定优先级及其以下优先级中断做开关处理,其它高于此优先级的中断不受影响。
现在各种RTOS基本都是采用的BASEPRI做开发中断,这样可以让一些需要高实时性的中断完全不受RTOS内核中断API影响。
如果我们自己的程序里面想调用BASEPRI寄存器,可以采用如下方法:
宏定义MAX_PRIORITY为0x10,表示调用函数TX_DISABLE关闭中断的时候,仅关闭抢占优先级1到15,抢占优先级0未不关闭(NVIC的优先级分组为4,STM32仅使用高4bit),大家可以根据自己的情况做修改调整。
四、实战应用场景二:尽量不使用全局调度锁,而使用调度阀值
当前ThreadX带了这么个功能,大家有需要,可以借鉴下,这种方式的优势是我们可以仅关闭指定范围内的任务调度,而不是一刀切关闭所有任务调度。
这种功能在实际项目中还是非常实用的。比如中断里面将挂起的高优先级任务加入到就绪列表了,退出中断后,由于全局调度锁的问题,无法得到执行。
五、实战应用场景三:RTOS内核源码带的各种开关中断
现在常用的RTOS内核源码里面都有各种开关中断,仅仅这一项的存在就很难做的零延迟,所以各家都搞了一些解决方案
(1)uCOS-III早期源码搞了个中断延迟提交功能:
从uCOS-III V3.07.XX已经将其删除了,不过大家使用可以借鉴下。
(2)采用BASEPRI寄存器做开关中断处理
也就是应用场景一里的用法,这种方式也有缺点,不受控的中断服务程序里面不能RTOS里面的API。
像uCOS-II,uCOS-III,FreeRTOS和ThreadX都是采用的方式了。
(3) 零中断延迟RTOS,典型代表是RTX4和RTX5
能实现主要得益于RTX充分发挥了M内核特性,缺点是RTX主要面向ARM自家内核芯片。
那么问题来了,为什么可以做到零中断延迟?
a、任务级API通过SVC软中断调用,这样就无需做开关中断操作了(这么做的本意是为了将RTX内核代码和用户应用层代码隔离)
b、中断级API最终实现都会整到ISR FIFO统一处理。
c、还有一个是需要互斥的地方使用CM内核指令 LDREX 和 STREX。
关于RTX,我们可以借鉴的是文件rtx_core_cm.h里面提供了一批原子操作API,这样就不需要开关中断了, 支持MDK,IAR和GCC:
部分截图:
六、实战应用场景四:降低中断服务程序执行时间
如果中断服务程序执行时间过长,会影响影响低优先级中断的执行,反过来还会影响任务的响应速度。
(1) 用户自己的代码比较好控制,在中断里面要执行的代码,最好发送消息给任务,在任务里面跑实际功能。像LwIP,RTX的各种中间和ThreadX的各种中间件底层驱动基本都是这种玩法。
(2) 一些C库函数执行时间比较长,中断里面慎用,比如sprintf。
(3)中断里面最好也不要调用uint64_t类型变量,uint64_t除法执行时间贼长,如果带硬件双精度浮点,推荐使用硬件双精度浮点,速度能差10倍出来,这差距太大了。
不过使用时注意双精度浮点的精度,双精度浮点不能覆盖所有64bit整数,精度到15个小数位左右。
MATLAB:
比如整数9223372036854775807用双精度浮点来表示。
下面是利用H7-TOOL的LUA小程序在H750上的硬件双精度运行效果:
总结:
程序做的庞大且复杂时,建议中断越少越好,中断频率越低越好,任务之间耦合度越低越好
1、很多地方,其实可以完全用不到中断,中断太多会大大增加程序的不可预测性,以及各种中断优先级配置造成的奇葩问题
比如QSPI Flash字库,图库存储加载,如果用QSPI MDMA方式就必须整个中断(因为要查询执行是否完毕),此时就可以上内存映射方式,简单方便,一下子省去两个中断。
2、另外就是中断不要搞得太频繁,造成仅仅进出中断时间就给系统增加很大的负担。
3、使用了RTOS的话,任务之间的耦合问题也相当重要,能够独立的最好独立,不要跟太任务有消息的管理,否则出了问题,后期维护非常辛苦。