提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- 一、Cortex-M 中断管理
- 1.1 中断优先级分组
- 1.2 相关寄存器
- 1.3 相关宏定义
- 1.4 FreeRTOS 开关中断
- 二、临界段及其保护
- 2.1 taskENTER_CRITICAL( ) 和 taskEXIT_CRITICAL( )
- 2.2 taskENTER_CRITICAL_FROM_ISR( ) 和 taskEXIT_CRITICAL_FROM_ISR( )
- 2.3 任务调度器的挂起及恢复
前言
本章主要是讲述Freertos的中断管理及临界保护,具体的基础知识不再赘述,可以移步观看裸机开发部分。
一、Cortex-M 中断管理
1.1 中断优先级分组
Cortex-M利用8位宽的寄存器来处理优先级,但stm32实际上只使用了高四位[7:4],即16级中断优先级。stm32的中断优先级可以分为抢占优先级(高优先级抢占低优先级)和子优先级(数值小先执行)。
由于FreeRTOS 的中断配置没有处理子优先级这种情况,所以只能配置为组 4,即 NVIC_PriorityGroup_4 。此时4 位优先级就都全是抢占优先级了,没有子优先级,那么就有 0~15 共 16 个优先级。
1.2 相关寄存器
PendSV 和 SysTick 寄存器主要用于中断服务,并且在 FreeRTOS 中扮演了重要的角色,尤其是在任务切换和系统计时方面。这里首先介绍设置PendSV(PRI_14)和SysTick(PRI_15)优先级的两个寄存器。
如上图所示,4 个相临的寄存器可以拼成一个32位的寄存器(SHPR1~ SHPR3),因而FreeRTOS 在设置 PendSV 和 SysTick 的中断优先级的时候都是直接操作的地址 0xE000_ED20 + offset。为了保证任务切换不会阻碍其他任务,在 FreeRTOS中 PendSV 和 SysTick 的中断优先级都是最低的!
接着这里再讲述三个中断屏蔽寄存器。值得注意的是FreeRTOS 的开关中断就是操作 BASEPRI 寄存器来实现的,它可以关闭低于某个阈值的中断,高于这个阈值的中断就不会被关闭。
寄存器 | 描述 |
---|---|
PRIMASK | 禁止除 NMI 和 HardFalut 外的所有异常和中断,置1视为开启 |
FAULTMASK | 禁止除 NM 外的所有异常和中断,置1视为开启 |
BASEPRI | 最大9bit,只屏蔽优先级低于设置阈值的中断 |
1.3 相关宏定义
宏 | 描述 |
---|---|
configPRIO_BITS | 设置 MCU 使用几位优先级 |
configLIBRARY_LOWEST_INTERRUPT_PRIORITY | 设置最低优先级 |
configKERNEL_INTERRUPT_PRIORITY | 设置内核中断优先级 |
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY | 设置 FreeRTOS 系统可管理的最大优先级 |
configMAX_SYSCALL_INTERRUPT_PRIORITY | 低于此优先级的中断可以调用 FreeRTOS 的 API 函数,反之则禁止 |
1.4 FreeRTOS 开关中断
FreeRTOS 开关中断函数为 portENABLE_INTERRUPTS ()和portDISABLE_INTERRUPTS(),这两个函数其实是宏定义,在 portmacro.h 中有定义,如下:
#define portDISABLE_INTERRUPTS( ) vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS( ) vPortSetBASEPRI(0)
函数 vPortSetBASEPRI( )是向寄存器 BASEPRI 写入一个值,此值作为参数 ulBASEPRI 传递进来,portENABLE_INTERRUPTS( )是开中断,它传递了个 0 给 vPortSetBASEPRI( ),根据我们前面讲解 BASEPRI 寄存器可知,结果就是开中断。
二、临界段及其保护
临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设的初始化需要严格的时序,初始化过程中不能被打断。
FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。FreeRTOS 系统本身就有很多的临界段代码,这些代码都加了临界段代码保护,我们在写自己的用户程序的时候有些地方也需要添加临界段代码保护。
2.1 taskENTER_CRITICAL( ) 和 taskEXIT_CRITICAL( )
这两个函数用于进入和退出临界区,在任务上下文中使用。它们通过禁用中断来保护临界区中的代码,确保该代码段在执行期间不会被打断。
- taskENTER_CRITICAL( ):调用此函数后,所有中断都会被禁用,任务调度也会被禁止,这样可以确保当前任务在执行关键代码时不会被其他任务抢占或中断。
- taskEXIT_CRITICAL( ):退出临界区时调用该函数,恢复中断和任务调度。必须确保在进入临界区后总是退出,以防止系统卡死或影响其他任务。
// 示例
void start_task(void *pvParameters)
{taskENTER_CRITICAL(); // 禁用中断,进入临界区... // 关键代码taskEXIT_CRITICAL(); // 重新启用中断,退出临界区
}
2.2 taskENTER_CRITICAL_FROM_ISR( ) 和 taskEXIT_CRITICAL_FROM_ISR( )
这两个函数是在中断服务程序(ISR)中使用的版本,与任务上下文中的函数不同,这些函数可以确保临界区在中断中安全运行。除此之外,这个中断的优先级一定要低于前面提到的configMAX_SYSCALL_INTERRUPT_PRIORITY。
- taskENTER_CRITICAL_FROM_ISR( ):与 taskENTER_CRITICAL() 类似,它也会禁用中断,但是该函数会返回一个变量,指示中断的先前状态,以便在退出临界区时恢复到正确的状态。
- taskEXIT_CRITICAL_FROM_ISR( ):退出临界区时使用,需要传入先前的中断状态来恢复中断。
// 示例
UBaseType_t uxSavedInterruptStatus; // FreeRTOS 中用于保存中断状态的变量
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();// 保存中断状态并进入临界区... // ISR 中的关键代码
taskEXIT_CRITICAL_FROM_ISR(uxSavedInterruptStatus);// 退出临界区并恢复中断状态
2.3 任务调度器的挂起及恢复
任务调度器的挂起和恢复常用于需要确保一段代码在不被其他任务打断的情况下执行,比如同时修改多个任务的优先级或共享资源。值得注意的是,调度器的挂起和恢复会影响整个系统,不管有多少任务,它都会停止任务的切换行为,而我们前面提到的任务挂起和恢复仅影响指定的任务。
此外,与临界区不同的是任务调度器的挂起不会影响中断,它仅仅是防止任务之间相互抢占资源。适用于临界区位于任务和任务之间,可以做到既不采用延时,也可以保护临界段。
函数 | 描述 |
---|---|
vTaskSuspendAll( ) | 暂停整个任务调度器的运行 |
xTaskResumeAll( ) | 恢复任务调度器,重新允许任务切换 |
免责声明:本文参考了网上公开资料,仅用于学习交流,若有错误或侵权请联系笔者。