<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

什么是FreeRTOS的延時

汽車電子技術 ? 來源:物聯網IoT開發 ? 作者:杰杰mcu ? 2023-02-14 09:45 ? 次閱讀

01

FreeRTOS 時間管理

時間管理包括兩個方面:系統節拍以及任務延時管理。

2

系統節拍:

在前面的文章也講得很多,想要系統正常運行,那么時鐘節拍是必不可少的,FreeRTOS的時鐘節拍通常由SysTick提供,它周期性的產生定時中斷,所謂的時鐘節拍管理的核心就是這個定時中斷的服務程序。FreeRTOS的時鐘節拍isr中核心的工作就是調用 vTaskIncrementTick() 函數。具體見上之前的文章。

3

今天主要講解延時的實現

FreeRTOS提供了兩個系統延時函數:

**相對延時函數vTaskDelay() **

絕對延時函數vTaskDelayUntil()。

這些延時函數可不像我們以前用裸機寫代碼的延時函數操作系統不允許CPU在死等消耗著時間,因為這樣效率太低了。

同時,要告誡學操作系統的同學,千萬別用裸機的思想去學操作系統。

4

任務延時

任務可能需要延時,兩種情況,一種是任務被vTaskDelay或者vTaskDelayUntil延時,另外一種情況就是任務等待事件(比如等待某個信號量、或者某個消息隊列)時候指定了 timeout (即最多等待timeout時間,如果等待的事件還沒發生,則不再繼續等待),在每個任務的循環中都必須要有阻塞的情況出現,否則比該任務優先級低的任務就永遠無法運行。

5

相對延時與絕對延時的區別

相對延時:vTaskDelay():

相對延時是指每次延時都是從任務執行函數 vTaskDelay() 開始,延時指定的時間結束

絕對延時:vTaskDelayUntil():

絕對延時是指調用 vTaskDelayUntil() 的任務每隔x時間運行一次。也就是任務周期運行。

6

相對延時:vTaskDelay()

相對延時 vTaskDelay() 是從調用 vTaskDelay() 這個函數的時候開始延時,但是任務執行的時候,可能發生了中斷,導致任務執行時間變長了,但是整個任務的延時時間還是1000tick ,這就不是周期性了,簡單看看下面代碼:

void vTaskA( void * pvParameters )  
 {  
    while(1) 
     {  
         //  ...
         //  這里為任務主體代碼
         //  ...

         /* 調用相對延時函數,阻塞1000個tick */
         vTaskDelay( 1000 );  
     }  
}

可能說的不夠明確,可以看看圖解。

圖片

當任務運行的時候,假設被某個高級任務或者是中斷打斷了,那么任務的執行時間就更長了,然而延時還是延時1000tick這樣子,整個系統的時間就混亂了。

如果還不夠明確,看看 vTaskDelay() 的源碼

void vTaskDelay( const TickType_t xTicksToDelay )
    {
    BaseType_t xAlreadyYielded = pdFALSE;

        /* 延遲時間為零只會強制切換任務。 */
        if( xTicksToDelay > ( TickType_t ) 0U )     (1)
        {
            configASSERT( uxSchedulerSuspended == 0 );
            vTaskSuspendAll();                      (2)
            {
                traceTASK_DELAY();
                /*將當前任務從就緒列表中移除,并根據當前系統節拍
                計數器值計算喚醒時間,然后將任務加入延時列表 */
                prvAddCurrentTaskToDelayedList( xTicksToDelay, pdFALSE );
            }
            xAlreadyYielded = xTaskResumeAll();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }

        /* 強制執行一次上下文切換 */
        if( xAlreadyYielded == pdFALSE )
        {
            portYIELD_WITHIN_API();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }

(1):如果傳遞進來的延時時間是0,只能進行強制切換任務了,調用的是 portYIELD_WITHIN_API() ,它其實是一個宏,真正起作用的是 portYIELD() ,下面是它的源碼:

#define portYIELD()                                                \\
{                                                                \\
    /* 設置PendSV以請求上下文切換。 */                         \\
    portNVIC_INT_CTRL_REG = portNVIC_PENDSVSET_BIT;             \\

    __dsb( portSY_FULL_READ_WRITE );                            \\
    __isb( portSY_FULL_READ_WRITE );                            \\
}

(2):掛起當前任務

然后將當前任務從就緒列表刪除,然后加入到延時列表。是調用函數 prvAddCurrentTaskToDelayedList() 完成這一過程的。由于這個函數篇幅過長,就不講解了,有興趣可以看看,我就簡單說說過程。在FreeRTOS中有這么一個變量,是用來記錄systick的值的。

PRIVILEGED_DATA static volatile TickType_t xTickCount     = ( TickType_t ) 0U;

在每次tick中斷時xTickCount加一,它的值表示了系統節拍中斷的次數,那么啥時候喚醒被加入延時列表的任務呢?其實很簡單,FreeRTOS的做法將 xTickCount (當前系統時間)+ xTicksToDelay (要延時的時間)即可。當這個相對的延時時間到了之后就喚醒了,這個 (xTickCount+ xTicksToDelay) 時間會被記錄在該任務的任務控制塊中。

看到這肯定有人問,這個變量是TickType_t類型(32位)的,那肯定會溢出啊,沒錯,是變量都會有溢出的一天,可是FreeRTOS乃是世界第一的操作系統啊,FreeRTOS使用了兩個延時列表:

** xDelayedTaskList1和xDelayedTaskList2,**

并使用兩個列表指針類型變量pxDelayedTaskListpxOverflowDelayedTaskList分別指向上面的延時列表1和延時列表2(在創建任務時將延時列表指針指向延時列表)如果內核判斷出xTickCount+xTicksToDelay溢出,就將當前任務掛接到列表指針 pxOverflowDelayedTaskList指向的列表中,否則就掛接到列表指針pxDelayedTaskList指向的列表中。當時間到了,就會將延時的任務從延時列表中刪除,加入就緒列表中,當然這時候就是由調度器覺得任務能不能運行了,如果任務的優先級大于當前運行的任務,那么調度器才會進行任務的調度。

7

絕對延時:vTaskDelayUntil()

vTaskDelayUntil()參數指定了確切的滴答計數值

調用 vTaskDelayUntil() 是希望任務以固定頻率定期執行,而不受外部的影響,任務從上一次運行開始到下一次運行開始的時間間隔是絕對的,而不是相對的。

圖片

下面看看 vTaskDelayUntil() 的使用方法,注意了,這 vTaskDelayUntil() 的使用方法與 vTaskDelay() 不一樣:

void vTaskA( void * pvParameters )  
 {  
    /* 用于保存上次時間。調用后系統自動更新 */
    static portTickType PreviousWakeTime;
    /* 設置延時時間,將時間轉為節拍數 */
    const portTickType TimeIncrement = pdMS_TO_TICKS(1000); 
    /* 獲取當前系統時間 */
    PreviousWakeTime = xTaskGetTickCount(); 
    while(1) 
     {  

         /* 調用絕對延時函數,任務時間間隔為1000個tick */
         vTaskDelayUntil( &PreviousWakeTime,TimeIncrement );  

         //  ...
         //  這里為任務主體代碼
         //  ...

     }  
}

在使用的時候要將延時時間轉化為系統節拍,在任務主體之前要調用延時函數。

任務會先調用 vTaskDelayUntil() 使任務進入阻塞態,等到時間到了就從阻塞中解除,然后執行主體代碼,任務主體代碼執行完畢。會繼續調用 vTaskDelayUntil() 使任務進入阻塞態,然后就是循環這樣子執行。即使任務在執行過程中發生中斷,那么也不會影響這個任務的運行周期,僅僅是縮短了阻塞的時間而已。

下面來看看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)
        {
            /* 保存系統節拍中斷次數計數器 */
            const TickType_t xConstTickCount = xTickCount;

            /* 生成任務要喚醒的滴答時間。*/
            xTimeToWake = *pxPreviousWakeTime + xTimeIncrement;

        /* pxPreviousWakeTime中保存的是上次喚醒時間,喚醒后需要一定時間執行任務主體代碼,
            如果上次喚醒時間大于當前時間,說明節拍計數器溢出了 具體見圖片 */
            if( xConstTickCount < *pxPreviousWakeTime )
            {
                / *由于此功能,滴答計數已溢出
                    持續呼喚。 在這種情況下,我們唯一的時間
                    實際延遲是如果喚醒時間也溢出,
                    喚醒時間大于滴答時間。 當這個
                    就是這樣,好像兩個時間都沒有溢出。*/
                if( ( xTimeToWake < *pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )
                {
                    xShouldDelay = pdTRUE;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }
            else
            {
                /  *滴答時間沒有溢出。 在這種情況下,如果喚醒時間溢出,
                    或滴答時間小于喚醒時間,我們將延遲。*/

                if( ( xTimeToWake < *pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )
                {
                    xShouldDelay = pdTRUE;
                }
                else
                {
                    mtCOVERAGE_TEST_MARKER();
                }
            }

            /* 更新喚醒時間,為下一次調用本函數做準備. */
            *pxPreviousWakeTime = xTimeToWake;

            if( xShouldDelay != pdFALSE )
            {
                traceTASK_DELAY_UNTIL( xTimeToWake );

                /* prvAddCurrentTaskToDelayedList()需要塊時間,而不是喚醒時間,因此減去當前的滴答計數。 */
                prvAddCurrentTaskToDelayedList( xTimeToWake - xConstTickCount, pdFALSE );
            }
            else
            {
                mtCOVERAGE_TEST_MARKER();
            }
        }
        xAlreadyYielded = xTaskResumeAll();

        /* 如果xTaskResumeAll尚未執行重新安排,我們可能會讓自己入睡。*/
        if( xAlreadyYielded == pdFALSE )
        {
            portYIELD_WITHIN_API();
        }
        else
        {
            mtCOVERAGE_TEST_MARKER();
        }
    }

與相對延時函數vTaskDelay不同,本函數增加了一個參數pxPreviousWakeTime用于指向一個變量,變量保存上次任務解除阻塞的時間,此后函數 vTaskDelayUntil() 在內部自動更新這個變量。由于變量xTickCount可能會溢出,所以程序必須檢測各種溢出情況,并且要保證延時周期不得小于任務主體代碼執行時間。

就會有以下3種情況,才能將任務加入延時鏈表中。

請記住這幾個單詞的含義:

** xTimeIncrement:任務周期時間

pxPreviousWakeTime:上一次喚醒的時間點

xTimeToWake:下一次喚醒的系統時間點

xConstTickCount:進入延時的時間點**

第三種情況:常規無溢出的情況。

以時間為橫軸,上一次喚醒的時間點小于下一次喚醒的時間點,這是很正常的情況。

圖片

第二種情況:喚醒時間計數器 (xTimeToWake) 溢出情況。

也就是代碼中*if( ( xTimeToWake < pxPreviousWakeTime ) || ( xTimeToWake > xConstTickCount ) )

圖片

第一種情況:喚醒時間 (xTimeToWake) 與進入延時的時間點 (xConstTickCount) 都溢出情況。

也就是代碼中*if( ( xTimeToWake < pxPreviousWakeTime ) && ( xTimeToWake > xConstTickCount ) )

圖片

從圖中可以看出不管是溢出還是無溢出,都要求在下次喚醒任務之前,當前任務主體代碼必須被執行完。也就是說任務執行的時間不允許大于延時的時間,總不能存在每10ms就要執行一次20ms時間的任務吧。計算的喚醒時間合法后,就將當前任務加入延時列表,同樣延時列表也有兩個。每次系統節拍中斷,中斷服務函數都會檢查這兩個延時列表,查看延時的任務是否到期,如果時間到期,則將任務從延時列表中刪除,重新加入就緒列表。如果新加入就緒列表的任務優先級大于當前任務,則會觸發一次上下文切換。

8

總結

如果任務調用相對延時,其運行周期完全是不可測的,如果任務的優先級不是最高的話,其誤差更大,就好比一個必須要在5ms內相應的任務,假如使用了相對延時 1ms ,那么很有可能在該任務執行的時候被更高優先級的任務打斷,從而錯過5ms內的相應,但是調用絕對延時,則任務會周期性將該任務在阻塞列表中解除,但是,任務能不能運行,還得取決于任務的優先級,如果優先級最高的話,任務周期還是比較精確的(相對vTaskDelay來說),如果想要更加想精確周期性執行某個任務,可以使用系統節拍鉤子函數 vApplicationTickHook() ,它在tick中斷服務函數中被調用,因此這個函數中的代碼必須簡潔,并且不允許出現阻塞的情況。

本文是杰杰原創,轉載請說明出處。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • FreeRTOS
    +關注

    關注

    12

    文章

    473

    瀏覽量

    61429
  • 定時中斷
    +關注

    關注

    0

    文章

    19

    瀏覽量

    8504
  • Systick
    +關注

    關注

    0

    文章

    62

    瀏覽量

    12963
收藏 人收藏

    評論

    相關推薦

    stm32中FREERTOS延時函數osDelayUntil()死機的原因?

    我在使用STM32F4跑freertos的時候發現一旦使用osDelayUntil()函數,就會死機,但是用osDelay()函數就不會,按理說不是都可以用的嗎?有知道原因的嗎,謝謝!
    發表于 03-22 07:56

    【設計技巧】從單片機到操作系統(7)-FreeRTOS延時介紹

    本文轉自公眾號物聯網IoT開發1 FreeRTOS 時間管理時間管理包括兩個方面:系統節拍以及任務延時管理。2系統節拍:在前面的文章也講得很多,想要系統正常運行,那么時鐘節拍是必不可少
    發表于 08-01 08:00

    FreeRTOS如何使用delay作為系統延時、任務調度

    請教一個問題,最近在學習使用FreeRTOS,想像原子一樣在delay.c里添加RTOS的系統支持,即使用tick時鐘作延時?,F在有幾個問題:1、在啟動任務調度器前,如果調用了delay_ms
    發表于 06-10 04:37

    FreeRTOSV8.2.3在探索者開發板上的移植怎么實現?

    、首先定義任務函數 [C] 純文本查看 復制代碼2、編寫main函數 [C] 純文本查看 復制代碼vTaskDelay(2000/portTICK_PERIOD_MS);//FreeRTOS延時函數
    發表于 07-30 08:02

    esp32s3延時出現問題求解

    不管是用的官方模板,還是自己寫的,使用的是freertos延時,或者是while(i--),都有問題。主要程序是這樣的:while(1){ vTaskDELAY(100); printf(“run
    發表于 02-10 06:43

    怎么去解決esp32s3延時出錯的問題?

    不管是用的官方模板,還是自己寫的,使用的是freertos延時,或者是while(i--),都有問題。主要程序是這樣的:while(1){ vTaskDELAY(100); printf(“run
    發表于 03-03 08:25

    esp32s3延時問題如何解決?

    不管是用的官方模板,還是自己寫的,使用的是freertos延時,或者是while(i--),都有問題。主要程序是這樣的:while(1){ vTaskDELAY(100); printf(“run
    發表于 03-08 08:38

    ETH-CH32v20x_v307在Freertos中添加以太網

    Freertos中,主函數作為一個線程,同時將中斷函數復制過來,增加freertos進入和釋放中斷函數,中斷聲明也不要忘記 修改驅動文件即eth_driver.c中的延時函數,修改成fre
    發表于 08-09 10:41

    請問FreeRTOS中GPIO模擬SPI延時如何處理?

    FreeRTOS中想使用GPIO模擬SPI與設備進行通訊,SPI傳輸速度要求在100K以上,FreeRTOS延時不能實現微妙級別的延時。模擬時的gpio翻轉
    發表于 11-10 07:56

    FreeRTOS中相對延時與絕對延時的區別

    FreeRTOS中相對延時和絕對延時的區別
    的頭像 發表于 03-12 10:32 ?7912次閱讀
    <b class='flag-5'>FreeRTOS</b>中相對<b class='flag-5'>延時</b>與絕對<b class='flag-5'>延時</b>的區別

    FreeRTOS中相對延時和絕對延時的區別

    嵌入式軟件代碼中延時是很常見的,只是延時種類有很多,看你用什么延時。 1 一個延時的問題 問題:周期性(固定一個時間)去處理某一件事情。你會通過什么方式去實現? 比如:間隔10ms去采
    的頭像 發表于 11-24 15:44 ?1798次閱讀
    <b class='flag-5'>FreeRTOS</b>中相對<b class='flag-5'>延時</b>和絕對<b class='flag-5'>延時</b>的區別

    FreeRTOS中相對延時和絕對延時的區別

    對計時精度要求比較高的地方適合定時器,像本章節說的周期性采集傳感器數據,要求不適合很高,那么就引入本文說的絕對延時。
    的頭像 發表于 11-29 10:19 ?1743次閱讀

    初入FreeRTOS

    目錄一、FreeRTOS介紹1、初識FreeRTOS,什么是 FreeRTOS2、FreeRTOS的特點二、FreeRTOS移植1、
    發表于 12-06 21:06 ?37次下載
    初入<b class='flag-5'>FreeRTOS</b>

    FreeRTOS高級篇9---FreeRTOS系統延時分析

    FreeRTOS提供了兩個系統延時函數:相對延時函數vTaskDelay()和絕對延時函數vTaskDelayUntil()。相對延時是指每
    發表于 01-26 17:34 ?6次下載
    <b class='flag-5'>FreeRTOS</b>高級篇9---<b class='flag-5'>FreeRTOS</b>系統<b class='flag-5'>延時</b>分析

    FreeRTOS系列第11篇---FreeRTOS任務控制

    FreeRTOS任務控制API函數主要實現任務延時、任務掛起、解除任務掛起、任務優先級獲取和設置等功能。
    發表于 01-26 17:54 ?11次下載
    <b class='flag-5'>FreeRTOS</b>系列第11篇---<b class='flag-5'>FreeRTOS</b>任務控制
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>