freeRtos学习笔记
freeRtos任务管理
freeRtos任务状态
freeRtos中任务有四种状态:就绪态、运行态、杜塞态、挂起态。
图 16-1(1): 创建任务→就绪态(Ready):任务创建完成后进入就绪态,表明任务已准备就绪,随时可以运行,只等待调度器进行调度。
图 16-1(2): 就绪态→运行态(Running):发生任务切换时,就绪列表中最高优先级的任务被执行,从而进入运行态。
图 16-1(3): 运行态→就绪态:有更高优先级任务创建或者恢复后,会发生任务调度,此刻就绪列表中最高优先级任务变为运行态,那么原先运行的任务由运行态变为就绪态,依然在就绪列表中,等待最高优先级的任务运行完毕继续运行原来的任务(此处可以看做是 CPU 使用权被更高优先级的任务抢占了)。
图 16-1(4): 运行态→阻塞态(Blocked):正在运行的任务发生阻塞(延时、读信号量等待)时,该任务会从就绪列表中删除,任务状态由运行态变成阻塞态,然后发生任务切换,运行就绪列表中当前最高优先级任务。
图 16-1(5): 阻塞态→就绪态:阻塞的任务被恢复后(任务恢复、延时时间超时、读信号量超时或读到信号量等),此时被恢复的任务会被加入就绪列表,从而由阻塞态变成就绪态;如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。
图 16-1(6) (7) (8): 就绪态、阻塞态、运行态→挂起态(Suspended):任务可以通过调用 vTaskSuspend() API 函数都可以将处于任何状态的任务挂起,被挂起的任务得不到CPU 的使用权,也不会参与调度,除非它从挂起态中解除。
图 16-1(9): 挂起态→就绪态: 把一个挂起状态的任务恢复的唯一途径就是调用 vTaskResume() 或 xTaskResumeFromISR() API 函数,如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换,将该任务将再次转换任务状态,由就绪态变成运行态。
freeRtos启动
void vTaskStartScheduler(void);
启动freeRtos的任务调度器,空闲任务和定时器任务是在调用这个函数后才自动创建的。
动态创建任务
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, /* 任务入口函数 */
const char * const pcName, /* 任务名 */
const configSTACK_DEPTH_TYPE usStackDepth,/* 任务栈大小 单位为字 */
void * const pvParameters, /* 任务入口参数 不用的时候配置为0 */
UBaseType_t uxPriority, /* 任务任务优先级 空闲任务优先级0 */
TaskHandle_t * const pxCreatedTask ) /* 任务控制块指针 */
函数 xTaskCreate 用于实现 FreeRTOS 操作系统的任务创建,并且还可以自定义任务栈的大小。
- 第 1 个参数填创建任务的函数名。
- 第 2 个参数是任务名,这个参数主要是用于调试目的,调试的时候方便看是哪个任务。
- 第 3 个参数是任务栈大小,单位 word,也就是 4 字节。
- 第 4 个参数是创建的任务函数的形参。
- 第 5 个参数是任务优先级,越大优先级越高
- 第 6 个参数是任务句柄,用于区分不同的任务。
- 返回值:当任务创建成功,返回pdPASS,否则返回pdFALL.
删除任务
void vTaskDelete( TaskHandle_t xTaskToDelete )
使用这个函数要注意以下问题:
- 使用此函数需要在 FreeRTOSConfig.h 配置文件中配置如下宏定义为 1
#define INCLUDE_vTaskDelete 1 - 如果用往此函数里面填的任务 ID 是 NULL,即数值 0 的话,那么删除的就是当前正在执行的任务,此任务被删除后, FreeRTOS 会切换到任务就绪列表里面下一个要执行的最高优先级任务。
- 在 FreeRTOS 中,创建任务所需的内存需要在空闲任务中释放, 如果用户在 FreeRTOS 中调用了这个函数的话,一定要让空闲任务有执行的机会, 否则这块内存是无法释放的。另外, 创建的这个任务在使用中申请了动态内存,这个内存不会因为此任务被删除而删除,这一点要注意, 一定要在删除前将此内存释放
挂起任务
void vTaskSuspend(TaskHandle xTaskToSuspend);
函数 vTaskSuspend 用于实现 FreeRTOS 操作系统的任务挂起。
第 1 个参数填要挂起任务的句柄
使用这个函数要注意以下问题:
- 使用此函数需要在 FreeRTOSConfig.h 配置文件中配置如下宏定义为 1
#define INCLUDE_vTaskSuspend 1 - 如果用往此函数里面填的任务 ID 是 NULL,即数值 0 的话,那么挂起的就是当前正在执行的任务,
此任务被挂起后, FreeRTOS 会切换到任务就绪列表里面下一个要执行的高优先级任务。 - 多次调用此函数的话,只需调用一次 vTaskResume 即可将任务从挂起态恢复。
恢复挂起任务
void vTaskResume(TaskHandle xTaskToResume);
BaseType_t xTaskResumeFromISR(TaskHandle xTaskToResume);
函数 vTaskResume 用于实现 FreeRTOS 操作系统的任务挂起
第 1 个参数填要恢复任务的句柄
使用这个函数要注意以下问题:
- 使用此函数需要在 FreeRTOSConfig.h 配置文件中配置如下宏定义为 1
#define INCLUDE_vTaskSuspend 1 - 多次调用函数 vTaskSuspend 的话,只需调用一次 vTaskResume 即可将任务从挂起态恢复。
- 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的
xTaskResumeFromISR(), 以后缀 FromISR 结尾。 - xTaskResumeFromISR()返回值:如果恢复挂起任务后会导致任务调度返回pdTRUE
修改任务优先级
void vTaskPrioritySet(TaskHandle xTask, UBaseType_t uxNewPriority);
函数 vTaskPrioritySet 用于实现 FreeRTOS 任务优先级的修改。
第 1 个参数是任务句柄,用于区分不同的任务。
第 2 个参数是给任务配置的新优先级。
使用这个函数要注意以下问题:
- 使用此函数需要在 FreeRTOSConfig.h 配置文件中配置如下宏定义为 1
#define INCLUDE_vTaskPrioritySet 1 - 如果第一个参数里面填的是 NULL,即数值 0 的话,那么配置的就是当前正在执行的任务。
- 如果被修改的任务的优先级,修改后高于正在执行的任务, 将执行任务切换,切换到修改好的高优先级任务。
- 第二个参数数值不可大于等于 FreeRTOSConfig.h 文件中的宏定义:
#define configMAX_PRIORITIES 配置的数值。
获取任务优先级
UBaseType_t uxTaskPriorityGet(TaskHandle xTask);
UBaseType_t uxTaskPriorityGetFromISR(TaskHandle_t xTask);
函数 vTaskPriorityGet 用于获取 FreeRTOS 任务优先级。
第 1 个参数是任务句柄,用于区分不同的任务。
使用这个函数要注意以下问题:
- 使用此函数需要在 FreeRTOSConfig.h 配置文件中配置如下宏定义为 1
#define INCLUDE_vTaskPriorityGet 1 - 如果第1个参数里面填的是 NULL,即数值 0 的话,那么获取的优先级就是当前正在执行的任务。
- 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的
uxTaskPriorityGetFromISR(), 以后缀 FromISR 结尾。
切换任务
taskYIELD()
调用 taskYIELD() 宏可以立刻进行任务调度,切换到就绪的最高优先级任务
获取当前系统时间
volatile TickType_t xTaskGetTickCount(void);
volatile TickType_t xTaskGetTickCountFromISR(void);
函数 xTaskGetTickCount 用于获取系统当前运行的时钟节拍数。
使用这个函数要注意以下问题:
- 此函数用于在任务代码里面调用, 如果在中断服务程序里面调用的话, 需要使用函数
xTaskGetTickCountFromISR, 这两个函数切不可混用
任务延时
void vTaskDelay(TickType_t xTickToDelay);
函数 vTaskDelay 用于任务的延迟。
参数 xTicksToDelay 用于设置延迟的时钟节拍个数,范围 1- 0xFFFFFFFF。 延迟时间的最大值在
portmacro.h 文件里面有定义:
typedef uint32_t TickType_t;
#define portMAX_DELAY ( TickType_t )0xffffffffUL
即延迟时间的范围是: 1- 0xFFFFFFFF
延时后会进行任务调度
任务绝对延时
void vTaskDelayUntil(TickType_t* pxPreviousWakeTime, TickType_t xTimeIncrement);
函数 vTaskDelayUntil 用于周期性延迟。
第 1 个参数,存储任务上次处于非阻塞状态时刻的变量地址。
第 2 个参数,周期性延迟时间。
使用这个函数要注意以下问题:
- 使用此函数需要在 FreeRTOSConfig.h 配置文件中配置如下宏定义为 1
#define INCLUDE_vTaskDelayUntil 1 - 用户要注意此函数跟 vTaskDelay 的区别是,例如在两个相同任务,用户处理程序运行需要50ms,然后分别使用vTaskDelay()和vTaskDelayUntil()进行200ms的延时,使用vTaskDelay()做200ms延时,整个任务运行状况就是先用户处理消耗50ms,再延时200ms,也就是250ms任务运行一次;而使用vTaskDelayUntil()进行200ms的延时,整个任务运行状况就是先用户处理消耗50ms,再延时150ms,也就是200ms任务运行一次;
下面是一个使用vTaskDelay()实现vTaskDelayUntil()的过程。
void vTaskA(void *pvParame)
{
TickType_t xDelay, xNextTime;
/* 200ms执行一次该任务 */
const TickType_t xFrequency = 200;
/* 获取 xFrequency 个时钟节拍后的时间 */
xNextTime = xTaskGetTickCount() + xFrequency;
while(1)
{
/* 如果用户函数需要运行50ms */
....
/* 计算实际需要延时的时间 200 - 50 = 150 */
xDelay = xNextTime - xTaskGetTickCount();
xNextTime += xFrequency;
if(xDelay <= xFrequency)
{
vTaskDelay(xDelay);
}
}
}
void vTaskB(void *pvParame)
{
TickType_t xLastWakeTime;
/* 200ms执行一次该任务 */
const TickType_t xFrequency = 200;
while(1)
{
/* 如果用户函数需要运行50ms */
....
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
调度器挂起
void vTaskSuspendAll(void);
函数 vTaskSuspendAll 用于实现 FreeRTOS 调度器挂起。
使用这个函数要注意以下问题:
- 注意和进入临界区不同,这里只是关掉了任务调度器,只能运行当前任务,但是并没有关中断,中断服务函数还是可以运行的。
- 调度锁开启函数 vTaskSuspendAll 和调度锁关闭函数 xTaskResumeAll 一定要成对使用。
- 切不可在调度锁开启函数 vTaskSuspendAll 和调度锁关闭函数 xTaskResumeAll 之间调用任何会引
起任务切换的 API, 比如 vTaskDelayUntil、 vTaskDelay、 xQueueSend 等(调用了也不会执行调度)。
调度器恢复
BaseType_t xTaskResumeAll(void);
函数 xTaskResumeAll 用于实现 FreeRTOS 调度器恢复
由于调度器操作可以进行嵌套,如果执行了两次调度器挂起,那么在第一次执行调度器恢复函数时,此函数返回 pdFALSE,调度器并没有恢复,第二次执行调度器恢复函数才会返回pdTRUE,调度器才会恢复正常。
使用这个函数要注意以下问题:
- 注意和进入临界区不同,这里只是关掉了任务调度器,只能运行当前任务,但是并没有关中断,中断服务函数还是可以运行的。
- 调度锁开启函数 vTaskSuspendAll 和调度锁关闭函数 xTaskResumeAll 一定要成对使用。
- 切不可在调度锁开启函数 vTaskSuspendAll 和调度锁关闭函数 xTaskResumeAll 之间调用任何会引
起任务切换的 API, 比如 vTaskDelayUntil、 vTaskDelay、 xQueueSend 等(调用了也不会执行调度)
本文参考 freertos官方文档 https://freertos.org/a00110.html
《野火-FreeRTOS 内核实现与应用开发实战指南》
《安富莱 STM32-V6 开发板 FreeRTOS 教程》