正点原子 STM32 FreeRTOS 学习笔记
第四章 FreeRTOS中断配置和临界端
Cortex-M中断
Cortex-M 处理器有多个用于管理中断和异常的可编程寄存器, 这些寄存器大多数都在NVIC 和系统控制块(SCB)中。
优先级分组定义
Cortex-M处理器有三个固定优先级和256个可编程的优先级,最多有128个抢占等级。但大多数芯片都会精简设计,以至于实际上支持优先级更少。在设计芯片的时候会裁掉表达优先级的几个低端有效位,以减少优先级数。下图就是8个优先级。
为了使抢占机能变得更可控, Cortex-M 处理器还把 256 个优先级按位分为高低两段:抢占优先级(分组优先级)和亚优先级(子优先级),STM32 使用了 4 位,因此最多有 5 组优先级分组设置(0:4;1:3;2:2;3:1;4:0)这 5 个分组在 msic.h 中有定义,如下:
注意! STM32 中定义的分组 0 对应的值是 7!如果我们选择分组 4,即 NVIC_PriorityGroup_4 的话,那 4 位优先级就都全是抢占优先级了,没有亚优先级,那么就有 0~15 共 16 个优先级。
ALIENTEK 的基础例程的话默认配置的组 2,所以在将基础例程中的外设驱动移植到 FreeRTOS 下面的时候需要修改优先级配置。 主要是 FreeRTOS 的中断配置没有处理亚优先级这种情况,所以只能配置为组 4,直接就 16 个优先级。(FreeRTOS不设置子优先级)
优先级设置
每个外部中断都有一个对应的优先级寄存器,每个寄存器占 8 位,因此最大宽度是 8 位,但是最小为 3 位。 4 个相临的优先级寄存器拼成一个 32 位寄存器。
0xE000_ED20~0xE000_ED23 这四个寄存器就可以拼接成一个地址为 0xE000_ED20 的 32 位寄存器。 这一点很重要! 因为 FreeRTOS 在设置 PendSV 和 SysTick 的中断优先级的时候都是直接操作的地址 0xE000_ED20。
用于中断屏蔽的特殊寄存器
在 STM32 上移植 FreeRTOS 的时候需要重点关注 PRIMASK、FAULTMASK 和 BASEPRI 这三个寄存器。
PRIMASK 和 FAULTMASK 寄存器
1. 在许多应用中,需要暂时屏蔽所有的中断一执行一些对时序要求严格的任务,这个时候就可以使用 PRIMASK 寄存器, PRIMASK 用于禁止除 NMI 和 HardFalut 外的所有异常和中断,汇编编程的时候可以使用 CPS(修改处理器状态)指令修改 PRIMASK 寄存器的数值。
UCOS 中的临界区代码代码保护就是通过开关中断实现的(UCOSIII 也可以使用禁止任务调度的方法来实现临界区代码保护,这里不讨论这种情况),而开关中断就是直接操作 PRIMASK寄存器的,所以在 UCOS 中关闭中断的时候时关闭了除复位、 NMI 和 HardFault 以外的所有中断!
2. FAULTMASK可以连 HardFault 都屏蔽掉,使用方法和 PRIMASK 类似, FAULTMASK 会在退出时自动清零。汇编编程的时候可以利用 CPS 指令修改 FAULTMASK 的当前状态。
BASEPRI 寄存器
只想屏蔽优先级低于某一个阈值的中断。那么这个作为阈值的优先级值存储在 BASEPRI 寄存器中。
FreeRTOS 的开关中断就是操作 BASEPRI 寄存器来实现的!它可以关闭低于某个阈值的中断,高于这个阈值的中断就不会被关闭。
FreeRTOS 中断配置宏
configKERNEL_INTERRUPT_PRIORITY:
宏configLIBRARY_LOWEST_INTERRUPT_PRIORITY 左移 8-configPRIO_BITS 位,也就是左移 4位。为什么要左移 4 位呢?前面我们说了, STM32 使用了 4 位作为优先级,而这 4 位是高 4 位,因 此 要 左 移 4 位 才 是 真 正 的 优 先 级 。 当 然 了 也 可 以 不 用 移 位 , 直 接 将 宏configLIBRARY_LOWEST_INTERRUPT_PRIORITY 定义为 0XF0! 不过这样看起来不直观。
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
FreeRTOS 系统可管理的最大优先级,也就是BASEPRI 寄存器说的那个阈值优先级,这个大家可以自由设置。如设置为 5。也就是高于 5 的优先级(优先级数小于 5)不归 FreeRTOS 管理!
FreeRTOS 开关中断
FreeRTOS 开关中断函数为 portENABLE_INTERRUPTS ()和 portDISABLE_INTERRUPTS(), 这两个函数其实是宏定义,在 portmacro.h 中有定义,如下:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)
临界区代码
临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设 的初始化需要严格的时序,初始化过程中不能被打断。FreeRTOS 在进入临界段代码的时候需要 关闭中断,当处理完临界段代码以后再打开中断。FreeRTOS 系统本身就有很多的临界段代码, 这些代码都加了临界段代码保护,我们在写自己的用户程序的时候有些地方也需要添加临界段 代码保护。 FreeRTOS 与 临 界 段 代 码 保 护 有 关 的 函 数 有 4 个 :
taskENTER_CRITICAL() 、 taskEXIT_CRITICAL() 、 taskENTER_CRITICAL_FROM_ISR() 和 taskEXIT_CRITICAL_FROM_ISR()。
这四个函数其实是宏定义,在 task.h 文件中有定义。这四 个函数的区别是前两个是任务级的临界段代码保护,后两个是中断级的临界段代码保护。
中断级临界代码保护使用方法如下:
//定时器 3 中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
status_value=taskENTER_CRITICAL_FROM_ISR();// (1)
total_num+=1;
printf("float_num 的值为: %d\r\n",total_num);
taskEXIT_CRITICAL_FROM_ISR(status_value); //(2)
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
(1)、进入临界区。
(2)、退出临界区。
作者:西几大人