目录
[1. FreeRTOS任务的概念](#1. FreeRTOS任务的概念)
[2. FreeRTOS任务的状态](#2. FreeRTOS任务的状态)
[3. FreeRTOS任务优先级](#3. FreeRTOS任务优先级)
[4. FreeRTOS任务的策略](#4. FreeRTOS任务的策略)
[5. 上下文切换](#5. 上下文切换)
[6. FreeRTOS任务的创建](#6. FreeRTOS任务的创建)
[7. FreeRTOS启动调度器](#7. FreeRTOS启动调度器)
[8. FreeRTOS任务的删除](#8. FreeRTOS任务的删除)
[9. FreeRTOS任务的挂起](#9. FreeRTOS任务的挂起)
[10. FreeRTOS任务的恢复](#10. FreeRTOS任务的恢复)
任务挂起vTaskSuspend和任务恢复vTaskResume的示例
FreeRTOS的任务管理
1. FreeRTOS任务的概念
FreeRTOS 是一个支持多任务的实时操作系统。
- 之前裸机开发时采用的轮询系统而言,主程序是一个死循环,CPU按照死循环中的流程执行代码。
- 而在多任务系统中,用户可以把整个系统分割为多个独立的且不能返回(死循环)的函数,这些函数就被称为任务。
应用程序中的任务都是由FreeRTOS的调度器进行调度 ,每个任务具有独立的 栈 空间 ,栈空间其实就是单片机中RAM的一段空间,通常可以提前定义一个全局数组,或者在创建任务的时候对任务的栈空间进行动态的分配,可以参考FreeRTOS的官方资料。
调度器 : 内核中负责决定在任何特定时间应执行哪些任务的部分。
调度策略 : 是调度器用来决定在任何时间点执行哪个任务的算法。

重复启停:
做不到并行,所以要频繁的切换任务,来达到每个任务都能跑的效果。
每个任务有自己的堆栈空间,即使任务停止了,处理好的没处理好的,都提前备份好。下一次又执行的时候,再从堆栈空间中的数据恢复回来,再执行一点,再把数据再备份。诸如此类一点一点地更新。

2. FreeRTOS任务的状态
对于FreeRTOS中的任务而言,FreeRTOS的调度器会根据任务的状态决定运行哪个任务,任务的状态一共有四种:运行态、就绪态、挂起态、阻塞态。
如果进入阻塞态和挂起态,CPU会被让渡出去。

3. FreeRTOS任务优先级
FreeRTOS可以为每一个创建的任务分配一个优先级,当然也可以让多个任务使用相同的优先级。
FreeRTOS的任务优先级是没有限制的,并且任务数量也没有限制。
优先级数字越小,则任务的优先级越低。
不要把任务优先级和中断优先级混为一谈。
FreeRTOS只是对应用层做优化,中断是MCU内部资源,中断是照样使用的。

设置时间片轮询的宏定义"configUSE_TIME_SLICING"
4. FreeRTOS任务的策略
FreeRTOS只有两种调度策略:
- 抢占式的调度策略
- 时间片轮询的调度策略
调度器 : 内核中负责决定在任何特定时间应执行哪些任务的部分。
调度策略 : 是调度器用来决定在任何时间点执行哪个任务的算法。


5. 上下文切换

++在任务挂起时++ 保存任务的上下文,++操作系统内核负责确保上下文保持不变++。
++在任务恢复时++ ,其保存的上下文在执行之前由++操作系统内核恢复++。
6. FreeRTOS任务的创建
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char * const pcName,
const uint16_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask )
- 默认处于就绪态
参数:
|-------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
| TaskFunction_t pxTaskCode | 任务的地址 |
| const char * const pcName | 任务的描述 |
| const uint16_t usStackDepth | 任务的堆栈大小 (stm32的MCU是32bit的,所有堆栈的宽度是32bit。每个任务的堆栈大小应该由任务的复杂度决定。 堆栈宽度 = 宽度 * 参数大小 " configMINIMAL_STACK_SIZE "任务的堆栈大小不要小于这个宏定义的值 " 130 "。) |
| void * const pvParameters | 任务接口的参数,无需传参则填NULL |
| UBaseType_t uxPriority | 任务的优先级,范围 0 ~ configMAX_PRIORITIES - 1 |
| TaskHandle_t * const pxCreatedTask | 任务的句柄,是为了提供给其他函数使用的(建议把任务的句柄进行存储,可以理解为任务的句柄) |
函数调用的嵌套深度:从一个函数跳到另一个函数,出现嵌套,每嵌套一次都要有一块空间来存储函数的堆栈内容。
uxStackDepth = 堆栈宽度 * 自定义uxStackDepth
堆栈宽度:MCU当前为32bit

任务的创建xTaskCreate的示例
cpp
#include "stm32f4xx.h" //必须要包含的头文件
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "LED.h"
void Task( void * pvParameters )
{
for(;;)
{
//控制灯默认不亮
GPIO_ResetBits(GPIOF,GPIO_Pin_9);
}
}
int main()
{
//1.硬件的初始化
LED_Config();
BaseType_t xReturned;
TaskHandle_t xHandle = NULL; //用于存储创建的任务句柄
//2.创建一个任务
xReturned = xTaskCreate(
Task, //任务的地址
"Task", //任务的描述
64, //任务的堆栈大小 64 * 32 / 8 = 256字节
NULL, //任务接口的参数,无需传参则填NULL
1, //任务的优先级 范围 0 ~ configMAX_PRIORITIES - 1
&xHandle //任务的句柄,是为了提供给其他函数使用的
);
//错误处理
if(xReturned != pdPASS)
{
}
//3.启动调度器
vTaskStartScheduler();
while(1)
{
}
}
7. FreeRTOS启动调度器
void vTaskStartScheduler( void ):
当创建了多个任务,是需要FreeRTOS的调度器利用相关的调度策略对任务进行调度,如果不启动调度器,则任务是不会被执行。
调度器在运行的时候会生成一个空闲任务(优先级为0)
空闲任务不需要手动创建,它在调度器启动时,自动创建空闲任务。

除非内存不足,否则不会执行到 " vTaskStartScheduler() "后面的代码。
一旦调度器启用之后,所有的任务全面由调度器接管,到底执行哪个任务由调度器的调度策略决定,调度策略由优先级决定。
8. FreeRTOS任务的删除
void vTaskDelete( TaskHandle_t xTask ):
被删除的任务将从所有的就绪、阻塞、挂起和事件的列表中移除。
调用vTaskDelect(),则之前申请的空间就会释放掉。但 " 任务代码分配的内存 "(eg.mallco申请的内存)就不会释放,就要在删除之前释放。
参数:
xTask 待删除的任务的句柄。传递NULL将导致调用任务被删除

任务删除vTaskDelete的示例
cpp
#include "stm32f4xx.h" //必须要包含的头文件
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "USART.h"
TaskHandle_t xHandle1 = NULL; //用于存储创建的任务句柄
TaskHandle_t xHandle2 = NULL;
void Task1( void * pvParameters )
{
SendComputerString("task_1 is start\r\n");
for(;;)
{
SendComputerString("task_1 is running\r\n");
vTaskDelete(NULL);
}
}
void Task2( void * pvParameters )
{
SendComputerString("task_2 is start\r\n");
for(;;)
{
SendComputerString("task_2 is running\r\n");
vTaskDelete(NULL);
}
}
int main()
{
/*现象预判:1.task_2 is start
2.task_2 is running
3.task2把自己的任务删除
4.task_1 is start
5.task_1 is running
6.task1把自己的任务删除
*/
//1.硬件的初始化
USART1_Config(9600);
//2.创建一个任务
xTaskCreate(
Task1, //任务的地址
"Task1", //任务的描述
64, //任务的堆栈大小 64 * 32 / 8 = 256字节
NULL, //任务接口的参数,无需传参则填NULL
1, //任务的优先级 范围 0 ~ configMAX_PRIORITIES - 1
&xHandle1 //任务的句柄,是为了提供给其他函数使用的
);
xTaskCreate(
Task2,
"Task2",
64,
NULL,
2,
&xHandle2
);
//3.启动调度器
vTaskStartScheduler();
while(1)
{
}
}
9. FreeRTOS任务的挂起
void vTaskSuspend( TaskHandle_t xTaskToSuspend ).
- task. h
- 必须将 INCLUDE_vTaskSuspend 定义为 1 才能使用此函数
- 当任务被挂起之后,自己是无法恢复的
- 对 vaskSuspend 的调用不会累积次数(就是说,对某个任务挂起多次,恢复该任务只要恢复1次即可)
参数:
xTaskToSuspend 被挂起的任务句柄。参数为NULL表示挂起自身

10. FreeRTOS任务的恢复
void vTaskResume( TaskHandle_t xTaskToResume ):
- 必须将 INCLUDE vTaskSuspend 定义为1才能使用此函数。
- 挂起的次数是不会累加的,所以恢复一次 " 被挂起的任务 " 就行。
参数:
xTaskToResume 要恢复的任务句柄。


任务挂起vTaskSuspend和任务恢复vTaskResume的示例
cpp
#include "stm32f4xx.h" //必须要包含的头文件
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "USART.h"
TaskHandle_t xHandle1 = NULL; //用于存储创建的任务句柄
TaskHandle_t xHandle2 = NULL;
void Task1( void * pvParameters )
{
SendComputerString("task_1 is start\r\n");
for(;;)
{
SendComputerString("task_1 is running\r\n");
vTaskResume(xHandle2);
}
}
void Task2( void * pvParameters )
{
SendComputerString("task_2 is start\r\n");
for(;;)
{
SendComputerString("task_2 is running\r\n");
vTaskSuspend(NULL);
}
}
int main()
{
/*现象预判:1.task_2 is start
2.task_2 is running
3.task2把自己挂起了,cpu让渡给task1
4.task_1 is start
5.task_1 is running
6.task1把task2恢复了,task2优先级比task1高且当前系统是抢占式调度策略,task2抢占cpu资源
7.task_2 is running
8.task2把自己挂起了,cpu让渡给task1
9.task_1 is running
...循环往复
*/
//1.硬件的初始化
USART1_Config(9600);
//2.创建一个任务
xTaskCreate(
Task1, //任务的地址
"Task1", //任务的描述
64, //任务的堆栈大小 64 * 32 / 8 = 256字节
NULL, //任务接口的参数,无需传参则填NULL
1, //任务的优先级 范围 0 ~ configMAX_PRIORITIES - 1
&xHandle1 //任务的句柄,是为了提供给其他函数使用的
);
xTaskCreate(
Task2,
"Task2",
64,
NULL,
2,
&xHandle2
);
//3.启动调度器
vTaskStartScheduler();
while(1)
{
}
}
细节汇总
FreeRTOS操作系统要移植到MCU上,MCU上有内核,内核和RTOS做了整合,确保可以正常运行操作系统。
FreeRTOS使用FreeRTOSConfig.h进行定制。
工程在运行的时候是要先运行汇编文件的 " xxx.s ",再从汇编文件跳转到main函数里。
FreeRTOSConfig.h和应用程序(程序/代码)有关,在内核源码RTOS中是找不到的。
这个头文件不能放在 " 与FreeRTOS相关的目录 " 里,必须放在自己的应用程序目录 。
Linux系统下创建的多线程,是为了用线程去访问进程中的资源。
任务的运行是需要资源的。
把创建的任务交给系统的调度器(管家)。
多任务系统通过调度器和调度策略,可以确保这些任务都能在规定的时间内完成处理和响应。
要搭载"多任务系统"需要调度器,操作系统提供调度器。==> 所以要在MCU上移植操作系统。
抢占式的调度策略无法并发 ,为了不一直运行优先级最高的任务,应该人为的挂起或阻塞最高的优先级,把CPU让渡出来。
重用某个任务,让它的优先级最高,但为了让其他任务也能执行,→ 所以给最高优先级的任务使用 "挂起" 或 "延时阻塞".
①"挂起" 自己,但自己无法让自己 "恢复"到就绪态,而其他任务不一定会帮最高优先级task恢复,所以挂起有风险。
②"延时阻塞" 可以把CPU资源让出去一段时间,时间到了,最高优先级task从 "阻塞" 回答 "就绪"。
优先级相同的就绪任务,RTOS采用时间片轮询的调度策略。
优先级不同的就绪任务,RTOS采用抢占式的调度策略。
上下文切换:任务在执行的时候要访问内存空间RAM和ROM、寄存器、堆栈空间等...
每个任务在自己的上下文执行 ==> 每个任务都有自己的堆栈
Q :重复启停每个任务
A :任务有自己的堆栈空间,就算任务现在停止了,就可以把处理好的、没处理好的提前先备份好。等到下一次又能够执行时,在从堆栈空间中把数据恢复回来再执行一点,再把数据备份,在到下次执行把数据从堆栈里读取出来。
优先级低的任务正在运行态,优先级高的任务是就绪态,当前算法是 "抢占式调度策略",此时 "优先级高的任务" 会 "抢占优先级低的任务" 的CPU资源,就算 "优先级低的时间片" 还没用完。
Q :单核MCU,如何让多个任务同时运行?
A :利用调度器,对任务进行频繁的切换。
Q :切换的原则 / 以什么样的规则去切换?
A :调度策略:抢占式调度策略、时间片的调度策略
任务都有优先级,FreeRTOS对任务的数量和优先级没有限制,所以如果使用抢占式的调度策略,我可以给每个任务分配一个任务优先级。
如果使用时间片轮询的调度策略,我可以给任务分配相同的任务优先级。
Q :优先级的规则是什么?
A :在FreeRTOS中的任务优先级是 "优先级的数值越小,则任务的优先级越低"。
Q :如何在MCU上移植FreeRTOS?
A :上章有写
Q :FreeRTOS的功能是如何使用的?
A :FreeRTOS是使用配置文件对实时操作系统进行定制的。
"FreeRTOSConfig.h" ,要启用或禁用某些功能,FreeRTOS提供了若干的宏定义,在"FreeRTOSConfig.h"中去设置宏定义的值,如果设置为0,则表示禁用,非0则表示启用。
Q :为什么通过宏定义就实现了?
A :条件编译。