目錄
- 1、軟體定時器概念
- 2、軟體定時器的運作機制
-
- 2.1 組成
- 2.2 建立
- 2.3 運作
- 3、軟體定時器的屬性和狀态
-
- 3.1 定時器的周期
- 3.2 定時器的類型
- 3.3 定時器的狀态
- 4、軟體定時器的回調函數原型
- 5、定時器的使用
-
- 5.1 建立定時器xTimeCreate()
- 5.2 啟動定時器xTimerStart()
- 5.3 終止定時器xTimerStop()
- 5.4 定時器重置xTimerReset()
- 5.5 執行個體
- 5.5 定時器停止運作xTimerStop()
- 5.6 删除定時器xTimerDelete()
- 5.7 改變定時器周期
- 6 Timer ID
-
- 6.1 定時器ID的概念
- 6.2 定時器ID的應用場景
- 6.3 定時器ID的初始化
- 6.4 設定定時器ID vTimerSetTimerID()
- 6.5 查詢定時器ID值
- 6.6 執行個體
- 7 總結
1、軟體定時器概念
軟體定時器用于在未來的某個設定時刻安排功能的執行,或以固定的頻率定期執行某個功能。軟體定時器執行的函數稱為軟體定時器的回調函數。
軟體定時器由 FreeRTOS 核心實作并受其控制。它們不需要硬體支援,并且與硬體定時器或硬體計數器無關。
軟體定時器不使用任何處理時間,除非軟體定時器回調函數實際正在執行。
軟體定時器功能是可選擇的:1、要包含 FreeRTOS/Source/timers.c檔案到你的項目中去。2、 在FreeRTOSConfig.h檔案中,設定宏configUSE_TIMERS 為 1 。
2、軟體定時器的運作機制
2.1 組成
軟體定時器的功能組成的實質是兩個部件:
一個是,定時器指令隊列(Timer Command Queue);
一個是,RTOS守護程序任務(RTOS Deamon Task),該任務過去被稱為“計時器服務任務(Timer Service task)”,因為最初它隻用于執行軟體計時器回調函數。現在,相同的任務也用于其他目的,是以它被稱為“RTOS守護程式任務”這一更通用的名稱。
2.2 建立
軟體定時器的運作部件必須是由系統建立的。具體是在排程程式vTaskStartSchedule()函數運作時,建立定時器的指令隊列,以及處理該隊列的RTOS守護程序任務。該任務一般優先級是大于1,高于其它系統自動建立的任務,例如系統自動建立的空閉任務。
針對守護程序的優先級,是可以設定的,在标準的FreeRTOS中,是在FreeRTOSConfig.h中的宏 configTIMER_TASK_PRIORITY進行設定,而在ESP-IDF for VSCode中,使用menuconfig去設定。設定選項如下:
2.3 運作
這樣,每個tick中斷,排程器會去排程RTOS守護程序任務,并在該任務裡确定哪個定時器到期,并執行哪個定時器對應的回調函數,以處理定時器到期任務。
此時,如果有高于RTOS守護程序任務的其它任務正在執行,則會出現延遲處理定時器到期任務的情況。或者如果定時器對應的回調函數裡有長時間運作的行為,比如出現了阻塞,則也會影響其它的定時器到期任務。
注意事項:
是以,實際在程式設計時,如果有用到定時器,一定要注意, 以上這兩種情況。即高于優先級數值1的其它優先級任務一定要有阻塞機制,使系統可以正常及時的排程RTOS守護程序。
另一個注意事項就是定時器的回調函數決不能進入阻塞狀态。軟體定時器回調函數在 FreeRTOS 排程程式啟動時自動建立的RTOS守護程序任務中運作。是以,軟體定時器回調函數絕對不能調用會導緻調用程式進入阻塞的 FreeRTOS API 函數。可以調用xQueueReceive()等函數,但前提是函數的xTicksToWait參數(指定函數的阻塞時間)設定為0。不可以調用vTaskDelay()等函數,調用vTaskDelay() 将始終将調用任務置于阻塞狀态。
3、軟體定時器的屬性和狀态
3.1 定時器的周期
軟體定時器的“周期period”是指軟體定時器啟動和軟體定時器回調函數執行之間的時間。
3.2 定時器的類型
在FreeRTOS中,軟體定時器有兩種類型:
1、One-shot timers (一次性定時器)
一旦啟動,一次性定時器将隻執行一次回調函數。一次性定時器可以手動重新啟動,但不會自動重新啟動。
2、Auto-reload timers (自動重載定時器)
一旦啟動,自動重載定時器将在每次到期時重新啟動,進而定期執行其回調函數。
3.3 定時器的狀态
使用者建立的軟體定時器,在任何時刻,都會處于兩種狀态之一:
1、Dormant 睡眠狀态
計時器處于睡眠狀态時,可以通過其句柄引用它,但由于計時器是未運作狀态,是以其回調函數不會執行。
2、Running 運作狀态
運作中的軟體定時器,在定時時間到後會執行其回調函數。
狀态切換的示意圖
4、軟體定時器的回調函數原型
在timers.h中,軟體定時器的回調函數的原型定義如下:
軟體計時器回調函數被實作為C函數。它們唯一特别的地方是它們的原型,它必須傳回void,并将軟體計時器的句柄作為其唯一參數。
5、定時器的使用
5.1 建立定時器xTimeCreate()
概述:
建立一個新的軟體計時器,并傳回一個句柄,通過該句柄可以引用建立的軟體計時器。
每個軟體計時器都需要少量RAM來保持計時器的狀态。如果使用xTimerCreate()建立軟體計時器,則會從FreeRTOS堆自動配置設定此RAM。如果使用xTimerCreateStatic()建立軟體計時器,則RAM由應用程式編寫器提供,這需要額外的參數,但允許在編譯時靜态配置設定RAM。
建立計時器不會啟動計時器運作。xTimerStart()、xTimerReset()、xDimerStartFromISR()、xMimerResetFromISR)、xTimerChangePeriod()和xTimerChangePeriodFromISR API函數都可以用于啟動計時器運作。
參數
參數 | 解釋 |
---|---|
pcTimerName | 配置設定給計時器的純文字名稱,純粹是為了幫助調試。 |
xTimerPeriod | 計時器周期。計時器周期以tick周期的倍數指定。pdMS_TO_TICKS()宏可用于将以毫秒為機關的時間轉換為以tick為機關的。例如,如果計時器必須在100個刻度後過期,則xNewPeriod可以直接設定為100。或者,如果計時器必須在500ms後過期,則xNewPeriod可以設定為pdMS_to_TICKS(500),前提是configTICK_RATE_HZ小于或等于1000。 |
uxAutoReload | 設定為pdTRUE以建立自動重載計時器。設定為pdFALSE以建立一次性計時器。一旦啟動,自動重載計時器将以xTimerPeriod參數設定的頻率重複過期。一旦啟動,一次性計時器将僅過期一次。一次性計時器到期後可以手動重新啟動 |
pvTimerID | 配置設定給正在建立的計時器的辨別符。稍後可以使用vTimerSetTimerID()API函數更新辨別符。如果将同一個回調函數配置設定給多個計時器,則可以在回調函數内檢查計時器辨別符,以确定哪個計時器實際過期。此外,計時器辨別符可用于在調用計時器的回調函數之間存儲值。 |
pxCallbackFunction | 計時器過期時要調用的函數。回調函數必須具有TimerCallbackFunction_t 定義的原型。所需的原型如上第4點所述。 |
傳回值
傳回值 | 解釋 |
---|---|
NULL | 無法建立軟體計時器,因為可用的FreeRTOS堆記憶體不足,無法成功配置設定計時器資料結構。 |
NULL以外的其它值 | 已成功建立軟體計時器,傳回的值是可以引用建立的軟體計時器的句柄。 |
注意事項
在FreeRTOSConfig.h中,configUSE_TIMERS和configSUPPORT_DYNAMIC_ALLOCATION必須都設定為1,以便xTimerCreate()可用。
如果未定義,configSUPPORT_DYNAMIC_ALLOCATION将預設為1。
5.2 啟動定時器xTimerStart()
描述
啟動計時器運作。xTimerStartFromISR()是一個可以從中斷服務例程調用的等效函數。
如果計時器尚未運作,則計時器将根據調用xTimerStart()的時間計算到期時間。
如果計時器已經在運作,則xTimerStart()在功能上等同于xTimerReset()。
參數
參數 | 解釋 |
---|---|
xTimer | 要重置、啟動或重新啟動的計時器。 |
xTicksToWait | 計時器功能不是由核心FreeRTOS代碼提供的,而是由計時器服務(或守護程式)任務提供的。FreeRTOS計時器API将指令發送到名為計時器指令隊列的隊列上的計時器服務任務。xTicksToWait指定任務應保留在“阻止”中的最長時間狀态以等待計時器指令隊列上的空間變為可用(如果隊列已滿)。 |
傳回值
傳回值 | 解釋 |
---|---|
pdPASS | pdPASS傳回值,表明啟動指令已成功發送到計時器指令隊列。如果指定了阻塞時間(xTicksToWait不為零),則pdPASS的傳回值說明在阻塞時間到期之前,就把資料寫入隊列了。實際處理指令的時間取決于計時器服務任務相對于系統中其他任務的優先級,盡管計時器的到期時間與實際調用xTimerStart()的時間有關。計時器服務任務的優先級由configTIMER_task_priority配置常量設定。 |
pdFAIL | 啟動指令未發送到計時器指令隊列,因為隊列已滿。如果指定了阻塞時間(xTicksToWait不為零),則一直到阻塞到期,資料也沒有被發送的定時器指令隊列中。 |
5.3 終止定時器xTimerStop()
參數
參數 | 解釋 |
---|---|
xTimer | 要被終止的定時器句柄 |
xTicksToWait | 計時器功能不是由核心FreeRTOS代碼提供的,而是由計時器服務(或守護程式)任務提供的。FreeRTOS計時器API将指令發送到名為計時器指令隊列的隊列上的計時器服務任務。xTicksToWait指定任務應保留在“阻止”中的最長時間狀态以等待計時器指令隊列上的空間變為可用(如果隊列已滿)。 |
傳回值
傳回值 | 解釋 |
---|---|
pdPASS | pdPASS傳回值,表明啟動指令已成功發送到計時器指令隊列。如果指定了阻塞時間(xTicksToWait不為零),則pdPASS的傳回值說明在阻塞時間到期之前,就把資料寫入隊列了。實際處理指令的時間取決于計時器服務任務相對于系統中其他任務的優先級,盡管計時器的到期時間與實際調用xTimerStart()的時間有關。計時器服務任務的優先級由configTIMER_task_priority配置常量設定。 |
pdFAIL | 啟動指令未發送到計時器指令隊列,因為隊列已滿。如果指定了阻塞時間(xTicksToWait不為零),則一直到阻塞到期,資料也沒有被發送的定時器指令隊列中。 |
5.4 定時器重置xTimerReset()
描述
重新啟動計時器。xTimerResetFromISR()是一個可以從中斷服務例程調用的等效函數。
如果計時器已經在運作,那麼計時器将重新計算其到期時間,以與調用xTimerReset()的時間相對應。
如果計時器未運作,則計時器将計算與調用xTimerReset()的時間相關的到期時間,計時器将開始運作。在這種情況下,xTimerReset()在功能上等同于xTimerStart()。
如果在排程程式啟動之前調用xTimerReset(),則在排程程式開始之前,計時器不會開始運作,并且計時器的到期時間将與排程程式啟動的時間相關
參數
參數 | 解釋 |
---|---|
xTimer | 要被重新開機的定時器句柄 |
xTicksToWait | 計時器功能不是由核心FreeRTOS代碼提供的,而是由計時器服務(或守護程式)任務提供的。FreeRTOS計時器API将指令發送到名為計時器指令隊列的隊列上的計時器服務任務。xTicksToWait指定任務應保留在“阻止”中的最長時間狀态以等待計時器指令隊列上的空間變為可用(如果隊列已滿)。 |
傳回值
傳回值 | 解釋 |
---|---|
pdPASS | 重置指令已成功發送到計時器指令隊列。如果指定了阻塞時間(xTicksToWait不為零),則阻塞到期之前,reset指令已成功寫入隊列。實際處理指令的時間取決于計時器服務任務相對于系統中其他任務的優先級,盡管計時器的到期時間與實際調用xTimerReset()的時間有關。計時器服務任務的優先級由configTIMER_task_priority配置常量設定 |
pdFAIL | 重置指令未發送到計時器指令隊列,因為隊列已滿 |
5.5 執行個體
在本例中,當按下一個鍵時,LCD背光将打開。如果5秒過去而沒有按下一個按鍵,則LCD背光将由一個一次性計時器關閉。以下是一段僞代碼,用于說明之前幾個函數的用法。
5.5 定時器停止運作xTimerStop()
描述
停止一個定時器的運作。
參數
參數 | 解釋 |
---|---|
xTimer | 需要被停止的定時器句柄 |
xTicksToWait | 計時器功能不是由核心FreeRTOS代碼提供的,而是由計時器服務(或守護程式)任務提供的。FreeRTOS計時器API将指令發送到名為計時器指令隊列的隊列上的計時器服務任務。xTicksToWait指定如果隊列已滿,任務應保持在“阻止”狀态以等待計時器指令隊列上的可用空間的最長時間 |
傳回值
傳回值 | 解釋 |
---|---|
pdPASS | stop指令已成功發送到計時器指令隊列。如果指定了阻塞時間(xTicksToWait不為零),則阻塞到期之前,stop指令已成功寫入隊列。 |
pdFAIL | stop指令未發送到計時器指令隊列,因為隊列已滿 |
5.6 删除定時器xTimerDelete()
描述
删除定時器。被删除的定時器,必須是已用xTimerCreate()建立的了 。
參數
參數 | 解釋 |
---|---|
xTimer | 将要被删除的定時器的句柄 |
xTicksToWait | 計時器功能不是由核心FreeRTOS代碼提供的,而是由計時器服務(或守護程式)任務提供的。FreeRTOS計時器API将指令發送到名為計時器指令隊列的隊列上的計時器服務任務。xTicksToWait指定任務應保留在“阻止”中的最長時間狀态以等待計時器指令隊列上的空間變為可用(如果隊列已滿)。 |
傳回值
傳回值 | 解釋 |
---|---|
pdPASS | 删除指令已成功發送到計時器指令隊列。如果指定了阻塞時間(xTicksToWait不為零),則阻塞到期之前,delete指令已成功寫入隊列。 |
pdFAIL | 删除指令未發送到計時器指令隊列,因為隊列已滿 |
5.7 改變定時器周期
如果使用xTimerChangePeriod()更改已在運作的定時器的周期,則計時器将使用新的周期值重新計算其到期時間。然後,重新計算的到期時間将與調用xTimerChangePeriod()的時間相關,而不是與定時器最初啟動的時間相關。
如果使用xTimerChangePeriod()更改尚未運作的定時器的周期,則計時器将使用新的周期值計算到期時間,定時器将開始運作。
參數
參數 | 解釋 |
---|---|
xTimer | 将要被改變的周期的定時器句柄 |
xNewPeriodxTimer | 參數引用的計時器的新周期。計時器周期以tick周期的倍數指定。pdMS_TO_TICKS()宏可用于将以毫秒為機關的時間轉換為以tick為機關的。例如,如果計時器必須在100個tick後過期,則xNewPeriod可以直接設定為100。或者,如果計時器必須在500ms後過期,則xNewPeriod可以設定為pdMS_to_TICKS(500),前提是configTICK_RATE_HZ小于或等于1000。 |
xTicksToWait | 計時器功能不是由核心FreeRTOS代碼提供的,而是由定時器提供的服務(守護程式)任務。FreeRTOS計時器API将指令發送到名為定時器指令隊列的隊列上的計時器服務任務。xTicksToWait指定在隊列已滿的情況下,任務應保持在“阻止”狀态以等待計時器指令隊列上的可用空間的最長時間。 |
傳回值
傳回值 | 解釋 |
---|---|
pdPASS | 更改周期指令已成功發送到計時器指令隊列。 |
pdFAIL | 更改周期指令未發送到計時器指令隊列,因為隊列已滿 |
6 Timer ID
6.1 定時器ID的概念
在定時器的結構體中,有一個void * pvTimerID這麼一個類型。見下圖。
每個軟體定時器都有一個ID,這是一個能讓程式開發者用于任何目的的标簽值。這個ID被存儲于void*指針,是以能存入一
個整型值直接指向任何對象或像函數指針一樣使用。
當軟體定時器被建立時,配置設定給ID一個初始化的值,(在xTimerCreate()函數的第四個參數)之後,可以使用vTimerSetTimerID()函數更新ID值,并用pvTimerGetTimerID()來查詢ID。
從上面的定時器結構體可以看出。該ID值是内嵌在定時器結構體内的,是以,對該值的操作,不像其它軟體定時器API函數,vTimerSetTimerID() 和 pvTimerGetTimerID()是直接通路軟體定時器,而不用發送指令到定時器指令隊列。
6.2 定時器ID的應用場景
由于這個資料是一個void *的類型。是以,我們可以非常靈活的使用它,并用這個值給定時器的其它行為傳遞任何資料。比如,這個值可以是一個32位的任意類型的資料。也可以是一個指針,指向一個更複雜的資料結構,可以是一個函數指針,用于向定時器傳遞一個當下對應的函數指針。
是以,這是一個非常靈活的設定。我們可以用于任何目的和我們想要的場景。比如,可以把這個void *當成一個整型變量,用以維護一個對應該定時器的計數器,記錄定時器的運作次數。也可以把這個指針指向一個更為複雜的資料結構,為這個定時器維護一個專有的資料結構用于更複雜的工作。
如果将同一個回調函數配置設定給多個計時器,則可以在回調函數内部檢查計時器辨別符,以确定哪個計時器實際過期。
計時器辨別符還可以用于在調用計時器的回調函數之間在計時器中存儲資料。
6.3 定時器ID的初始化
在xTimerCreate()建立定時器時,我們就需要初始化該定時器ID了。這個從建立函數的定義中就可以清楚:
如果無需要該值,則我們一般初始化為NULL。
6.4 設定定時器ID vTimerSetTimerID()
建立計時器時,會為定時器配置設定辨別符(定時器ID),可以使用vTimerSetTimerID()API函數随時更改。
參數
參數 | 解釋 |
---|---|
xTimer | 需要被更新辨別符ID的定時器的句柄 |
pvNewID | 計時器辨別符将設定為的值。 |
6.5 查詢定時器ID值
傳回配置設定給計時器的辨別符(定時器ID)。建立計時器時,會為計時器配置設定一個辨別符,可以使用vTimerSetTimerID()API函數進行更新。有關詳細資訊,請參見xTimerCreate()API函數。
如果将同一個回調函數配置設定給多個計時器,則可以在回調函數内部檢查計時器辨別符,以确定哪個計時器實際過期。
此外,計時器的辨別符可用于在調用計時器的回調函數之間存儲值
參數
xTimer : 将要被查詢的定時器句柄
傳回值
定時器ID值。
6.6 執行個體
執行個體1
執行個體2
同一個回調函數可以配置設定給多個軟體定時器。完成後,回調函數參數用于确定哪個軟體計時器到期。
本示例14建立了兩個定時器,但為兩個軟體定時器配置設定了同一個回調函數。其中prvTimerCallback()用作兩個計時器的回調函數。
prvTimerCallback() 将在任一計時器到期時執行。 prvTimerCallback() 的實作使用函數的參數來确定調用它是因為一
次性計時器到期,還是因為自動重載計時器到期。
prvTimerCallback() 還示範了如何使用軟體定時器 ID 作為定時器特定的存儲;每個軟體定時器都會在自己的 ID 中
記錄它已過期的次數,自動重載定時器會在第五次執行時使用該計數自行停止。
7 總結
Timer 是FreeRTOS中不依賴硬體的一個軟體定時器。機制上由三個部分組成,一個是Timer結構體,用于存放定時器的具體參數及必要資料結構。二是系統定時器守護程序任務,用于具體處理定時器到期檢測以及定時器回調函數的運作,三是軟體定時器指令隊列,用于向守護程序任務傳遞定時器的處理指令。
定時器守護程序任務與定時器指令隊列由系統在排程程式xTaskStartSchedule()啟動時自動建立。
程式設計人員在使用定時器時。必須首先建立定時器對象(即資料結構)。明确定時器的周期,定時器的類型(一次性的或者是自動重載的),Timer ID等,回調函數。
然後由開發者在過程中用定時器API來向指令隊列發送具體的定時器指令,如xTimerStart(),xTimerStop(),xTimerDelete()等。這些指令由定時器守護程序任務在适當時候執行。這裡的适當時候指排程程式根據守護程序任務的優先級進行排程運作。是以守護程序的優先級是必須由開發人員具體調整 的。在FreeRTOSConfig.h中的宏 configTIMER_TASK_PRIORITY進行設定。系統預設該宏為1。