一 背景
网上有许多关于FreeRTOS中断优先级的配置资料,但是在v10.0中个别配置项发生了变化,在这篇文章中详细的介绍了整个优先级管理配置方案。
ARM芯片的优先级为0~255,不同的芯片厂商所用的优先级不同,并不是全部使用,这里以ARM Cortex-M3系列为例。CM3内核的优先级分组如下:
为了更方便管理中断优先级,ST官方建议并且默认使用第5分组:NVIC_PriorityGroup_4,其优先级寄存器为8位寄存器,CM3只使用了高4位,构成0~15共16个优先级,如下图:
在stm32f2xx.h文件中,通过__NVIC_PRIO_BITS定义了CM3内核优先级使用高四位,有如下代码:
/**
* @brief Configuration of the Cortex-M3 Processor and Core Peripherals
*/
#define __CM3_REV 0x0200 /*!< Core Revision r2p0 */
#define __MPU_PRESENT 1 /*!< STM32F2XX provides an MPU */
#define __NVIC_PRIO_BITS 4 /*!< STM32F2XX uses 4 Bits for the Priority Levels */
#define __Vendor_SysTickConfig 0 /*!< Set to 1 if different SysTick Config is used */
二 FreeRTOS优先级配置(重要)
在FreeRTOSConfig.h中有三个配置项,负责优先级的管理:
#define configKERNEL_INTERRUPT_PRIORITY 255 //配置内核使用的中断优先级,这里是8 bit,默认最低优先级
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 95 /**配置系统所能调用管理的最高优先级,95转为十六进制是0x5F,最高优先级是5,优先级0-4不归系统管理**/
#define configLIBRARY_KERNEL_INTERRUPT_PRIORITY 15 /*配置系统内核优先级,这里使用的是4bit优先级,所以最低为15,跟1 中的配置相同*/
以上的配置在ARM-CM3内核中的具体释义为:FreeRTOS操作系统内核使用最低优先级,操作系统所能调用管理的最高优先级为5~15级。
我举了两个例子具体解释这个配置的含义,图1 是官方在手册中给出的例子,在这里,优先级1最低,优先级7为最高。如果分别配置 configMAX_SYSCALL_INTERRUPT_PRIORITY=3,且configKERNEL_INTERRUPT_PRIORITY=1,则优先级处于1~3的中断受到操作系统内核的影响和管理,在临界区可以被屏蔽等;优先级4~7的中断则不受操作系统影响,但是中断中绝不可以使用系统API函数(在不受系统管理的优先级中使用操作系统的函数可能会导致时序混乱,发生异常等)。
图 2 是以ARM-CM3内核,例如STM32F2xx和STM32F1系列单片机中,该内核使用4bit 优先级,优先级0最高,优先级15最低。如果设定 configMAX_SYSCALL_INTERRUPT_PRIORITY=111(0x6F),且configKERNEL_INTERRUPT_PRIORITY=255(0xFF),则0-5的优先级不受系统管理,同时也不可以调用系统的API函数;优先级6-15的中断受到系统的管理,可以使用API函数(结尾带有FromISR的函数),同时可能受到内核延时的影响,因为操作系统的临界区可以暂时屏蔽全局中断,导致中断发生时不能及时的相应,需等到退出临界区后方可执行。
图1 FreeRTOS官方的例子
图2 CM内核的ARM优先级
三 FreeRTOS任务优先级
FreeRTOS的任务优先级配置为系统专有,与硬件无关,只负责操作系统中多个任务之间调度顺序。任务优先级的个数由FreeRTOSConfig.h中的#define configMAX_PRIORITIES ( 16 )
,任务优先级0为最低,16为最高。这里简单的说一下,由于操作系统任务调度中使用的查找前导零的汇编(CLZ)指令,效率高,所以建议设定的最大任务优先级数量不超过32。
- 配置
#define configUSE_PORT_OPTIMISED_TASK_SELECTION 1
,开启前导零方式,前提是内核支持CLZ指令,百度一下。 - 配置
#define configMAX_PRIORITIES ( 16 )
,使用16个优先级数量。内核默认使用0级,最低优先级,空闲任务。 - 配置
#define configIDLE_SHOULD_YIELD 0
,仅影响空闲优先级任务,使处于空闲优先级(0级)的任务获的相同的CPU时间。
四 问题总结
临界区问题
任务名 | 任务A | 任务B |
---|---|---|
优先级 | 1 | 2 |
临界区 | 有,耗时2s | 无 |
阻塞时间 | 10ms | 20ms |
运行结果 | 一直运行 | 执行一次 |
原因分析:由于阻塞使用vTaskDelay(),在临界区时,该函数处于屏蔽状态,不计入阻塞周期,所以当任务A的阻塞时间为任务B的一半时,任务A执行两次的阻塞时间才可以使任务B进入就绪状态,进而在任务A退出临界区时切换到高优先级的任务B。
任务名 | 任务A | 任务B |
---|---|---|
优先级 | 1 | 2 |
临界区 | 无 | 无 |
阻塞时间 | 10ms | 20ms |
运行结果 | 一直运行 | 一直运行 |
当关闭临界区时,高优先级的任务可以正常抢占低优先级任务A。