任務間通信 5.4.1 信號量 信號量是用來保護共享資源用的,表示共享資源的個數。共享資源被占用一個,信號量的指會減1,共享資源被釋放一個,信號量的值會加1。 (理解:USB口的占用與釋放,假設一個電腦有3個USB,插入一個USB設備,電腦的USB資源會減少1,此時電腦的USB資源還有2;拔出USB設備,電腦的USB資源會增加1,此時電腦的USB資源有3個USB) 其實信號量的本質就是一個操作系統計數器(0~65535),實際用于實現任務間同步執行。 需要掌握的函數如下: 
實驗代碼如下:
- #include "main.h"
- #include "includes.h"
-
- /** 任務0 **/
- #define TASK0_PRI 8 //任務優先級
- #define TASK0_STK_SIZE 256 //任務棧大小
- OS_STK stack0[TASK0_STK_SIZE]; //數組作為任務堆棧
- void Task0 (void *p_arg); //函數聲明
-
- /** 任務1 **/
- #define TASK1_PRI 9 //任務優先級
- #define TASK1_STK_SIZE 256 //任務棧大小
- OS_STK stack1[TASK1_STK_SIZE]; //數組作為任務堆棧
- void Task1 (void *p_arg); //函數聲明
-
- /** 任務2 **/
- #define TASK2_PRI 10 //任務優先級
- #define TASK2_STK_SIZE 256 //任務棧大小
- OS_STK stack2[TASK2_STK_SIZE]; //數組作為任務堆棧
- void Task2 (void *p_arg); //函數聲明
-
- OS_EVENT *sem;//全局變量,創建信號量、等待信號量、發布信號量都需要用到該指針。
-
- int main(void)
- {
- OSSysTickInit();//滴答定時器初始化
- USART1_Init(115200);//初始化串口,用于調試
-
- OSInit(); //操作系統初始化
-
- sem=OSSemCreate(2);//初始化--默認創建2個信號量
- OSTaskCreate (Task0,NULL,&stack0[TASK0_STK_SIZE-1],TASK0_PRI);//創建一個任務,任務名:Task0
- OSTaskCreate (Task1,NULL,&stack1[TASK1_STK_SIZE-1],TASK1_PRI);//創建一個任務,任務名:Task1
- OSTaskCreate (Task2,NULL,&stack2[TASK2_STK_SIZE-1],TASK2_PRI);//創建一個任務,任務名:Task2
-
- OSStart(); //啟動操作系統
- }
-
- void Task0 (void *p_arg) //實現任務Task0
- {
- u8 k=0;//給個默認值0,表示無鍵狀態
- KEY_Init();//按鍵初始化,放在對應任務中
- while(1)
- {
- k=Key_Scanf(0);//按鍵掃描
- switch (k)
- {
- case KEY_ONE: //按鍵1按下,發布一個信號量
- OSSemPost(sem);//發送信號量
- break;
- default:
- break;
- }
- OSTimeDly(1);//高優先級釋放CPU,延遲不可太長,延時太長會影響按鍵的靈敏度。
- }
- }
-
- void Task1 (void *p_arg) //實現任務Task1
- {
- while(1)
- {
- //等待一個信號量
- OSSemPend (sem,//信號量
- 0,//超時時間
- 0);//錯誤類型--沒有錯誤
- printf("任務1\r\n");
- OSTimeDly(100);//5ms一次滴答,100*5=500ms,打印一次
- }
- }
-
- void Task2 (void *p_arg) //實現任務Task2
- {
- while(1)
- {
- //等待一個信號量
- OSSemPend (sem,//信號量
- 0,//超時時間
- 0);//錯誤類型--沒有錯誤
- printf("任務2\r\n");
- OSTimeDly(100);//5ms一次滴答,100*5=500ms,打印一次
- }
- }
-
復制代碼
因為創建了兩個信號量,按下復位按鍵,同時打印出任務1、任務2。 
按下按鍵1,打印出任務1,因為任務1的優先級比較高。 
快速按下按鍵1,能打印出任務1,和任務2,因為他們都在等信號量。 
5.4.2 互斥信號量 用來保護共享資源,但是這個共享資源只有一個。兩個任務同時操作一個硬件,這時候需要加互斥信號量保護。 (理解:電話亭的使用,假設電話亭里只有一個電話,有3個人想打電話,需要排隊,還需要等待,等待電話亭里面沒有人,排在前面的人就能進入電話亭打電話了) 其實互斥信號量的本質就是一個操作系統計數器(0-1)。 注意:互斥信號量中需要有一個空閑的優先級作為優先級反轉用,該優先級必須比所有能夠獲得該互斥信號量的優先級還高。 理解:假設能獲得該互斥信號量的所有任務的優先級分別為:4、10、11、13,則該空閑優先級的取值(0~3);在如,假設能獲得該互斥信號量的所有任務的優先級分別為:8、10、11、13,則該空閑優先級的取值(0~7)。 需要掌握的函數如下: 
實驗代碼如下:
- #include "main.h"
- #include "includes.h"
-
- /** 任務0 **/
- #define TASK0_PRI 8 //任務優先級
- #define TASK0_STK_SIZE 256 //任務棧大小
- OS_STK stack0[TASK0_STK_SIZE]; //數組作為任務堆棧
- void Task0 (void *p_arg); //函數聲明
-
- /** 任務1 **/
- #define TASK1_PRI 9 //任務優先級
- #define TASK1_STK_SIZE 256 //任務棧大小
- OS_STK stack1[TASK1_STK_SIZE]; //數組作為任務堆棧
- void Task1 (void *p_arg); //函數聲明
-
- OS_EVENT *mutex;//全局變量,創建信號量、等待信號量、發布信號量都需要用到該指針。
-
- int main(void)
- {
- OSSysTickInit();//滴答定時器初始化
- USART1_Init(115200);//初始化串口,用于調試
-
- OSInit(); //操作系統初始化
-
- //創建互斥信號量
- mutex=OSMutexCreate (5,//空閑優先級
- 0);//錯誤類型
- OSTaskCreate (Task0,NULL,&stack0[TASK0_STK_SIZE-1],TASK0_PRI);//創建一個任務,任務名:Task0
- OSTaskCreate (Task1,NULL,&stack1[TASK1_STK_SIZE-1],TASK1_PRI);//創建一個任務,任務名:Task1
-
- OSStart(); //啟動操作系統
- }
-
- void Task0 (void *p_arg) //實現任務Task0
- {
- while(1)
- {
- //獲取互斥信號量
- OSMutexPend (mutex,//互斥信號量
- 0,//超時時間
- 0);//錯誤類型
- for(int i=0;i<5;i++)
- {
- printf("任務0--%d\r\n",i);//
- OSTimeDly(100);//
- }
- //釋放互斥信號量
- OSMutexPost (mutex);
- }
- }
-
- void Task1 (void *p_arg) //實現任務Task1
- {
- while(1)
- {
- //獲取互斥信號量
- OSMutexPend (mutex,//互斥信號量
- 0,//超時時間
- 0);//錯誤類型
- for(int i=0;i<5;i++)
- {
- printf("任務1--%d\r\n",i);//
- OSTimeDly(100);//
- }
- //釋放互斥信號量
- OSMutexPost (mutex);
- }
- }
-
復制代碼
燒錄代碼,結果如下圖: 
實驗表明,假設多個任務在訪問同一資源,只有等正在訪問的任務使用完并釋放資源,下一個任務才能訪問使用。 假設不加互斥信號量進行互斥訪問,代碼如下, 
結果如下: 
5.4.3 消息郵箱 用于任務與任務之間交換數據(任務與任務之間的通信)。消息郵箱只能存放一則消息,消息的內容長短不限制。 需要掌握的函數: 
實驗代碼:
- #include "main.h"
- #include "includes.h"
-
- /** 任務0 **/
- #define TASK0_PRI 8 //任務優先級
- #define TASK0_STK_SIZE 256 //任務棧大小
- OS_STK stack0[TASK0_STK_SIZE]; //數組作為任務堆棧
- void SendTask (void *p_arg); //函數聲明
-
- /** 任務1 **/
- #define TASK1_PRI 9 //任務優先級
- #define TASK1_STK_SIZE 256 //任務棧大小
- OS_STK stack1[TASK1_STK_SIZE]; //數組作為任務堆棧
- void ReceiveTask (void *p_arg); //函數聲明
-
- OS_EVENT *mbox;//全局變量,創建郵箱、發送消息、接收消息,都需要用到該指針。
-
- int main(void)
- {
- OSSysTickInit();//滴答定時器初始化
- USART1_Init(115200);//初始化串口,用于調試
-
- OSInit(); //操作系統初始化
- //創建一個郵箱
- mbox=OSMboxCreate (NULL);//初始化消息的地址:NULL
-
- OSTaskCreate (SendTask,NULL,&stack0[TASK0_STK_SIZE-1],TASK0_PRI);//創建發送任務
- OSTaskCreate (ReceiveTask,NULL,&stack1[TASK1_STK_SIZE-1],TASK1_PRI);//創建接收任務
-
- OSStart(); //啟動操作系統
- }
-
- void SendTask (void *p_arg) //發送任務
- {
- u8 k=0;//給個默認值0,表示無鍵狀態
- KEY_Init();//按鍵初始化,放在對應任務中
- while(1)
- {
- k=Key_Scanf(0);//按鍵掃描
- switch (k)
- {
- case KEY_ONE: //按鍵1按下,發布一則消息
- OSMboxPost (mbox,//郵箱
- "hello 51黑");//郵箱內容
- break;
- case KEY_TWO: //按鍵2按下,發布一則消息
-
- OSMboxPost (mbox,//郵箱
- "hello world");//郵箱內容
- OSMboxPost (mbox,//郵箱
- "hello xixi");//郵箱內容
- OSMboxPost (mbox,//郵箱
- "hello haha");//郵箱內容
- break;
- default:
- break;
- }
- OSTimeDly(1);//釋放CPU使用權
- }
- }
-
- void ReceiveTask (void *p_arg) //接收任務
- {
- u8 *str;
- while(1)
- {
- //接收一條消息
- str=OSMboxPend (mbox,//郵箱地址
- 0,//死等
- 0);//發送成功
- printf("接收到的消息:%s\r\n",str);//開始默認打印三次,因為默認開始創建3個信號量。
- OSTimeDly(1);//釋放CPU使用權
- }
- }
-
復制代碼
實驗結果如下: 按下按鍵1: 
按下按鍵2: 
原因是接收方接收不過來了,造成了數據丟失。這時需要引入消息隊列。 5.4.4 消息隊列 消息郵箱只能發送一則消息,獲取消息的地方如果處理比較慢就會丟失消息。消息隊列能存儲一隊消息,能很好的避免接收方處理能力弱而丟失消息的問題。隊列是一種數據結構,遵循先進先出原則。 
需要掌握的函數: 
實驗代碼:
- #include "main.h"
- #include "includes.h"
-
- /** 任務0 **/
- #define TASK0_PRI 8 //任務優先級
- #define TASK0_STK_SIZE 256 //任務棧大小
- OS_STK stack0[TASK0_STK_SIZE]; //數組作為任務堆棧
- void SendTask (void *p_arg); //函數聲明
-
- /** 任務1 **/
- #define TASK1_PRI 9 //任務優先級
- #define TASK1_STK_SIZE 256 //任務棧大小
- OS_STK stack1[TASK1_STK_SIZE]; //數組作為任務堆棧
- void ReceiveTask (void *p_arg); //函數聲明
-
- OS_EVENT *q;//全局變量,創建隊列、發送消息、接收消息,都需要用到該指針。
-
-
- void *queue[10];//隊列
-
- int main(void)
- {
- OSSysTickInit();//滴答定時器初始化
- USART1_Init(115200);//初始化串口,用于調試
-
- OSInit(); //操作系統初始化
-
- //創建一個隊列
- q=OSQCreate(queue,//隊列
- 10);//隊列的大小
-
- OSTaskCreate (SendTask,NULL,&stack0[TASK0_STK_SIZE-1],TASK0_PRI);//創建發送任務
- OSTaskCreate (ReceiveTask,NULL,&stack1[TASK1_STK_SIZE-1],TASK1_PRI);//創建接收任務
-
- OSStart(); //啟動操作系統
- }
-
- void SendTask (void *p_arg) //發送任務
- {
- u8 k=0;//給個默認值0,表示無鍵狀態
- KEY_Init();//按鍵初始化,放在對應任務中
- while(1)
- {
- k=Key_Scanf(0);//按鍵掃描
- switch (k)
- {
- case KEY_ONE: //按鍵1按下,發布一則消息
- OSQPost (q,//隊列地址
- "lele");//需要發送的內容
- OSQPost (q,//隊列地址
- "學習");//需要發送的內容
- OSQPost (q,//隊列地址
- "不可能");//需要發送的內容
- break;
- case KEY_TWO: //按鍵2按下,發布一則消息
- OSQPost (q,//隊列地址
- "哈哈");//需要發送的內容
- break;
- default:
- break;
- }
- OSTimeDly(1);//釋放CPU使用權
- }
- }
-
- void ReceiveTask (void *p_arg) //接收任務
- {
- char *strs;
- while(1)
- {
- //接收一條消息
- strs=OSQPend(q,//隊列
- 0,//死等
- 0);//錯誤類型
- printf("接收到的消息:%s\r\n",strs);//開始默認打印三次,因為默認開始創建3個信號量。
- OSTimeDly(1);//釋放CPU使用權
- }
- }
復制代碼
實驗結果如下: 
發送三條消息,接收到三條消息,沒有數據丟失。 5.4.5 補充 信號量:就理解成有多個電話的電話亭,這些電話是共享資源,當有很多人使用時,需要排隊(優先級),需要等待(等待信號量),當電話亭的電話空閑時(有信號量),就可以讓排在前面的人使用,依次使用。 互斥信號量:和信號量基本一致,理解成只用一個電話的電話亭,多個用戶要互斥使用這個電話。 消息郵箱:發送一條消息。如果消息發送太快,接收方接收不過來,會造成數據丟失。 消息隊列:發送多條消息。實現原理是把多條消息存放在隊列中。 最重要的最重要的是: - 概念的理解,領會
- 代碼的實現、代碼的流程
- 多實踐與思考
5.5 其他補充 5.5.1 延時函數 這里的延時與STM32的延遲有不同的含義,對于STM32F407,系統時鐘為21M,即21 000 000 次脈沖為1秒鐘=21 000 000個滴答為1秒鐘,UCOS的延時規定,200個脈沖為1秒鐘=200個滴答為1秒鐘,所以UCOS的一個滴答為5ms。 

常用第一個函數,第二函數用來延時,會有點誤差,因為任務調度會消耗一點時間。 為什么使用第一個函數,能發生任務調度?看代碼如下: 
因為函數的實現中有任務調度函數。 實際的任務調度代碼源頭在哪?進入看看 

在點擊進入發現。進不了了。實際這個函數由匯編代碼實現 

5.5.2 軟件定時器 實現UCOS軟件定時器需要注意兩點: - 打開定時器代碼,會發現創建定時器代碼都是灰色,需要修改一個宏


第二,定義一個優先級,編譯你就會發現這個優先級了,注意優先級不能設置跟其他任務的有效一樣。 


編寫代碼驗證,編寫如下代碼: #include "main.h"
#include "includes.h"
OS_TMR *tmr;//定時器。
void MyCallback (OS_TMR *ptmr, void *p_arg);//回調函數
int main(void)
{
OSSysTickInit();//滴答定時器初始化
USART1_Init(115200);//初始化串口,用于調試
OSInit(); //操作系統初始化
//創建定時器
tmr=OSTmrCreate (50,//第一次使用,規定10個滴答為1秒鐘--所以第一次定時5秒鐘
10,//第二次以后使用--定時1秒鐘
OS_TMR_OPT_PERIODIC,//循環模式
(void *)MyCallback,//回調函數
0,//回調函數參數
0,//定時器名字
0);//錯誤類型
//啟動定時器
OSTmrStart (tmr,//要啟動的定時器
0);//錯誤類型--成功
OSStart();//啟動操作系統
}
void MyCallback (OS_TMR *ptmr, void *p_arg)
{
printf("定時器創建成功\r\n");
} 實驗結果如下: 
定時器,一次滴答多少時間? 
5.5.3 其他函數 
給調度器上鎖與解鎖,一般用于初始化任務。 臨界區:是被保護的區域,一般不允許被中斷,所以進入之前需要關閉中斷,出來時,要打開中斷功能。
全部資料51hei下載地址:
程序.7z
(373.07 KB, 下載次數: 11)
2020-6-30 18:18 上傳
點擊文件名下載附件
下載積分: 黑幣 -5
UCOS之任務間通信、軟件定時器補充.docx
(4.21 MB, 下載次數: 11)
2020-6-29 09:18 上傳
點擊文件名下載附件
下載積分: 黑幣 -5
|