《FreeRTOS的配置与临界段》
目录
1.FreeRTOS配置的重要性
2.初学者使用FreeRTOSConfig.h 文件
3.“INCLUDE_”开始的宏
4.FreeRTOS 中断配置和临界段
4.1 中断简介
4.2 中断优先级分组定义
4.3优先级设置
4.4 重要的中断屏蔽寄存器
一、PRIMASK 和 FAULTMASK 寄存器
二、BASEPRI 寄存器
4.5 FreeRTOS 重要的中断配置宏
configMAX_SYSCALL_INTERRUPT_PRIORITY
4.6 FreeRTOS 开关中断
4.7FreeRTOS临界端代码
一、任务级临界段代码保护
二、中断级临界段代码保护
总结:
1.FreeRTOS配置的重要性
为什么要关心FreeRTOS的配置问题?
FreeRTOS 的系统配置文件为 FreeRTOSConfig.h,在实际使用 FreeRTOS 的时候我们时常需要根据自己需求来配置 FreeRTOS,而且不同架构的 MCU 在使用的时候配置也不同,在此配置文件中可以完成 FreeRTOS 的裁剪和配置,这是非常重要的一个文件,必须要熟悉。所以这也是我们学习FreeRTOS配置的意义。
2.初学者使用FreeRTOSConfig.h 文件
我们初学者先不必要大刀阔斧的从头编写FreeRTOSConfig文件,在 FreeRTOS 的官方 demo 中,每个工程都有一个 FreeRTOSConfig.h 文件,我们在使用的时候可以参考这个文件,甚至直接复制粘贴使用。
这里说明FreeRTOS 的配置基本是通过在 FreeRTOSConfig.h 中使用“#define”这样的语句来定义宏定义实现的。
3.“INCLUDE_”开始的宏
使用“INCLUDE_”开头的宏用来表示使能或除能 FreeRTOS 中相应的 API 函数,作用就是用来配置 FreeRTOS 中的可选 API 函数的。比如当宏 INCLUDE_vTaskPrioritySet 设置为 0 的时候 表示不能 使用函数 vTaskPrioritySet() ,当设置 为 1 的时 候就表示可 以使用函 数vTaskPrioritySet()。这个功能其实就是条件编译,在文件 tasks.c 中有如下图代码
从图,可以看出当满足条件:NCLUDE_vTaskPrioritySet == 1 的 时 候 ,函数vTaskPrioritySet()才会被编译,注意,这里为了缩小篇幅将函数 vTaskPrioritySet()的内容进行了折叠。FreeRTOS 中的裁剪和配置就是这种用条件编译的方法来实现的,不止FreeRTOS这么干,很多的协议栈、RTOS 系统和 GUI 库等都是使用条件编译的方法来完成配置和裁剪的。条件编译的好处就是节省空间,不需要的功能就不用编译,这样就可以根据实际需求来减少系统占用 的 ROM 和 RAM 大小,根据自己所使用的 MCU 来调整系统消耗,降低成本。
综上所述,当你的RTOS代码里突然出现找不到函数,函数没有调用,函数在实际情况下没有起到作用时,那就要思考有没有可能是有关函数没有配置的问题了。
上图中这样的,需要改写0和1控制是否配置的函数还有很多,且在FreeRTOS.h、task.h等文件都有,大家可以自行查阅有关手册指南,查找相关配置文件。
下面着重讲解一下中断配置文件
4.FreeRTOS 中断配置和临界段
4.1 中断简介
中断是微控制器一个很常见的特性,中断由硬件产生,当中断产生以后 CPU 就会中断当前的流程转而去处理中断服务,Cortex-M 内核的 MCU 提供了一个用于中断管理的嵌套向量中断控制器(NVIC)。Cotex-M3 的 NVIC 最多支持 240 个 IRQ(中断请求)、1 个不可屏蔽中断(NMI)、1 个 Systick(滴答定时器)定时器中断和多个系统异常。
4.2 中断优先级分组定义
当多个中断来临的时候处理器应该响应哪一个中断是由中断的优先级来决定的,高优先级的中断(优先级编号小)肯定是首先得到响应,而且高优先级的中断可以抢占低优先级的中断,这个就是中断嵌套。Cortex-M 处理器的有些中断是具有固定的优先级的,比如复位、NMI、 HardFault,这些中断的优先级都是负数,优先级也是最高的。
Cortex-M 处理器有三个固定优先级和 256 个可编程的优先级,最多有 128 个抢占等级,但是实际的优先级数量是由芯片厂商来决定的。但是,绝大多数的芯片都会精简设计的,以致实际上支持的优先级数会更少,如 8 级、16 级、32 级等,比如 STM32 就只有 16 级优先级。在设计芯片的时候会裁掉表达优先级的几个低端有效位,以减少优先级数,所以不管用多少位来表达优先级,都是 MSB 对齐的。如下图就是使用三位来表达优先级。
在图中,Bit0~Bit4 没有实现,所以读它们总是返回零,写如它们的话则会忽略写入的值。因此,对于 3 个位的情况,可是使用的优先级就是 8 个:0X00(最高优先级)、0X20、0X40、0X60、0X80、0XA0、0XC0 和 0XE0。
注意,这个是芯片厂商来决定的!不是我们能决定的,比如 STM32 就选择了 4 位作为优先级!
有读者可能就会问,优先级配置寄存器是 8 位宽的,为什么却只有 128 个抢占等级?8 位不应该是 256 个抢占等级吗?为了使抢占机能变得更可控,Cortex-M 处理器还把 256 个优先级按位分为高低两段:抢占优先级(分组优先级)和亚优先级(子优先级),NVIC 中有一个寄存器是“应用程序中断及复位控制寄存器(AIRCR)”,AIRCR 寄存器里面有个位段名为“优先级组”,AIRCR寄存器如下图:
图中中 PRIGROUP 就是优先级分组,它把优先级分为两个位段:MSB 所在的位段(左
边的)对应抢占优先级,LSB 所在的位段(右边的)对应亚优先级,如下图所示:
特别注意:这里解释一下【7:1】的意思,即共8位的优先级配置寄存器中第7位到第1位都是抢占优先级,以此类推......
在看一下 STM32 的优先级分组情况,我们前面说了 STM32 使用了 4 位,因此最多有 5 组优先级分组设置,这 5 个分组在 msic.h 中有定义,如下:
#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
0 bits for subpriority */
可以看出 STM32 有 5 个分组,但是一定要注意!STM32 中定义的分组 0 对应的值是 7!
如果我们选择分组 4,即 NVIC_PriorityGroup_4 的话,那 4 位优先级就都全是抢占优先级了,没有亚优先级,那么就有 0~15 共 16 个优先级。而移植 FreeRTOS 的时候我们配置的就是组 4,相关代码如下图:
如果使用 ALIENTEK 的基础例程的话默认配置的组 2,所以在将基础例程中的外设驱动移植到 FreeRTOS 下面的时候需要修改优先级配置。主要是 FreeRTOS 的中断配置没有处理亚优先级这种情况,所以只能配置为组 4,直接就 16 个优先级,使用起来也简单!
4.3优先级设置
每个外部中断都有一个对应的优先级寄存器,每个寄存器占 8 位,因此最大宽度是 8 位,但是最小为3位。4 个相临的优先级寄存器拼成一个 32 位寄存器。如前所述,根据优先级组的设置,优先级又可以分为高、低两个位段,分别抢占优先级和亚优先级。STM32 我们已经设置位组 4,所以就只有抢占优先级了。优先级寄存器都可以按字节访问,当然也可以按半字/字来访问,有意义的优先级寄存器数目由芯片厂商来实现
下表是中断优先级寄存器阵列(地址:0xE000_E400~0xE000_E4EF)
下表为系统异常优先级阵列(地址:0XE000_ED18~0xE000_ED23)
上面说了,4个相临的寄存器可以拼成一个32位的寄存器,
因此地址0xE000_ED20~0xE000_ED23 这四个寄存器就可以拼接成一个地址为 0xE000_ED20 的 32 位寄存器。
这一点很重要!因为 FreeRTOS 在设置 PendSV 和 SysTick 的中断优先级的时候都是直接
操作的地址 0xE000_ED20。
4.4 重要的中断屏蔽寄存器
我们重点关心的是是三个中断屏蔽寄存器:PRIMASK、FAULTMASK 和 BASEPRI
一、PRIMASK 和 FAULTMASK 寄存器
在许多应用中,需要暂时屏蔽所有的中断一执行一些对时序要求严格的任务,这个时候就
可以使用 PRIMASK 寄存器,PRIMASK 用于禁止除 NMI 和 HardFalut 外的所有异常和中断,
汇编编程的时候可以使用 CPS(修改处理器状态)指令修改 PRIMASK 寄存器的数值:
下面为有关代码:
PRIMASK 寄存器还可以通过 MRS 和 MSR 指令访问,如下:
以及:
UCOS 中的临界区代码代码保护就是通过开关中断实现的(UCOSIII 也可以使用禁止任务调度的方法来实现临界区代码保护,这里不讨论这种情况),而开关中断就是直接操作 PRIMASK寄存器的,所以在 UCOS 中关闭中断的时候时关闭了除复位、NMI 和 HardFault 以外的所有中断!FAULTMASK 比 PRIMASK 更狠,它可以连 HardFault 都屏蔽掉,使用方法和 PRIMASK 类似,FAULTMASK 会在退出时自动清零。
汇编编程的时候可以利用 CPS 指令修改 FAULTMASK 的当前状态
二、BASEPRI 寄存器
注意!FreeRTOS 的开关中断就是操作 BASEPRI 寄存器来实现的!它可以关闭低于某个阈值的中断,高于这个阈值的中断就不会被关闭!
4.5 FreeRTOS 重要的中断配置宏
configMAX_SYSCALL_INTERRUPT_PRIORITY
这里着重讲一下configMAX_SYSCALL_INTERRUPT_PRIORITY,理解它,与使用stm32中断关系非常大。
此宏是 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 左移 4 位而来的,原因和宏configKERNEL_INTERRUPT_PRIORITY 一样。此宏设置好以后,低于此优先级的中断可以安全的调用 FreeRTOS 的 API 函数,高于此优先级的中断 FreeRTOS 是不能禁止的,中断服务函数也不能调用 FreeRTOS 的 API 函数!
以 STM32 为例,有 16 个优先级,0 为最高优先级,15 为最低优先级,配置如下:
● configMAX_SYSCALL_INTERRUPT_PRIORITY==5
● configKERNEL_INTERRUPT_PRIORITY==15
由于高于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的优先级不会被 FreeRTOS 内核屏蔽,因此那些对实时性要求严格的任务就可以使用这些优先级,比如四轴飞行器中的壁障检测。
4.6 FreeRTOS 开关中断
FreeRTOS 开关中断函数为 portENABLE_INTERRUPTS ()和portDISABLE_INTERRUPTS(),这两个函数其实是宏定义,在 portmacro.h 中有定义,如下:
函数 vPortSetBASEPRI()是向寄存器 BASEPRI 写入一个值,此值作为参数 ulBASEPRI 传
递进来,portENABLE_INTERRUPTS()是开中断,它传递了个 0 给 vPortSetBASEPRI(),根据我们前面讲解 BASEPRI 寄存器可知,结果就是开中断。
函数vPortRaiseBASEPRI()是向寄存器 BASEPRI写入宏configMAX_SYSCALL_INTERRUPT_PRIORITY,那么优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断就会被屏蔽!
4.7FreeRTOS临界端代码
临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,比如有的外设
的初始化需要严格的时序,初始化过程中不能被打断。FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断。FreeRTOS 系统本身就有很多的临界段代码,这些代码都加了临界段代码保护,我们在写自己的用户程序的时候有些地方也需要添加临界段代码保护。
FreeRTOS与临界段代码保护有关的函数有4个:
taskENTER_CRITICAL()
taskEXIT_CRITICAL()
taskENTER_CRITICAL_FROM_ISR()
taskEXIT_CRITICAL_FROM_ISR(),
这四个函数其实是宏定义,在 task.h 文件中有定义。这四个函数的区别是前两个是任务级的临界段代码保护,后两个是中断级的临界段代码保护。
一、任务级临界段代码保护
taskENTER_CRITICAL()和 taskEXIT_CRITICAL()是任务级的临界代码保护,一个是进入临界段,一个是退出临界段,这两个函数是成对使用的。
用快捷键进行追根溯源后会看到这下图代码:
可以看出在进入函数 vPortEnterCritical()以后会首先关闭中断,然后给变量uxCriticalNesting加一,uxCriticalNesting 是个全局变量,用来记录临界段嵌套次数的。函数 vPortExitCritical()是退出临界段调用的,函数每次将 uxCriticalNesting 减一,只有当 uxCriticalNesting 为 0 的时候才会调用函数 portENABLE_INTERRUPTS()使能中断。这样保证了在有多个临界段代码的时候不会因为某一个临界段代码的退出而打乱其他临界段的保护,只有所有的临界段代码都退出以后才会使能中断!
任务级临界代码保护往往使用在执行任务之中,如创建、运算、判断时会用到这对临界代码
注意临界区代码一定要精简!因为进入临界区会关闭中断,这样会导致优先级低于 configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断得不到及时的响应!
二、中断级临界段代码保护
函数 taskENTER_CRITICAL_FROM_ISR()和 taskEXIT_CRITICAL_FROM_ISR()中断级别
临界段代码保护,是用在中断服务程序中的,而且这个中断的优先级一定要低于
configMAX_SYSCALL_INTERRUPT_PRIORITY!该原理是因为向寄存器 BASEPRI写入宏configMAX_SYSCALL_INTERRUPT_PRIORITY,那么优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断才会被屏蔽!
所以只能关闭低于configMAX_SYSCALL_INTERRUPT_PRIORITY优先级的中断。
中断级临界代码保护使用方法如下:
//定时器 3 中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
status_value=taskENTER_CRITICAL_FROM_ISR();//进入临界区。
total_num+=1;
printf("float_num 的值为: %d\r\n",total_num);
taskEXIT_CRITICAL_FROM_ISR(status_value); //退出临界区
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
总结:
学习完FreeRTOS的配置后,更加熟悉了使用FreeRTOS时的注意事项,在做相关实验时也出现了,因为FreeConfig.h没有配置而导致的中断失灵现象,而且临界段代码也是非常重要的一部分,常常出现在创建任务以及执行需要不被打断的任务之时,必须掌握!
希望我的文章能帮助到大家,相关FreeRTOS指南资料可以看我以前发的博客xun'zhao