一、概述

1、任務間的通信:
一個任務或者中斷服務程序有時候需要和另一個任務交流信息,這個就是消息傳遞的過程就叫做任務間通信。
任務間的消息傳遞可以通過2種途徑:一是通過全局變量,二是通過發布消息。
使用全局變量的時候每個任務或者中斷服務程序都必須保證其對全局變量的獨占訪問。消息也可以通過消息隊列作為中介發布給任務。
2、消息構成
- 指向數據的指針
- 數據長度
- 記錄消息發布時刻的時間戳。
3、消息隊列的使用
- 指針指向的可以是一塊數據區域或者甚至是一個函數。
- 消息的內容必須一直保持可見性,可見性是指代表消息的變量必須在接收消息的任務代碼范圍內有效。這是因為發布的數據采用的是指針傳遞,也就是引用傳遞,並不是值傳遞。也就是說,發布的消息本身並不產生內存拷貝,可以使用動態內存分配的方式來給消息分配一個內存塊,或者,也可以傳遞一個指向全局變量、全局數據結構、全局數組或者函數的指針。
- 消息隊列的相關操作,ISR只能發布消息,不能等待。
- 消息發布方發布消息時可以指定是先入先出模式(FIFO)還是設置為后入先出模式(LIFO),例如ISR發布的消息可以使用LIFO這樣可以繞開其他消息最先傳遞到任務。
二、函數接口
1、創建消息隊列
void OSQCreate (OS_Q *p_q,
CPU_CHAR *p_name,
OS_MSG_QTY max_qty,
OS_ERR *p_err)
p_ q:指向一個消息隊列,消息隊列的存儲空間必須由應用程序分配,我們采用如下的語句定義一個消息隊列。OS_ Q Msg_ Que;
p_ name:消息隊列的名字。
max_ qty:指定消息隊列的長度,必須大於0。當然,如果OS_ MSGs緩沖池中沒有足夠多的OS_ MSGs可用,那么發送消息將會失敗,並且返回相應的錯誤碼,指明當前沒有可用的OS_ MSGs
p_ err:保存調用此函數后返回的錯誤碼
2、等待消息隊列
當一個任務想要從消息隊列中接收一個消息的話就需要使用函數OSQPend()。當任務調用這個函數的時候,如果消息隊列中有至少一個消息時,這些消息就會返回給函數調用者。函數原型如下:
void *OSQPend (OS_Q *p_q,
OS_TICK timeout,
OS_OPT opt,
OS_MSG_SIZE *p_msg_size,
CPU_TS *p_ts,
OS_ERR *p_err)
p_q:指向一個消息隊列。
timeout:等待消息的超時時間,如果在指定的時間沒有接收到消息的話,任務就會被喚醒,接着運行。這個參數也可以設置為0,表示任務將一直等待下去,直到接收到消息。
opt:用來選擇是否使用阻塞模式,有兩個選項可以選擇
OS_OPT_PEND _BLOCKING:
如果沒有 任何消息存在的話就阻塞任務,一直等待,直到接收到消息。
OS_OPT_PEND_NON_BLOCKING:
如果消息隊列沒有任何消息的話任務就直接返回。
p_msg_ size: 指向一個變量用來表示接收到的消息長度(字節數)。
p ts:指向一個時間戳,表明什么時候接收到消息。如果這個指針被賦值為NULL的話,說明用戶沒有要求時間戳。
p_ err:用來保存調用此函數后返回的錯誤碼。
如果消息隊列中沒有任何消息,並且參數opt為OS OPT PEND NON BLOCKING時,那么調用OSQPend()函數的任務就會被掛起,直到接收到消息或者超時。如果有消息發送給消息隊列,但是同時有多個任務在等待這個消息,那么UCOSIII將恢復等待中的最高優先級的任務。
3、向消息隊列發送消息
可以通過函數OSQPostO向消息隊列發送消息,如果消息隊列是滿的,則函數OSQPost()就會立刻返回,並且返回一個特定的錯誤代碼,函數原型如下:
void OSQPost (OS_Q *p_q,
void *p_void,
OS_MSG_SIZE msg_size,
OS_OPT opt,
OS_ERR *p_err)
如果有多個任務在等待消息隊列的話,那么優先級最高的任務將獲得這個消息。如果等待消息的任務優先級比發送消息的任務優先級高,則系統會執行任務調度,等待消息的任務立即恢復運行,而發送消息的任務被掛起。可以通過opt設置消息隊列是FIFO還是LIFO.
如果有多個任務在等待消息隊列的消息,則OSQPost)函數可以設置僅將消息發送給等帶任務中優先級最高的任務(opt設置為OS_ OPT_ POST_ _FIF 或者OS_ _OPT_ POST_ LIFO),也可以將消息發送給所有等待的任務(opt設置為OS_ OPT_ _POST_ ALL)。如果opt 設置為OS_ _OPT_ POST_ NO_ SCHED,則在發送完消息后,會進行任務調度。
p_q;指向一個消息隊列。
p_void:指向實際發送的內容,p_ _void 是-一個執行void類型的指針,其具體含義由用戶程序的決定。
msg size:設定消息的大小,單位為字節數。
opt:用來選擇消息發送操作的類型,基本的類型可以有下面四種。
OS_OPT_POST_ ALL 將消息發送給所有等待該消息隊列的任務,需要和選項OS_OPT_ POST_FIFO 或者OS_OPT_ POST LIFO配合使用。
OS_OPT_POST_ FIFO 待發送消息保存在消息隊列的末尾
OS_OPT_POST_LIFO 待發送的消息保存在消息隊列的開頭
OS_ OPT_POST_NO_SCHED 禁止在本函數內執行任務調度。
我們可以使用上面四種基本類型來組合出其他幾種類型,如下:
OS_OPT_POST_FIFO + OS_OPT_POST_ALL
OS_OPT_POST_LIFO + OS_OPT_POST_ALL
OS_OPT_POST_FIFO + OS_OPT_POST_NO_SCHED
OS_OPT_POST_LIFO + OS_OPT_POST_NO_SCHED
OS_OPT_POST_FIFO + OS_OPT_POST_ALL + OS_OPT_POST_NO_SCHED
OS_OPT_POST_LIFO + OS_OPT_POST_ALL+ OS_OPT_POST_NO_SCHED
p_ err:用來保存調用此函數后返回的錯誤碼。
四、實例
任務一通過消息隊列發消息給任務二。
#include "sys.h" #include "delay.h" #include "usart.h" #include "led.h" #include "includes.h" //任務1控制塊 OS_TCB Task1_TCB; void task1(void *parg); CPU_STK task1_stk[128]; //任務1的任務堆棧,大小為128字,也就是512字節 //任務2控制塊 OS_TCB Task2_TCB; void task2(void *parg); CPU_STK task2_stk[128]; //任務2的任務堆棧,大小為128字,也就是512字節 OS_Q g_queue; //主函數 int main(void) { OS_ERR err; systick_init(); //時鍾初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中斷分組配置 usart_init(9600); //串口初始化 LED_Init(); //LED初始化 //OS初始化,它是第一個運行的函數,初始化各種的全局變量,例如中斷嵌套計數器、優先級、存儲器 OSInit(&err); //創建任務1 OSTaskCreate( (OS_TCB *)&Task1_TCB, //任務控制塊,等同於線程id (CPU_CHAR *)"Task1", //任務的名字,名字可以自定義的 (OS_TASK_PTR)task1, //任務函數,等同於線程函數 (void *)0, //傳遞參數,等同於線程的傳遞參數 (OS_PRIO)6, //任務的優先級6 (CPU_STK *)task1_stk, //任務堆棧基地址 (CPU_STK_SIZE)128/10, //任務堆棧深度限位,用到這個位置,任務不能再繼續使用 (CPU_STK_SIZE)128, //任務堆棧大小 (OS_MSG_QTY)0, //禁止任務消息隊列 (OS_TICK)0, //默認時間片長度 (void *)0, //不需要補充用戶存儲區 (OS_OPT)OS_OPT_TASK_NONE, //沒有任何選項 &err //返回的錯誤碼 ); if(err!=OS_ERR_NONE) { printf("task 1 create fail\r\n"); while(1); } //創建任務2 OSTaskCreate( (OS_TCB *)&Task2_TCB, //任務控制塊 (CPU_CHAR *)"Task2", //任務的名字 (OS_TASK_PTR)task2, //任務函數 (void *)0, //傳遞參數 (OS_PRIO)6, //任務的優先級7 (CPU_STK *)task2_stk, //任務堆棧基地址 (CPU_STK_SIZE)128/10, //任務堆棧深度限位,用到這個位置,任務不能再繼續使用 (CPU_STK_SIZE)128, //任務堆棧大小 (OS_MSG_QTY)0, //禁止任務消息隊列 (OS_TICK)0, //默認時間片長度 (void *)0, //不需要補充用戶存儲區 (OS_OPT)OS_OPT_TASK_NONE, //沒有任何選項 &err //返回的錯誤碼 ); //創建消息隊列,支持16條消息(實際上能夠存儲16個指針) OSQCreate(&g_queue,"g_queue",16,&err); //啟動OS,進行任務調度 OSStart(&err); printf(".......\r\n"); while(1); } void task1(void *parg) { OS_ERR err; char *p=NULL; OS_MSG_SIZE msg_size=0; printf("task1 is create ok\r\n"); while(1) { //等待消息隊列 //0:一直等待 //OS_OPT_PEND_BLOCKING 阻塞形式等待信號量,若等不了信號量,則讓出CPU使用權給其他任務 //不需要時間戳(時間標記):用於記錄等待信號量花了多長時間,默認寫NULL,不記錄 p = OSQPend(&g_queue,0,OS_OPT_PEND_BLOCKING ,&msg_size,NULL,&err); if(err != OS_ERR_NONE) { printf("OSQPend g_queue error code=%d\r\n",err); continue; } //以下就是臨界區代碼一定要趕緊完成,否則會影響到中斷延遲 printf("msg[%s] len[%d]\r\n",p,msg_size); memset(p,0,msg_size); //!!注意消息發送完之后,要清空消息指針 } } //給task1發送消息"hello task1" void task2(void *parg) { OS_ERR err; printf("task2 is create ok\r\n"); while(1) { OSQPost(&g_queue,"hell o task1",11,OS_OPT_POST_FIFO,&err); delay_ms(1000); } }