前言
本篇文章将介绍FreeRTOS任务的创建(动态方法和静态方法),了解什么是任务和任务的具体创建方法。
一、FreeRTOS任务基本概念
任务(Task):FreeRTOS 中的任务是程序执行的基本单位。任务以优先级的方式管理,高优先级的任务比低优先级的任务更容易被执行。每个任务都具有不同的堆栈和一组标志,用于控制任务的行为和与其他任务和内核进行通信。
二、动态创建任务
在FreeRTOS中的task.c中我们可以找到动态创建任务的函数原型:
创建一个任务:
使用 xTaskCreate 函数是在 FreeRTOS 中创建任务的一种方法,函数原型如下:
BaseType_t xTaskCreate( TaskFunction_t id * const pvParameters, UBaseType_t uxPriority, TaskHandle_t * const pxCreatedTask );
1.TaskFunction_t pvTaskCode:任务函数的指针。这个参数是一个函数指针,指向一个任务的实际执行代码。任务函数的原型必须符合 TaskFunction_t 类型,即 void func(void *pvParameters),其中 pvParameters 是一个指向和任务相关的参数的指针。
2.const char * const pcName:任务的名称。这个参数是一个字符串常量,用于标识任务。
3.const configSTACK_DEPTH_TYPE usStackDepth:任务栈的大小。这个参数表示任务栈的深度、空间或者大小,以字节为单位。在创建任务时需要提供一个足够大的堆栈以确保任务可以运行。
4.void * const pvParameters:任务参数的指针。这个参数是任务的实际参数,它的类型可以是 void 指针或者其他任意类型的指针。在任务执行时,可以通过此参数来传递任务需要的参数。
5.UBaseType_t uxPriority:任务的优先级。这个参数用于指定任务的优先级,数值越大表示优先级越高。优先级的取值范围根据量化数值的位数而定,一般来说,取值范围是 0-31,在 FreeRTOS 中,0 号优先级最低,而 31 号优先级最高。
6.TaskHandle_t * const pxCreatedTask:用于返回创建的任务的句柄。创建任务成功后,系统将返回一个指向该任务的任务句柄。
示例代码:
TaskHandle_t xHandleTask1;//任务句柄 TaskHandle_t xHandleTask2;//任务句柄 //任务执行函数 void Task1(void * param) { while (1) { printf("1"); } } //任务执行函数 void Task2(void * param) { while (1) { printf("2"); } } xTaskCreate(Task1, "Task1", 100, NULL, 1, &xHandleTask1);//创建一个任务 xTaskCreate(Task2, "Task2", 100, NULL, 1, &xHandleTask2);//创建一个任务
在keil5中打开模拟仿真运行代码:
点击调试运行:
这里我将程序下载到板子上使用串口打印观察实验现象:
根据现象可以知道Task1和Task2任务轮流执行。
三、静态创建任务
使用 xTaskCreateStatic() 函数静态创建任务。该函数与 xTaskCreate() 类似,但它使用静态分配的任务控制块和堆栈空间,而不是在运行时动态分配。函数原型如下:
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 );
通过静态创建方式,可以使用编译器的内存分配器来分配任务所需的内存空间,从而避免了动态分配内存的开销和潜在的内存泄漏问题。静态创建方式还可以在一些对内存使用有限制的场合下有效地管理系统资源,提高系统的稳定性和性能。
看到这里很多人可能认为没有这个函数,这其实是FreeRTOS内部没有开启这个函数的使用。
进入task.c这个文件我们会看到只有当这个宏被设置的时候才能够使用这个静态创建任务的函数。
既然这样那么我们就来定义这个宏:
找到FreeRTOSConfig.h这个头文件在这里定义这个宏:
编译代码发现还是出错:
这里说的是没有vApplicationGetIdleTaskMemory这个函数:
使用静态创建函数创建 FreeRTOS 任务时,需要记住一点:必须实现 vApplicationGetIdleTaskMemory 函数来完成空闲任务的内存分配。这是因为在使用静态内存分配时,FreeRTOS 会自动创建一个空闲任务,该任务会在系统空闲时进行调度。空闲任务可以执行某些不需要 CPU 时间的操作,比如暂时挂起 CPU、等待时钟中断、等待消息等,以便释放 CPU 资源给其它任务使用。
空闲任务需要分配堆栈和任务控制块,而这些内存区域需要手动向 FreeRTOS 系统申请。在这种情况下,我们需要实现 vApplicationGetIdleTaskMemory 函数,该函数会在空闲任务创建之前被调用。在该函数中,我们需要声明一个静态的变量作为空闲任务控制块的内存池,并将该内存池的首地址和大小等信息传递给空闲任务创建函数,以完成空闲任务的内存分配。
vApplicationGetTimerTaskMemory函数的实现:
StackType_t xIdleTaskStack[100]; StaticTask_t xIdleTaskTCB; void vApplicationGetIdleTaskMemory( StaticTask_t ** ppxIdleTaskTCBBuffer, StackType_t ** ppxIdleTaskStackBuffer, uint32_t * pulIdleTaskStackSize ) { *ppxIdleTaskTCBBuffer = &xIdleTaskTCB; *ppxIdleTaskStackBuffer = xIdleTaskStack; *pulIdleTaskStackSize = 100; }
再次编译后会发现错误已经没有了。
示例代码:
StackType_t xTask3Stack[100]; StaticTask_t xTask3TCB; //任务执行函数 void Task3(void * param) { while (1) { printf("3"); } } xTaskCreateStatic(Task3, "Task3", 100, NULL, 1, xTask3Stack, &xTask3TCB);//创建一个静态任务
四、静态创建任务和动态创建任务的区别
静态创建任务是在编译时为任务分配内存,这意味着任务在运行时之前已经预分配了足够的内存。这种方法不需要在运行时使用动态内存分配函数,因此更加可靠和节省内存。在静态方式下,任务可以使用xTaskCreateStatic()函数创建。
动态创建任务是在运行时通过动态内存分配函数分配任务内存。这种分配方式可以更灵活地适应不同大小和数量的任务,并且支持删除或重新创建任务。然而,在动态方式下,程序需要在运行时使用动态内存分配函数,这可能会导致内存泄漏和堆碎片等问题。在动态方式下,任务需要使用xTaskCreate()函数创建。
选择使用哪种方式创建任务取决于应用程序的设计和实际需要。如果应用程序中的任务数量和大小已知,则可以使用静态方式分配内存,并且无需动态内存分配。如果应用程序需要更多的灵活性,并且需要在运行时根据需要创建或删除任务,则应使用动态方式分配内存。
需要注意的是,静态方式创建任务需要事先知道任务所需的内存大小,以及将任务的堆栈和控制块明确地分配给该任务。如果任务使用的内存超出了分配的内存,则可能会发生严重错误,例如内存泄漏或严重的崩溃。在动态方式下,内存分配在运行时动态进行,因此可以更好地处理任务所需的内存。
五、任务的删除
有任务的创建那么肯定就有任务的删除,下面我们来看看怎么进行任务的删除。
在FreeRTOS中,任务可以使用vTaskDelete()函数进行删除,该函数会立即终止当前正在运行的任务,并释放该任务所使用的内存资源。
vTaskDelete函数原型:
只需要传入要删除的任务句柄即可将任务删除,在一个任务中想要将自己删除的话需要传入的是NULL。
void vTaskDelete( TaskHandle_t xTaskToDelete );
//任务执行函数 void Task1(void * param) { static int i = 0; while (1) { i++; if(i == 100) { vTaskDelete(xHandleTask2);//删除任务2 } if(i == 200) { vTaskDelete(NULL);//删除自己 } printf("1"); } }
总结
本篇文章我们详细的介绍了FreeRTOS任务的创建和删除,并介绍了这两种方法创建任务的区别,希望大家好好理解并多加练习。