RTX——第16章 消息郵箱


以下內容轉載自安富萊電子: http://forum.armfly.com/forum.php

前面幾個章節主要給大家講解了任務間的同步和資源共享機制,本章節為大家講解任務間的通信機制
消息郵箱,RTX 的消息郵箱其實就是消息隊列,注意和 uCOS-II 中的消息郵箱區分開,uCOS-II 的消息郵
箱只能實現一個數據的傳遞。這里的消息郵箱可以實現多個數據的傳遞。
消息郵箱的概念及其作用
RTX 的消息郵箱實際上就是消息隊列,通過內核提供的服務,任務或中斷服務子程序可以將一個消息
注意,RTX 消息郵箱傳遞的是消息的地址而不是實際的數據)放入到消息隊列。同樣,一個或者多個任
務可以通過內核服務從消息隊列中得到消息。通常,先進入消息隊列的消息先傳給任務,也就是說,任務
先得到的是最先進入到消息隊列的消息,即先進先出的原則(FIFO)。
也許有不理解的初學者會問采用消息郵箱多麻煩,搞個全局數組不是更簡單,其實不然。在裸機編程
時,使用全局數組的確比較方便,但是在加上 RTOS 后就是另一種情況了。 使用全局數組相比消息郵箱主
要有如下四個問題:
使用消息郵箱可以讓 RTOS 內核有效的管理任務,全局數組是無法做到的,任務的超時等機制需要用
戶自己去實現。
使用了全局數組就要防止多任務的訪問沖突,使用消息郵箱已經處理好了這個問題。 用戶無需擔心。
使用消息郵箱可以有效的解決中斷服務程序跟任務之間消息傳遞的問題。
FIFO 機制更有利於數據的處理。
RTX 任務間消息郵箱的實現
任務間消息郵箱的實現是指各個任務之間使用消息郵箱實現任務間的通信。 下面我們通過如下的框圖
來說明一下 RTX 消息郵箱的實現,讓大家有一個形象的認識。

運行條件:
創建消息郵箱,可以存放 10 個消息。
創建 2 個任務 Task1 和 Task2,任務 Task1 向消息郵箱放數據地址,任務 Task2 從消息郵箱取數據地址。
RTX 的消息讀取和存放僅支持 FIFO 方式。
運行過程主要有以下兩種情況:
任務 Task1 向消息郵箱放數據地址,任務 Task2 從消息郵箱取數據地址,如果放數據地址的速度快
於取數據的速度,那么會出現消息郵箱存放滿的情況,RTX 的消息存放函數 os_mbx_send 支持超時
等待,可以設置超時等待,直到有位置可以存放消息放或者設置時間超時。
任務 Task1 向消息郵箱放數據地址,任務 Task2 從消息郵箱取數據地址,如果放數據地址的速度慢
於取數據的速度,那么會出現消息郵箱為空的情況,RTX 的消息獲取函數 os_mbx_wait 支持超時等
待,可以設置超時等待,直到消息郵箱中有消息放或者設置時間超時。
上面就是一個簡單的 RTX 任務間消息郵箱通信過程。

RTX 中斷方式消息郵箱的實現
RTX 中斷方式消息郵箱的實現是指中斷函數和 RTX 任務之間使用消息郵箱。 下面我們通過如下的框
圖來說明一下 RTX 消息郵箱的實現,讓大家有一個形象的認識。

創建 1 個任務 Task1 和一個串口接收中斷。
RTX 的消息讀取和存放僅支持 FIFO 方式。
運行過程主要有以下兩種情況:
中斷服務程序向消息郵箱放數據地址,任務 Task1 從消息郵箱取數據地址,如果放數據地址的速度快
於取數據的速度,那么會出現消息郵箱存放滿的情況。由於中斷服務程序里面的消息郵箱發送函數
isr_mbx_send 不支持超時設置,所有發送前要通過函數 isr_mbx_check 檢測郵箱是否滿。
中斷服務程序向消息郵箱放數據地址,任務 Task1 從消息郵箱取數據地址,如果放數據地址的速度慢
於取數據的速度,那么會出現消息郵箱存為空的情況。在 RTX 的任務中可以通過函數 os_mbx_wait
獲取消息,因為此函數可以設置超時等待,直到消息郵箱中有消息存放或者設置時間超時。
上面就是一個簡單 RTX 消息郵箱通信過程。 實際應用中,中斷方式的消息機制切記注意以下四個問題:
中斷函數的執行時間越短越好,防止其它低於這個中斷優先級的異常不能得到及時響應。
實際應用中,建議不要在中斷中實現消息處理,用戶可以在中斷服務程序里面發送消息通知任務,在
任務中實現消息處理,這樣可以有效的保證中斷服務程序的實時響應。同時此任務也需要設置為高優
先級,以便退出中斷函數后任務可以得到及時執行。
中斷服務程序中一定要調用專用於中斷的消息郵箱函數 isr_mbx_send,isr_mbx_receive 和
isr_mbx_check。
在 RTX 操作系統中實現中斷函數和裸機編程是一樣的。
另外強烈推薦用戶將 Cortex-M3 內核的 STM32F103 和 Cortex-M4 內核的 STM32F407 的
NVIC 優先級分組設置為 4,即:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);這樣中斷
優先級的管理將非常方便。
用戶要在 RTX 多任務開啟前就設置好優先級分組,一旦設置好切記不可再修改。

消息郵箱 API 函數
使用如下 8 個函數可以實現 RTX 消息郵箱:
os_mbx_check
os_mbx_declare
os_mbx_init
os_mbx_send
os_mbx_wait
isr_mbx_check
isr_mbx_receive
isr_mbx_send

函數 os_mbx_declare
函數原型:
#define os_mbx_declare( \
name, \ /* 消息郵箱名 */
cnt ) \ /* 消息個數 */
U32 name [4 + cnt]
函數描述:
函數 os_mbx_declare 定義了消息郵箱的大小和消息郵箱名,用於消息郵箱的初始化。 其實就是通過宏定
義的方式定義了一個數組 U32 類型的數組 name[4 + cnt]。 定義成 32 位數組是因為 CM3/CM4 是 32 位
機,地址也就是 32 位的,從而指針變量也就是固定的 32 位變量。 RTX 的消息郵箱傳遞的是變量的地址,
而不是變量的內容,所以要將郵箱定義成 32 位數組。
第 1 個參數表示定義的消息郵箱名。
第 2 個參數是郵箱支持的消息個數。
一般的應用中,消息郵箱支持 20 個消息就夠了。

os_mbx_declare (mailbox, 10);

 函數 os_mbx_init
函數原型:
void os_mbx_init (
OS_ID mailbox, /* 消息郵箱的 ID 標識 */
U16 mbx_size ); /* 郵箱大小,單位字節 */
函數描述:
函數 os_mbx_init 用於消息郵箱的初始化。其實就是將函數 os_mbx_declare 定義的數組進行初始化用於
消息郵箱的 FIFO。
第 1 個參數填寫消息郵箱的 ID 標識,即函數 os_mbx_declare 第一個參數。
第 2 個參數填寫函數 os_mbx_declare 定義的郵箱大小,單位字節。

函數 os_mbx_send
函數原型:
OS_RESULT os_mbx_send (
OS_ID mailbox, /* 消息郵箱 ID 標識 */
void* message_ptr, /* 消息指針,即數據的地址 */
U16 timeout ); /* 超時時間設置 */
函數描述:
函數 os_mbx_send 用於向消息郵箱存放數據指針,或者說數據地址。 如果消息郵箱已經滿了,調用此函
數的任務將被掛起,等待消息郵箱可用,直到消息郵箱有空間可用或者超時時間溢出才會返回。
第 1 個參數填寫消息郵箱的 ID 標識,即函數 os_mbx_declare 第一個參數。
第 2 個參數填寫消息指針,即數據的地址。
第 3 個參數表示設置的等待時間,范圍 0-0xFFFF,當參數設置為 0-0xFFFE 時,表示等待這么多個
時鍾節拍,參數設置為 0xFFFF 時表示無限等待直到消息郵箱有可用的空間。
返回值 OS_R_OK,表示消息指針成功放到消息郵箱中。
返回值 OS_R_TMO,表示消息郵箱已經滿了,在設置的超時時間范圍內也沒有等到可用的空間。
使用這個函數要注意以下問題:
1. 使用此函數前一定要調用函數 os_mbx_init 進行初始化。
2. 此函數用於往消息郵箱放消息指針,另一個函數 os_mbx_wait 用於從消息郵箱取出消息指針,取出后
相應的位置也就釋放出來了,方便下次存放數據。
函數 os_mbx_wait
函數原型:
OS_RESULT os_mbx_wait (
OS_ID mailbox, /* 消息郵箱的 ID 標識 */
void** message, /* 存放消息指針的變量地址 */
U16 timeout ); /* 超時時間設置 */
函數描述:
函數 os_mbx_wait 用於從消息郵箱中獲取消息。如果消息郵箱為空,調用此函數的任務將被掛起,直到
消息郵箱有消息可用或是設置的超時時間溢出才會返回。
第 1 個參數填寫消息郵箱的 ID 標識,即函數 os_mbx_declare 第一個參數。
第 2 個參數填寫從消息郵箱中獲取消息后,存放消息的指針變量。
第 3 個參數表示設置的等待時間,范圍 0-0xFFFF,當參數設置為 0-0xFFFE 時,表示等待這么多個
時鍾節拍,參數設置為 0xFFFF 時表示無限等待直到消息郵箱有消息。
返回值 OS_R_OK,表示從消息郵箱中有消息,立即從消息郵箱中獲得消息,無需等待。。
返回值 OS_R_TMO,表示消息郵箱為空,在設置的超時時間范圍內也沒有等到消息。
返回值 OS_R_MBX,表示在設置的超時時間范圍內收到消息。
使用這個函數要注意以下問題:
1. 使用此函數前一定要調用函數 os_mbx_init 進行初始化。
2. 此函數用於往消息郵箱放消息指針,另一個函數 os_mbx_wait 用於從消息郵箱取出消息指針,取出后
相應的位置也就釋放出來了,方便下次存放數據。
這里對函數 os_mbx_wait 的第二個參數再做一下解釋。
定義一個指針變量 uint8_t *pMsg,含義:pMsg 是指向 uint8_t 型變量的指針變量。
指針變量 pMsg(注意,這里說的是 pMsg,不是*pMsg,前面沒有*號)在內存中也是要占空間的,
對於 CM3/CM4 內核來說是占用 4 個字節。指針變量 pMsg 里面存儲的就是從消息郵箱中獲取的消
息指針,或者說消息地址。 從而*pMsg 就是 pMsg 所指向存儲單元的實際內容。
由於函數 os_mbx_wait 第二個參數是 void **message,有兩級指針,所以填寫的時候要填寫成
os_mbx_wait(&mailbox, (void *)&pMsg, usMaxBlockTime)。 &pMsg 就是表示指針變量在內存
所在的地址。
這里再說說 void 指針類型,使用 void 指針類型可定義一個指針變量,但不指定它是指向哪一種類型
的數據。 用戶在使用這種類型指針變量時可以用來指向一個抽象的類型的數據,在將它的值賦值給另
一個指針變量時要進行強制類型轉換使之適合於被賦值的變量的類型。 比如:
char *p1;
void *p2;
p1 = (char *)p2
如果細心的同學會發現,郵箱發送函數 os_mbx_send 和接收函數 os_mbx_wait 消息指針傳遞都是
用的 void 指針類型,這樣就給消息數據傳遞帶來了極大的方便,方便在哪里了呢?這樣的話使用消
息郵箱傳遞任意數據類型變量都變的十分方便,用戶只需做一下強制類型轉換即可。
函數 isr_mbx_check
函數原型:
OS_RESULT isr_mbx_check (
OS_ID mailbox ); /*消息郵箱的 ID 標識*/
函數描述:
函數 isr_mbx_check 用來檢測消息郵箱剩余空間可以存儲的消息個數。 建議配合函數 isr_mbx_send 一起
使用。
第 1 個參數填寫消息郵箱的 ID 標識,即函數 os_mbx_declare 第一個參數。
函數返回消息郵箱剩余空間可以存儲的消息個數。
使用這個函數要注意以下問題:
1. 使用此函數前一定要調用函數 os_mbx_init 進行初始化。
2. 此函數只能在中斷服務程序中調用。
函數 isr_mbx_send
函數原型:
void isr_mbx_send (
OS_ID mailbox, /*消息郵箱的 ID 標識*/
void* message_ptr ); /* 消息指針,即數據的地址*/
函數描述:
函數 isr_mbx_send 用於向消息郵箱存放數據指針,或者說數據地址。 如果消息郵箱已經滿了,再次調用
此函數會造成消息郵箱溢出。 所以調用此函數前,強烈建議調用函數 isr_mbx_check 進行檢測,檢測是否
還有空間可用。
第 1 個參數填寫消息郵箱的 ID 標識,即函數 os_mbx_declare 第一個參數。
第 2 個參數填寫消息指針,即數據的地址。
使用這個函數要注意以下問題:
1. 使用此函數前一定要調用函數 os_mbx_init 進行初始化。
2. 為了防止消息郵箱溢出,強烈建議調用此函數前,先調用函數 isr_mbx_check 進行檢測,檢測是否還
有空間可用。
實戰演習場:

 

 

 

 

__task void AppTaskMsgPro(void)
{

    uint8_t *pMsg;
    OS_RESULT xResult;
    const uint16_t usMaxBlockTime = 5000; /* 延遲周期 */
    
    while(1)
    {
        xResult = os_mbx_wait(&mailbox, (void **)&pMsg, usMaxBlockTime);
        
        switch (xResult)
        {
            /* 無需等待接受到消息郵箱數據 */
            case OS_R_OK:
                printf("無需等待接受到消息郵箱數據,pMsg = %d\r\n", *pMsg);
                break;    

            /* 消息郵箱空,usMaxBlockTime等待時間從消息郵箱內獲得數據 */
            case OS_R_MBX:
                printf("消息郵箱空,usMaxBlockTime等待時間從消息郵箱內獲得數據,pMsg = %d\r\n", *pMsg);
                break;

            /* 超時 */
            case OS_R_TMO:
                macBeep_TOGGLE();
                break;
            
            /* 其他值不處理 */
            default:                     
                break;
        }    
    }

串口輸出:

 其他測試:(極其重要)

上面試一個發送任務,一個接收任務,現在我想一個發送,兩個接收任務,看RTX怎么反饋:

經過測試,在一個發送,兩個接收的時候,如果發送速度,慢於接收速度,即我每次發送之后,都立馬被接收了,此時,我的兩個接收任務,優先級高的會一直接收,優先級低的不能接收到消息,使用的是按鍵模擬,每次按鍵就發送消息,這樣接收的時候只會在高優先級的任務接收;

再測試,我把兩個接收任務的優先級設置成一樣,這樣的話,會出現交替接收我的發送消息:

而且注意,創建優先級相同的任務時,先創建的先接收,后創建的后接收:把上面圖片的任務創建順序改變之后如下(之前是LED先創建,MsgPro后創建):

    HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro,             /* 任務函數 */ 
                                          3,                         /* 任務優先級 */ 
                                          &AppTaskMsgProStk,         /* 任務棧 */
                                          sizeof(AppTaskMsgProStk)); /* 任務棧大小,單位字節數 */
        HandleTaskLED = os_tsk_create_user(AppTaskLED,              /* 任務函數 */ 
                                       3,                       /* 任務優先級 */ 
                                       &AppTaskLEDStk,          /* 任務棧 */
                                       sizeof(AppTaskLEDStk));  /* 任務棧大小,單位字節數 */

輸出如下,這樣就是先打印AppTaskMsgPro:

 再行測試:發送消息采取一直發送,不等待:

接收的時候,有兩個任務去接收,但是只有高優先級的任務能夠接收到:

如果把接收消息的兩個任務改成優先級相同的(前提是使能了時間輪轉片調度),就會交替出現:

Summary:

總結一下RTX的消息郵箱,其實就是一個存儲數據的結構,類似我們裸機編程的全局數組,但是消息郵箱支持FIFO。

我們項目中,如果需要交互類似數組這樣的數據,就選用消息郵箱。並且,盡量一個任務發送,另一個任務接收,單對單的,比較試用也更便於程序員自己的邏輯管理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM