FreeRTOS(二)FreeRTOS任务创建和删除

目录

函数介绍

动态创建任务函数

函数xTaxkCreate()

任务句柄

任务句柄的作用

获取任务句柄

 使用任务句柄

实现动态函数创建任务流程

动态创建任务函数内部实现

静态创建任务函数

实现静态函数创建任务流程

静态创建内部实现

删除任务函数

参数说明

功能描述

注意事项

动态方法的任务创建和删除


FreeRTOS 最基本的功能就是任务管理,而任务管理最基本的操作就是创建和删除任务

函数介绍

动态创建任务函数

动态创建任务是 FreeRTOS 中的一项基本功能,它允许你在运行时根据需要创建任务,而不是在系统启动时静态定义所有任务。动态创建任务提供了更大的灵活性,特别是在需要根据系统运行状态或外部事件来调整任务数量和类型的情况下。

任务的任务控制块以及任务的栈空间所需的内存,均由 FreeRTOS FreeRTOS 管理的堆中分配

函数xTaxkCreate()

使用函数xTaskCreate()来创建任务的话那么这些 所需的RAM就会自动的从FreeRTOS的堆中分配

 同时新创建的任务默认就 是就绪态的,如果当前没有比它更高优先级的任务运行那么此任务就会立即进入运行态开始运 行,不管在任务调度器启动前还是启动后,都可以创建任务

函数原型 

BaseType_t xTaskCreate(
    TaskFunction_t pxTaskCode,  // 任务函数
    const char * const pcName,  // 任务名称
    configSTACK_DEPTH_TYPE usStackDepth,  // 堆栈深度
    void *pvParameters,        // 传递给任务的参数
    UBaseType_t uxPriority,     // 任务优先级
    TaskHandle_t * const pxCreatedTask  // 任务句柄
);

参数说明

  • pxTaskCode:这是一个函数指针,指向任务函数。任务函数是新创建的任务将要执行的函数。
  • pcName:这是一个指向字符串的指针,表示任务的名称。这个名称主要用于调试目的,例如在任务列表中识别任务。(任务名字,一般用于追踪和调试,任务名字长度不能超过。 configMAX_TASK_NAME_LEN。)
  • usStackDepth:这个参数指定任务堆栈的深度,单位是字节。堆栈深度应该足够大,以存储任务执行期间的所有局部变量和函数调用的返回地址。(任务堆栈大小,注意实际申请到的堆栈是usStackDepth的4倍。其中空闲任 务的任务堆栈大小为configMINIMAL_STACK_SIZE。)
  • pvParameters:这是一个可选参数,可以传递给任务函数。这允许在任务创建时传递初始化数据。(传递给任务函数的参数。)
  • uxPriority:这个参数指定任务的优先级。在 FreeRTOS 中,任务优先级的范围是从 0 到 configMAX_PRIORITIES - 1,其中 0 是最高的优先级。
  • pxCreatedTask:这是一个可选的输出参数,它是一个指向 TaskHandle_t 类型的指针。如果提供了这个参数,函数将在成功创建任务后,将新创建的任务的句柄存储在这里。(任务句柄,任务创建成功以后会返回此任务的任务句柄,这个句柄其实就是 任务的任务堆栈。此参数就用来保存这个任务句柄。其他API函数可能会使用到这个句柄。)

返回值

  • pdPASS:表示任务成功创建。
  • errC:表示任务创建失败,通常是因为内存不足。

eg. 

//创建LED0任务
   xTaskCreate((TaskFunction_t )led0_task,     	  
               (const char*    )"led0_task",   	
               (uint16_t       )LED0_STK_SIZE, 
               (void*          )NULL,				
               (UBaseType_t    )LED0_TASK_PRIO,	
               (TaskHandle_t*  )&LED0Task_Handler); 

任务句柄

在 FreeRTOS 中,任务句柄(Task Handle)是一个非常重要的概念,它为任务提供了一个唯一的标识符,使得任务间的各种操作和交互成为可能。任务句柄是一个指向任务控制块(Task Control Block, TCB)的指针,每个任务都有一个 TCB,其中包含了任务的各种状态信息和控制数据。

任务句柄的作用
  1. 任务识别:任务句柄提供了一种识别和引用特定任务的方式。
  2. 任务操作:通过任务句柄,可以对任务进行各种操作,如挂起(suspend)、恢复(resume)、删除(delete)等。
  3. 传递信息:任务句柄可以作为参数传递给其他任务或函数,用于任务间的通信和同步。
获取任务句柄

任务句柄可以通过以下几种方式获取:

1.在任务创建时:使用 xTaskCreate()xTaskCreateStatic() 创建任务时,可以通过传递一个指向 TaskHandle_t 的指针来获取新创建任务的句柄。

TaskHandle_t xTaskHandle = NULL;
xTaskCreate(vTaskFunction, "MyTask", STACK_SIZE, NULL, PRIORITIES, &xTaskHandle);

2.通过任务的名称:如果你知道任务的名称,可以使用 xTaskGetHandle() 函数来获取任务的句柄。

TaskHandle_t xTaskHandle;
xTaskGetHandle("MyTaskName", &xTaskHandle);
 使用任务句柄

1.删除任务:使用 vTaskDelete() 函数删除任务。

vTaskDelete(xTaskHandle);

2. 挂起任务:使用 vTaskSuspend() 函数挂起任务。

vTaskSuspend(xTaskHandle);

3. 恢复任务:使用 vTaskResume() 函数恢复被挂起的任务。

vTaskResume(xTaskHandle);

4. 获取任务状态:使用 uxTaskGetState() 函数获取任务的状态。

UBaseType_t xState = uxTaskGetState(xTaskHandle);

5. 等待任务通知:使用 ulTaskNotifyTake() 函数等待任务的通知。

uint32_t ulValue;
ulValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);

实现动态函数创建任务流程

1、将宏configSUPPORT_DYNAMIC_ALLOCATION 配置为 1

#define configSUPPORT_DYNAMIC_ALLOCATION 1

2、定义函数入口参数

任务函数通常需要一个参数,这个参数可以是 void* 类型,允许你传递任意类型的数据给任务。这个参数在任务函数内部可以被转换为需要的类型。

3、编写任务函数

任务函数是任务执行的代码。它必须是一个无限循环,因为任务函数一旦返回,任务就会结束,除非在任务函数中显式调用 vTaskDelete(NULL) 来删除自己。

void vTaskFunction(void *pvParameters)
{
    for(;;)
    {
        // 任务代码
    }
}

 4、创建任务

使用 xTaskCreate() 函数来动态创建任务。你需要提供任务函数、任务名称、堆栈大小、任务参数和优先级。

#include "FreeRTOS.h"
#include "task.h"

void StartTask(void)
{
    TaskHandle_t xHandle = NULL;
    // 定义任务参数
    MyParameters_t xParams;
    xParams.someValue = 10;
    // 创建任务
    if(xTaskCreate(vTaskFunction, "MyTask", configMINIMAL_STACK_SIZE, &xParams, tskIDLE_PRIORITY + 1, &xHandle) == pdPASS)
    {
        // 任务创建成功
    }
    else
    {
        // 任务创建失败,处理错误
    }
}

 5、启动调度器

在主函数或适当的初始化代码中,调用 vTaskStartScheduler() 来启动任务调度器。

int main(void)
{
    // 初始化硬件和库
    // ...
    // 创建任务
    StartTask();
    // 启动调度器
    vTaskStartScheduler();
    // 如果调度器启动成功,以下代码将永远不会执行
    for(;;);
}

动态创建任务函数内部实现

1、申请堆栈内存&任务控制块内存

首先,xTaskCreate() 会为新任务分配内存空间,包括任务控制块(TCB)和堆栈。这些内存通常通过调用内存管理函数(如 pvPortMalloc())来分配。

2、TCB结构体成员赋值

每个任务都有一个与之关联的 TCB,它包含了任务的各种状态信息和控制数据。xTaskCreate() 会初始化这个 TCB,包括设置任务的入口函数、优先级、堆栈指针等。

tskTCB *pxNewTCB = (tskTCB *)pvPortMalloc(sizeof(tskTCB));
if (pxNewTCB != NULL) {
    // 初始化 TCB
    pxNewTCB->pxTopOfStack = NULL; // 堆栈指针
    pxNewTCB->uxPriority = uxPriority;
    // ... 其他成员的初始化
}

3、设置任务堆栈

接下来,xTaskCreate() 会为任务分配堆栈空间,并设置初始堆栈的内容。这通常涉及到将返回地址、寄存器状态等压入堆栈,以便任务在开始执行时能够正确地跳转到任务函数。

StackType_t *pxStack = (StackType_t *)pvPortMalloc(sizeof(StackType_t) * usStackDepth);
if (pxStack != NULL) {
    // 设置堆栈,使其准备好任务函数的第一次调用
    pxNewTCB->pxTopOfStack = pxStack + usStackDepth - 1; // 堆栈顶指针
    // 压入初始值,如返回地址、寄存器等
    pxNewTCB->pxTopOfStack = pxPortInitialiseStack(pxNewTCB->pxTopOfStack, pxTaskCode, pvParameters);
}

4、添加新任务到就绪列表中

一旦 TCB 和堆栈被正确设置,xTaskCreate() 会将任务添加到就绪列表中。这样,任务调度器就可以根据任务的优先级来调度任务的执行。

if (xTaskGenericCreate(pxNewTCB, ...) == pdPASS) {
    // 任务创建成功
}

5、返回任务句柄

如果提供了任务句柄的指针,xTaskCreate() 会将新创建的任务的句柄存储在这个位置。这个句柄可以用于后续的任务控制操作,如删除、挂起或恢复任务

静态创建任务函数

静态创建任务是 FreeRTOS 提供的一种任务创建方式,它允许你在编译时分配任务的堆栈和任务控制块(TCB),而不是在运行时动态分配。这种方式通常用于内存受限的系统,因为它可以减少运行时的内存碎片和内存分配的开销。

函数xTaxkCreate()

函数 xTaskCreateStatic() 允许在 FreeRTOS 中静态创建任务,这意味着任务的堆栈和任务控制块(TCB)是在编译时静态分配的,而不是在运行时动态分配。这种方式对于内存资源受限的系统非常有用,因为它可以减少运行时的内存碎片和分配开销。

函数原型

BaseType_t xTaskCreateStatic(
    TaskFunction_t pxTaskCode,  // 任务函数
    const char * const pcName,  // 任务名称
    configSTACK_DEPTH_TYPE ulStackDepth,  // 堆栈深度
    void *pvParameters,        // 传递给任务的参数
    UBaseType_t uxPriority,     // 任务优先级
    StackType_t *pxStackBuffer, // 静态堆栈缓冲区
    StaticTask_t *pxTaskBuffer  // 静态任务控制块
);

参数说明

  • pxTaskCode:指向任务函数的指针。
  • pcName:任务的名称,主要用于调试目的。
  • ulStackDepth:任务堆栈的深度,单位是堆栈的“词”数,而不是字节。在32位系统中,一个“词”通常是4个字节。
  • pvParameters:传递给任务函数的可选参数。
  • uxPriority:任务的优先级,数值越小,优先级越高。
  • pxStackBuffer:指向静态堆栈缓冲区的指针,该缓冲区必须足够大,以存储任务执行期间的所有局部变量和函数调用的返回地址。
  • pxTaskBuffer:指向静态任务控制块的指针,该控制块用于存储任务的状态信息。

返回值

  • pdPASS:任务成功创建。
  • errC:任务创建失败,通常是因为提供的堆栈或任务控制块大小不正确。

实现静态函数创建任务流程

配置宏 configSUPPORT_STATIC_ALLOCATION

在 FreeRTOS 的配置文件 FreeRTOSConfig.h 中,你需要定义一些宏来启用特定的功能。对于静态内存分配,关键的宏是 configSUPPORT_STATIC_ALLOCATION。将其设置为 1 表示你打算在代码中静态分配任务的堆栈和任务控制块(TCB)。

#define configSUPPORT_STATIC_ALLOCATION 1

定义空闲任务和定时器任务的堆栈及 TCB

FreeRTOS 要求你为系统的关键任务提供内存:

  • 空闲任务 (Idle Task):当没有其他任务运行时,空闲任务运行。它通常执行一些低优先级的工作,如内存碎片检测或简单的监控。
  • 定时器任务 (Timer Task):管理软件定时器和执行周期性任务。
// 空闲任务堆栈
static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];

// 空闲任务 TCB
static StaticTask_t xIdleTaskTCB;

// 定时器任务堆栈
static StackType_t xTimerStack[configTIMER_TASK_STACK_DEPTH];

// 定时器任务 TCB
static StaticTask_t xTimerTaskTCB;

实现两个接口函数

FreeRTOS 提供了两个可选的钩子函数,允许你为空闲任务和定时器任务提供静态内存:

  • vApplicationGetIdleTaskMemory():返回指向空闲任务堆栈和 TCB 的指针。
  • vApplicationGetTimerTaskMemory():返回指向定时器任务堆栈和 TCB 的指针。

定义函数入口参数 

任务函数可能需要参数来初始化或配置其行为。这些参数在任务创建时传递,并在任务函数中通过 pvParameters 指针访问。

void vTaskFunction(void *pvParameters)
{
    // 使用 pvParameters
}

编写任务函数

任务函数是任务执行的代码。它通常包含一个无限循环,因为任务不会自行结束,除非显式删除。

void vTaskFunction(void *pvParameters)
{
    for(;;)
    {
        // 任务逻辑
    }
}

静态创建内部实现

TCB 结构体成员赋值: 在静态创建任务时,需要初始化任务控制块(TCB)的各个成员,包括任务的入口函数、堆栈指针、任务状态、优先级等。

添加新任务到就绪列表中: 一旦任务的 TCB 被正确初始化,新创建的任务会被添加到就绪列表中。任务调度器会根据任务的优先级和状态来调度任务的执行。

删除任务函数

函数 vTaskDelete() 是 FreeRTOS 中用于删除任务的 API。当一个任务完成了它的工作或者不再需要时,你可以使用这个函数来删除它,释放它所使用的资源,包括堆栈和任务控制块(TCB)

void vTaskDelete( TaskHandle_t xTaskToDelete );

参数说明

  • xTaskToDelete:这是一个指向要删除的任务的任务控制块(TCB)的指针,即 TaskHandle_t 类型。如果传递 NULL,那么 vTaskDelete() 将删除调用它的任务。

功能描述

当调用 vTaskDelete() 时,会发生以下事情:

  1. 资源释放:任务的堆栈和 TCB 将被释放回 FreeRTOS 的内存管理池中,以便将来可以被其他任务或系统组件使用。

  2. 任务移除:任务将从任何任务列表(就绪列表、阻塞列表等)中移除。

  3. 调度器更新:调度器将更新,以确保不会尝试调度一个已经被删除的任务。

  4. 上下文恢复:如果被删除的任务是当前运行的任务(即调用 vTaskDelete() 的任务),那么调度器将立即进行任务切换,恢复到另一个任务的上下文。

实例:

void vATaskFunction( void *pvParameters )
{
    // 任务代码
    // ...

    // 任务完成工作后,删除自身
    vTaskDelete( NULL );
}

// 在另一个任务或初始化代码中创建任务
TaskHandle_t xTaskHandle = NULL;
xTaskCreate( vATaskFunction, "A Task", STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, &xTaskHandle );

// 稍后,如果需要删除这个任务,可以调用
vTaskDelete( xTaskHandle );

注意事项

  1. 慎用 NULL:调用 vTaskDelete(NULL); 会删除调用它的任务。这在任务完成其工作并希望自我删除时非常有用,但需要谨慎使用,以避免意外删除当前正在运行的任务。

  2. 资源回收:虽然任务被删除了,但是它所持有的任何同步原语(如信号量、互斥锁等)不会自动释放。你需要在删除任务之前手动释放这些资源。

  3. 删除阻塞任务:如果尝试删除一个处于阻塞状态的任务,那么任务将被删除,但任何等待该任务的同步原语也将被释放。

  4. 调度器行为vTaskDelete() 可以影响调度器的行为。例如,如果删除了一个高优先级任务,那么调度器可能会立即调度其他任务。

  5. 内存碎片:在某些配置下,频繁创建和删除任务可能会导致内存碎片。在这种情况下,考虑使用静态内存分配或调整内存管理策略。

动态方法的任务创建和删除

实例代码:

//任务优先级
#define START_TASK_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		128  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define TASK1_TASK_PRIO		2
//任务堆栈大小	
#define TASK1_STK_SIZE 		128  
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);

//任务优先级
#define TASK2_TASK_PRIO		3
//任务堆栈大小	
#define TASK2_STK_SIZE 		128  
//任务句柄
TaskHandle_t Task2Task_Handler;
//任务函数
void task2_task(void *pvParameters);
int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4	 
	delay_init();	    				//延时函数初始化	 
	uart_init(115200);					//初始化串口
	LED_Init();		  					//初始化LED
	
	//创建开始任务
    xTaskCreate((TaskFunction_t )start_task,            //任务函数
                (const char*    )"start_task",          //任务名称
                (uint16_t       )START_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )START_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
    vTaskStartScheduler();          //开启任务调度
}

//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           //进入临界区
    //创建TASK1任务
    xTaskCreate((TaskFunction_t )task1_task,             
                (const char*    )"task1_task",           
                (uint16_t       )TASK1_STK_SIZE,        
                (void*          )NULL,                  
                (UBaseType_t    )TASK1_TASK_PRIO,        
                (TaskHandle_t*  )&Task1Task_Handler);   
    //创建TASK2任务
    xTaskCreate((TaskFunction_t )task2_task,     
                (const char*    )"task2_task",   
                (uint16_t       )TASK2_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )TASK2_TASK_PRIO,
                (TaskHandle_t*  )&Task2Task_Handler); 
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}

//task1任务函数
void task1_task(void *pvParameters)
{
	u8 task1_num=0;
	while(1)
	{
		task1_num++;	//任务执1行次数加1 注意task1_num1加到255的时候会清零!!
		LED0=!LED0;
		printf("任务1已经执行:%d次\r\n",task1_num);
		if(task1_num==5) 
		{
            vTaskDelete(Task2Task_Handler);//任务1执行5次删除任务2
			printf("任务1删除了任务2!\r\n");
		}
        vTaskDelay(1000);                           //延时1s,也就是1000个时钟节拍	
	}
}

//task2任务函数
void task2_task(void *pvParameters)
{
	u8 task2_num=0;
	while(1)
	{
		task2_num++;	//任务2执行次数加1 注意task1_num2加到255的时候会清零!!
        LED1=!LED1;
		printf("任务2已经执行:%d次\r\n",task2_num);
        vTaskDelay(1000);                           //延时1s,也就是1000个时钟节拍	
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值