FreeRTOS任务相关API简介

前言

本文介绍了几个常用的FreeRTOS任务相关的API,列举了任务的创建、删除,挂起和恢复的API,并介绍了函数功能、入参等信息。专为初学者入门而写。希望可以给大家带来帮助。



任务的创建和删除

下面简介各种任务创建API和任务删除API:

函数描述
xTaskCreat()使用动态的方法创建一个任务
xTaskCreatStatic()使用静态的方法创建一个任务
xTaskCreatRestricted()创建一个使用MPU进行限制的任务,使用动态内存分配此任务使用的内存
vTaskDelet()删除一个任务

动态创建任务(xTaskCreate)

使用此API创建的任务,会自动从FreeRTOS的堆里面分配RAM空间,来存储任务控制块(TCB)和任务堆栈。所以在使用之前要确保内存管理文件在工程中被使用,并且确保下面这个宏的值是1:

#define configSUPPORT_DYNAMIC_ALLOCATION 1

xTaskCreate() 是 FreeRTOS 实时操作系统中用于创建新任务的核心函数。它动态分配内存并初始化一个新的任务控制块(TCB)和任务栈。

另外,此API创建的任务默认处于就绪态,所有处于就绪态的任务,按照优先级的高低以此执行。在调度器启动前后都可以调用此API创建任务。函数原型如下:

函数原型

BaseType_t xTaskCreate(
    TaskFunction_t pvTaskCode,        // 任务函数指针
    const char * const pcName,        // 任务名称
    unsigned short usStackDepth,      // 任务栈大小(单位:字,4字节)
    void *pvParameters,              // 传递给任务函数的参数
    UBaseType_t uxPriority,           // 任务优先级
    TaskHandle_t * const pxCreatedTask // 任务句柄
);

参数说明

  1. pvTaskCode:

    • 类型: TaskFunction_t (通常定义为 void (*)(void *))
    • 描述: 指向任务函数的指针,该函数应是一个无限循环且不应返回
  2. pcName:

    • 类型: const char *
    • 描述: 任务的可读名称,用于调试目的,最大长度由 configMAX_TASK_NAME_LEN 定义
  3. usStackDepth:

    • 类型: configSTACK_DEPTH_TYPE (通常为 uint16_t)
    • 描述: 指定任务栈的大小,以字(word)为单位(不是字节)。例如,在32位架构上,1字=4字节
  4. pvParameters:

    • 类型: void *
    • 描述: 传递给任务函数的参数指针
  5. uxPriority:

    • 类型: UBaseType_t
    • 描述: 任务优先级(0为最低优先级,configMAX_PRIORITIES-1为最高)
  6. pxCreatedTask:

    • 类型: TaskHandle_t *
    • 描述: 用于返回任务句柄的指针,可用于后续引用该任务。可以传入NULL

返回值

  • pdPASS (1): 任务创建成功
  • pdFAIL (0): 任务创建失败(通常由于内存不足)

使用示例

void vTaskFunction(void *pvParameters) {
    const char *msg = (const char *)pvParameters;
    for(;;) {
        printf("%s\n", msg);
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

void main() {
    TaskHandle_t xHandle = NULL;
    
    if(xTaskCreate(vTaskFunction,   // 任务函数
                   "PrintTask",     // 任务名称
                   128,            // 栈大小(字)
                   "Hello World",  // 传递给任务的参数
                   1,              // 优先级
                   &xHandle)       // 任务句柄
       != pdPASS) {
        // 任务创建失败处理
    }
    
    vTaskStartScheduler();  // 启动调度器
}

注意事项

  1. 任务函数不应返回,通常应包含一个无限循环
  2. 栈大小需要足够大以容纳局部变量和函数调用栈
  3. 使用 vTaskDelete(NULL) 可以让任务自我删除
  4. 对于静态内存分配的任务创建,可以使用 xTaskCreateStatic()
  5. xTaskCreate() 可以在 vTaskStartScheduler() 之前调用,因为 FreeRTOS 允许先创建任务,再启动调度器。
  6. xTaskCreate() 创建的任务必须在 FreeRTOS 的运行时环境准备好之后才能被调用(即 FreeRTOS 的堆内存、数据结构等已配置正确)。
  7. 真正的任务执行是从 vTaskStartScheduler() 开始的,在此之前任务只是被创建,但不会运行。

内存管理

xTaskCreate() 使用 FreeRTOS 的内存管理函数动态分配任务栈和任务控制块(TCB)。如果使用自定义内存管理方案,需要确保有足够的堆空间。

特点

  • 适用于大多数应用场景,系统自动管理内存。
  • 任务删除后,内存可以被系统回收。
  • 适合任务数量较多或内存动态分配的场景。

静态创建任务(xTaskCreateStatic)

静态创建任务需要用户手动分配任务栈空间和任务控制块(TCB),通常用于对内存使用有严格要求的嵌入式系统。
需要下面的宏等于1。

#define configSUPPORT_STATIC_ALLOCATION 1

函数原型

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 	// 任务栈
	);

参数说明

  1. pxTaskCode: 任务入口函数(与 xTaskCreate() 相同)
  2. pcName: 任务名称(与 xTaskCreate() 相同)
  3. ulStackDepth: 栈深度,以字(word)为单位(与 xTaskCreate() 相同)
  4. pvParameters: 传递给任务的参数(与 xTaskCreate() 相同)
  5. uxPriority: 任务优先级(与 xTaskCreate() 相同)
  6. pxStackBuffer: 指向用户提供的栈内存空间的指针
  7. pxTaskBuffer: 指向用户提供的任务控制块(TCB)内存空间的指针

返回值

  • 成功时返回任务句柄
  • 失败时返回 NULL(通常由于提供的缓冲区无效)

示例代码


//空闲任务任务堆栈
static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];
//空闲任务控制块
static StaticTask_t IdleTaskTCB;
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, 
									StackType_t **ppxIdleTaskStackBuffer, 
									uint32_t *pulIdleTaskStackSize )
{
	*ppxIdleTaskTCBBuffer = &IdleTaskTCB;
	*ppxIdleTaskStackBuffer = IdleTaskStack;
	*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}

//定时器服务任务堆栈
static StackType_t TimerTaskStack[configTIMER_TASK_STACK_DEPTH];
//定时器服务任务控制块
static StaticTask_t TimerTaskTCB;
void vApplicationGetTimerTaskMemory( StaticTask_t **ppxTimerTaskTCBBuffer, 
									StackType_t **ppxTimerTaskStackBuffer, 
									uint32_t *pulTimerTaskStackSize )
{
	*ppxTimerTaskTCBBuffer = &TimerTaskTCB;
	*ppxTimerTaskStackBuffer = TimerTaskStack;
	*pulTimerTaskStackSize = configTIMER_TASK_STACK_DEPTH;
}

// 定义任务栈和TCB所需的空间
#define TASK_STACK_SIZE 128
TaskHandle_t xHandle;
StackType_t xTaskStack[TASK_STACK_SIZE];
StaticTask_t xTaskTCB;

/*开始任务的相关宏定义*****/
//任务优先级
#define STATIC_START_TASK_PRIO		1
//任务堆栈大小	
#define STATIC_START_STK_SIZE 		128  
//任务堆栈
StackType_t Static_StartTaskStack[STATIC_START_STK_SIZE];
//任务控制块
StaticTask_t StaticStartTaskTCB;
//任务句柄
TaskHandle_t StaticStartTask_Handler;
//任务函数
void static_start_task(void *pvParameters);

void vTaskFunction(void *pvParameters) {
    for(;;) {
        // 任务代码
		LED0 = !LED0;
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

int main() 
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
	delay_init(168);
	uart_init(115200);
	LED_Init();
	KEY_Init();							//初始化按键

    //创建开始任务
	StartTask_Handler=xTaskCreateStatic((TaskFunction_t	)static_start_task,		//任务函数
										(const char* 	)"static_start_task",		//任务名称
										(uint32_t 		)STATIC_START_STK_SIZE,	//任务堆栈大小
										(void* 		  	)NULL,				//传递给任务函数的参数
										(UBaseType_t 	)STATIC_START_TASK_PRIO, 	//任务优先级
										(StackType_t*   )Static_StartTaskStack,	//任务堆栈
										(StaticTask_t*  )&StaticStartTaskTCB);	//任务控制块              
    vTaskStartScheduler();  // 启动调度器
}
//开始任务函数
void static_start_task(void * pxParameters)
{
	taskENTER_CRITICAL();           //进入临界区    
    // 创建静态任务
    xHandle = xTaskCreateStatic(
        vTaskFunction,
        "StaticTask",
        TASK_STACK_SIZE,
        NULL,
        1,
        xTaskStack,
        &xTaskTCB
    );    
	vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}

说明:以上代码在正点原子的工程代码基础上修改而来。

关于示例代码的说明

由于上面的代码比较长,所以需要简要说明一下,方便大家理解。

  • 首先,我们要确定configSUPPORT_STATIC_ALLOCATION 等于1:
#define configSUPPORT_STATIC_ALLOCATION 1
  • 在任务调度器函数vTaskStartScheduler()内部会创建空闲任务,在创建之前会调用下图的vApplicationGetIdleTaskMemory()函数为空闲任务分配内存,TCB等。那么,问题来了,vApplicationGetIdleTaskMemory()这个函数没有被定义啊!
    在这里插入图片描述
    所以,就需要我们自己去定义了。
//空闲任务任务堆栈
static StackType_t IdleTaskStack[configMINIMAL_STACK_SIZE];
//空闲任务控制块
static StaticTask_t IdleTaskTCB;
void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, 
									StackType_t **ppxIdleTaskStackBuffer, 
									uint32_t *pulIdleTaskStackSize )
{
	*ppxIdleTaskTCBBuffer = &IdleTaskTCB;
	*ppxIdleTaskStackBuffer = IdleTaskStack;
	*pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;
}

在这里设置了TCB,任务栈、栈深度。关键一点,栈深度不是我们自己自由设定的,它有最小值,就是上面代码的宏。它的定义如下:
位于FreeRTOSConfig.h,

#define configMINIMAL_STACK_SIZE		( ( unsigned short ) 130 )
  • 而任务调度器同样会创建软件定时器任务。
    在这里插入图片描述
    而宏configUSE_TIMERS的值在FreeRTOSConfig.h中定义。
/* Software timer definitions. */
#define configUSE_TIMERS				1
#define configTIMER_TASK_PRIORITY		( 2 )
#define configTIMER_QUEUE_LENGTH		10
#define configTIMER_TASK_STACK_DEPTH	( configMINIMAL_STACK_SIZE * 2 )

因为configUSE_TIMERS等于1,所以还需要为软件定时器的任务分配栈空间和TCB。【如果不需要软件定时器,就可以手动将此处改为0,也就不用管它了】具体如下图:
在这里插入图片描述
于是回顾上面的示例代码,你就明白了。
在这里插入图片描述

  • 因为静态创建任务函数需要我们自行定义任务栈和TCB,所以对于每一个任务都需要定义下面的代码。
#define TASK_STACK_SIZE 128//定义栈深度
TaskHandle_t xHandle;//定义任务句柄,方便操作任务
StackType_t xTaskStack[TASK_STACK_SIZE];//任务栈定义
StaticTask_t xTaskTCB;//TCB定义
  • 创建任务的时候,需要将任务栈和TCB都作为入参传递到函数中。
  • 如果需要删除任务,需要将任务句柄作为入参,例如:
vTaskDelete(StartTask_Handler); //删除开始任务

特点

  • 需要用户手动分配任务栈和控制块,适合对内存使用有严格要求的场景。
  • 任务删除后,内存不会被系统回收,需手动管理。
  • 适合任务数量较少或内存有限的嵌入式系统。

与 xTaskCreate() 的主要区别

特性xTaskCreate()xTaskCreateStatic()
内存分配方式动态静态
内存来源FreeRTOS 堆用户提供
适用场景通用无动态内存环境
配置要求需要配置堆需要配置configSUPPORT_STATIC_ALLOCATION为1

适用场景

  1. 无动态内存环境:在禁止使用动态内存分配的系统中使用
  2. 确定性要求高:避免动态内存分配带来的不确定性
  3. 内存受限系统:精确控制内存使用
  4. 安全关键系统:避免内存碎片问题

受MPU保护的任务(xTaskCreateRestricted)

xTaskCreateRestricted 是 FreeRTOS 提供的一个用于创建受内存保护单元(MPU)保护的任务的函数。它允许在具有 MPU 的 MCU 上创建任务,并对任务的内存访问进行限制。该函数的功能与 xTaskCreate() 类似,但增加了对 MPU 的支持,适用于需要保护任务内存的场景。函数原型如下:

BaseType_t xTaskCreateRestricted(
    const TaskParameters_t * const pxTaskDefinition,
    TaskHandle_t * const pxCreatedTask
);

参数说明

参数名类型说明
pxTaskDefinitionconst TaskParameters_t * const指向任务定义结构体的指针,包含任务的函数、名称、堆栈大小、参数、优先级等信息。
pxCreatedTaskTaskHandle_t * const用于返回创建任务的句柄,以便后续操作任务。

任务定义结构体 TaskParameters_t

TaskParameters_t 是一个包含任务所有必要信息的结构体,通常包括以下字段:

  • pvTaskCode:任务函数指针。
  • pcName:任务名称。
  • usStackDepth:任务栈大小。
  • pvParameters:传递给任务的参数。
  • uxPriority:任务优先级。
  • xRegions:内存区域定义,用于 MPU 保护。

示例代码

以下是一个使用 xTaskCreateRestricted 创建受 MPU 保护任务的示例代码:

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

// 定义任务参数结构体
typedef struct {
    TaskFunction_t pvTaskCode;
    const char * const pcName;
    unsigned short usStackDepth;
    void *pvParameters;
    UBaseType_t uxPriority;
    const MemoryRegion_t * const xRegions;
} TaskParameters_t;

// 任务函数
void vMyTask(void *pvParameters) {
    // 任务代码
    while (1) {
        // 任务逻辑
    }
}

// 任务定义
TaskParameters_t xTaskDefinition = {
    .pvTaskCode = vMyTask,
    .pcName = "MyTask",
    .usStackDepth = 1024,
    .pvParameters = NULL,
    .uxPriority = tskIDLE_PRIORITY,
    .xRegions = NULL // 可以定义 MPU 保护区域
};

// 创建任务
TaskHandle_t xTaskHandle;

int main() {
    // 创建任务
    if (xTaskCreateRestricted(&xTaskDefinition, &xTaskHandle) != pdPASS) {
        // 任务创建失败
    }

    // 启动调度器
    vTaskStartScheduler();

    // 任务创建成功后,进入死循环
    for (;;);
}

注意事项

  1. MPU 支持xTaskCreateRestricted 仅在 FreeRTOS-MPU 环境下可用,因此需要在 FreeRTOSConfig.h 中启用 configSUPPORT_DYNAMIC_ALLOCATION
  2. 内存分配:该函数使用动态内存分配来创建任务的控制块和栈空间。
  3. 任务删除:使用 vTaskDelete() 删除任务时,需要提供任务的句柄和任务控制块的地址。

vTaskDelete 是 FreeRTOS 中用于删除任务的函数,其主要功能是将指定任务从系统中移除,并释放其占用的资源。以下是关于该函数的详细说明:


删除任务(vTaskDelete)

vTaskDelete 用于删除一个已创建的任务。该函数会从任务的就绪列表、阻塞列表、挂起列表和事件列表中移除该任务,并释放其任务控制块(TCB)和堆栈内存。需要注意的是,空闲任务(Idle Task)负责释放由系统分配的内存,而用户分配的内存需要在任务删除前手动释放,否则可能导致内存泄漏 。

void vTaskDelete(TaskHandle_t xTaskToDelete);

参数说明

  • xTaskToDelete:要删除的任务的句柄。若传入 NULL,则表示删除当前正在运行的任务(即调用该函数的任务)。

注意事项

  • 必须在 FreeRTOSConfig.h 中启用 INCLUDE_vTaskDelete,否则该函数不可用。
  • 删除任务时,需确保空闲任务有足够时间执行内存释放。通常,将空闲任务的优先级设置为最低,以避免内存泄漏 。
  • 删除任务后,系统会将该任务添加到“待删除列表” ,由空闲任务在下次运行时进行清理 。

函数执行流程

  1. 检查任务句柄:若 xTaskToDeleteNULL,则删除当前任务。
  2. 从任务列表中移除:将任务从就绪、阻塞、挂起和事件列表中移除。
  3. 更新任务状态:更新任务优先级记录,并递增 uxTaskNumber 以通知内核调试器重新生成任务列表。
  4. 释放内存
    • 若删除的是当前任务,将其插入“待删除列表”,并增加 uxDeletedTasksWaitingCleanUp 变量。
    • 若删除的是其他任务,调用 prvDeleteTCB() 函数释放任务的 TCB 和堆栈内存。
  5. 强制任务切换:若删除的是当前任务,调用 portPRE_TASK_DELETE_HOOK 钩子函数,并执行任务切换 。

任务的挂起和恢复

在 FreeRTOS 中,任务的挂起和恢复是通过一系列 API 函数实现的。这些函数允许开发人员在需要暂停任务执行时将其挂起,并在需要时恢复任务的运行。以下是主要的挂起和恢复 API 函数及其功能说明:

API 函数功能描述说明
vTaskSuspend(TaskHandle_t xTaskToSuspend)挂起指定任务将任务设置为挂起状态,任务将永远处于挂起状态,除非调用 vTaskResume()xTaskResumeFromISR()
vTaskResume(TaskHandle_t xTaskToResume)恢复挂起任务将任务从挂起状态恢复到就绪状态,如果此时被恢复任务的优先级高于正在运行任务的优先级,则会发生任务切换 。
xTaskResumeFromISR(TaskHandle_t xTaskToResume)在中断服务函数中恢复任务用于在中断服务函数中恢复任务,返回值指示是否需要进行上下文切换 。

vTaskSuspend()

  • 功能:将指定任务设置为挂起状态。
  • 参数xTaskToSuspend 是要挂起的任务句柄。如果传入 NULL,则表示挂起当前任务。
  • 特点:一旦任务被挂起,它将永远处于挂起状态,直到被显式恢复。

vTaskResume()

  • 功能:将挂起的任务恢复到就绪状态。
  • 参数xTaskToResume 是要恢复的任务句柄。
  • 特点:恢复任务后,任务将重新加入就绪队列,等待调度器调度。如果恢复任务的优先级高于当前运行任务,则会发生任务切换。

xTaskResumeFromISR()

  • 功能:在中断服务函数中恢复任务。
  • 参数xTaskToResume 是要恢复的任务句柄。
  • 特点:该函数用于在中断上下文中恢复任务,返回值 pdTRUE 表示需要进行上下文切换,pdFALSE 表示不需要。

使用场景示例

以下是一个简单的示例,展示了如何在按键中断中挂起和恢复任务:

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

// 任务函数
void vTaskFunction(void *pvParameters) {
    while (1) {
        printf("Task is running\n");
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

int main() {
    TaskHandle_t xTaskHandle = NULL;

    // 创建任务
    xTaskCreate(vTaskFunction, "Task", 1024, NULL, tskIDLE_PRIORITY, &xTaskHandle);

    // 启动调度器
    vTaskStartScheduler();

    // 任务创建成功后,进入死循环
    for (;;);
}

在中断服务函数中,可以使用 vTaskSuspend()xTaskResumeFromISR() 来控制任务的挂起和恢复:

void EXTI0_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_LINE0) != RESET) {
        printf("Suspend task\n");
        vTaskSuspend(xTaskHandle); // 挂起任务
        EXTI_ClearITPendingBit(EXTI_LINE0);
    }
}

void EXTI1_IRQHandler(void) {
    if (EXTI_GetITStatus(EXTI_LINE1) != RESET) {
        printf("Resume task\n");
        xTaskResumeFromISR(xTaskHandle); // 恢复任务
        EXTI_ClearITPendingBit(EXTI_LINE1);
    }
}

注意事项

  • 任务挂起和恢复的顺序:必须确保在恢复任务之前,任务已经被正确挂起。
  • 中断优先级:在中断服务函数中调用 xTaskResumeFromISR() 时,中断优先级不能高于 FreeRTOS 管理的最高优先级,否则可能导致程序异常 。
  • 任务状态:挂起的任务不会参与调度,也不会执行任何代码,直到被显式恢复 。

通过合理使用 vTaskSuspend()vTaskResume()xTaskResumeFromISR(),可以有效地管理任务的执行流程,确保任务在需要时暂停或恢复。


本文到此结束,欢迎点赞、转发、收藏!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值