任务概念
在FreeRTOS中,一个任务相当于一个线程,可以有很多的任务,每个人任务可以设置不同的优先级。相同优先级的任务轮流使用CPU,高优先级的任务可以一直使用CPU,直到主动放弃,低级的任务才有被执行的机会。
任务函数原型如下
void ATaskFunction( void *pvParameters );
要注意的是:
- 这个函数不能返回 ,并且必须处于死循环中或主动终止的情况,否则整个操作系统将无法使用
- 同一个函数,可以用来创建多个任务;换句话说,多个任务可以运行同一个
- 函数内部,尽量使用局部变量:每个任务都有自己的栈 ,每个任务运行这个函数时
- 函数使用全局变量、静态变量的话,只有一个副本:多个任务使用的是同一个副本
void ATaskFunction( void *pvParameters )
{
/* 对于不同的任务,局部变量放在任务的栈里,有各自的副本 */
int32_t lVariableExample = 0;
/* 任务函数通常实现为一个无限循环 */
for( ;; )
{
/* 任务的代码 */
}
/* 如果程序从循环中退出,一定要使用vTaskDelete删除自己
* NULL表示删除的是自己
*/
vTaskDelete( NULL );
/* 程序不会执行到这里, 如果执行到这里就出错了 */ ```
}
任务创建
创建任务时可以使用2个函数:动态分配内存、静态分配内存。
使用动态分配内存的函数如下:
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 ); // 任务句柄, 以后使用它来操作这个任务
参数说明:
- 任务句柄:任务句柄(Task Handle)是在 FreeRTOS中用于标识和引用任务的数据类型。每个创建的任务都会分配一个唯一的任务句柄,通过该句柄可以对任务进行操作和管理。
- 任务句柄是一个指向任务控制块(Task Control Block,TCB)的指针。任务控制块是 FreeRTOS
中用于描述和管理任务的数据结构,包含了任务的状态、优先级、堆栈等信息。- 使用任务句柄,可以通过 FreeRTOS 提供的 API 函数对任务进行操作,例如挂起(suspend)、恢复(resume)、删除(delete)任务,或者查询任务的状态等。另外,任务句柄还可以用于任务通信和同步的机制,例如向任务发送信号量或消息。
使用静态分配内存的函数如下:
TaskHandle_t xTaskCreateStatic ( TaskFunction_t pxTaskCode, // 函数指针, 任务函数 const char * const pcName, // 任务的名字 const uint32_t ulStackDepth, // 栈大小,单位为word,10表示40字节 void * const pvParameters, // 调用任务函数时传入的参数 UBaseType_t uxPriority, // 优先级 StackType_t * const puxStackBuffer, // 静态分配的栈,就是一个buffer StaticTask_t * const pxTaskBuffer // 静态分配的任务结构体的指针,用它来操作这个任务
);
相比于使用动态分配内存创建任务的函数,最后2个参数不一样:
动态分配内存与静态分配内存创建任务的区别:
xTaskCreate与 xTaskCreateStatic的功能上的区别是,xTaskCreate是操作系统自动分配内存,xTaskCreateStatic是需要程序员手动定义内存;
xTaskCreate适用于项目开发中内存余量比较充足的项目,只是简单的分配大小就可以了;
xTaskCreateStatic适用于项目开发中内存比较紧张的项目,事先定义好内存大小并占用内存空间,这样在系统编译的时候就可以确定总内存大小,也不会出现系统运行到当前任务时内存不足而出现崩溃的情况
任务删除
删除任务时使用的函数如下:
void vTaskDelete( TaskHandle_t xTaskToDelete );
怎么删除任务?举个不好的例子:
⚫ 自杀:vTaskDelete(NULL)
⚫ 被杀:别的任务执行vTaskDelete(pvTaskCode),pvTaskCode是自己的句柄
⚫ 杀人:执行vTaskDelete(pvTaskCode),pvTaskCode是别的任务的句柄
任务执行流程Tick
对于同优先级的任务,它们“轮流”执行。怎么轮流?你执行一会,我执行一会。
"一会"怎么定义?
人有心跳,心跳间隔基本恒定。
FreeRTOS中也有心跳,它使用定时器产生固定间隔的中断。这叫Tick、滴答,比如每10ms发生一次时钟中断。
有了Tick的概念后,我们就可以使用Tick来衡量时间了,比如:
vTaskDelay(2); // 等待2个Tick,假设configTICK_RATE_HZ=100, Tick周期时10ms, 等待20ms // 还可以使用pdMS_TO_TICKS宏把ms转换为tick
vTaskDelay(pdMS_TO_TICKS(100)); // 等待100ms
注意,基于Tick实现的延时并不精确,比如vTaskDelay(2)的本意是延迟2个Tick周期,有可能经过1个Tick多一点就返回了。
使用vTaskDelay 函数时,建议以ms为单位,使用pdMS_TO_TICKS把时间转换为Tick。 这样的代码就与configTICK_RATE_HZ无关,即使配置项configTICK_RATE_HZ改变了,我们也不用去修改代码。
任务状态
在FreeRTOS中任务状态有:运行状态,暂停状态,阻塞状态,就绪状态
- 运行态(runnnig):当任务正在运行,此时的状态被称为运行态,即CPU的使用权被这个任务占用;
- 挂起态(suspended):任务被暂时停止,通过调用挂起函数(vTaskSuspend())可以把指定任务挂起,任务挂起后暂时不会运行,只有调用恢复函数(xTaskResume())才可以退出挂起状态;
- 阻塞态(blocked):任务在等待信号量、消息队列、事件标准组、系统延时时,被称为阻塞态,如果等待的事件到了,就会自动退出阻塞态,准备运行;
- 就绪态(ready):任务已经具备了运行条件(没有被挂起或阻塞),但是又更高优先级或同优先级的任务正在运行,所以需要等待的状态。
一个函数要进入阻塞状态,只有在运行的时候才能进入,可以阻塞别人也可以阻塞自己,暂停状态也一样。
当任务进入暂停状态,只能有别人来操作,才能恢复成就绪态