一、FreeRTOS的任务概念
在FreeRTOS中,任务(Task)是操作系统调度的基本单位。每个任务都是一个无限循环的函数,它执行特定的功能。任务可以被看作是一个轻量级的线程,具有自己的堆栈和优先级。下面是如何定义一个任务函数:任务拥有了独立的堆栈,所以在任务中要合理的分配任务堆栈,避免内存浪费。
任务函数需要符合一定的格式。它必须接受一个 void* 类型的参数,并且返回类型为 void。这个函数通常包含一个无限循环,以便任务能够持续运行。
void TaskFunction( void *pvParameters )
{
for( ;; ) //while(1)
{
// 任务要执行的代码
// ...
// 可以调用vTaskDelay()来让出CPU时间片
vTaskDelay(1); // 延迟1毫秒
}
}
每一个任务while(1)中都必须有vTaskDelay函数,至少vTaskDelay(1); // 延迟1毫秒,作用保证任务必须让出CPU占用权。
- 任务创建:调用xTaskCreate函数创建任务,并指定任务的名称、堆栈大小、优先级以及传递给任务函数的参数。
- 启动调度器:在创建完所有任务后,通过调用 vTaskStartScheduler() 来启动FreeRTOS内核,这将开始执行任务调度。
二、任务的创建和删除
2.1 函数xTaskCreate(动态创建任务)-常用
此函数用来创建一个任务,任务需要RAM来保存与任务有关的状态信息(任务控制块),任务也需要一定的RAM 来作为任务堆栈。如果使用函数xTaskCreate()来创建任务的话那么这些所需的RAM就会自动的从FreeRTOS的堆中分配,因此必须提供内存管理文件,默认我们使用heap_4.c这个内存管理文件,而且宏configSUPPORT_DYNAMIC_ALLOCATION必须为1。如果使用函数xTaskCreateStatic()创建的话这些RAM就需要用户来提供了。新创建的任务默认就是就绪态的,如果当前没有比它更高优先级的任务运行那么此任务就会立即进入运行态开始运行,不管在任务调度器启动前还是启动后,都可以创建任务。此函数也是我们以后经常用到的,本教程所有例程均用此函数来创建任务,函数原型如下:
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode,
const char *const pcName,
const uint16_t usStackDepth,
void *const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t *const pxCreatedTask)
参数:
- pxTaskCode:任务函数。
- pcName:任务名字,一般用于追踪和调试,任务名字长度不能超过configMAX_TASK_NAME_LEN。
- usStackDepth:任务堆栈大小,注意实际申请到的堆栈是usStackDepth的4倍,
例如数值128,即128字。其中空闲任务的任务堆栈大小为configMINIMAL_STACK_SIZE。
当前栈类型为StackType_t,而StackType_t是uint32_t。
- pvParameters:传递给任务函数的参数。
- uxPriotiry:任务优先级,范围0~configMAX_PRIORITIES-1。若填写的数值超过configMAX_PRIORITIES-1,则静默地配置为configMAX_PRIORITIES-1。有些版本增加了configASSERT( uxPriority < configMAX_PRIORITIES )语句,且使能了configASSERT_DEFINED,则停留在该处。
任务的优先级不能设置为0,优先级为0是空闲优先的优先级
任务的优先级数字越小,优先级越低,任务的优先级数字越大,优先级越高。
- pxCreatedTask:任务句柄,任务创建成功以后会返回此任务的任务句柄,
这个句柄其实就是任务的任务堆栈。此参数就用来保存这个任务句柄。
其他API函数可能会使用到这个句柄。
2.2 函数xTaskCreateStatic(静态创建任务)-了解
它与动态创建任务的区别是:它们的区别在开辟堆栈。xTaskCreate由系统动态堆栈 xTaskCreateStatic:堆栈需要用用户来提供
此函数和xTaskCreate()的功能相同,也是用来创建任务的,但是使用此函数创建的任务所需的 RAM需要用用户来提供。如果要使用此函数的话需要将宏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 );
- pxTaskCode:任务函数。
- pcName:任务名字,一般用于追踪和调试,任务名字长度不能超过configMAX_TASK_NAME_LEN。
- usStackDepth:任务堆栈大小,由于本函数是静态方法创建任务,所以任务堆栈由用户给出,一般是个数组,此参数就是这个数组的大小。
- pvParameters:传递给任务函数的参数。
- uxPriotiry:任务优先级,范围 0 ~ configMAX_PRIORITIES-1,数值越大,优先级就越高,在FreeRTOSConfig.h可以配置configMAX_PRIORITIES。若填写的数值超过configMAX_PRIORITIES-1,则静默地配置为configMAX_PRIORITIES-1。有些版本增加了configASSERT( uxPriority < configMAX_PRIORITIES )语句,且使能了configASSERT_DEFINED,则停留在该处。
- puxStackBuffer:任务堆栈,一般为数组,数组类型要为StackType_t类型。
- pxTaskBuffer:任务控制块。
- NULL;任务创建失败,puxStackBuffer 或 pxTaskBuffer为 NULL的时候会导致这个错误的发生。
- 其他值:任务创建成功,返回任务的任务句柄。
2.3 函数vTaskDelete
删除一个用函数xTaskCreate()或者xTaskCreateStatic()创建的任务,被删除了的任务不再存在,也就是说再也不会进入运行态。任务被删除以后就不能再使用此任务的句柄!如果此任务是使用动态方法创建的,也就是使用函数 xTaskCreate()创建的,那么在此任务被删除以后此任务之前申请的堆栈和控制块内存会在空闲任务中被释放掉,因此当调用函数vTaskDelete()删除任务以后必须给空闲任务一定的运行时间。
只有那些由内核分配给任务的内存才会在任务被删除以后自动的释放掉,用户分配给任务的内存需要用户自行释放掉,比如某个任务中用户调用函数 pvPortMalloc()分配了500字节的内存,那么在此任务被删除以后用户也必须调用函数 vPortFree()将这500字节的内存释放掉,否则会导致内存泄露。此函数原型如下:
//xTaskToDelete 为待删除的任务的句柄
vTaskDelete( TaskHandle_t xTaskToDelete )
三、任务的挂起和恢复
有时候我们需要暂停某个任务的运行,过一段时间以后在重新运行。这个时候要是使用任务删除和重建的方法的话那么任务中变量保存的值肯定丢失了!FreeRTOS给我们提供了解决这种问题的方法,那就是任务挂起和恢复,当某个任务要停止运行一段时间的话就将这个任务挂起,当要重新运行这个任务的话就恢复这个任务的运行。
3.1函数vTaskSuspend
此函数用于将某个任务设置为挂起态,进入挂起态的任务永远都不会进入运行态。退出挂起态的唯一方法就是调用任务恢复函数vTaskResume或xTaskResumeFromISR,函数原型如下:
//xTaskToSuspend为要挂起的任务的任务句柄
void vTaskSuspend( TaskHandle_t xTaskToSuspend)
3.2 函数vTaskResume
将一个任务从挂起态恢复到就绪态,只有通过函数vTasksSuspend()设置为挂起态的任务才可以使用vTaskRexume()恢复!函数原型如下:
//xTaskToResume为要恢复的任务的任务句柄
void vTaskResume( TaskHandle_t xTaskToResume)
3.3 函数vTaskResumeFromISR
此函数是vTaskResume()的中断版本,用于在中断服务函数中恢复一个任务。函数原型如下:
- xTaskToResume:要恢复的任务的任务句柄。
BaseType_t xTaskResumeFromISR(TaskHandle t xTaskToResume)
3.4 函数vTaskSuspendAll(慎用)
此函数是挂起调度器可以停止上下文切换而不用关中断。如果某个中断在调度器挂起过程中要求进行上下文切换,则个这请求也会被挂起,直到调度器被唤醒后才会得到执行。
void vTaskSuspendAll( void )
3.5 函数xTaskResumeAll
此函数是恢复调度器。在调度器挂起过程中,上下文切换请求也会被挂起,直到调度器被唤醒后才会得到执行。
BaseType_t xTaskResumeAll( void )
注意:
vTaskSuspendAll被调用后,禁止调用引起任务切换相关函数(例如vTaskDelay、等待信号量/互斥锁/消息队列等)。
示例代码
int main(void)
{
/* 创建app_task1任务 */
xTaskCreate((TaskFunction_t )app_task1, /* 任务入口函数 */
(const char* )"app_task1", /* 任务名字 */
(uint16_t )256, /* 任务栈大小 */
(void* )NULL, /* 任务入口函数参数 */
(UBaseType_t )2, /* 任务的优先级 */
(TaskHandle_t* )&app_task1_handle); /* 任务控制块指针 */
/* 开启任务调度器 */
vTaskStartScheduler();
}
//一个专门初始化硬件的任务,该任务可以包含创建任务的功能
static void app_task1(void* pvParameters)
{
BaseType_t xReturn;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
//硬件初始化
Led_Init();
Usart1_Init(9600);
//任务1优先高
xTaskCreate( user_task1, "usertask1", 256, NULL, 3, &user_task1_handle);
//任务2优先低
xTaskCreate( user_task2, "usertask2", 256, NULL, 2, &user_task2_handle);
//它是一个系统指示灯
for(;;)
{
//GPIO_ToggleBits(GPIOF, GPIO_Pin_9);
vTaskDelay(1000);
}
}
//任务函数实现
static void user_task1(void* pvParameters)
{
//先执行
while(1)
{
/* 挂起调度器 */
vTaskSuspendAll();
//函数会执行
printf("app_task1 is running ...\r\n");
//这个函数会引起任务切换,由于挂起调度器,所有任务会卡死
vTaskDelay(1000);
/* 恢复调度器 */
xTaskResumeAll();
}
}
//任务函数实现
static void user_task2(void* pvParameters)
{
int cut = 0;
while(1)
{
//这个打印不会执行,原因是任务1挂起调度器
printf("app_task2 is running ...\r\n");
//这个函数会引起任务切换,由于挂起调度器,任务会卡死
vTaskDelay(1000);
}
}
四、任务的状态机
任务状态任务可以存在于以下状态中:
运行;就绪;阻塞;挂起;
4.1 优先级
每个任务均被分配了从 0 到 ( configMAX_PRIORITIES - 1 ) 的优先级,其中的 configMAX_PRIORITIES 在 FreeRTOSConfig.h 中定义。
也就是说默认的优先级:0~4
#define configMAX_PRIORITIES ( 10 ) //将任务优先等级设置10
在 FreeRTOS 中,configUSE_PORT_OPTIMISED_TASK_SELECTION 是一个配置选项,
用于优化任务调度时的最高优先级任务选择算法,以提高调度器的执行效率。
任意数量的任务可共用相同的优先级。 如果configUSE_TIME_SLICING 未经定义,或者如果 configUSE_TIME_SLICING 设置为 1,则相同优先级的就绪状态任务 将使用时间切片轮询调度方案共享可用的处理时间。
4.1.1 相同优先级
代码例子:
https://download.csdn.net/download/m0_63622771/90887306
实验结果:
4.1.2 不同优先级
代码例子:
https://download.csdn.net/download/m0_63622771/90887308
实验结果: