前言:本文是基于韦东山-FreeRTOS入门与工程实践的学习笔记。总结了freertos任务相关的一些操作,如任务创建,删除,修改优先级。探究了任务的几种状态和执行的顺序。
一、创建默认任务
(CUBEMX生成工程)具体步骤见:第6章 创建FreeRTOS工程 | 百问网
注意:
1、使用STM32CubeMX时,有一个默认任务StartDefaultTask,此任务无法删除,只能修改其名称和函数类型。
2、文件修改位置:打开freertos.c文件,找到StartDefaultTask函数里的循环。我们编写的代码,需要位于“USER CODE BEGIN xxx”和“USER CODE END xxx”之间
二、创建另一个任务
1、打开freertos.c文件,先创建一个自己的函数
/* USER CODE BEGIN FunctionPrototypes */
void MyTask(void *argument)
{
while (1)
{
Led_Test();
}
}
/* USER CODE END FunctionPrototypes */
2、找到MX_FREERTOS_Init函数。创建任务
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
xTaskCreate(MyTask, "myfirsttask", 128, NULL, osPriorityNormal, NULL);
/* USER CODE END RTOS_THREADS */
三、创建任务的方式
(1)动态创建任务
动态创建任务是在运行时通过动态内存分配函数分配任务内存。
- 好处:可以更灵活地适应不同大小和数量的任务,并且支持删除或重新创建任务。
- 坏处:可能会导致内存泄漏和堆碎片等问题。
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数
const char * const pcName, // 任务的名字(随便起)
const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节
void * const pvParameters, // 调用任务函数时传入的参数
UBaseType_t uxPriority, // 优先级
TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任务(比如删除该任务)
返回值:
- 成功时返回 pdPASS
- 失败时返回 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(失败原因是内存不足)
若使用任务句柄,则需事先定义。
(2)静态创建任务
静态创建任务,是任务在运行时之前已经预分配了足够的内存。需要事先知道任务所需的内存大小,以及将任务的堆栈和控制块明确地分配给该任务。
- 好处:更加可靠和节省内存
- 坏处:若任务使用的内存超出了分配的内存,则可能会发生严重错误,如内存泄漏或严重的崩溃。
TaskHandle_t xTaskCreateStatic (
TaskFunction_t pxTaskCode, // 函数指针, 任务函数
const char * const pcName, // 任务的名字
const uint32_t ulStackDepth, // 任务栈大小
void * const pvParameters, // 调用任务函数时传入的参数
UBaseType_t uxPriority, // 优先级
StackType_t * const puxStackBuffer, // 任务栈
StaticTask_t * const pxTaskBuffer // TCB(任务控制块)
);
返回值:
- 成功:返回任务句柄()
- 失败:NULL
静态创建任务需事先定义3个值:
/* USER CODE BEGIN Variables */
static StackType_t g_pucStackOfLightTask[128]; //栈的深度
static StaticTask_t g_TCBofLightTask; //TCB结构体的指针
static TaskHandle_t xLightTaskHandle; //任务句柄
/* USER CODE END Variables */
实际静态创建任务:
/* USER CODE BEGIN RTOS_THREADS */
xLightTaskHandle = xTaskCreateStatic(Led_Test, "LightTask", 128, NULL, osPriorityNormal, g_pucStackOfLightTask, &g_TCBofLightTask);
/* USER CODE END RTOS_THREADS */
notes:
- StackType_t 是数组的数据
- static作用:将局部变量变为“全局变量”
- 关于任务句柄、TCB、任务栈(转载自witheart)
四、多个函数使用同一任务
定义好结构体,将任务参数保存到不同的结构体变量里面,将不同结构体变量传入任务创建函数
/* USER CODE BEGIN FunctionPrototypes(函数原型) */
struct TaskPrintInfo {
uint8_t x;
uint8_t y;
char name[16];
};
static struct TaskPrintInfo g_Task1Info = {0, 0, "Task1"};
static struct TaskPrintInfo g_Task2Info = {0, 3, "Task2"};
static struct TaskPrintInfo g_Task3Info = {0, 6, "Task3"};
void LcdPrintTask(void *params)
{
/* 函数 */
};
/* USER CODE END FunctionPrototypes */
/* USER CODE BEGIN RTOS_THREADS */
xTaskCreate(LcdPrintTask, "task1", 128, &g_Task1Info, osPriorityNormal, NULL);
xTaskCreate(LcdPrintTask, "task2", 128, &g_Task2Info, osPriorityNormal, NULL);
xTaskCreate(LcdPrintTask, "task3", 128, &g_Task3Info, osPriorityNormal, NULL);
/* USER CODE END RTOS_THREADS */
五、任务优先级
1、概念
- 优先级的取值范围是:0~(configMAX_PRIORITIES – 1),数值越大优先级越高。
- FreeRTOS会确保最高优先级的、可运行的任务,马上就能执行。对于相同优先级的、可运行的任务,轮流执行
2、获得任务的优先级
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );
/* 使用参数xTask来指定任务,设置为NULL表示获取自己的优先级。 */
3、设置任务的优先级
void vTaskPrioritySet( TaskHandle_t xTask, UBaseType_t uxNewPriority );
/*
**参数xTask用来指定任务,设置为NULL表示设置自己的优先级。
**参数uxNewPriority表示新的优先级,取值范围是0~(configMAX_PRIORITIES – 1)。
*/
六、删除任务
void vTaskDelete( TaskHandle_t xTaskToDelete ); /* 参数pvTaskCode是指任务句柄 */
- 我删除我:vTaskDelete(NULL)
- 别人删除我:别的任务执行vTaskDelete(pvTaskCode),pvTaskCode是自己的句柄
- 我删除别人:执行vTaskDelete(pvTaskCode),pvTaskCode是别的任务的句柄
实际应用:先定义一个任务句柄,创建任务时传入该句柄,在相关位置判断该句柄的值。若该句柄的值不等于NULL,则表示已创建任务。便可以删除任务
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
TaskHandle_t xSoundTaskHandle = NULL;
while (1)
{
if (xSoundTaskHandle != NULL) /*如果该任务已经创建*/
{
vTaskDelete(xSoundTaskHandle); /*任务删除函数,参数是被删除任务的句柄*/
xSoundTaskHandle = NULL;
}
}
/* USER CODE END StartDefaultTask */
}
七、任务的状态
1、Ready状态
- 任务一经创建,就处于ready状态。
- 这个任务完全准备好了,随时可以运行,只是还轮不到它。这时,它就处于ready状态。
2、Blocked状态
- 在等待事件的过程中,这个任务就处于阻塞状态,它不消耗CPU资源
- 任务处于blocked状态,这个任务会被移到xDelayTaskList链表去,使得其他任务可以运行。
3、Suspended状态
- 通过过vTaskSuspend函数暂停自己或其他任务,参数是任务句柄。
- 调用suspend函数时,任务会从pxReadyTaskLists链表中,移入xSuspendTaskList链表中
- 实际开发中,暂停状态用得不多。
4、任务状态存放的位置
pxReadyTaskLists是一个数组,这个数组里面每一项都是一个链表(list),链表里面放的就是对应优先级处于ready或running状态的任务。
- pxReadyTaskLists[55] 放的是优先级为55,处于ready或running状态的任务
- 默认优先级的优先等级为24
- 空闲任务的优先级是0,放在pxReadyTaskLists[0] 里面
5、任务运行的顺序
同优先级的链表里面,
创建第一个任务Task1时,指针pxCurrentTCB指向该任务,
创建第二个任务Task2时,指针pxCurrentTCB变为指向Task2,
创建第三个任务Task3时,指针pxCurrentTCB变为指向Task3,
定时器产生固定间隔的中断叫做Tick,两次中断之间的时间被称为时间片(time slice、tick period)时间片的长度由configTICK_RATE_HZ 决定,假设configTICK_RATE_HZ为100,那么时间片长度就是10ms
Tick会触发中断服务函数,在中断服务函数中会进行
- cnt++
- 调度
- 判读xDelayTaskList链表里的任务是否可恢复
- ...
调度就是从pxReadyTaskLists[55] 遍历到 pxReadyTaskLists[0] (就是从55逐个“查看”到0,如果是空的,指针pxCurrentTCB就继续指向下一个,如果不是空的就执行)
指针pxCurrentTCB指向的任务先执行,所以先执行Task3。然后Tick会触发中断服务函数,从高优先级对应的链表往低遍历,在同一优先级链表中,会指向先创建的任务。所以,任务的执行顺序为:Task3 → Task1 → Task2
八、vTaskDelay 与 vTaskDelayUntil
1、vTaskDelay:至少等待指定个数的Tick Interrupt才能变为就绪状态
void vTaskDelay( const TickType_t xTicksToDelay );
/* xTicksToDelay: 等待多少个Tick */
2、vTaskDelayUntil:等待到指定的绝对时刻,才能变为就绪态。
BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement );
/* pxPreviousWakeTime: 上一次被唤醒的时间(起始时间)
* xTimeIncrement: 要阻塞到(pxPreviousWakeTime + xTimeIncrement)(增量时间)
* 单位都是Tick Count
*/
3、对比
使用vTaskDelay(n)时,进入任务、退出vTaskDelay的时间间隔至少是n个Tick中断
使用xTaskDelayUntil(&Pre, n)时,前后两次退出xTaskDelayUntil的时间间隔至少是n个Tick中断
- 退出xTaskDelayUntil时任务就进入的就绪状态,一般都能得到执行机会
- 所以可以使用xTaskDelayUntil来让任务周期性地运行