【STM32之FreeRTOS(三)】任务的调度与状态
文章目录
- 【STM32之FreeRTOS(三)】任务的调度与状态
- 一、什么是任务调度?
- 二、FreeRTOS的任务调度规则
- 1.抢占式调度运行过程
- 2.时间片调度运行过程
- 三、任务的状态
- 四、任务综合小实验
- 1.实验需求
- 2.CubeMX配置
- 2.1 配置KEY1,KEY2引脚
- 2.2 打开串口配置
- 2.3 创建任务
- 2.4 串口打印测试
- 2.5 编写任务函数业务逻辑代码
- 3.效果演示
一、什么是任务调度?
任务调度就是调度器使用相关的调度算法来决定当前需要执行的哪个任务。
FreeRTOS中开启任务调度的函数是 vTaskStartScheduler()
,但在 CubeMX 中被封装为osKernelStart()
二、FreeRTOS的任务调度规则
FreeRTOS 是一个实时操作系统,它所奉行的调度规则:
-
1.高优先级抢占低优先级任务,系统永远执行最高优先级的任务(即抢占式调度)
-
抢占式调度——比如,陪女朋友 > 玩游戏
-
2. 同等优先级的任务轮转调度(即时间片调度)
-
时间片调度——比如,三个好朋友找你打球,陪第一个打1s,再陪等二个打1s,接着陪第三个打1s,依次类推循环
还有一种调度规则是协程式调度,但官方已明确表示不更新,主要是用在小容量的芯片上,用得也不多。
1.抢占式调度运行过程
当优先级更高的任务开始运行后,会去打断运行中的低优先级的任务;
当某个任务进入阻塞态后,会根据优先级去执行其他任务;
Task 1:玩游戏
Task 2:老妈喊你吃饭
Task 3:女朋友喊你看电视
阻塞:比如女朋友肚子疼上厕所
总结:
- 高优先级任务,优先执行;
- 高优先级任务不停止,低优先级任务无法执行;
- 被抢占的任务将会进入就绪态
2.时间片调度运行过程
Task 1:陪A打1s篮球
Task 2:陪B打1s兵乓球
Task 3:陪C打1s桌球
阻塞:A/B/C肚子痛上厕所(阻塞的时间0.5s就不管了,直接去和下一个人打球)
总结:
- 同等优先级任务,轮流执行,时间片流转;
- 一个时间片大小,取决为滴答定时器中断周期;
- 注意没有用完的时间片不会再使用,下次任务 Task3 得到执行,还是按照一个时间片的时钟节拍运行
三、任务的状态
FreeRTOS中任务共存在4种状态:
- Running 运行态
当任务处于实际运行状态称之为运行态,即CPU的使用权被这个任务占用(同一时间仅一个任务 处于运行态)。
比如,汽车已经在公路上跑起来了
- Ready 就绪态
处于就绪态的任务是指那些能够运行(没有被阻塞和挂起),但是当前没有运行的任务,因为同优先级或更高优先级的任务正在运行。
比如,汽车加满了油,引擎发动了,就等着一脚油门出发;分时复用,同一个时间只能跑一辆车
- Blocked 阻塞态
如果一个任务因延时,或等待信号量、消息队列、事件标志组等而处于的状态被称之为阻塞态。
比如,汽车跑着跑着遇到了红灯(调用delay延时);此时进入阻塞,就可以根据优先级安排另一辆汽车出发
- Suspended 挂起态
类似暂停,通过调用函数vTaskSuspend()
对指定任务进行挂起,挂起后这个任务将不被执行, 只有调用函数 xTaskResume()
才可以将这个任务从挂起态恢复。
比如,车故障了,我们调用vTaskSuspend() 把该辆汽车拿去修理厂维修,暂时就不跑了;等人家调用xTaskResume()告诉我们说修好了才能上路
总结:
- 仅
就绪态
可转变成运行态
- 其他状态的任务想运行,必须先转变成
就绪态
四、任务综合小实验
1.实验需求
创建 4 个任务:taskLED1,taskLED2,taskKEY1,taskKEY2
开发板采用STM32F103C8T6
任务要求如下:
- taskLED1:间隔 500ms 闪烁 LED1;
- taskLED2:间隔 1000ms 闪烁 LED2;
- taskKEY1:如果 taskLED1 存在,则按下 KEY1 后删除 taskLED1 ,否则创建 taskLED1 ;
- taskKEY2:如果 taskLED2 正常运行,则按下 KEY2 后挂起 taskLED2 ,否则恢复 taskLED2;
2.CubeMX配置
复制一份之前的工程模板(muban_freertos),打开它的CubeMX,再进行下面的操作
2.1 配置KEY1,KEY2引脚
根据自己的开发板原理图来,我这里是PA0和PA1
2.2 打开串口配置
2.3 创建任务
taskLED1,taskLED2可根据上一章的内容直接创建,这里不过多重复
创建taskKEY1
创建taskKEY2
2.4 串口打印测试
生成工程代码后,打开工程编写串口代码
/* USER CODE BEGIN 0 */
#include "stdio.h"
int fputc(int ch,FILE *f)
{
unsigned char temp[1] = {ch};
HAL_UART_Transmit(&huart1,temp,1,0xffff);
return ch;
}
/* USER CODE END 0 */
打开库设置,并勾选✔
可以在main.c里测试一下,串口打印是否成功
使用打印函数printf()
引入头文件
#include “stdio.h”
串口接线
STM32F103C8T6 | 串口调试模块 |
---|---|
PA9 | RX |
PA10 | TX |
3.3V/5V | 3.3V/5V |
GND | GND |
编译后烧录,测试成功
2.5 编写任务函数业务逻辑代码
在freertos.c下去编写代码
FreeRTOS帮我们创建好了4个任务
实现TaskLED1和TaskLED2
/* USER CODE END Header_StartTaskLED1 */
void StartTaskLED1(void const * argument)
{
/* USER CODE BEGIN StartTaskLED1 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
osDelay(500); //ms
}
/* USER CODE END StartTaskLED1 */
}
/* USER CODE END Header_StartTaskLED2 */
void StartTaskLED2(void const * argument)
{
/* USER CODE BEGIN StartTaskLED2 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);
osDelay(1000);
}
/* USER CODE END StartTaskLED2 */
}
实现TaskKEY1
/* USER CODE END Header_StartTaskKEY1 */
void StartTaskKEY1(void const * argument)
{
/* USER CODE BEGIN StartTaskKEY1 */
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){
//软件实现简单防抖
osDelay(20); //20ms
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){
printf("KEY1被按下!\r\n");
if(taskLED1Handle == NULL){
printf("任务1!不存在\r\n");
printf("开始创建任务1!\r\n");
osThreadDef(taskLED1, StartTaskLED1, osPriorityNormal, 0, 128);
taskLED1Handle = osThreadCreate(osThread(taskLED1), NULL);
if(taskLED1Handle!= NULL){
printf("任务1创建完成!\r\n");
}
}
else{
printf("开始删除任务1!\r\n");
osThreadTerminate(taskLED1Handle);
//手动将句柄置为NULL
taskLED1Handle = NULL;
printf("任务1删除成功!\r\n");
}
}
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET);
}
osDelay(10);
}
/* USER CODE END StartTaskKEY1 */
实现TaskKEY2
/* USER CODE END Header_StartTaskKEY2 */
void StartTaskKEY2(void const * argument)
{
/* USER CODE BEGIN StartTaskKEY2 */
static int flag = 0;
/* Infinite loop */
for(;;)
{
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){
//软件实现简单防抖
osDelay(20); //20ms
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET){
printf("KEY2被按下!\n");
if(flag == 0){
//任务2挂起
osThreadSuspend(taskLED2Handle);
printf("任务2已挂起(暂停)!\n");
flag = 1;
}
else{
osThreadResume(taskLED2Handle);
printf("任务2恢复!\n");
flag = 0;
}
}
while(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_1) == GPIO_PIN_RESET);
}
osDelay(10);
}
/* USER CODE END StartTaskKEY2 */
}
到此完成业务代码编写,可以进行编译烧录了
3.效果演示
编译烧录重启后,蓝灯0.5s翻转一次,黄灯1s翻转一次
当按下KEY1,蓝灯任务被删除,蓝灯状态停滞,同时串口有删除任务信息
当再次按下KEY1,蓝灯任务再次被创建,任务执行,蓝灯0.5s翻转一次,同时串口有创建任务信息
当按下KEY2,黄灯任务被挂起(暂停),黄灯状态停滞,同时串口有挂起(暂停)任务信息
当按下KEY2,黄灯任务恢复,黄灯1s翻转一次,同时串口有恢复任务信息