01
FreeRTOS 時間管理
時間管理包括兩個方面:系統(tǒng)節(jié)拍以及任務(wù)延時管理。
2
系統(tǒng)節(jié)拍:
在前面的文章也講得很多,想要系統(tǒng)正常運(yùn)行,那么時鐘節(jié)拍是必不可少的,FreeRTOS的時鐘節(jié)拍通常由SysTick提供,它周期性的產(chǎn)生定時中斷,所謂的時鐘節(jié)拍管理的核心就是這個定時中斷的服務(wù)程序。FreeRTOS的時鐘節(jié)拍isr中核心的工作就是調(diào)用 vTaskIncrementTick() 函數(shù)。具體見上之前的文章。
3
今天主要講解延時的實(shí)現(xiàn)
FreeRTOS提供了兩個系統(tǒng)延時函數(shù):
**相對延時函數(shù)vTaskDelay() **
絕對延時函數(shù)vTaskDelayUntil()。
這些延時函數(shù)可不像我們以前用裸機(jī)寫代碼的延時函數(shù)操作系統(tǒng)不允許CPU在死等消耗著時間,因?yàn)檫@樣效率太低了。
同時,要告誡學(xué)操作系統(tǒng)的同學(xué),千萬別用裸機(jī)的思想去學(xué)操作系統(tǒng)。
4
任務(wù)延時
任務(wù)可能需要延時,兩種情況,一種是任務(wù)被vTaskDelay或者vTaskDelayUntil延時,另外一種情況就是任務(wù)等待事件(比如等待某個信號量、或者某個消息隊(duì)列)時候指定了 timeout (即最多等待timeout時間,如果等待的事件還沒發(fā)生,則不再繼續(xù)等待),在每個任務(wù)的循環(huán)中都必須要有阻塞的情況出現(xiàn),否則比該任務(wù)優(yōu)先級低的任務(wù)就永遠(yuǎn)無法運(yùn)行。
5
相對延時與絕對延時的區(qū)別
相對延時:vTaskDelay():
相對延時是指每次延時都是從任務(wù)執(zhí)行函數(shù) vTaskDelay() 開始,延時指定的時間結(jié)束
絕對延時:vTaskDelayUntil():
絕對延時是指調(diào)用 vTaskDelayUntil() 的任務(wù)每隔x時間運(yùn)行一次。也就是任務(wù)周期運(yùn)行。
6
相對延時:vTaskDelay()
相對延時 vTaskDelay() 是從調(diào)用 vTaskDelay() 這個函數(shù)的時候開始延時,但是任務(wù)執(zhí)行的時候,可能發(fā)生了中斷,導(dǎo)致任務(wù)執(zhí)行時間變長了,但是整個任務(wù)的延時時間還是1000個 tick ,這就不是周期性了,簡單看看下面代碼:
void vTaskA( void * pvParameters )
{
while(1)
{
// ...
// 這里為任務(wù)主體代碼
// ...
/* 調(diào)用相對延時函數(shù),阻塞1000個tick */
vTaskDelay( 1000 );
}
}
可能說的不夠明確,可以看看圖解。
當(dāng)任務(wù)運(yùn)行的時候,假設(shè)被某個高級任務(wù)或者是中斷打斷了,那么任務(wù)的執(zhí)行時間就更長了,然而延時還是延時1000個tick這樣子,整個系統(tǒng)的時間就混亂了。
如果還不夠明確,看看 vTaskDelay() 的源碼
void vTaskDelay( const TickType_t xTicksToDelay )
{
BaseType_t xAlreadyYielded = pdFALSE;
/* 延遲時間為零只會強(qiáng)制切換任務(wù)。 */
if( xTicksToDelay > ( TickType_t ) 0U ) (1)
{
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll(); (2)
{
traceTASK_DELAY();
/*將當(dāng)前任務(wù)從就緒列表中移除,并根據(jù)當(dāng)前系統(tǒng)節(jié)拍
計(jì)數(shù)器值計(jì)算喚醒時間,然后將任務(wù)加入延時列表 */
prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
}
xAlreadyYielded = xTaskResumeAll();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
/* 強(qiáng)制執(zhí)行一次上下文切換 */
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
(1):如果傳遞進(jìn)來的延時時間是0,只能進(jìn)行強(qiáng)制切換任務(wù)了,調(diào)用的是 portYIELD_WITHIN_API() ,它其實(shí)是一個宏,真正起作用的是 portYIELD() ,下面是它的源碼:
#define portYIELD() \\
{ \\
/* 設(shè)置PendSV以請求上下文切換。 */ \\
portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT; \\
__dsb( portSY_FULL_READ_WRITE ); \\
__isb( portSY_FULL_READ_WRITE ); \\
}
(2):掛起當(dāng)前任務(wù)
然后將當(dāng)前任務(wù)從就緒列表刪除,然后加入到延時列表。是調(diào)用函數(shù) prvAddCurrentTaskToDelayedList() 完成這一過程的。由于這個函數(shù)篇幅過長,就不講解了,有興趣可以看看,我就簡單說說過程。在FreeRTOS中有這么一個變量,是用來記錄systick的值的。
PRIVILEGED_DATA static volatile TickType_t xTickCount = ( TickType_t ) 0U;
在每次tick中斷時xTickCount加一,它的值表示了系統(tǒng)節(jié)拍中斷的次數(shù),那么啥時候喚醒被加入延時列表的任務(wù)呢?其實(shí)很簡單,FreeRTOS的做法將 xTickCount (當(dāng)前系統(tǒng)時間)+ xTicksToDelay (要延時的時間)即可。當(dāng)這個相對的延時時間到了之后就喚醒了,這個 (xTickCount+ xTicksToDelay) 時間會被記錄在該任務(wù)的任務(wù)控制塊中。
看到這肯定有人問,這個變量是TickType_t類型(32位)的,那肯定會溢出啊,沒錯,是變量都會有溢出的一天,可是FreeRTOS乃是世界第一的操作系統(tǒng)啊,FreeRTOS使用了兩個延時列表:
** xDelayedTaskList1和xDelayedTaskList2,**
并使用兩個列表指針類型變量pxDelayedTaskList和pxOverflowDelayedTaskList分別指向上面的延時列表1和延時列表2(在創(chuàng)建任務(wù)時將延時列表指針指向延時列表)如果內(nèi)核判斷出xTickCount+xTicksToDelay溢出,就將當(dāng)前任務(wù)掛接到列表指針 pxOverflowDelayedTaskList指向的列表中,否則就掛接到列表指針pxDelayedTaskList指向的列表中。當(dāng)時間到了,就會將延時的任務(wù)從延時列表中刪除,加入就緒列表中,當(dāng)然這時候就是由調(diào)度器覺得任務(wù)能不能運(yùn)行了,如果任務(wù)的優(yōu)先級大于當(dāng)前運(yùn)行的任務(wù),那么調(diào)度器才會進(jìn)行任務(wù)的調(diào)度。
7
絕對延時:vTaskDelayUntil()
vTaskDelayUntil() 的參數(shù)指定了確切的滴答計(jì)數(shù)值
調(diào)用 vTaskDelayUntil() 是希望任務(wù)以固定頻率定期執(zhí)行,而不受外部的影響,任務(wù)從上一次運(yùn)行開始到下一次運(yùn)行開始的時間間隔是絕對的,而不是相對的。
下面看看 vTaskDelayUntil() 的使用方法,注意了,這 vTaskDelayUntil() 的使用方法與 vTaskDelay() 不一樣:
void vTaskA( void * pvParameters )
{
/* 用于保存上次時間。調(diào)用后系統(tǒng)自動更新 */
static portTickType PreviousWakeTime;
/* 設(shè)置延時時間,將時間轉(zhuǎn)為節(jié)拍數(shù) */
const portTickType TimeIncrement = pdMS_TO_TICKS(1000);
/* 獲取當(dāng)前系統(tǒng)時間 */
PreviousWakeTime = xTaskGetTickCount();
while(1)
{
/* 調(diào)用絕對延時函數(shù),任務(wù)時間間隔為1000個tick */
vTaskDelayUntil( &PreviousWakeTime,TimeIncrement );
// ...
// 這里為任務(wù)主體代碼
// ...
}
}
在使用的時候要將延時時間轉(zhuǎn)化為系統(tǒng)節(jié)拍,在任務(wù)主體之前要調(diào)用延時函數(shù)。
任務(wù)會先調(diào)用 vTaskDelayUntil() 使任務(wù)進(jìn)入阻塞態(tài),等到時間到了就從阻塞中解除,然后執(zhí)行主體代碼,任務(wù)主體代碼執(zhí)行完畢。會繼續(xù)調(diào)用 vTaskDelayUntil() 使任務(wù)進(jìn)入阻塞態(tài),然后就是循環(huán)這樣子執(zhí)行。即使任務(wù)在執(zhí)行過程中發(fā)生中斷,那么也不會影響這個任務(wù)的運(yùn)行周期,僅僅是縮短了阻塞的時間而已。
下面來看看vTaskDelayUntil()的源碼:
void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime, const TickType_t xTimeIncrement )
{
TickType_t xTimeToWake;
BaseType_t xAlreadyYielded, xShouldDelay = pdFALSE;
configASSERT( pxPreviousWakeTime );
configASSERT( ( xTimeIncrement > 0U ) );
configASSERT( uxSchedulerSuspended == 0 );
vTaskSuspendAll(); (1)
{
/* 保存系統(tǒng)節(jié)拍中斷次數(shù)計(jì)數(shù)器 */
const TickType_t xConstTickCount = xTickCount;
/* 生成任務(wù)要喚醒的滴答時間。*/
xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;
/* pxPreviousWakeTime中保存的是上次喚醒時間,喚醒后需要一定時間執(zhí)行任務(wù)主體代碼,
如果上次喚醒時間大于當(dāng)前時間,說明節(jié)拍計(jì)數(shù)器溢出了 具體見圖片 */
if( xConstTickCount < *pxPreviousWakeTime )
{
/ *由于此功能,滴答計(jì)數(shù)已溢出
持續(xù)呼喚。 在這種情況下,我們唯一的時間
實(shí)際延遲是如果喚醒時間也溢出,
喚醒時間大于滴答時間。 當(dāng)這個
就是這樣,好像兩個時間都沒有溢出。*/
if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
{
xShouldDelay = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/ *滴答時間沒有溢出。 在這種情況下,如果喚醒時間溢出,
或滴答時間小于喚醒時間,我們將延遲。*/
if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
{
xShouldDelay = pdTRUE;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
/* 更新喚醒時間,為下一次調(diào)用本函數(shù)做準(zhǔn)備. */
*pxPreviousWakeTime = xTimeToWake;
if( xShouldDelay != pdFALSE )
{
traceTASK_DELAY_UNTIL( xTimeToWake );
/* prvAddCurrentTaskToDelayedList()需要塊時間,而不是喚醒時間,因此減去當(dāng)前的滴答計(jì)數(shù)。 */
prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
xAlreadyYielded = xTaskResumeAll();
/* 如果xTaskResumeAll尚未執(zhí)行重新安排,我們可能會讓自己入睡。*/
if( xAlreadyYielded == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
與相對延時函數(shù)vTaskDelay不同,本函數(shù)增加了一個參數(shù)pxPreviousWakeTime用于指向一個變量,變量保存上次任務(wù)解除阻塞的時間,此后函數(shù) vTaskDelayUntil() 在內(nèi)部自動更新這個變量。由于變量xTickCount可能會溢出,所以程序必須檢測各種溢出情況,并且要保證延時周期不得小于任務(wù)主體代碼執(zhí)行時間。
就會有以下3種情況,才能將任務(wù)加入延時鏈表中。
請記住這幾個單詞的含義:
** xTimeIncrement:任務(wù)周期時間
pxPreviousWakeTime:上一次喚醒的時間點(diǎn)
xTimeToWake:下一次喚醒的系統(tǒng)時間點(diǎn)
xConstTickCount:進(jìn)入延時的時間點(diǎn)**
第三種情況:常規(guī)無溢出的情況。
以時間為橫軸,上一次喚醒的時間點(diǎn)小于下一次喚醒的時間點(diǎn),這是很正常的情況。
第二種情況:喚醒時間計(jì)數(shù)器 (xTimeToWake) 溢出情況。
也就是代碼中*if( ( xTimeToWake < pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
第一種情況:喚醒時間 (xTimeToWake) 與進(jìn)入延時的時間點(diǎn) (xConstTickCount) 都溢出情況。
也就是代碼中*if( ( xTimeToWake < pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
從圖中可以看出不管是溢出還是無溢出,都要求在下次喚醒任務(wù)之前,當(dāng)前任務(wù)主體代碼必須被執(zhí)行完。也就是說任務(wù)執(zhí)行的時間不允許大于延時的時間,總不能存在每10ms就要執(zhí)行一次20ms時間的任務(wù)吧。計(jì)算的喚醒時間合法后,就將當(dāng)前任務(wù)加入延時列表,同樣延時列表也有兩個。每次系統(tǒng)節(jié)拍中斷,中斷服務(wù)函數(shù)都會檢查這兩個延時列表,查看延時的任務(wù)是否到期,如果時間到期,則將任務(wù)從延時列表中刪除,重新加入就緒列表。如果新加入就緒列表的任務(wù)優(yōu)先級大于當(dāng)前任務(wù),則會觸發(fā)一次上下文切換。
8
總結(jié)
如果任務(wù)調(diào)用相對延時,其運(yùn)行周期完全是不可測的,如果任務(wù)的優(yōu)先級不是最高的話,其誤差更大,就好比一個必須要在5ms內(nèi)相應(yīng)的任務(wù),假如使用了相對延時 1ms ,那么很有可能在該任務(wù)執(zhí)行的時候被更高優(yōu)先級的任務(wù)打斷,從而錯過5ms內(nèi)的相應(yīng),但是調(diào)用絕對延時,則任務(wù)會周期性將該任務(wù)在阻塞列表中解除,但是,任務(wù)能不能運(yùn)行,還得取決于任務(wù)的優(yōu)先級,如果優(yōu)先級最高的話,任務(wù)周期還是比較精確的(相對vTaskDelay來說),如果想要更加想精確周期性執(zhí)行某個任務(wù),可以使用系統(tǒng)節(jié)拍鉤子函數(shù) vApplicationTickHook() ,它在tick中斷服務(wù)函數(shù)中被調(diào)用,因此這個函數(shù)中的代碼必須簡潔,并且不允許出現(xiàn)阻塞的情況。
本文是杰杰原創(chuàng),轉(zhuǎn)載請說明出處。
-
FreeRTOS
+關(guān)注
關(guān)注
12文章
485瀏覽量
63494 -
定時中斷
+關(guān)注
關(guān)注
0文章
19瀏覽量
8661 -
Systick
+關(guān)注
關(guān)注
0文章
63瀏覽量
13431
發(fā)布評論請先 登錄
相關(guān)推薦
stm32中FREERTOS的延時函數(shù)osDelayUntil()死機(jī)的原因?
【設(shè)計(jì)技巧】從單片機(jī)到操作系統(tǒng)(7)-FreeRTOS延時介紹
FreeRTOS如何使用delay作為系統(tǒng)延時、任務(wù)調(diào)度
FreeRTOSV8.2.3在探索者開發(fā)板上的移植怎么實(shí)現(xiàn)?
esp32s3延時出現(xiàn)問題求解
怎么去解決esp32s3延時出錯的問題?
esp32s3延時問題如何解決?
ETH-CH32v20x_v307在Freertos中添加以太網(wǎng)
請問FreeRTOS中GPIO模擬SPI延時如何處理?
FreeRTOS中相對延時和絕對延時的區(qū)別

FreeRTOS中相對延時和絕對延時的區(qū)別
初入FreeRTOS

FreeRTOS高級篇9---FreeRTOS系統(tǒng)延時分析

FreeRTOS系列第11篇---FreeRTOS任務(wù)控制

評論