前言:FreeRTOS内容较多,分篇发布,较为基础,旨在梳理知识,适合入门的同学
(基于正点原子STM32F103开发板V2)
(对于本篇,若有疑问,欢迎在评论区留言,作者将在两小时内解答)
(本篇参考B站正点原子FreeRTOS)
目录
前言:FreeRTOS内容较多,分篇发布,较为基础,旨在梳理知识,适合入门的同学 (基于正点原子STM32F103开发板V2)(对于本篇,若有疑问,欢迎在评论区留言,作者将在两小时内解答)(本篇参考B站正点原子FreeRTOS)
1.任务的挂起与恢复
编辑1.1任务挂起函数
1.2任务恢复函数(任务中恢复)
1.3任务恢复函数(中断中恢复)
1.4任务挂起与恢复实验
1.5总结
2.中断管理
2.1什么是中断?
2.2中断优先级分组设置
2.3中断优先级配置寄存器
2.4中断屏蔽寄存器
2.5中断管理实验
3.临界段代码保护
3.1临界段
3.2临界段代码保护函数
4.任务调度器的挂起和恢复
5.总结
1.任务的挂起与恢复
挂起:挂起任务类似暂停,可恢复; 删除任务,无法恢复
恢复:恢复被挂起的任务
FromISR:带FromISR后缀是在中断函数中专用的API函数
1.1任务挂起函数void vTaskSuspend(TaskHandle_t xTaskToSuspend)
注意:
1.此函数用于挂起任务,使用时需将宏 INCLUDE_vTaskSuspend 配置为 1
2.无论优先级如何,被挂起的任务都将不再被执行,直到任务被恢复
3.当传入的参数为NULL,则代表挂起任务自身(当前正在运行的任务)
1.2任务恢复函数(任务中恢复)
void vTaskResume(TaskHandle_t xTaskToResume)
注意:
1.使用该函数注意宏:INCLUDE_vTaskSuspend必须定义为 1
2.任务无论被 vTaskSuspend() 挂起多少次,只需在任务中调用 vTakResume() 恢复一次,就可以继续运行,且被恢复的任务会进入就绪态(不支持嵌套)
1.3任务恢复函数(中断中恢复)
BaseType_t xTaskResumeFromISR(TaskHandle_t xTaskToResume)
返回值
pdTRUE:当恢复的任务优先级大于正在执行任务的优先级时,由于抢占式调度,任务优先级高的抢占任务优先级低的,进行任务切换
pdFALSE:当恢复的任务优先级小于于正在执行任务的优先级时,不需要进行任务切换
注意:
1.使用该函数注意宏:INCLUDE_vTaskSuspend 和 INCLUDE_xTaskResumeFromISR 必须定义为 1
2.该函数专用于中断服务函数中,用于解挂被挂起任务
3.中断服务程序中要调用freeRTOS的API函数则中断优先级不能高于FreeRTOS所管理的最高优先级(特别注意)
重点说明第3点:
中断服务程序(ISR)通常在非常短的周期内运行,并且不能阻塞。如果 ISR 的优先级高于任务,那么 ISR 可能会不断打断任务的执行,导致任务无法完成,不断打断任务的执行:由于 ISR 的优先级更高,每次 ISR 被触发时,它都会抢占当前正在执行的任务。这会导致以下情况:
- 任务无法完成:如果任务正在处理一个复杂或耗时的操作,ISR 的频繁打断会导致任务无法完成其当前的工作。
- 上下文切换:每次 ISR 打断任务时,操作系统都需要保存任务的状态(上下文),然后切换到 ISR。ISR 完成后,再恢复任务的状态并继续执行。这种频繁的上下文切换会消耗大量的系统资源,并可能导致性能下降。
- 系统响应性降低:如果高优先级的中断频繁发生,低优先级任务可能会长时间得不到执行,从而降低系统的响应性和实时性
1.4任务挂起与恢复实验
(基于正点原子STM32F103开发板V2)
本实验在<动态/静态创建任务实验>的基础上进行修改
动态/静态创建任务实验的start_task、task1、task2与本次实验相同,task3在本次实验进行修改
第一步:
将任务 挂起/恢复 函数宏 INCLUDE_vTaskSuspend 配置为 1(默认为1)
路径:\User\FreeRTOSConfig.h
第二步:
修改task3
/*** @brief task3* @param pvParameters : 传入参数(未用到)* @retval 无*/ void task3(void *pvParameters) {uint8_t key = 0;while (1){key = key_scan(0);switch (key){case KEY0_PRES: /* 挂起任务1 */{vTaskSuspend(Task1Task_Handler); //调用挂起任务函数break;}case KEY1_PRES: /* 恢复任务1 */{vTaskResume(Task1Task_Handler);break;}default:{break;}}vTaskDelay(10);} }
实验现象:
每500ms,两块区域颜色变化,并计数+1
按下KEY0 task1挂起(暂停变色和计数),按下KEY1task1恢复挂起(继续变色和计数)
1.5总结
2.中断管理
2.1什么是中断?
中断:让CPU打断正常运行的程序,转而去处理紧急的事件(程序)中断优先级分组设置
2.2中断优先级分组设置
ARM Cortex-M 使用了 8 位宽的寄存器来配置中断的优先等级,这个寄存器就是中断优先级配置寄存器 ,但STM32,只用了中断优先级配置寄存器的高4位 [7 : 4],所以STM32提供了最大16级的中断优先等级
STM32 的中断优先级可以分为抢占优先级和子优先级
抢占优先级: 抢占优先级高的中断可以打断正在执行但抢占优先级低的中断
子优先级:当同时发生具有相同抢占优先级的两个中断时,子优先级数值小的优先执行
特点:
中断优先级数值越小越优先
所以,我们STM32一共有五个分配方式
通过调用函数HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4)即可完成设置(在HAL_Init中设置)
FreeRTOS官网关于中断说明:https://www.freertos.org/RTOS-Cortex-M3-M4.html
注意事项:
1.低于configMAX_SYSCALL_INTERRUPT_PRIORITY优先级的中断里才允许调用FreeRTOS 的API函数(本套实验低于5优先级,即5-15中断被允许调用API函数)
2.建议将所有优先级位指定为抢占优先级位,方便FreeRTOS管理(调用函数HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4)
3.中断优先级数值越小越优先,任务优先级数值越大越优先
2.3中断优先级配置寄存器
三个系统中断优先级配置寄存器,分别为 SHPR1、 SHPR2、 SHPR3
SHPR1寄存器地址:0xE000ED18
SHPR2寄存器地址:0xE000ED1C
SHPR3寄存器地址:0xE000ED20
表出自:《Cortex M3权威指南(中文)》第286页
例如:
我们要设置PendSV的优先级,我们只要让SHPR3寄存器地址:0xE000ED20左移16位(8
+8)我们要设置SysTick的优先级,我们只要让SHPR3寄存器地址:0xE000ED20左移24位(8
+8+8)
设置某位中断优先级路径
\Middlewares\FreeRTOS\portable\RVDS\ARM_CM3\port.c
配置FreeRTOS可管理的最高中断优先级 5-15
configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS):
将
configLIBRARY_LOWEST_INTERRUPT_PRIORITY
的值左移(8 - configPRIO_BITS)
位。如果configPRIO_BITS
是 4,那么这个表达式就变成了15 << 4
在二进制中,15 是
0000 1111
。左移 4 位后,它变成了1111 0000,这样15配置的就是寄存器的高四位了
注意:
PendSV和SysTick设置最低优先级,保证系统任务切换不会阻塞系统其他中断的响应
2.4中断屏蔽寄存器
三个中断屏蔽寄存器,分别为 PRIMASK、 FAULTMASK 和BASEPRI
FreeRTOS所使用的中断管理就是利用的BASEPRI这个寄存器
BASEPRI:屏蔽优先级低于某一个阈值的中断
FreeRTOS中的逻辑澄清
低优先级任务B执行时: 当低优先级任务B正在执行时,理想情况下,你不希望任何低优先级(数值上更大)的中断打断它,但是你希望允许高优先级(数值上更小)的中断,比如高优先级任务A的中断,能够打断B。因此,BASEPRI应该设置为一个值,该值允许高于B优先级的中断发生,而屏蔽掉低于或等于B优先级的中断。高优先级任务A执行时: 在高优先级任务A执行时,理论上没有必要调整BASEPRI,因为A已经处于较高优先级,系统中的其他中断应该都是低于A的优先级(除了可能的系统保留中断,这些通常由操作系统管理,不受用户控制)。因此,对于A的执行,不需要通过BASEPRI来保护它免受低优先级任务的中断
例如:
BASEPRI设置为0x50(5<<4,高四位执行中断),代表中断优先级在5~15内的均被屏蔽,0~4的中断优先级正常执行
(但即使5-15优先级的中断被屏蔽,在特定状态下会被触发,一旦触发,系统就会进入相应的中断服务例程(ISR)来处理这个中断)
关中断:
中断服务函数中调度FreeRTOS的API函数需特别注意:
1.中断服务函数的优先级需在FreeRTOS所管理的范围内
2.在中断服务函数里边需调用FreeRTOS的API函数,必须使用带“FromISR”后缀的函数
开中断:
BASEPRI:屏蔽优先级低于某一个阈值的中断,当设置为0时,则不关闭任何中断
2.5中断管理实验
(基于正点原子STM32F103开发板V2)
第一步:
定义两个定时器 TIM3、TIM5
路径:
\Drivers\BSP\TIMER\btim.h
#ifndef __BTIM_H #define __BTIM_H#include "./SYSTEM/sys/sys.h"/******************************************************************************************/ /* 基本定时器 定义 *//* TIMX 中断定义 * 默认是针对TIM2~TIM5, TIM12~TIM17.* 注意: 通过修改这4个宏定义,可以支持TIM1~TIM17任意一个定时器.*/#define BTIM_TIM3_INT TIM3 #define BTIM_TIM3_INT_IRQn TIM3_IRQn #define BTIM_TIM3_INT_IRQHandler TIM3_IRQHandler #define BTIM_TIM3_INT_CLK_ENABLE() do{ __HAL_RCC_TIM3_CLK_ENABLE(); }while(0) /* TIM3 时钟使能 */#define BTIM_TIM5_INT TIM5 #define BTIM_TIM5_INT_IRQn TIM5_IRQn #define BTIM_TIM5_INT_IRQHandler TIM5_IRQHandler #define BTIM_TIM5_INT_CLK_ENABLE() do{ __HAL_RCC_TIM5_CLK_ENABLE(); }while(0) /* TIM5 时钟使能 *//******************************************************************************************/void btim_tim3_int_init(uint16_t arr, uint16_t psc); void btim_tim5_int_init(uint16_t arr, uint16_t psc);#endif
第二步:
设置定时器优先级 TIM3 优先级为4 TIM5 优先级为6
路径:\Drivers\BSP\TIMER\btim.c
#include "./BSP/TIMER/btim.h" #include "./SYSTEM/USART/usart.h"TIM_HandleTypeDef g_tim3_handle; /* 定时器3句柄 */ TIM_HandleTypeDef g_tim5_handle; /* 定时器5句柄 *//*** @brief 基本定时器TIM3定时中断初始化函数* @note* 基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候* 基本定时器的时钟为APB1时钟的2倍, 而APB1为36M, 所以定时器时钟 = 72Mhz* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.* Ft=定时器工作频率,单位:Mhz** @param arr: 自动重装值。* @param psc: 时钟预分频数* @retval 无*/ void btim_tim3_int_init(uint16_t arr, uint16_t psc) {BTIM_TIM3_INT_CLK_ENABLE(); /* 使能TIM3时钟 */g_tim3_handle.Instance = BTIM_TIM3_INT; /* 通用定时器3 */g_tim3_handle.Init.Prescaler = psc; /* 分频 */g_tim3_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数器 */g_tim3_handle.Init.Period = arr; /* 自动装载值 */g_tim3_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* 时钟分频因子 */HAL_TIM_Base_Init(&g_tim3_handle);HAL_NVIC_SetPriority(BTIM_TIM3_INT_IRQn, 4, 0); /* 设置中断优先级,抢占优先级4,子优先级0 */HAL_NVIC_EnableIRQ(BTIM_TIM3_INT_IRQn); /* 开启ITM3中断 */HAL_TIM_Base_Start_IT(&g_tim3_handle); /* 使能定时器3和定时器3更新中断 */ }/*** @brief 基本定时器TIM5定时中断初始化函数* @note* 基本定时器的时钟来自APB1,当PPRE1 ≥ 2分频的时候* 基本定时器的时钟为APB1时钟的2倍, 而APB1为36M, 所以定时器时钟 = 72Mhz* 定时器溢出时间计算方法: Tout = ((arr + 1) * (psc + 1)) / Ft us.* Ft=定时器工作频率,单位:Mhz** @param arr: 自动重装值。* @param psc: 时钟预分频数* @retval 无*/ void btim_tim5_int_init(uint16_t arr, uint16_t psc) {BTIM_TIM5_INT_CLK_ENABLE(); /* 使能TIM5时钟 */g_tim5_handle.Instance = BTIM_TIM5_INT; /* 通用定时器5 */g_tim5_handle.Init.Prescaler = psc; /* 分频 */g_tim5_handle.Init.CounterMode = TIM_COUNTERMODE_UP; /* 向上计数器 */g_tim5_handle.Init.Period = arr; /* 自动装载值 */g_tim5_handle.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; /* 时钟分频因子 */HAL_TIM_Base_Init(&g_tim5_handle);HAL_NVIC_SetPriority(BTIM_TIM5_INT_IRQn, 6, 0); /* 设置中断优先级,抢占优先级6,子优先级0 */HAL_NVIC_EnableIRQ(BTIM_TIM5_INT_IRQn); /* 开启ITM5中断 */HAL_TIM_Base_Start_IT(&g_tim5_handle); /* 使能定时器5和定时器5更新中断 */ }/*** @brief 定时器中断服务函数* @param 无* @retval 无*/ void BTIM_TIM3_INT_IRQHandler(void) {HAL_TIM_IRQHandler(&g_tim3_handle); }/*** @brief 定时器中断服务函数* @param 无* @retval 无*/ void BTIM_TIM5_INT_IRQHandler(void) {HAL_TIM_IRQHandler(&g_tim5_handle); }/*** @brief 定时器更新中断回调函数 * @param htim:定时器句柄指针* @retval 无*/ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //htim 参数可以用来判断是哪个定时器触发了事件 {if (htim == (&g_tim3_handle)){printf("TIM3输出\r\n");}else if (htim == (&g_tim5_handle)){printf("TIM5输出\r\n");} }
第三步:
配置定时器 TIM3 和 TIM5
路径:\User\freertos_demo.c
任务设置与前两个实验一样
我们主要看start_task
void start_task(void *pvParameters) {taskENTER_CRITICAL(); /* 进入临界区 *//* 初始化TIM3、TIM5 */btim_tim3_int_init(10000-1, 7200-1);btim_tim5_int_init(10000-1, 7200-1);/* 创建任务1 */xTaskCreate((TaskFunction_t )task1,(const char* )"task1",(uint16_t )TASK1_STK_SIZE,(void* )NULL,(UBaseType_t )TASK1_PRIO,(TaskHandle_t* )&Task1Task_Handler);vTaskDelete(StartTask_Handler); /* 删除开始任务 */taskEXIT_CRITICAL(); /* 退出临界区 */ }
重点解释 TIM3和5的配置
我们的实验基于正点原子STM32F103开发板V2,主频时钟频率为72Mhz
TIM3 和 TIM5 都通过 APB1 总线工作,APB1 的最大频率为系统时钟的 1/2,因此在 72 MHz 的情况下,APB1 的频率为 36 MHz
1. 第一个参数(
10000-1
)
- ARR(自动重装载寄存器):
10000-1
设置了自动重装载值为 9999。这意味着定时器的计数范围是从 0 到 9999,共计 10000 个计数。当计数达到 9999 时,定时器将溢出并触发相应的中断(如果启用了中断)。2. 第二个参数(
7200-1
)
- 预分频器(PSC):
7200-1
设置了预分频器为 7199。预分频器的作用是将 TIM 时钟频率降低。以 36 MHz 的 TIM 时钟为例,设置预分频器为 7199 的计算方式如下:
3. 结果
- 由于计数频率为
5 kHz
,每个计数周期为1 / 5000
秒,即0.2 ms,
所以,当计数到9999
(10000 次计数)时,实际的溢出时间为:10000×0.2 ms=2000 ms=2 s10000×0.2 ms=2000 ms=2 s- 计数频率:当你将定时器初始化为 9999 和 7199 时,定时器将以 5 kHz 的频率计数,并且每当计数到 9999 时,会溢出并触发中断。这将使得程序每 2秒(1 秒 / 5 kHz)执行一次相应的中断服务程序(ISR)
第四步:
编写开中断、关中断函数
路径:\User\freertos_demo.c
/*** @brief task1* @param pvParameters : 传入参数(未用到)* @retval 无*/ void task1(void *pvParameters) {uint32_t task1_num = 0;while (1){if (++task1_num == 5){uint32_t task1_num = 0;printf("FreeRTOS关闭中断\r\n");portDISABLE_INTERRUPTS(); /* FreeRTOS关闭中断 */delay_ms(5000); //这里不用vTaskDelay是因为vTaskDelay会开启中断printf("FreeRTOS打开中断\r\n");portENABLE_INTERRUPTS(); /* FreeRTOS打开中断 */}vTaskDelay(1000);} }
实验现象:
task1每执行五次,FreeRTOS打开/关闭中断
2.6总结
3.临界段代码保护
3.1临界段
临界段:临界段代码也叫做临界区,是指那些必须完整运行,不能被打断的代码段,临界区直接屏蔽了中断
使用场景
3.2临界段代码保护函数
FreeRTOS 在进入临界段代码的时候需要关闭中断,当处理完临界段代码以后再打开中断
中断级进入临界段:
中断级临界段的主要目的是防止在中断服务程序(ISR)中被其他中断打断,确保在处理关键任务时的数据一致性和完整性
任务级临界区调用格式示例:
taskENTER_CRITICAL() ; //关中断 {… … /* 临界区 */ } taskEXIT_CRITICAL() ; //开中断
中断级临界区调用格式示例:
ISR{uint32_t save_status; save_status = taskENTER_CRITICAL_FROM_ISR(); //备份save_status 中断屏蔽寄存器之前的值,然后关闭中断 {… … /* 临界区 */ } taskEXIT_CRITICAL_FROM_ISR(save_status ); //将之前save_status的值读回来}
特点:
1.成对使用
2.支持嵌套
3.尽量保持临界段耗时短
特别注意:
不允许临界区代码量太大,执行时间太长,否则会造成延时中断,中断得不到及时响应,这在FreeRTOS中是不允许的
4.任务调度器的挂起和恢复
挂起任务调度器(关闭任务切换), 调用此函数不需要关闭中断
使用格式示例:
vTaskSuspendAll() ; {… … /* 内容 */ } xTaskResumeAll() ;
特点:
1.与临界区不一样的是,挂起任务调度器,未关闭中断
2.它仅仅是防止了任务之间的资源争夺,中断照样可以直接响应
3.挂起调度器的方式,适用于临界区位于任务与任务之间;既不用去延时中断,又可以做到临界区的安全
5.总结
FreeRTOS官网:https://www.freertos.org/
正点原子STM32F103开发板V2 a盘资料: https://pan.baidu.com/s/1iebOVd87jBVtoudMijboIg 提取码:1ypa
FreeRTOS内容较多,内容分篇发布,感谢大家观看
连载中...
作者留言:本人学生,大多数为网络资源,如有侵权,及时联系,如有错误的地方,欢迎指出
创作时间:2024.10.25