FreeRTOS-任务

前言:本文是基于韦东山-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来让任务周期性地运行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值