基于STM32的FreeRTOS任务管理
一、FreeRTOS操作系统特点
FreeRTOS是一款实时操作系统,可以设置任务的优先级。
FreeRTOS用于MCU,占用iROM约4~9Kbyte,FreeRTOS支持裁剪功能(可裁剪性强)。
FreeRTOS 与 ucos-II区别:
- 在商用条件下,FreeRTOS免费开源,而ucos-II需要收费(非商用免费)。
- 任务设计上更容易。在ucos-II中,每个任务的优先级互不相同;而在FreeRTOS中,任务的优先级的可以相同(对于相同优先级任务,采用时间片轮询方式调度)。
ucos中表示任务优先级时,数字小的级别高;在FreeRTOS中表示任务优先级时,数字大的级别高。 - FreeRTOS是ST钦定的第三方程序。
比如STM32CubeMX软件可以直接生成含FreeRTOS的工程。
二、任务优先级
在FreeRTOS中,可以设定任务的优先级(一般是在创建任务的时候设置优先级)
任务优先级的范围 受限于工程中FreeRTOSConfig.h的configMAX_PRIORITIES 的值,低优先级号表示任务的优先级低,优先级号 0 表示最低优先级。有效的优先级号范围从 0 (configMAX_PRIORITES – 1)。
可以多个任务共用同一个优先级,那么当他们被调度运行时,调度器会让他们轮流执行(采用时间片机制)。
三、任务调度
任务调度由调度器控制,一般优先级调度当前可运行的任务中级别最高的任务。
而查询是否需要切换任务调度需要特定操作,一般,MCU会在每个时钟节拍产生后,检测是否需要切换任务调度。
- 时钟节拍发生的频率
心跳中断频率由FreeRTOSConfig.h 中的编译时配置常量 configTICK_RATE_HZ 进行配置。
比如,若当前工程中configTICK_RATE_HZ 为100,则当前系统每隔10ms产生一次时钟节拍,检测是否需要切换任务。 - 时钟节拍的来源
由STM32的SysTick(滴答定时器)提供。具体操作如下
(1)之后设计SysTick_init() 需要考虑,溢出时间为1.0/ configTICK_RATE_HZ 秒,使用SysTick的溢出中断
(2)重新设计SysTick的中断服务函数(包含检测是否需要切换任务)
四、任务的状态
-
运行状态
当前正在运行的任务处于运行状态,同一时间只有一个任务处于运行状态。 -
就绪状态
如果任务处于非运行状态,但既没有阻塞也没有挂起,则这个任务处于就绪(ready,
准备或就绪)状态 -
挂起状态
(运行vTaskSuspend()、vTaskResume())“挂起(suspended)”也是非运行状态的子状态。处于挂起状态的任务对调度器而言是不可见的。 -
阻塞状态
如果一个任务正在等待某个事件,则称这个任务处于”阻塞态(blocked)”。阻塞态是非运行态的一个子状态。
比如:void vTaskDelay( const TickType_t xTicksToDelay ); 使当前任务阻塞xTicksToDelay个节拍
五、状态的切换
上图反映了FreeRTOS下任务在就绪态、运行态、挂起态和阻塞态切换。结论:
- 只有处于就绪态的任务才能直接进入到运行态。
- 处于运行态的任务A,若运行期间存在优先级更高的其他任务B(任务B必须是处于就绪态或运行态),则在最近时钟节拍后,任务B取代任务A进入到运行态,原先任务A回到就绪态。
- 处于运行态的任务A,若运行期间调用vTaskSuspend(),则进入到挂起态。处于挂起态的任务,需要其他任务对它调用vTaskResume(),才能进入到就绪态。
- 处于运行态的任务A,若运行期间调用含有阻塞功能函数(比如延时阻塞),则进入到阻塞态。处于阻塞态的任务需要满足一定的条件才能进入到就绪态。
六、FreeRTOS的使用
以STM32一个已移植FreeRTOS的工程为例:
1.任务函数
任务函数是用于实现任务功能的。
任务函数要求:
- 必须返回 void,而且带有一个 void *指针参数。其函数原型如下
void ATaskFunction( void *pvParameters ); - 作为系统中主要执行的任务函数,函数体中要求包含while(1); 即使任务不会因为运行到函数结束而结束。任务函数设计如下:
void ATaskFunction( void *pvParameters )
{
//变量定义
//任务主要内容放在死循环中
while(1)
{
//任务主要代码
}
删除任务
}
2.创建任务
Eg:创建LED1连续闪烁任务。
- 设计LED1的任务函数
void LED1TaskFunction( void *pvParameters )
{
//变量定义
//任务主要内容放在死循环中
while(1)
{
//任务主要代码(包含至少一条让当前任务阻塞代码,使其他任务可以被执行)
LED1_FZ;
//TIM2_delayms(500);//不使用,因为该函数不会使任务进入到阻塞态
vTaskDelay( 500 );//使当前任务在500个节拍内,处于阻塞状态
}
//删除任务
}
补充:void vTaskDelay( const TickType_t xTicksToDelay );
使一个任务进入阻塞状态,等待规定时钟节拍次数达到后,回到就绪态
-
考虑给任务分配栈区深度
需要结合任务函数进行分析,最终分配栈区大小必须要大于在该任务函数中分配/申请/使用到的空间之和。
比如:当前任务函数中由于未定义变量,不需要分配特别多的空间,分配100字大小即可。 -
调用创建任务的函数
TaskHandle_t LED1_TaskHandle; //创建任务句柄(作为任务的ID)
BaseType_t xTaskCreate( LED1TaskFunction,
“LED1Task”,
100,
NULL,
1,//优先级
& LED1_TaskHandle );
3.删除任务
void vTaskDelete( TaskHandle_t xTaskToDelete )
参数:任务的结构体(ID)
4.挂起任务与解除挂起
void vTaskSuspend( TaskHandle_t xTaskToSuspend )
挂起任务(参数填保存任务ID的变量/ 任务的句柄)
让指定任务进入挂起态。
void vTaskResume( TaskHandle_t xTaskToResume )
让任务解除挂起继续执行(参数填保存任务ID的变量/ 任务的句柄)
让指定任务从挂起态到就绪态。
5.任务调度与停止
void vTaskStartScheduler( void )
使任务开始调度
void vTaskEndScheduler( void )
停止调度
七、实战
LED1:0.3秒状态反转
LED2:0.7秒状态反转
LED3:1.1秒状态反转
UART:从‘A’开始每隔1秒发送字母
#include "main.h"
void led1_taskfunction(void *pvParameters);
void led2_taskfunction(void *pvParameters);
void led3_taskfunction(void *pvParameters);
void uart1_taskfunction(void *pvParameters);
TaskHandle_t led1TaskHandle;
TaskHandle_t led2TaskHandle;
TaskHandle_t led3TaskHandle;
TaskHandle_t uart1TaskHandle;
int main(void)
{
//外设初始化
LED_init();
uart1_init(9600);
//操作系统时钟初始化
SysTick_Config(SystemCoreClock/configTICK_RATE_HZ);//溢出时间和操作系统中断间隔相同
//至少创建一个任务
xTaskCreate(led1_taskfunction,
"led1_task",
64, //任务空间,单位:字
NULL,
1, //优先级
&led1TaskHandle);
xTaskCreate(led2_taskfunction,
"led2_task",
64, //任务空间,单位:字
NULL,
2, //优先级
&led2TaskHandle);
xTaskCreate(led3_taskfunction,
"led3_task",
64, //任务空间,单位:字
NULL,
1, //优先级
&led3TaskHandle);
xTaskCreate(uart1_taskfunction,
"uart1_task",
64, //任务空间,单位:字
NULL,
2, //优先级
&uart1TaskHandle);
//开始任务的调度
vTaskStartScheduler();
}
//先设计LED1任务
void led1_taskfunction(void *pvParameters)
{
while(1)//任务函数中必须有:1.死循环结构 2.释放对CPU的占用
{
LED1_FZ;
vTaskDelay(300);//使任务阻塞300个节拍后,回到就绪态
}
}
//先设计LED2任务
void led2_taskfunction(void *pvParameters)
{
while(1)//任务函数中必须有:1.死循环结构 2.释放对CPU的占用
{
LED2_FZ;
vTaskDelay(700);//使任务阻塞700个节拍后,回到就绪态
}
}
//先设计LED3任务
void led3_taskfunction(void *pvParameters)
{
while(1)//任务函数中必须有:1.死循环结构 2.释放对CPU的占用
{
LED3_FZ;
vTaskDelay(1100);//使任务阻塞1100个节拍后,回到就绪态
}
}
//先设计uart1任务
void uart1_taskfunction(void *pvParameters)
{
static u8 ch = 'A';
while(1)//任务函数中必须有:1.死循环结构 2.释放对CPU的占用
{
printf("ch = %c\r\n",ch++);
vTaskDelay(1000);//使任务阻塞1000个节拍后,回到就绪态
}
}
本文地址:https://blog.csdn.net/qq_29011025/article/details/109014880
下一篇: golang接口解决跨域问题