FreeRTOS源码分析与应用开发01:中断配置与临界段
目录
1. 异常与中断的基本概念
1.1 异常分类
1.2 中断概述
1.2.1 中断处理宜短暂
1.2.2 临界段影响中断实时性
1.3 中断硬件基础
1.3.1 外设
1.3.2 中断控制器
1.3.3 CPU
1.4 中断发生环境
1.4.1 在任务上下文中发生中断
1.4.2 在中断上下文发生中断
1.5 中断相关术语
2. Cortex-M体系结构中断简介
2.1 NVIC简介
2.2 中断向量表
2.3 中断优先级分组
2.3.1 中断优先级
2.3.2 优先级分组
2.4 中断屏蔽寄存器
2.4.1 PRIMASK
2.4.2 FAULTMASK
2.4.3 BASEPRI
2.5 进入异常流程
2.6 退出异常流程
3. FreeRTOS中断配置
3.1 configPRIO_BITS
3.2 configLIBRARY_LOWEST_INTERRUPT_PRIORITY
3.3 configKERNEL_INTERRUPT_PRIORITY
3.4 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
3.5 configMAX_SYSCALL_INTERRUPT_PRIORITY
4. FreeRTOS开关中断
4.1 关闭中断
4.2 打开中断
5. FreeRTOS临界段代码保护
5.1 任务级临界段保护
5.2 中断级临界段保护
6. SMP支持
6.1 打开 / 关闭中断
6.2 任务级临界段保护
6.3 中断级临界段保护
1. 异常与中断的基本概念
1.1 异常分类
① 异常分为同步异常和异步异常
② 同步异常事件是由于执行某些指令而从处理器内部产生的,例如被除数为0异常
③ 异步异常事件的来源是外部硬件装置,中断就属于异步异常
1.2 中断概述
1.2.1 中断处理宜短暂
无论任务具有何种优先级,中断都能打断任务的运行,因此中断一般用于处理紧急事件,而且只做简单的处理(e.g. 标记该事件)
在使用FreeRTOS时,一般建议使用信号量、消息或事件标志组等标记中断的发生,将这些内核对象发布给处理任务,由处理任务再做具体处理
1.2.2 临界段影响中断实时性
FreeRTOS源码中有很多临界段,临界段虽然保护了关键代码的执行不被打断,但也会影响系统的实时性
因此调用中断屏蔽函数进入临界段时,也需要快进快出
1.3 中断硬件基础
1.3.1 外设
当外设需要请求CPU时,产生一个中断信号,该信号连接至中断控制器
1.3.2 中断控制器
① 中断控制器一方面接收外设中断信号的输入,另一方面会发送中断信号给CPU
② 可以通过对中断控制器编程实现对中断源的优先级、触发方式和打开关闭的控制
③ 在Cortex-M体系结构中,常用的中断控制器为NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器)
1.3.3 CPU
CPU会响应中断请求,中断当前正在执行的任务,转而执行中断处理程序
1.4 中断发生环境
1.4.1 在任务上下文中发生中断
如果在运行任务时发生中断,无论任务的优先级,都会打断当前任务的运行,转而运行相应的中断服务函数
1.4.2 在中断上下文发生中断
如果在执行中断服务函数的过程中,有更高优先级的中断源触发中断,根据不同的体系结构有不同的处理方式,典型为如下2种,
① 将新的中断挂起,直到当前中断处理完成后再响应
② 新的高优先级中断打断当前中断处理过程,先响应更高优先级的中断,即允许中断嵌套
说明1:Cortex-M体系结构使用的NVIC支持中断嵌套
说明2:在硬实时环境中,第1种情况是不允许发生的,因为不能尽快响应高优先级中断
1.5 中断相关术语
① 中断优先级:为使系统能够及时响应并处理所有中断,系统根据中断的重要性和紧迫程度,将中断源分为若干个级别
② 中断触发类型:外部中断请求通过一个物理信号发送到NVIC,可以是电平触发或边沿触发
③ 中断号:每个中断请求信号都会有特定的标志,使得计算机能够判断是哪个设备发出了中断请求,这个标志就是中断号
④ 中断处理程序:当外设产生中断请求后,CPU暂停当前任务,转而响应中断申请,即执行中断处理程序
⑤ 中断向量:中断服务程序的入口地址
⑥ 中断向量表:存储中断向量的存储区,中断向量与中断号对应,中断向量在中断向量表中按照中断号顺序存储
2. Cortex-M体系结构中断简介
2.1 NVIC简介
① NVIC属于Cortex-M内核,Cortex-M3/4的NVIC最多支持240个中断请求(IRQ)、1个不可屏蔽中断(NMI)、1个滴答定时器中断(SysTick)和多个系统异常
② Cortex-M处理器有多个用于管理中断和异常的寄存器,定义在NVIC和系统控制块(SCB)中,CMSIS将这些寄存器定义为结构体
以CMSIS中的core_cm3.h文件为例,
说明1:SCB / NVIC / SysTick的寄存器都位于系统控制空间(SCS)中
说明2:实际芯片支持的中断数量由芯片厂商裁剪决定
2.2 中断向量表
在Cortex-M体系结构中,所有中断都采用中断向量表的方式进行处理,即当一个中断触发时,处理器直接判定中断源,然后直接跳转到相应的固定位置进行处理
下面为CMSIS中Cortex-M3的中断向量表,
当用户需要使用自定义的中断服务函数时,只需要定义与中断向量同名的函数即可
说明:中断向量表重定位
Cortex-M体系结构支持中断向量表重定位,通过设置SCB中的VTOR寄存器实现,下面给出实例(STM32 lib库)
2.3 中断优先级分组
2.3.1 中断优先级
在Cortex-M体系结构中,有3个固定优先级(且均为负数)和256个可编程优先级(NVIC的Interrupt Priority寄存器为8位),但实际的优先级数量由芯片厂商决定,绝大多数芯片都会进行精简设计,以致实际上支持的优先级级数会更少
以STM32为例,只有16个优先级
说明1:裁剪优先级方式
NVIC的Interrupt Priority寄存器为8位,即最多有256个优先级,裁剪的方式就是限制Interrupt Priority寄存器的有效位
以下图为例,在设计芯片时裁剪掉表达优先级的5个低端有效位,则可使用的优先级为8个
说明2:每个优先级寄存器为8位,4个相邻的优先级寄存器可以组合成一个32位寄存器,因此优先级寄存器可以按字节 / 半字 / 字来访问
2.3.2 优先级分组
在确定了Interrupt Priority寄存器的有效位基础上,还有一个优先级分组的概念,即将Interrupt Priority寄存器的比特位划分为抢占优先级 + 亚优先级
共有如下8种优先级分组模式,
说明1:256个优先级,128个抢占等级的由来
如果Interrupt Priority寄存器的8位全部有效,则有256个优先级。但是即使选择分组0,也只有7bit表示抢占优先级,所以只有128个抢占等级
说明2:优先级分组设置
SCB的AIRCR寄存器可设置优先级分组
说明3:芯片可用的优先级分组
以STM32为例,只使用了bit[7:4]表示优先级,所以可用的优先级分组只有3 ~ 7
说明4:STM32的优先级分组设置
可见STM32将优先级寄存器中可用的4位均作为抢占优先级使用,共16个优先级,没有亚优先级
2.4 中断屏蔽寄存器
2.4.1 PRIMASK
PRIMASK寄存器用于禁止除NMI和HardFault之外的所有异常和中断,可使用如下两种方式开关中断
① CPS指令
CPSIE I // 清除PRIMASK寄存器,使能中断
CPSID I // 设置PRIMASK寄存器,禁止中断
② MSR指令
MOVS R0, #1
MSR PRIMASK, R0 // 将PRIMASK寄存器置1,禁止中断MOVS R0, #0
MSR PRIMASK, R0 // 将PRIMASK寄存器清零,使能中断
2.4.2 FAULTMASK
FAULTMASK在PRIMASK的基础上,连HardFault都屏蔽,也是通过两种方式开关中断
① CPS指令
CPSIE F // 清除FAULTMASK寄存器,使能中断
CPSID F // 设置FAULTMASK寄存器,禁止中断
② MSR指令
MOVS R0, #1
MSR FAULTMASK, R0 // 将FAULTMASK寄存器置1,禁止中断MOVS R0, #0
MSR FAULTMASK, R0 // 将FAULTMASK寄存器清零,使能中断
2.4.3 BASEPRI
BASEPRI寄存器用于设置屏蔽优先级的阈值,只屏蔽优先级低于阈值的中断,向该寄存器写0,则停止屏蔽
MOV R0, #0x50
MSR BASEPRI, R0 // 在STM32中,屏蔽优先级小于等于5的中断// 只使用优先级寄存器中的4位MOV R0, #0
MSR BASEPRI, R0 // 将BASEPRI清零,停止屏蔽
2.5 进入异常流程
说明1:Cortex-M中需要保存LR,是因为LR只有一个,并非banked register(在Cortex-A系列中,LR为banked register)
而且保存后LR寄存器还要在异常返回时起特殊作用,这点和Cortex-A系列非常不同。Cortex-A系列异常处理的思路是通过banked LR保存返回地址,然后用LR恢复PC
说明2:进入异常时,LR寄存器值在入栈后,会被设置为特殊的EXC_RETURN值,这个值在异常退出时影响返回动作
经过仿真,Cortex-M在首次进入PendSV异常时,LR值为0xFFFFFFF9,即退出异常时会返回线程模式,并使用MSP
2.6 退出异常流程
说明:将进入异常时设置的特殊LR(即EXC_RETURN值)写入PC,就会进入异常返回流程
在Cortex M中,只有bit 3 & bit 2是可变动的,各种组合情况如下,
3. FreeRTOS中断配置
注意:某些平台并不一定定义了所有配置宏
3.1 configPRIO_BITS
表示使用多少位来表示中断优先级,STM32芯片该宏应该为4
#define configPRIO_BITS 4
3.2 configLIBRARY_LOWEST_INTERRUPT_PRIORITY
表示最低优先级,STM32使用4位表示优先级,所以最低优先级为15
#define configLIBRARY_KERNEL_INTERRUPT_PRIORITY 15
3.3 configKERNEL_INTERRUPT_PRIORITY
FreeRTOS中设置最低优先级时使用的值,由于STM32只使用优先级寄存器的最高4位,所以255(0xFF)就是设置最低优先级
#define configKERNEL_INTERRUPT_PRIORITY 255// 一种更直观的设置方式
#define configKERNEL_INTERRUPT_PRIORITY \
(configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS))
3.4 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
表示FreeRTOS系统可管理的最大优先级,高于该优先级的中断不在系统的管理范围之内,不会被系统屏蔽(当然也就不可以使用FreeRTOS的API)
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
说明:对于实时性要求严格的中断可以使用不在FreeRTOS管理范围内的中断优先级,比如四轴飞行器中的壁障检测
3.5 configMAX_SYSCALL_INTERRUPT_PRIORITY
FreeRTOS中设置系统可管理最大优先级的值,也是考虑到STM32只使用优先级寄存器中的4位
#define configMAX_SYSCALL_INTERRUPT_PRIORITY \
( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
4. FreeRTOS开关中断
4.1 关闭中断
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{msr basepri, ulNewBASEPRIdsbisb}
}
在FreeRTOS中关中断,只是屏蔽在系统管理范围内的中断
4.2 打开中断
define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{__asm{msr basepri, ulBASEPRI}
}
在FreeRTOS中开中断,就是将BASEPRI寄存器清零,取消屏蔽
5. FreeRTOS临界段代码保护
5.1 任务级临界段保护
// 进入临界段
#define taskENTER_CRITICAL() portENTER_CRITICAL()#define portENTER_CRITICAL() vPortEnterCritical()void vPortEnterCritical( void )
{// 关闭中断portDISABLE_INTERRUPTS();// 增加临界段嵌套计数uxCriticalNesting++;if( uxCriticalNesting == 1 ){configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );}
}// 退出临界段
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()#define portEXIT_CRITICAL() vPortExitCritical()void vPortExitCritical( void )
{// 调用该函数时,临界段嵌套计数必须大于0configASSERT( uxCriticalNesting );// 减少临界段嵌套计数uxCriticalNesting--;// 当临界段嵌套计数减少到0时,打开中断if( uxCriticalNesting == 0 ){portENABLE_INTERRUPTS();}
}
说明1:任务级临界段保护只能在任务上下文中调用
taskENTER_CRITICAL函数只能在任务上下文中调用(个人觉得是使用方法上的限定,不是理论上的限制),因此当临界段嵌套计数为1时(也就是首次进入临界段时),判断当前是否在中断上下文中,如果在,则使用assert断言报错
说明2:任务级临界段保护通过临界段嵌套计数支持临界段的嵌套进入
5.2 中断级临界段保护
// 进入临界段
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{mrs ulReturn, baseprimsr basepri, ulNewBASEPRIdsbisb}// 返回之前的中断屏蔽状态return ulReturn;
}// 退出临界段
#define taskEXIT_CRITICAL_FROM_ISR( x ) \
portCLEAR_INTERRUPT_MASK_FROM_ISR( x )#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
说明:中断级临界段保护的要点,是在进入临界段时会返回当前中断屏蔽状态,并在退出临界段时恢复之前的中断屏蔽状态
6. SMP支持
说明:每篇笔记的SMP支持章节,以ESP FreeRTOS(xtensa体系结构)为例,分析如何修改FreeRTOS使其支持SMP
6.1 打开 / 关闭中断
// 关闭中断
#define portDISABLE_INTERRUPTS() \
do { XTOS_SET_INTLEVEL(XCHAL_EXCM_LEVEL); \portbenchmarkINTERRUPT_DISABLE();\
} while (0)// 打开中断
#define portENABLE_INTERRUPTS() \
do { portbenchmarkINTERRUPT_RESTORE(0); XTOS_SET_INTLEVEL(0); } while (0)
打开 / 关闭中断的命令随体系结构不同,但是思路是一致的,都是设置中断屏蔽寄存器
6.2 任务级临界段保护
// 进入临界段
#define taskENTER_CRITICAL(mux) portENTER_CRITICAL(mux)#define portENTER_CRITICAL(mux) vPortEnterCritical(mux)void __attribute__((optimize("-O3"))) vPortEnterCritical(portMUX_TYPE *mux)
{// 关中断,并返回之前的中断屏蔽状态BaseType_t oldInterruptLevel = portENTER_CRITICAL_NESTED();// 获取自旋锁vPortCPUAcquireMutex( mux );// 获取CPU编号BaseType_t coreID = xPortGetCoreID();// 临界段嵌套计数加1// 临界段嵌套计数按CPU个数分配BaseType_t newNesting = port_uxCriticalNesting[coreID] + 1;port_uxCriticalNesting[coreID] = newNesting;if( newNesting == 1 ){// 如果是首次进入临界段,保存之前的中断屏蔽状态port_uxOldInterruptState[coreID] = oldInterruptLevel;}
}// 退出临界段
#define taskEXIT_CRITICAL(mux) portEXIT_CRITICAL(mux)#define portEXIT_CRITICAL(mux) vPortExitCritical(mux)void __attribute__((optimize("-O3"))) vPortExitCritical(portMUX_TYPE *mux)
{// 释放自旋锁vPortCPUReleaseMutex( mux );BaseType_t coreID = xPortGetCoreID();BaseType_t nesting = port_uxCriticalNesting[coreID];// 递减对应CPU核的临界段嵌套计数if(nesting > 0U){nesting--;port_uxCriticalNesting[coreID] = nesting;// 当该核临界段嵌套计数为0,恢复之前的中断屏蔽状态if( nesting == 0U ){portEXIT_CRITICAL_NESTED(port_uxOldInterruptState[coreID]);}
}
说明1:自旋锁的引入
在单核FreeRTOS中,进入临界段只需要关闭中断即可,因为关闭中断后PendSV中断也不会响应,则任务切换也被终止,所以不会有任务 & 中断进入当前临界区
但是在SMP中,单纯关闭一个核的中断无法达到保护临界区的目的,因为另一个核仍在运行,所以ESP FreeRTOS中引入了自旋锁作为进出临界段函数的参数
自旋锁类型如下,
获取与释放自旋锁的操作如下,
而具体自旋锁的实现,则依据不同的体系结构而异
说明2:按CPU保存临界段嵌套计数 & 之前的中断屏蔽状态
6.3 中断级临界段保护
// 进入临界段
#define taskENTER_CRITICAL_ISR(mux) portENTER_CRITICAL_ISR(mux)#define portENTER_CRITICAL_ISR(mux) vPortEnterCritical(mux)// 退出临界段
#define taskEXIT_CRITICAL_ISR(mux) portEXIT_CRITICAL_ISR(mux)#define portEXIT_CRITICAL_ISR(mux) vPortExitCritical(mux)
在ESP FreeRTOS中,中断级临界段保护和任务级临界段保护的实现本质上是一样的,但是使用时仍然遵循FreeRTOS的标准,区分不同的上下文
个人:引入自旋锁之后,解决了多核间的临界段保护问题,但是由于自旋锁为忙等锁,因此对性能是有影响的
为了将降低对性能的影响,对于不同的共享资源使用了不同的自旋锁处理多核互斥,但是在实现中xTaskQueueMutex自旋锁保护的任务队列资源使用非常广泛,所以也只是一种缓解,还是要依靠临界段的快进快出
FreeRTOS源码分析与应用开发01:中断配置与临界段相关推荐
- FreeRTOS源码分析与应用开发02:任务管理
目录 1. 任务概述 1.1 任务表示 1.2 任务状态 1.2.1 运行态 1.2.2 就绪态 1.2.3 阻塞态 1.2.4 挂起态 1.3 任务优先级 1.3.1 FreeRTOS优先级配置 1 ...
- FreeRTOS源码分析与应用开发04:消息队列
目录 1. 队列结构 2. 创建队列 2.1 动态创建队列 2.1.1 xQueueCreate函数 2.1.2 xQueueGenericCreate函数 2.1.3 xQueueGenericRe ...
- FreeRTOS源码分析与应用开发07:事件标志组
目录 1. 概述 2. 事件标志组类型 3. 创建事件标志组 4. 删除事件标志组 5. 设置事件标志位 5.1 任务级设置 5.2 中断级设置 6. 清除事件标志位 6.1 任务级清除 6.2 中断 ...
- FreeRTOS源码分析与应用开发08:任务通知
目录 1. 概述 1.1 任务通知概念 1.2 任务通知控制结构 2. 发送任务通知 2.1 任务级发送 2.2 中断级发送 2.2.1 xTaskNotifyFromISR函数 2.2.2 vTas ...
- FreeRTOS源码分析与应用开发06:软件定时器
目录 1. 概述 1.1 软件定时器 & 硬件定时器 1.2 软件定时器精度 1.3 单次模式 & 周期模式 2. 软件定时器组件 2.1 定时器任务 2.2 定时器列表 2.3 定时 ...
- FreeRTOS源码分析与应用开发05:信号量
目录 1. 信号量概述 1.1 信号量概念 1.2 4种信号量 1.2.1 二值信号量 1.2.2 计数信号量 1.2.3 互斥信号量 1.2.4 递归互斥信号量 1.3 信号量相关控制结构 1.3. ...
- FreeRTOS源码分析与应用开发09:低功耗Tickless模式
目录 1. STM32F4低功耗模式简介 2. Tickless模式详解 2.1 如何降低功耗 2.2 关闭SysTick的问题与解决方案 2.2.1 关闭SysTick导致系统节拍计数器停止 2.2 ...
- FreeRTOS源码分析与应用开发11(完):编译、链接与部署
目录 1. 存储设备布局 2. 链接器脚本 2.1 链接器脚本生成 2.2 链接器脚本分析 2.2.1 分散加载文件 2.2.2 加载区 & 运行区 2.2.3 ER_IROM1运行区分析 2 ...
- FreeRTOS源码分析与应用开发10:内存管理
目录 1. 概述 1.1 RTOS中内存分配特点 1.2 内存堆(heap space)来源 1.2.1 ucHeap数组 1.2.2 链接器设置的堆 1.2.3 多个非连续内存堆 1.3 关于字节对 ...
最新文章
- “因为这 4 个回答,我决定录用这位软件工程师!”
- PreparedStatement动态参数的引入
- oracle中的open,Oracle 深入分析Open过程
- Spring AOP基础—JDK动态代理
- android 圆环温度控件,android 圆环倒计时控件
- 深入浅出 ASP.NET Core 与 Docker 入门课程说明
- 题解 P5301 【[GXOI/GZOI2019]宝牌一大堆】
- laravel 项目迁移_在Laravel迁移
- ionic 去掉启动页的加载动画 菊花转
- 《Essential C++》笔记之(static)静态类成员
- 2020年10月“省时查报告”十大热门报告盘点(附下载链接)
- 初探HTML5.x新特性《dialog》标签
- 数据库原理及应用(思维导图、索引、合集)
- 使用websocket实现协同编辑
- 数据集的文字标签(label)转成数字标签
- ROS导航【01】: move_base包(导航和路径规划)
- 《运营力——微信公众号 设计 策划 客服 管理 一册通》一一1.2 团队岗位介绍...
- 全景图的种类及opencv实现
- 关于adsl宽带猫的一个奇怪问题
- NULL与nullptr
热门文章
- 远程桌面配置php,Win2008 R2实现多用户远程连接设置方法(图)
- centos安装python3.6_Centos安装python3.6和pip步骤记录
- Python中使用xpath获取select option的每一行的text和value
- kafka集群部署成功后,创建生产者往指定主题里面发送消息时出错
- Spring Cloud 负载均衡
- graphpad如何加标注_如何以YOLOv3训练自己的数据集 以小蕃茄为例
- python的pip_Python3中安装pip3
- 输出100以内所有的质数
- vue 请求在子组件加载后了_从零单排vue第九课--Vue实例及生命周期
- mysql主从io为no_mysql主从同步错误解决和Slave_IO_Running: NO