前言
本篇文章将为大家讲解FreeRTOS中的任务状态,在FreeRTOS任务是有非常多种状态的,了解了任务的这些状态有助于我们理解任务是如何运行和停止的。
一、简单实验
在将这个之前我们先来做一个实验来观察任务是怎么样运行的:
代码:
下面的代码先定义了三个标志位,这三个标志位标志着哪一个任务在运行。
static int Task1Flag = 0; static int Task2Flag = 0; static int Task3Flag = 0; //任务执行函数 void Task1(void * param) { static int i = 0; while (1) { Task1Flag = 1; Task2Flag = 0; Task3Flag = 0; } } //任务执行函数 void Task2(void * param) { while (1) { Task1Flag = 0; Task2Flag = 1; Task3Flag = 0; } } //任务执行函数 void Task3(void * param) { while (1) { Task1Flag = 0; Task2Flag = 0; Task3Flag = 1; } }
打开keil中的模拟串口:
打开keil中的逻辑分析仪:
将定义的三个变量加入逻辑分析仪中:
将状态都设置为bit:
设置好后全速运行代码:
我们可以观察到其实这三个任务并不是在同一时刻执行的,而是分开执行,只是执行时间较短我们无法分辨出到底是谁在执行。
通过观察每个任务的执行时间可以得知每个任务运行的时间大概是1ms左右,这是什么原因呢?
在FreeRTOS中存在一个Tick中断每当发生一次中断时就会判断是否需要进行任务的切换,那么这个Tick中断又是多少ms发生一次呢?
在FreeRTOSConfig这个文件中我们可以找到答案:
configTICK_RATE_HZ指定了内核时钟节拍的频率,以赫兹(Hz)为单位。
在FreeRTOS中,内核时钟节拍是一种时间标准,它用于测量任务运行时间、等待时间、计时器等功能的时间。内核时钟节拍的频率可以通过configTICK_RATE_HZ来设置,其默认值是1000,表示每秒进行1000次时钟节拍,即时钟节拍的周期为1毫秒。
还有一个问题就是为什么是任务3先执行呢?
在创建任务函数内部可以看到这样一个函数,这个函数会将创建好的任务添加进入就绪链表,后创建的任务在链表的最前面,所以后创建的任务将会被先取出来执行。
二、任务状态概念讲解
这里使用百问网的一张任务转换图片来讲解:
在FreeRTOS中,每个任务都有一个状态,表示当前任务的情况。FreeRTOS使用一些特定的宏定义来表示不同的任务状态,这些宏定义包括:
eRunning:表示任务正在运行。
eReady:表示任务处于就绪状态,等待调度器将其调度执行。
eBlocked:表示任务处于阻塞状态,即等待某些事件的发生,例如等待信号量、消息队列、定时器超时等。
eSuspended:表示任务处于暂停状态,即该任务已经被暂停,不参与调度,但它的状态和资源保留,能够在需要时恢复运行。
eDeleted:表示任务已被删除,对应的控制块和栈空间已被释放。
任务状态之间的转换是由FreeRTOS内核自动管理的。任务常常在下列几种情况下会发生状态变化:
创建任务时,任务状态由“未开始”变为“就绪”。
调度器根据任务优先级选取该任务并将其状态变为“运行中”。
任务等待某个事件(如信号量)时,任务状态变为“阻塞”。
任务等待其他任务释放资源时,任务状态可能会转变为“挂起”。
任务自己调用删除函数删除自己时,任务状态变为“已删除”。
三、vTaskDelay和vTaskDelayUntil
1.vTaskDelay
vTaskDelay函数用于使当前任务暂停一段时间之后再继续执行。它的参数是一个整数,表示需要延迟的系统节拍数。例如,在默认的配置下,内核节拍周期为1毫秒,因此vTaskDelay(100)即为使当前任务暂停100毫秒。
需要注意的是,vTaskDelay会引起任务阻塞,同时该延迟时间不是绝对准确的。在等待期间FreeRTOS会尝试进行其他任务的调度,因此实际的延迟时间可能会比指定的时间长。
代码示例:
void Task1(void * param) { static int i = 0; while (1) { printf("Task1\r\n"); vTaskDelay(1000); } } //任务执行函数 void Task2(void * param) { while (1) { printf("Task2\r\n"); vTaskDelay(2000); } } //任务执行函数 void Task3(void * param) { while (1) { printf("Task3\r\n"); vTaskDelay(3000); } }
观察串口的打印结果:
2.vTaskDelayUntil
vTaskDelayUntil 是一个精确的定时函数,它使任务等待到特定的时间点才重新变为就绪状态。
调用 vTaskDelayUntil 时,需要提供一个时间戳(以 TickType_t 类型表示),任务将休眠,直到系统时钟达到或超过该时间戳。
这使得任务可以以精确的时间间隔执行,非常适合实时性要求高的应用。
// 定义一个任务,该任务会每隔1秒输出一次消息 void Task1(void* pvParameters) { TickType_t xLastWakeTime; const TickType_t xFrequency = pdMS_TO_TICKS(1000); // 1秒的时间间隔 // 获取当前时间作为初始时间 xLastWakeTime = xTaskGetTickCount(); while (1) { // 执行任务1的操作,例如输出消息 printf("Task1 is running...\n"); // 等待到达下一个时间间隔 vTaskDelayUntil(&xLastWakeTime, xFrequency); } } int main(void) { // 初始化FreeRTOS内核和硬件 // 创建任务1 xTaskCreate(Task1, "Task1", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY + 1, NULL); // 启动调度器 vTaskStartScheduler(); // 此处不会执行,因为调度器会接管控制权 return 0; }
3.vTaskDelay和vTaskDelayUntil的区别
vTaskDelay函数和vTaskDelayUntil函数都用于在FreeRTOS中实现任务的时间延迟,但它们的方式不同。
vTaskDelay函数通过传递一个相对延迟的节拍数来工作。任务会阻塞指定的节拍数,然后继续执行。这意味着vTaskDelay的延迟时间是相对于当前任务的执行时间而言的,实际的延迟时间可能会受到任务切换和系统负载的影响。因此,无法保证精确的延迟时间,可能会有一定的误差。
vTaskDelayUntil函数通过传递一个绝对时间点(以节拍数表示)来工作。任务会等待直到当前时间达到或超过传递的绝对时间点,然后继续执行。这意味着vTaskDelayUntil提供了更精确的延迟控制,可以实现准确的定时任务。您可以根据需要计算下一个执行时间点,并将其传递给vTaskDelayUntil函数,任务将在该时间点进行阻塞,确保精确的延迟时间。
vTaskDelay用于相对延迟,而vTaskDelayUntil用于绝对时间点延迟,使得在实现定时任务时更加方便和精确。
总结
本篇文章就讲解到这里。