06. FreeRTOS中断管理
1. 中断的基础知识
-
什么是中断:
ARM Cortex-M的NVIC最大可支持256个中断源,其中包括16个系统中断和240个外部中断。然而芯片厂商一般情况下都用不完这些资源,以正点原子的战舰开发板为例,所使用的 STM32F103ZET6芯片就只用到了10个系统中断和60个外部中断。
-
中断优先级管理:
NVIC在CMSIS中的定义
typedef struct { __IOM uint32_t ISER[8U]; /* 中断使能寄存器 */ uint32_t RESERVED0[24U]; __IOM uint32_t ICER[8U]; /* 中断除能寄存器 */ uint32_t RSERVED1[24U]; __IOM uint32_t ISPR[8U]; /* 中断使能挂起寄存器 */ uint32_t RESERVED2[24U]; __IOM uint32_t ICPR[8U]; /* 中断除能挂起寄存器 */ uint32_t RESERVED3[24U]; __IOM uint32_t IABR[8U]; /* 中断有效位寄存器 */ uint32_t RESERVED4[56U]; __IOM uint8_t IP[240U]; /* 中断优先级寄存器 */ uint32_t RESERVED5[644U]; __OM uint32_t STIR; /* 软件触发中断寄存器 */ } NVIC_Type;
其中,
__IOM uint8_t IP[240U];
为uint8_t类型的数组,数组一共有240个元素,数组中每一个8bit的元素就用来配置对应的外部中断的优先级。STM32中每个中断的优先级就由抢占优先级和子优先级共同组成,使用中断优先级配置寄存器的高4位来配置抢占优先级和子优先级,一共由5种分配方式,对应这中断优先级分组的5个组,优先级分组的5种分组情况在HAL中进行了定义,如下所示:
#define NVIC_PRIORITYGROUP_0 0x00000007U /* 优先级分组 0 */ #define NVIC_PRIORITYGROUP_1 0x00000006U /* 优先级分组 1 */ #define NVIC_PRIORITYGROUP_2 0x00000005U /* 优先级分组 2 */ #define NVIC_PRIORITYGROUP_3 0x00000004U /* 优先级分组 3 */ #define NVIC_PRIORITYGROUP_4 0x00000003U /* 优先级分组 4 */
-
三个系统中断优先级配置寄存器:
-
SHPR1
-
SHPR2
-
SHPR3
-
-
三个中断屏蔽寄存器:
-
PRIMASK
作用: PRIMASK寄存器有32bit,但只有bit0有效,是可读可写的,将PRIMASK寄存器设置为1用于屏蔽除
NMI
和HardFault
外的所有异常和中断,将PRIMASK寄存器清0用于使能中断。//用法一: CPSIE I /* 清除 PRIMASK(使能中断) */ CPSID I /* 设置 PRIMASK(屏蔽中断) */ //用法二: MRS R0, PRIMASK /* 读取 PRIMASK 值 */ MOV R0, #0 MSR PRIMASK, R0 /* 清除 PRIMASK(使能中断) */ MOV R0, #1 MSR PRIMASK, R0 /* 设置 PRIMASK(屏蔽中断) */ //用法三: __get_PRIMASK(); /* 读取 PRIMASK 值 */ __set_PRIMASK(0U); /* 清除 PRIMASK(使能中断) */ __set_PRIMASK(1U); /* 设置 PRIMASK(屏蔽中断) */
-
FAULTMASK
作用: FAULTMASK寄存器有32bit,但只有bit0有效,也是可读可写的,将FAULTMASK寄存器设置为1用于屏蔽除
NMI
外的所有异常和中断,将FAULTMASK寄存器清零用于使能中断。//用法一: CPSIE F /* 清除 FAULTMASK(使能中断) */ CPSID F /* 设置 FAULTMASK(屏蔽中断) */ //用法二: MRS R0, FAULTMASK /* 读取 FAULTMASK 值 */ MOV R0, #0 MSR FAULTMASK, R0 /* 清除 FAULTMASK(使能中断) */ MOV R0, #1 MSR FAULTMASK, R0 /* 设置 FAULTMASK(屏蔽中断) */ //用法三: __get_FAULTMASK(); /* 读取 FAULTMASK 值 */ __set_FAULTMASK(0U); /* 清除 FAULTMASK(使能中断) */ __set_FAULTMASK(1U); /* 设置 FAULTMASK(屏蔽中断) */
-
BASEPRI
作用: BASEPRI有32bit,但只有低8位[7:0]有效,也是可读可写的。BASEPRI寄存器比起PRIMASK和FAULTMASK寄存器直接屏蔽掉大部分中断的方式,BASEPRI寄存器的功能显得更加细腻,BASEPRI用于设置一个中断屏蔽的阈值,设置好BASEPRI后,中断优先级低于BASEPRI的中断就都会被屏蔽掉,FreeRTOS就是使用BASEPRI寄存器来管理受FreeRTOS管理的中断的,而不受FreeRTOS管理的中断,则不受FreeRTOS的影响。
//用法一: MRS R0, BASEPRI /* 读取 BASEPRI 值 */ MOV R0, #0 MSR BASEPRI, R0 /* 清除 BASEMASK(使能中断) */ MOV R0, #0x60 /* 举例 */ MSR BASEPRI, R0 /* 设置 BASEMASK(屏蔽优先级低于 0x60 的中断) */ //用法二: __get_BASEPRI(); /* 读取 BASEPRI 值 */ __set_BASEPRI(0); /* 清除 BASEPRI(使能中断) */ __set_BASEPRI(0x60); /* 设置 BASEPRI(屏蔽优先级小于 0x60 的中断) */
-
2. FreeRTOS中断配置项
配置项 | 功能 |
---|---|
configPRIO_BITS | 为MCU的8位优先级配置寄存器实际使用的位数。 具体配置为4 |
configLIBRARY_LOWEST_INTERRUPT_PRIORITY | 为MCU的最低中断优先等级,对于STM32,在使用FreeRTOS时,建议将中断优先级分组设置为组4,此时中断的最低优先级为15。此宏定义用于辅助配置宏 configKERNEL_INTERRUPT_PRIORITY 。 |
configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY | 用于设置FreeRTOS可管理中断的最高优先级,当中断的优先级数值小于 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 时,此中断不受FreeRTOS管理。此宏定义用于辅助配置宏configMAX_SYSCALL_INTERRUPT_PRIORITY 。 |
configKERNEL_INTERRUPT_PRIORITY | 为MCU的最低中断优先等级在中断优先级配置寄存器中的值,对于STM32,即宏configLIBRARY_LOWEST_INTERRUPT_PRIORITY 偏移4bit的值。 |
configMAX_SYSCALL_INTERRUPT_PRIORITY | 为FreeRTOS可管理中断的最高优先等级在中断优先级配置寄存器中的值,对于STM32,即宏configLIBRARY_LOWEST_INTERRUPT_PRIORITY 偏移4bit的值。 |
configMAX_API_CALL_INTERRUPT_PRIORITY | 与宏configMAX_SYSCALL_INTERRUPT_PRIORITY 是等价的。 |
FreeRTOSConfig.h中具体配置:
/* 中断嵌套行为配置 */
#ifdef __NVIC_PRIO_BITS
#define configPRIO_BITS __NVIC_PRIO_BITS
#else
#define configPRIO_BITS 4
#endif
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15 // 中断最低优先级
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5 // FreeRTOS可管理的最高中断优先级
#define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
#define configMAX_API_CALL_INTERRUPT_PRIORITY configMAX_SYSCALL_INTERRUPT_PRIORITY
FreeRTOS配置PendSV和Systick中断优先级:
FreeRTOS中开关中断的宏定义:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)
#define taskDISABLE_INTERRUPTS() portDISABLE_INTERRUPTS()
#define taskENABLE_INTERRUPTS() portENABLE_INTERRUPTS()
函数vPortRaiseBASEPRI()
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
/* 设置 BasePRI 寄存器 */
msr basepri, ulNewBASEPRI
dsb //数据同步隔离
isb //指令同步隔离
将BASEPRI
寄存器设置为宏configMAX_SYSCALL_INTERRUPT_PRIORITY
配置的制值。
函数vPortSetBASEPRI()
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
/* 设置 BasePRI 寄存器 */
msr basepri, ulBASEPRI
}
}
函数vPortSetBASEPRI()
将BASEPRI
寄存器设置为指定的值
-
临界区:
临界区是指那些必须完整运行的区域,在临界区中的代码必须完整运行,不能被打断。例如一些使用软件模拟的通信协议,通信协议在通信时,必须严格按照通信协议的时序进行,不能被打断。FreeRTOS在进出临界区的时候,通过关闭和打开受FreeRTOS管理的中断,以保护临界区中的代码。FreeRTOS的源码中就包含了许多临界区的代码,这部分代码都是用临界区进行保护,用户在使用FreeRTOS编写应用程序的时候,也要注意一些不能被打断的操作,并为这部分代码加上临界区进行保护。
/* 进入临界区 */ #define taskENTER_CRITICAL() portENTER_CRITICAL() #define portENTER_CRITICAL() vPortEnterCritical() /* 中断中进入临界区 */ #define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR() #define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI() /* 退出临界区 */ #define taskEXIT_CRITICAL() portEXIT_CRITICAL() #define portEXIT_CRITICAL() vPortExitCritical() /* 中断中退出临界区 */ #define taskEXIT_CRITICAL_FROM_ISR(x) portCLEAR_INTERRUPT_MASK_FROM_ISR(x) #define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
void vPortEnterCritical( void ) { /* 关闭受 FreeRTOS 管理的中断 */ portDISABLE_INTERRUPTS(); /* 临界区支持嵌套 */ uxCriticalNesting++; if( uxCriticalNesting == 1 ) { /* 这个函数不能在中断中调用 */ configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 ); } }
函数
vPortEnterCritical()
进入临界区就是关闭中断,支持嵌套static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void ) { uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY; __asm { /* 读取 BASEPRI 寄存器 */ mrs ulReturn, basepri /* 设置 BASEPRI 寄存器 */ msr basepri, ulNewBASEPRI dsb isb } return ulReturn; }
将BASEPRI寄存器设置为宏
configMAX_SYSCALL_INTERRUPT_PRIORITY
的值,以达到关闭中断的效果,不支持嵌套。void vPortExitCritical( void ) { /* 必须是进入过临界区才能退出 */ configASSERT( uxCriticalNesting ); uxCriticalNesting--; if( uxCriticalNesting == 0 ) { /* 打开中断 */ portENABLE_INTERRUPTS(); } }
3. 代码实现
-
程序流程图:
-
代码实现:
定时器相关配置:
/*定时器6中断初始化*/ void btim_tim6_int_init(uint16_t psc, uint16_t per) { g_tim6_handle.Instance = TIM6; g_tim6_handle.Init.Prescaler = psc; g_tim6_handle.Init.Period = per; HAL_TIM_Base_Init(&g_tim6_handle); HAL_TIM_Base_Start_IT(&g_tim6_handle); }
/*定时器7中断初始化*/ void btim_tim7_int_init(uint16_t psc, uint16_t per) { g_tim7_handle.Instance = TIM7; g_tim7_handle.Init.Prescaler = psc; g_tim7_handle.Init.Period = per; HAL_TIM_Base_Init(&g_tim7_handle); HAL_TIM_Base_Start_IT(&g_tim7_handle); //使能定时器和定时器更新中断 }
/*定时器基础MSP初始化函数*/ void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM6) { __HAL_RCC_TIM6_CLK_ENABLE(); HAL_NVIC_SetPriority(TIM6_IRQn, 6, 0); HAL_NVIC_EnableIRQ(TIM6_IRQn); } if(htim->Instance == TIM7) { __HAL_RCC_TIM7_CLK_ENABLE(); HAL_NVIC_SetPriority(TIM7_IRQn, 4, 0); HAL_NVIC_EnableIRQ(TIM7_IRQn); } }
/*定时器6中断服务函数*/ void TIM6_IRQHandler() { HAL_TIM_IRQHandler(&g_tim6_handle); }
/*定时器7中断服务函数*/ void TIM7_IRQHandler() { HAL_TIM_IRQHandler(&g_tim7_handle); }
/*定时器溢出中断回调函数*/ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM6) { printf("优先级6的正在运行!\r\n"); } if(htim->Instance == TIM7) { printf("优先级为4的中断正在运行!\r\n"); } }
相关任务的创建:
/*创建开始任务*/ void freertos_Dynamic_Create(void) { xTaskCreate((TaskFunction_t ) start_task, //指向任务函数的指针 (char * ) "start_task", //任务名称 (configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,//任务堆栈大小,字节为单位 (void * ) NULL, //传递给任务函数的参数 (UBaseType_t ) START_TASK_PRIO, //任务优先级 (TaskHandle_t * ) &start_task_handler //任务句柄:任务控制块 ); vTaskStartScheduler(); //开启任务调度 }
/*开始任务*/ void start_task(void* pvParamter) { taskENTER_CRITICAL(); // 进入临界区 xTaskCreate((TaskFunction_t ) task1, //指向任务函数的指针 (char * ) "task1", //任务名称 (configSTACK_DEPTH_TYPE) TASK1_TASK_STACK_SIZE, //任务堆栈大小,字节为单位 (void * ) NULL, //传递给任务函数的参数 (UBaseType_t ) TASK1_TASK_PRIO, //任务优先级 (TaskHandle_t * ) &task1_task_handler //任务句柄:任务控制块 ); vTaskDelete(NULL); //删除开始任务 taskEXIT_CRITICAL(); // 退出临界区 }c
/*任务一:开关中断*/ void task1(void* pvParamter) { uint32_t task1_num = 0; while(1) { if(++task1_num == 5) { printf("关中断!!!\r\n"); portDISABLE_INTERRUPTS(); delay_ms(5000); printf("开中断!!!\r\n"); portENABLE_INTERRUPTS(); } vTaskDelay(1000); } }
-
实验结果: