FreeRTOS系统延时实例分析
FreeRTOS系统延时实例分析
这篇文章主要讲解了“FreeRTOS系统延时实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“FreeRTOS系统延时实例分析”吧!
FreeRTOS提供了两个系统延时函数:相对延时函数vTaskDelay()和绝对延时函数
vTaskDelayUntil()。相对延时是指每次延时都是从任务执行函数vTaskDelay()开始,延时指定的时间结束;
绝对延时是指每隔指定的时间,执行一次调用vTaskDelayUntil()函数的任务。换句话说:任务以固定的频率执行。
1. 相对延时函数vTaskDelay()
考虑下面的任务,任务A在执行任务主体代码后,调用相对延时函数vTaskDelay()进入阻塞状态。系统中除了任务A外,还有其它任务,但是任务A的优先级最高。
voidvTaskA(void*pvParameters){/*阻塞500ms.注:宏pdMS_TO_TICKS用于将毫秒转成节拍数,FreeRTOSV8.1.0及以上版本才有这个宏,如果使用低版本,可以使用500/portTICK_RATE_MS*/constportTickTypexDelay=pdMS_TO_TICKS(500);for(;;){//...//这里为任务主体代码//.../*调用系统延时函数,阻塞500ms*/vTaskDelay(xDelay);}}
对于这样一个任务,执行过程如图1-1所示。当任务A获取CPU使用权后,先执行任务A的主体代码,之后调用系统延时函数vTaskDelay()进入阻塞状态。任务A进入阻塞后,其它任务得以执行。
FreeRTOS内核会周期性的检查任务A的阻塞是否达到,如果阻塞时间达到,则将任务A设置为就绪状态。由于任务A的优先级最高,会抢占CPU,再次执行任务主体代码,不断循环。
从图1-1可以看出,任务A每次延时都是从调用延时函数vTaskDelay()开始算起的,延时是相对于这一时刻开始的,所以叫做相对延时函数。
从图1-1还可以看出,如果执行任务A的过程中发生中断,那么任务A执行的周期就会变长,所以使用相对延时函数vTaskDelay(),不能周期性的执行任务A。
图1-1:相对延时函数执行示意图
我们来看一下源码。
voidvTaskDelay(constTickType_txTicksToDelay){BaseType_txAlreadyYielded=pdFALSE;/*如果延时时间为0,则不会将当前任务加入延时列表*/if(xTicksToDelay>(TickType_t)0U){vTaskSuspendAll();{/*将当前任务从就绪列表中移除,并根据当前系统节拍计数器值计算唤醒时间,然后将任务加入延时列表*/prvAddCurrentTaskToDelayedList(xTicksToDelay,pdFALSE);}xAlreadyYielded=xTaskResumeAll();}/*强制执行一次上下文切换*/if(xAlreadyYielded==pdFALSE){portYIELD_WITHIN_API();}}
如果延时大于0,则会将当前任务从就绪列表删除,然后加入到延时列表。是调用函数prvAddCurrentTaskToDelayedList()完成这一过程的。我们在前面一系列博文中多次提到,tasks.c中定义了很多局部静态变量,其中有一个变量xTickCount定义如下所示:
staticvolatileTickType_txTickCount=(TickType_t)0U;
这个变量用来计数,记录系统节拍中断的次数,它在启动调度器时被清零,在每次系统节拍时钟发生中断后加1。相对延时函数会使用到这个变量,xTickCount表示了当前的系统节拍中断次数,这个值加上参数规定的延时时间(以系统节拍数表示)xTicksToDelay,就是下次唤醒任务的时间,xTickCount+ xTicksToDelay会被记录到任务TCB中,随着任务一起被挂接到延时列表。
我们知道变量xTickCount是TickType_t类型的,它也会溢出。在32位架构中,当xTicksToDelay达到4294967295后再增加,就会溢出变成0。为了解决xTickCount溢出问题,FreeRTOS使用了两个延时列表:xDelayedTaskList1和xDelayedTaskList2,并使用两个列表指针类型变量pxDelayedTaskList和pxOverflowDelayedTaskList分别指向上面的延时列表1和延时列表2(在创建任务时将延时列表指针指向延时列表)。顺便说一下,上面的两个延时列表指针变量和两个延时列表变量都是在tasks.c中定义的静态局部变量。
如果内核判断出xTickCount+ xTicksToDelay溢出,就将当前任务挂接到列表指针pxOverflowDelayedTaskList指向的列表中,否则就挂接到列表指针pxDelayedTaskList指向的列表中。
每次系统节拍时钟中断,中断服务函数都会检查这两个延时列表,查看延时的任务是否到期,如果时间到期,则将任务从延时列表中删除,重新加入就绪列表。如果新加入就绪列表的任务优先级大于当前任务,则会触发一次上下文切换。
2. 绝对延时函数vTaskDelayUntil()
考虑下面的任务,任务B首先调用绝对延时函数vTaskDelayUntil ()进入阻塞状态,阻塞时间到达后,执行任务主体代码。系统中除了任务B外,还有其它任务,但是任务B的优先级最高。
voidvTaskB(void*pvParameters){staticportTickTypexLastWakeTime;constportTickTypexFrequency=pdMS_TO_TICKS(500);//使用当前时间初始化变量xLastWakeTime,注意这和vTaskDelay()函数不同xLastWakeTime=xTaskGetTickCount();for(;;){/*调用系统延时函数,周期性阻塞500ms*/vTaskDelayUntil(&xLastWakeTime,xFrequency);//...//这里为任务主体代码,周期性执行.注意这和vTaskDelay()函数也不同//...}}
对于这样一个任务,执行过程如图2-1所示。当任务B获取CPU使用权后,先调用系统延时函数vTaskDelayUntil()使任务进入阻塞状态。任务B进入阻塞后,其它任务得以执行。FreeRTOS内核会周期性的检查任务A的阻塞是否达到,如果阻塞时间达到,则将任务A设置为就绪状态。
由于任务B的优先级最高,会抢占CPU,接下来执行任务主体代码。任务主体代码执行完毕后,会继续调用系统延时函数vTaskDelayUntil()使任务进入阻塞状态,周而复始。
从图2-1可以看出,从调用函数vTaskDelayUntil()开始,每隔固定+
-周期,任务B的主体代码就会被执行一次,即使任务B在执行过程中发生中断,也不会影响这个周期性,只是会缩短其它任务的执行时间!所以这个函数被称为绝对延时函数,它可以用于周期性的执行任务A的主体代码。
图2-1:绝对延时函数执行示意图
函数vTaskDelayUntil()是如何做到周期性的呢,我们来看一下源码。
voidvTaskDelayUntil(TickType_t*constpxPreviousWakeTime,constTickType_txTimeIncrement){TickType_txTimeToWake;BaseType_txAlreadyYielded,xShouldDelay=pdFALSE;vTaskSuspendAll();{/*保存系统节拍中断次数计数器*/constTickType_txConstTickCount=xTickCount;/*计算任务下次唤醒时间(以系统节拍中断次数表示)*/xTimeToWake=*pxPreviousWakeTime+xTimeIncrement;/**pxPreviousWakeTime中保存的是上次唤醒时间,唤醒后需要一定时间执行任务主体代码,如果上次唤醒时间大于当前时间,说明节拍计数器溢出了*/if(xConstTickCount<*pxPreviousWakeTime){/*只有当周期性延时时间大于任务主体代码执行时间,才会将任务挂接到延时列表.*/if((xTimeToWake<*pxPreviousWakeTime)&&(xTimeToWake>xConstTickCount)){xShouldDelay=pdTRUE;}}else{/*也都是保证周期性延时时间大于任务主体代码执行时间*/if((xTimeToWake<*pxPreviousWakeTime)||(xTimeToWake>xConstTickCount)){xShouldDelay=pdTRUE;}}/*更新唤醒时间,为下一次调用本函数做准备.*/*pxPreviousWakeTime=xTimeToWake;if(xShouldDelay!=pdFALSE){/*将本任务加入延时列表,注意阻塞时间并不是以当前时间为参考,因此减去了当前系统节拍中断计数器值*/prvAddCurrentTaskToDelayedList(xTimeToWake-xConstTickCount,pdFALSE);}}xAlreadyYielded=xTaskResumeAll();/*强制执行一次上下文切换*/if(xAlreadyYielded==pdFALSE){portYIELD_WITHIN_API();}}
与相对延时函数vTaskDelay不同,本函数增加了一个参数pxPreviousWakeTime用于指向一个变量,变量保存上次任务解除阻塞的时间。这个变量在任务开始时必须被设置成当前系统节拍中断次数(见上文的任务B举例),此后函数vTaskDelayUntil()在内部自动更新这个变量。
由于变量xTickCount可能会溢出,所以程序必须检测各种溢出情况,并且要保证延时周期不得小于任务主体代码执行时间。这很好理解,不可能出现每5毫秒执行一个需要20毫秒才能执行完的任务。
如果我们以横坐标表示变量xTickCount的范围,则横坐标左端为0,右端为变量xTickCount所能表示的最大值。在如图2-2所示的三种情况下,才可以将任务加入延时列表。
图2-2中
*pxPreviousWakeTime和xTimeToWake之间表示任务周期性延时时间,
*pxPreviousWakeTime和xConstTickCount之间表示任务B主体代码执行时间。
图2-2中
第一种情况处理系统节拍中断计数器(xConstTickCount)和唤醒时间计数器(xTimeToWake)溢出情况;
第二种情况处理唤醒时间计数器(xTimeToWake)溢出情况
第三种情况处理常规无溢出的情况。
从图中可以看出,不管是溢出还是无溢出,都要求在下次唤醒任务之前,当前任务主体代码必须被执行完。表现在图2-2中,就是变量xTimeToWake总是大于变量xConstTickCount(每溢出一次的话相当于加上一次最大值Max)。
图2-2:将任务加入延时列表的三种情况
计算的唤醒时间合法后,就将当前任务加入延时列表,同样延时列表也有两个。每次系统节拍中断,中断服务函数都会检查这两个延时列表,查看延时的任务是否到期,如果时间到期,则将任务从延时列表中删除,重新加入就绪列表。如果新加入就绪列表的任务优先级大于当前任务,则会触发一次上下文切换。
感谢各位的阅读,以上就是“FreeRTOS系统延时实例分析”的内容了,经过本文的学习后,相信大家对FreeRTOS系统延时实例分析这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是恰卡编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!