FreeRTOS中断管理简介
一、中断介绍(了解)
1、什么是中断
答:让CPU打断正常运行的程序,转而去处理紧急的事件(程序),就叫中断。

2、中断执行机制
答:可简单概括为三步:
- 中断请求 :外设产生中断请求(如:GPIO外部中断、定时器中断…)。
- 响应中断 :CPU停止执行当前程序,转而执行中断处理程序(ISR)。
- 退出中断 :执行完毕,返回被打断的程序处,继续往下执行。
二、中断优先级分组设置(熟悉)
1、中断优先级分组介绍
答:ARM Cortex-M 使用了8位宽的寄存器(0-255)来配置中断的优先等级,这个寄存器就是中断优先级配置寄存器。但STM32只用了中断优先级配置寄存器的高4位[7:4],所以STM32提供了最大16级(0~15)的中断优先等级。

2、什么是抢占优先级,什么是子优先级
答:STM32的中断优先级可以分为抢占优先级和子优先级。
- 抢占优先级 :抢占优先级的中断可以打断正在执行但抢占优先级低的中断。
- 子优先级 :当同时发生具有相同抢占优先级的两个中断时,子优先级数小的优先执行。
注意 :中断优先级(抢占优先级和子优先级)数值越小,优先级越高。
- 扩展:
当两个中断的抢占优先级相同时,响应优先级(子优先级)仅决定“同时到达时的执行顺序”,而不具备中断嵌套能力。
具体逻辑可拆解为:
- 同时到达:抢占优先级相同的中断同时触发时,响应优先级高的先执行。
- 先后到达:若低响应优先级的中断已在执行,即使此时高响应优先级的中断触发,也无法打断前者,需等待低响应优先级中断处理完毕后才能执行。
这是因为响应优先级仅用于“仲裁”,不赋予“抢占权”。只有抢占优先级更高的中断才能嵌套打断当前中断
3、中断优先级配置方式
答:一共有5种配置方式,对应着中断优先级分组的5个组。

注意:FreeRTOS中为了方便管理,采用第4号(NVIC_PriorityGroup_4)分配方式。
我们用的是标准库,用NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4)来设置中断优先级分组
4、FreeRTOS中对中断优先级的管理
答:
-
低于configMAX_SYSCALL_INTERRUPT_PRIORITY优先级的中断才允许调用FreeRTOS的API函数。
-
建议将所有优先级位指定为抢占优先级位,方便FreeRTOS管理。
-
==中断优先级数值越小越优先,任务优先级数值越大越优先==。
宏configMAX_SYSCALL_INTERRUPT_PRIORITY的值是5,所以FreeRTOS中的中断优先级只能是5-15这个范围。
中断优先级数值越小越优先,跟FreeRTOS的任务优先级相反

三、中断相关寄存器(熟悉)
1、系统中断优先级配置寄存器
答:三个系统中断优先级配置寄存器,分别为 SHPR1、 SHPR2、 SHPR3 。
- SHPR1寄存器地址:0xE000ED18 ~ 0xE000ED1B
- SHPR2寄存器地址:0xE000ED1C ~ 0xE000ED1F
- SHPR3寄存器地址:0xE000ED20 ~ 0xE000ED23
一个地址是8位,一个寄存器是32位

注意:FreeRTOS主要是使用SHPR3寄存器对PendSV和Systick中断优先级进行设置(设置为最低优先级)。
==SVC、PendSV、SysTick三个系统中断很重要==
2、FreeRTOS如何配置PendSV和Systick中断优先级
答:

这个是对寄存器进行一个偏移,从而找到自己想要的那一个位

各个寄存器的首地址

偏移量:把寄存器的地址进行一个偏移,此时这个是SHPR3寄存器地址向左偏移16位是PendSV的地址,向左偏移24位是SysTick的地址

因为低4位是没有用的,因此向左偏移4位
注意:在FreeRTOS系统中PendSV和SysTick设置最低优先级(将PenSV和SysTick中断优先级设置到了15(最低优先级))。
寄存器的高地址在左,低地址在右
3、为什么将PendSV和SysTick设置最低优先级
答:保证系统任务切换不会阻塞系统其他中断的响应。
4、中断屏蔽寄存器
答:三个中断屏蔽寄存器,分别为PRIMASK、FAULTMASK和BASEPRI 。

FreeRTOS所使用的中断管理就是利用的BASEPRI这个寄存器。
FreeRTOS使用的BASEPRI这个寄存器来进行中断管理,优先级比它的值大的中断都会被屏蔽
5、FreeRTOS的关中断程序
==BASEPRI:屏蔽优先级低于某一个阈值的中断,当设置为0时,则不关闭任何中断==
答:
中断优先级在5 ~ 15的全部被关闭,0~4的我们是操作不了(意思是我们无法关闭0-4的,可以关闭5-15)
6、BASEPRI中断屏蔽寄存器
答:BASEPRI:屏蔽优先级低于某一个阈值的中断,当设置为0时,则不关闭任何中断。
比如: BASEPRI设置为0x50,代表中断优先级在5~15内的均被屏蔽,0~4的中断优先级正常执行

中断服务函数调用FreeRTOS的API函数需注意:
- 中断服务函数的优先级需在FreeRTOS所管理的范围内( 5 - 15 )。
- 在中断服务函数里边需调用FreeRTOS的API函数,必须使用带“FromISR”后缀的函数。
- 优先级分组要分为组4,全部设置为抢占式优先级
7、FreeRTOS的开中断程序
答:
总结:FreeRTOS中断管理就是利用BASEPRI寄存器实现的
8、 FreeRTOS 开关中断函数
FreeRTOS 开关中断函数其实是宏定义,在 portmacro.h 中有定义,如下:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )调用portENABLE_INTERRUPTS() 它, FreeRTOS会打开管理的所有中断
调用portDISABLE_INTERRUPTS() 它, FreeRTOS会关闭管理的所有中断
(本质上就是设置BASEPRI)
BASEPRI 寄存器是关键
在 ARM Cortex-M 内核中,BASEPRI 寄存器用于屏蔽特定优先级及以下的所有中断。FreeRTOS 利用这一特性来实现高效的中断管理。
// 实际底层函数实现(通常在 port.c 中)static portFORCE_INLINE void vPortRaiseBASEPRI( void ){ // 将 configMAX_SYSCALL_INTERRUPT_PRIORITY 写入 BASEPRI // 屏蔽所有优先级 >= configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断 __set_BASEPRI( configMAX_SYSCALL_INTERRUPT_PRIORITY << (8 - __NVIC_PRIO_BITS) );}
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulNewMask ){ // 写入 0 表示取消所有中断屏蔽 __set_BASEPRI( ulNewMask );}四、FreeRTOS中断管理实验

#include "stm32f4xx.h" // STM32F4系列微控制器头文件#include "freertos.h" // FreeRTOS实时操作系统头文件#include "task.h" // FreeRTOS任务管理头文件#include "queue.h" // FreeRTOS队列管理头文件#include "usart.h" // 串口通信头文件#include "delay.h"
// 任务配置宏定义#define START_TASK_STACK_SIZE 128 // 启动任务栈大小(字)#define START_TASK_PRIORITY 2 // 启动任务优先级
/** * @brief LED初始化函数 * 配置GPIOC的Pin13作为LED控制引脚 */void LED_Init(void){ // 使能GPIOC时钟(AHB1总线) RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
// 配置GPIO初始化结构体 GPIO_InitTypeDef GPIO_InitStructure; GPIO_StructInit(&GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13; // 选择引脚13 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // 输出模式 GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 推挽输出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输出速度50MHz GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // 无上拉下拉电阻 GPIO_Init(GPIOC, &GPIO_InitStructure); // 初始化GPIOC}
void Timer3_Init(uint16_t arr, uint16_t psc){ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_Period = arr - 1; TIM_TimeBaseInitStructure.TIM_Prescaler = psc - 1; TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
NVIC_SetPriority(TIM3_IRQn, 7); NVIC_EnableIRQ(TIM3_IRQn);
TIM_Cmd(TIM3, ENABLE);
}
void Timer4_Init(uint16_t arr, uint16_t psc){ RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInitStructure.TIM_Period = arr - 1; TIM_TimeBaseInitStructure.TIM_Prescaler = psc - 1; TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM4 ,TIM_IT_Update, ENABLE);
NVIC_SetPriority(TIM4_IRQn, 3); NVIC_EnableIRQ(TIM4_IRQn);
TIM_Cmd(TIM4, ENABLE);}
// 任务句柄声明(用于引用和管理任务)TaskHandle_t MytaskHandler1; // 任务1句柄TaskHandle_t StarttaskHandler; // 启动任务句柄
/** * @brief 任务1函数 * 功能:每500个时钟节拍打印"Task1 running" * @param arg 任务参数(未使用) */void Task1(void * arg){
uint8_t task1_num = 0; while(1){ if(++task1_num == 5){ printf("关闭中断\r\n"); portDISABLE_INTERRUPTS(); delay_ms(5000); portENABLE_INTERRUPTS(); printf("开启中断\r\n");
} vTaskDelay(500); // 延迟500个时钟节拍(约500ms,取决于系统时钟配置)
}}
/** * @brief 启动任务函数 * 功能:创建一个任务后自行删除 * @param arg 任务参数(未使用) */void Starttask(void * arg){ // 进入临界区保护串口输出 taskENTER_CRITICAL(); printf("Starttask running\r\n"); // 打印启动任务运行信息 taskEXIT_CRITICAL();
// 使用动态内存分配方式创建任务1 xTaskCreate(Task1, // 任务函数指针 "MyFirstTask", // 任务名称(用于调试) START_TASK_STACK_SIZE, // 栈大小(字) NULL, // 传递给任务的参数 START_TASK_PRIORITY, // 任务优先级 &MytaskHandler1); // 任务句柄指针
// 删除当前任务(启动任务),释放其占用的资源 vTaskDelete(NULL);}
/** * @brief 主函数 * 程序入口点,初始化硬件并启动RTOS调度器 */int main(void){ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); LED_Init(); // 初始化LED GPIO引脚 Usart1_Init(115200); // 初始化串口1,波特率115200 delay_init(168); Timer3_Init(1000, 8400); Timer4_Init(1000, 8400); // 创建启动任务(使用动态内存分配) xTaskCreate(Starttask, // 任务函数指针 "StartTask", // 任务名称 START_TASK_STACK_SIZE, // 栈大小 NULL, // 任务参数 START_TASK_PRIORITY, // 任务优先级 &StarttaskHandler); // 任务句柄
// 启动FreeRTOS调度器(程序将在此处开始运行任务) vTaskStartScheduler();
// 正常情况下,调度器启动后不会执行到这里 // 如果调度器启动失败,程序将在此处循环 while (1) { // 空循环,用于处理调度器启动失败的情况 }}
/** * @brief TIM3中断服务函数 * @retval 无 */void TIM3_IRQHandler(void){ if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) // 检查更新中断 { printf("TIM3优先级为6的正在运行\r\n"); TIM_ClearITPendingBit(TIM3, TIM_IT_Update); // 清除中断标志
}}
void TIM4_IRQHandler(void){ if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET) // 检查更新中断 { printf("TIM4优先级为4的正在运行\r\n"); TIM_ClearITPendingBit(TIM4, TIM_IT_Update); // 清除中断标志
}}
在关中断后,只有TIM4中断函数还在继续运行,TIM3中断函数被RTOS关闭了,因为TIM4优先级是3,不在5~15范围内
这里设置的TIM3和TIM4定时中断是100ms触发一次,正点原子的这个延时函数最大只支持798ms的阻塞延时(不用OS[操作系统]的情况下),至于为啥不用OS,因为它修改后的版本是HAL库的,我用的标准库,懒得再改了,知道是这样就行
总结: 关中断和开中断实验中==不能使用FreeRTOS自带的vTaskDelay()函数进行非阻塞式延时==,==因为关闭所有中断后,SysTick中断也将被禁止==,==导致节拍计数器无法更新==,==因此vTaskDelay()所等待的节拍数将永远不会达到==,任务将无限期阻塞,无法继续运行。同时也不能进行任务切换
五、课堂总结
