以下基礎內容轉載自安富萊電子: http://forum.armfly.com/forum.php
本章節為大家講解 FreeRTOS 計數信號量的另一種實現方式----基於任務通知(Task Notifications)
的計數信號量,這里我們將這種方式實現的計數信號量稱之為任務計數信號量。 任務計數信號量效率更高,
需要的 RAM 空間更小。當然,缺點也是有的,它沒有之前介紹的計數信號量實現的功能全面。
任務通知(Task Notifications)介紹
FreeRTOS 每個已經創建的任務都有一個任務控制塊(task control block),任務控制塊就是一個
結構體變量,用於記錄任務的相關信息。 結構體變量中有一個 32 位的變量成員 ulNotifiedValue 是專門
用於任務通知的。
通過任務通知方式可以實現計數信號量,二值信號量,事件標志組和消息郵箱(消息郵箱就是消息隊
列長度為 1 的情況)。 使用方法與前面章節講解的事件標志組和信號量基本相同,只是換了不同的函數來
實現。 任務通知方式實現的計數信號量,二值信號量,事件標志組和消息郵箱是通過修改變量
ulNotifiedValue 實現的:
設置接收任務控制塊中的變量 ulNotifiedValue 可以實現消息郵箱。
如果接收任務控制塊中的變量 ulNotifiedValue 還沒有被其接收到,也可以用新數據覆蓋原有數據
,這就是覆蓋方式的消息郵箱。
設置接收任務控制塊中的變量 ulNotifiedValue 的 bit0-bit31 數值可以實現事件標志組。
設置接收任務控制塊中的變量 ulNotifiedValue 數值進行加一或者減一操作可以實現計數信號量和二
值信號量。
介紹了這么多,那么問題來了,采用這種方式有什么優勢呢?根據官方的測試數據,喚醒由於信號量
和事件標志組而處於阻塞態的任務,速度提升了 45%,而且這種方式需要的 RAM 空間更小。 但這種方式
實現的信號量和事件標志組也有它的局限性,主要表現在以下兩個方面:
任務通知方式僅可以用在只有一個任務等待信號量,消息郵箱或者事件標志組的情況,不過實際項目
項目中這種情況也是最多的。
使用任務通知方式實現的消息郵箱替代前面章節講解的消息隊列時,發送消息的任務不支持超時等待,
即消息隊列中的數據已經滿了,可以等待消息隊列有空間可以存新的數據,而任務通知方式實現的消
息郵箱不支持超時等待。
任務計數信號量
前面,我們對計數信號量進行了講解,計數信號量就是對一個變量進行計數,變量的范圍是
從 0 到用戶創建計數信號量時所設置的大小。當計數變量大於 0 的時候計數信號量管理的資源才可以使用,
計數變量的具體數值就是可用的資源大小。
本章節講解的任務計數信號量與前面章節講解的計數信號量要實現的功能是一樣的,不同的是調用的
函數和使用的計數變量:
任務計數信號量的計數變量是通過任務控制塊中的一個 32 位變量 ulNotifiedValue 實現計數。 前面講解的計數信號量創建后會有自己的計數變量。
任務計數信號量是通過函數 ulTaskNotifyTake()替代之前講解的函數 xSemaphoreTake()實現
資源獲取,即對計數信號量數值進行減一操作。
任務計數信號量是通過函數 xTaskNotifyGive() 和 vTaskNotifyGiveFromISR()替代前面講解的
函數 xSemaphoreGive() 和 xSemaphoreGiveFromISR()實現資源釋放,即對計數信號量的數值進
行加一操作。
實際項目中,如果使用計數信號量和任務計數信號量都能實現相應功能,強烈建議使用任務計數信號量。
任務計數信號量 API 函數
使用如下 9 個函數可以實現 FreeRTOS 的任務信號量(含任務計數信號量和任務二值信號量):
xTaskNotifyGive()
vTaskNotifyGiveFromISR()
ulTaskNotifyTake()
xTaskNotify()
xTaskNotifyAndQuery()
xTaskNotifyAndQueryFromISR()
xTaskNotifyFromISR()
xTaskNotifyWait()
xTaskNotifyStateClear()
關於這 9 個函數的講解及其使用方法可以看 FreeRTOS 在線版手冊 。
這里我們重點的說以下 3 個函數:
xTaskNotifyGive
vTaskNotifyGiveFromISR
ulTaskNotifyTake
因為本章節配套的例子使用的是這 3 個函數。
函數 xTaskNotifyGive
函數原型:
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify ); /* 任務句柄 */
函數描述:
函數 xTaskNotifyGive 用於釋放信號量(含任務二值信號量,任務計數信號量)。
第 1 個參數是任務句柄。
返回值,僅有一個返回值 pdPASS。
使用這個函數要注意以下問題:
1. 任務信號量的初始計數值是 0。 任務信號量不像前面章節講解的信號量,無需單獨創建即可使用。
2. 默認配置此函數可以使用的的宏定義已經在 FreeRTOS.h 文件中使能:
#define configUSE_TASK_NOTIFICATIONS 1
當然,如果用戶不需要使用任務通知功能相關的函數,可以在 FreeRTOSConfig.h 文件中配置此宏定
義為 0 來禁止,這樣創建的每個任務可以節省 8 個字節的需求。
3. 此函數是用於任務代碼中調用的,故不可以在中斷服務程序中調用此函數,中斷服務程序中使用的是
vTaskNotifyGiveFromISR。
函數 vTaskNotifyGiveFromISR
函數原型:
void vTaskNotifyGiveFromISR(
TaskHandle_t xTaskToNotify, /* 任務句柄 */
BaseType_t * pxHigherPriorityTaskWoken ); /* 高優先級任務是否被喚醒的狀態保存 */
函數描述:
函數 xTaskNotifyGive 用於釋放信號量(含任務二值信號量,任務計數信號量)。
第 1 個參數是任務句柄。
第 2 個參數用於保存是否有高優先級任務准備就緒。如果函數執行完畢后,此參數的數值是 pdTRUE,
說明有高優先級任務要執行,否則沒有。
使用這個函數要注意以下問題:
1. 任務信號量的初始計數值是 0。任務信號量不像前面章節講解的信號量,無需單獨創建即可使用。
2. 默認配置此函數可以使用的的宏定義已經在 FreeRTOS.h 文件中使能:
#define configUSE_TASK_NOTIFICATIONS 1
當然,如果用戶不需要使用任務通知功能相關的函數,可以在 FreeRTOSConfig.h 文件中配置此宏定
義為 0 來禁止,這樣創建的每個任務可以節省 8 個字節的需求。
3. 此函數是用於中斷服務程序中調用的,故不可以在任務代碼中調用此函數,任務代碼中使用的是xTaskNotifyGive。
函數 ulTaskNotifyTake
函數原型:
uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, /* 選擇是否清零用於任務通知的 ulNotifiedValue */
TickType_t xTicksToWait ); /* 等待信號量可用的最大等待時間 */
函數描述:
函數 ulTaskNotifyTake用於獲取信號量(含任務二值信號量,任務計數信號量)。
第 1 個參數配置為 pdFALSE 表示函數返回前用於任務信號量的內部變量 ulNotifiedValue數值減一,這種方式用於任務計數信號量。 參數配置為 pdTRUE 表示函數返回前用於任務信號量的內部變量 ulNotifiedValue數值被清零,這種方式用於任務二值信號量。
第 2 個參數是沒有任務信號量可用時,等待信號量可用的最大等待時間,單位系統時鍾節拍。
返回值:任務的通知值在其遞減或清除之前的值
使用這個函數要注意以下問題:
1. 任務信號量的初始計數值是 0。任務信號量不像前面章節講解的信號量,無需單獨創建即可使用。
2. 默認配置此函數可以使用的的宏定義已經在 FreeRTOS.h 文件中使能:
#define configUSE_TASK_NOTIFICATIONS 1
當然,如果用戶不需要使用任務通知功能相關的函數可以在 FreeRTOSConfig.h 文件中配置此宏定義
為 0 來禁止,這樣創建的每個任務可以節省 8 個字節的需求。
3. 如果用戶將 FreeRTOSConfig.h 文件中的宏定義 INCLUDE_vTaskSuspend 配置為 1 且第 2 個參數配
置為 portMAX_DELAY,那么此函數會永久等待直到信號量可用。
實驗展示台:
發送任務:值加1,默認為0
接受任務:值減1
實驗輸出:
中斷方式發送:
接收函數還是不變,ulTaskNotifyTake函數中斷和非中斷方式都是它。
實驗現象:按鍵按下之后,每隔500ms釋放一次
注意中斷方式,這里是定時器中斷,定時器中斷優先級的數學值要大於等於configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY配置的值,也就是定時器中斷優先級的邏輯值要小於等於config.h中configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY這個宏的配置值,這樣才可以強制上下文切換。
FreeRTOS 任務二值信號量
任務二值信號量
前面,我們對二值信號量進行了講解,二值信號量只有兩種數值 0 和 1。 本章節講解的任務
二值信號量與前面章節講解的二值信號量要實現的功能是一樣的,不同的是調用的函數和使用的計數變量:
任務二值信號量的計數變量是通過任務控制塊中的一個 32 位變量 ulNotifiedValue 實現計數。前面講解的二值信號量創建后會有自己的計數變量。
任務二值信號量是通過函數 ulTaskNotifyTake()替代前面章節講解的函數 xSemaphoreTake()實現
資源獲取,即對二值信號量數值進行清零操作。
任務二值信號量是通過函數 xTaskNotifyGive() 和 vTaskNotifyGiveFromISR()替代第前面章節講解的
函數 xSemaphoreGive() 和 xSemaphoreGiveFromISR()實現資源釋放,即對二值信號量的數值進行加一操作。
多次調用函數 xTaskNotifyGive ()難免會出現計數值大於 1 的情況,用作任務二值信號量時,我們可
以將所有大於 1 的計數理解為一種情況,即二值信號量管理的資源可用。因此,不管當前的計數是多少,
大於 0 的計數在通過函數 ulTaskNotifyTake()獲取二值信號量的時候統一清零,這樣就實現了二值信號量的功能。
實際項目中,如果使用二值信號量和任務二值信號量都能實現相應功能,強烈建議使用任務二值信號量。
實驗展示場:
發送:
接收:
實驗現象:
由於接收端采用無限時間等待,那么按鍵按一次,才會接收到一次。雖然任務1一直在發送,但是接收一次之后就清零了計數值,這樣就實現了任務二值信號量。中斷方式和之前任務計數信號量類似,故不再展示實驗現象了。
FreeRTOS 任務事件標志組
任務事件標志組
前面章節,我們對事件標志組進行了講解。 本章節講解的任務事件標志組與前面章節講解的事件標
志組要實現的功能是一樣的,不同的是調用的函數和支持的事件標志個數,任務事件標志組支持 32 個事
件標志設置,而之前章節介紹的事件標志組,每創建一個支持 24 個事件標志設置:
任務事件標志組的事件標志位是通過任務控制塊中的一個 32 位變量 ulNotifiedValue 實現。前面
章節講解的事件標志組創建后會有自己可以設置的事件標志位。
任務事件標志組是通過函數 xTaskNotifyWait()替代前面章節講解的函數 xEventGroupWaitBits ()
實現等待事件標志位被設置。
任務事件標志組是通過函數 xTaskNotify() 和 xTaskNotifyFromISR()替代之前講解的函數
xEventGroupSetBits() 和 xEventGroupSetBitsFromISR 實現對事件標志位的設置。
之前講解的函數 xEventGroupSetBitsFromISR 是通過給 Daemon 任務(定時器任務)發消
息,在定時器任務中執行實際的操作,而我們本章節要介紹的函數 xTaskNotifyFromISR 是直接在
中斷服務程序里面執行操作,效率要高很多。
函數 xTaskNotify
函數描述:
函數 xTaskNotify 通過設置任務控制塊中的變量 ulNotifiedValue 可以在任務代碼中實現任務事件標志組,
任務計數信號量,任務消息郵箱和任務二值信號量四種方式的消息通知。
第 1 個參數是任務句柄。
第 2 個參數是用來更新任務控制塊中的 32 位變量 ulNotifiedValue。
第 3 個參數是任務通知模式設置,支持以下 5 個參數:
返回值,根據上面第 3 個參數的說明,將其設置為 eSetValueWithoutOverwrite,有可能返回
pdFALSE,其余所有情況都返回值 pdPASS。
使用這個函數要注意以下問題:
1. 任務創建后,任務控制塊中的變量 ulNotifiedValue 初始計數值是 0。
2. 默認配置此函數可以使用的的宏定義已經在 FreeRTOS.h 文件中使能:
#define configUSE_TASK_NOTIFICATIONS 1
當然,如果用戶不需要使用任務通知功能相關的函數,可以在 FreeRTOSConfig.h 文件中配置此宏定
義為 0 來禁止,這樣創建的每個任務可以節省 8 個字節的需求。
3. 此函數是用於任務代碼中調用的,故不可以在中斷服務程序中調用此函數,中斷服務程序中使用的是
xTaskNotifyFromISR。
4. 根據 FreeRTOS 的建議,實現二值信號量和計數信號量時使用函數 xTaskNotifyGive()替代此函數xTaskNotify()。
函數 xTaskNotifyFromISR
函數描述:
函數 xTaskNotifyFromISR 通過設置任務控制塊中的變量 ulNotifiedValue 可以在中斷服務程序中實現任
務事件標志組,任務計數信號量,任務消息郵箱和任務二值信號量四種方式的消息通知(見 26.1 說明)。
第 1 個參數是任務句柄。
第 2 個參數是用來更新任務控制塊中的 32 位變量 ulNotifiedValue。
第 3 個參數是任務通知模式設置,支持5 個參數,如上面5個參數一樣:
第 4 個參數用於保存是否有高優先級任務准備就緒。如果函數執行完畢后,此參數的數值是 pdTRUE,
說明有高優先級任務要執行,否則沒有。
返回值,根據上面第 3 個參數的說明,將其設置為 eSetValueWithoutOverwrite,有可能返回
pdFALSE,其余所有情況都返回值 pdPASS。
使用這個函數要注意以下問題:
1. 任務創建后,任務控制塊中的變量 ulNotifiedValue 初始計數值是 0。
2. 默認配置此函數可以使用的的宏定義已經在 FreeRTOS.h 文件中使能:
#define configUSE_TASK_NOTIFICATIONS 1
當然,如果用戶不需要使用任務通知功能相關的函數,可以在 FreeRTOSConfig.h 文件中配置此宏定
義為 0 來禁止,這樣創建的每個任務可以節省 8 個字節的需求。
3. 此函數是用於中斷服務程序中調用的,故不可以在任務代碼中調用此函數,任務代碼中使用的是xTaskNotify。
4. 根據 FreeRTOS 的建議,實現二值信號量和計數信號量時使用函數 vTaskNotifyGiveFromISR ()替代此函數 xTaskNotifyFromISR ()。
函數 xTaskNotifyWait
函數描述:
函數 xTaskNotifyWait 可以在任務代碼中實現任務事件標志組,任務計數信號量,任務消息郵箱和任務二
值信號量四種方式的消息獲取。
第 1 個參數 ulBitsToClearOnEntry 用於函數執行之前,將任務控制塊中的變量 ulNotifiedValue 進
行如下操作 :
ulNotifiedValue &= ~ulBitsToClearOnEntry
簡單的說就是參數 ulBitsToClearOnEntry 哪個位是 1,那么變量 ulNotifiedValue 的那個位就會被
清零。 比如 ulBitsToClearOnEntry = 0x01 表示將變量 ulNotifiedValue 的 bit0 清零,又比如
ulBitsToClearOnEntry = 0xffffffff 表示將變量 ulNotifiedValue 的所有位清零。
第 2 個參數 ulBitsToClearOnExit 用於函數退出前,將任務控制塊中的變量 ulNotifiedValue 進行如下操作 :
簡單的說就是參數 ulBitsToClearOnExit 哪個位是 1,那么變量 ulNotifiedValue 的那個位就會被清
零。 比如 ulBitsToClearOnExit= 0x01 表示將變量 ulNotifiedValue 的 bit0 清零,又比如
ulBitsToClearOnExit= 0xffffffff 表示將變量 ulNotifiedValue 的所有位清零。
第 3 個參數用於將任務控制塊中的變量 ulNotifiedValue 保存到此參數指針所指向的存儲單元。 如果
此參數沒有用上,可以將其設置為 NULL。
第 4 個參數是沒有消息時,等待消息的最大等待時間,單位系統時鍾節拍。
返回值,如果成功接收到消息返回 pdTRUE,否則返回 pdFALSE,比如在設置的超時時間內沒有收
到消息。
使用這個函數要注意以下問題:
1. 任務創建后,任務控制塊中的變量 ulNotifiedValue 初始計數值是 0。
2. 默認配置此函數可以使用的的宏定義已經在 FreeRTOS.h 文件中使能:
#define configUSE_TASK_NOTIFICATIONS 1
當然,如果用戶不需要使用任務通知功能相關的函數,可以在 FreeRTOSConfig.h 文件中配置此宏定
義為 0 來禁止,這樣創建的每個任務可以節省 8 個字節的需求。
3. 如果用戶將 FreeRTOSConfig.h 文件中的宏定義 INCLUDE_vTaskSuspend 配置為 1 且第 2 個參數配
置為 portMAX_DELAY,那么此函數會永久等待直到消息可用。
4. 根據 FreeRTOS 的建議,實現二值信號量和計數信號量時使用函數 ulTaskNotifyTake ()替代此函數
xTaskNotifyWait ()。
實驗練兵場:
發送:
接收端:
void vTaskBeep(void *pvParameters) { BaseType_t xResult; const TickType_t xMaxBlockTime = pdMS_TO_TICKS(500); /* 設置最大等待時間為500ms */ uint32_t ulValue; while(1) { /* 第一個參數 ulBitsToClearOnEntry的作用(函數執行前): ulNotifiedValue &= ~ulBitsToClearOnEntry 簡單的說就是參數ulBitsToClearOnEntry那個位是1,那么notification value 的那個位就會被清零。 這里ulBitsToClearOnEntry = 0x00000000就是函數執行前保留所有位。 第二個參數 ulBitsToClearOnExit的作用(函數退出前): ulNotifiedValue &= ~ulBitsToClearOnExit 簡單的說就是參數ulBitsToClearOnEntry那個位是1,那么notification value 的那個位就會被清零。 這里ulBitsToClearOnExi = 0xFFFFFFFF就是函數退出前清除所有位。 注:ulNotifiedValue表示任務vTaskBeep的任務控制塊里面的變量。 */ xResult = xTaskNotifyWait(0x00000000, 0xFFFFFFFF, &ulValue, /* 保存ulNotifiedValue到變量ulValue中 */ xMaxBlockTime); /* 最大允許延遲時間 */ if( xResult == pdPASS ) { /* 接收到消息,檢測那個位被按下 */ if((ulValue & BIT_0) != 0) { printf("接收到K1按鍵按下消息, ulNotifiedValue = 0x%08x\r\n", ulValue); } if((ulValue & BIT_1) != 0) { printf("接收到K2按鍵按下消息, ulNotifiedValue = 0x%08x\r\n", ulValue); } } else { /* 超時 */ BEEP_TOGGLE; } } }
實驗現象:
中斷方式不再演示,只是API函數不同。注意其中一個C語言知識點,printf輸出十六進制數據,如何操作設置成只顯示八位的。
任務消息郵箱
本章節為大家講解 FreeRTOS 消息隊列(消息隊列長度固定為 1)的另一種實現方式----基於任務通
知(Task Notifications)的消息隊列,這里我們將這種方式實現的消息隊列(消息隊列長度固定為 1)稱
之為任務消息郵箱。 這種方式實現的消息隊列效率更高,需要的 RAM 空間更小。當然,缺點也是有的,
它沒有前面章節介紹的消息隊列實現的功能全面(消息郵箱就是將消息隊列長度設置為 1 的情況)。
任務消息郵箱
前面章節,我們對消息隊列進行了講解,而消息郵箱就是將消息隊列的長度設置為 1 的情況。 本
章節講解的任務消息郵箱與前面章節講解的消息隊列長度是 1 時要實現的功能是一樣的,不同的是調用的
函數和消息存儲的位置:
任務消息郵箱是通過任務控制塊中的一個 32 位變量 ulNotifiedValue 對數據進行存取。 前面章節講
解的消息隊列創建后會有自己可以存取數據的空間。
任務消息郵箱是通過函數 xTaskNotifyWait()替代之前講解的函數 xQueueReceive ()實現從消
息郵箱獲取數據。
任務消息郵箱是通過函數 xTaskNotify() 和 xQueueSendFromISR ()替代之前講解的函數
xQueueSend () 和 xEventGroupSetBitsFromISR 實現向消息郵箱存入數據。
任務消息郵箱通過函數 xTaskNotify 向消息郵箱中發送數據時,如果消息郵箱中上次的數據還沒有
處理,不支持超時等待,而消息隊列的函數 xQueueSend 是支持超時等待的。
實際項目中,如果使用任務消息郵箱和消息隊列都能實現相應功能,強烈建議使用任務消息郵箱。
這個實驗和上面的只是在xTaskNotify 函數時的參數設置不同而已,其他使用完全相同,故不再演示。
有個地方的API需要注意一下,隊列集的操作,可能會用到:
具體解釋,看官網說明。
最后強調一下:
上面表格的5個參數,決定了是用於事件標志組,還是信號量,還是消息郵箱。