STM32mini使用UCOSII信号量和邮箱实现任务挂起和恢复
本文結構
- 1.UCOSII原理
- 2.UCOSII實驗代碼
1.UCOSII原理
UCOSII 是一個可以基于ROM 運行的、可裁減的、搶占式、實時多任務內核,具有高度可
移植性,特別適合于微處理器和控制器,是和很多商業操作系統性能相當的實時操作系統
(RTOS)。
UCOSII 是專門為計算機的嵌入式應用設計的, 絕大部分代碼是用C 語言編寫的。CPU 硬
件相關部分是用匯編語言編寫的、總量約200 行的匯編語言部分被壓縮到最低限度,為的是便
于移植到任何一種其它的CPU 上。用戶只要有標準的ANSI 的C 交叉編譯器,有匯編器、連
接器等軟件工具,就可以將UCOSII 嵌人到開發的產品中。UCOSII 具有執行效率高、占用空間
小、實時性能優良和可擴展性強等特點, 最小內核可編譯至 2KB 。UCOSII 已經移植到了幾
乎所有知名的CPU 上。
UCOSII體系結構如圖所示
UCOSII在版本2.80后,支持任務數提高到255個。
任務,其實就是一個死循環函數,該函數實現一定的功能,一個工程可以有很多這樣的任務(最多255個),UCOSII對這些任務進行調度管理,讓這些任務可以并發工作(并發的意思是:各任務輪流占用CPU,而不是同時占用,任何時候還是只有1個任務能夠占用CPU),這就是UCOSII最基本的功能。Ucos任務的一般格式為:
下面有幾個概念需要掌握
1.任務優先級
每個任務都有一個唯一的優先級,換言之,每個任務的優先級都不一樣。在UCOSII中,優先級高的任務比優先級低的任務有CPU優先使用權,只有高優先級的任務讓出使用權時(用的多的是函數OSTimeDlyHMSM();//調用任務延時函數,釋放cpu控制權),低優先級的任務才可以使用CPU。
2.任務堆棧
任務堆棧是存儲器中的一塊連續區域,每個任務都有自己的堆棧,主要是為了滿足任務切換和響應中斷時保存CPU寄存器中的內容。
3.任務控制塊
任務控制塊OS_TCB,用來記錄任務堆棧指針,任務當前狀態以及任務優先級等任務屬性。UCOSII的任何任務都是通過任務控制塊(TCB)來控制,一旦任務創建,任務控制塊OS_TCB就會被賦值。每個任務管理塊有3個最重要的參數:任務函數指針;任務堆棧指針;任務優先級。任務控制塊就是任務在系統里面的身份證(UCOSII通過優先級識別任務)。
4 .任務就緒表
任務就緒表,用來記錄系統中所有處于就緒狀態的任務。它是一個位圖,系統中每個任務都在這個位圖中占據一個進制位,該位置的狀態(1或者0)就表示任務是否處于就緒狀態。
5.任務調度
任務調度的作用一是在任務就緒表中查找優先級最高的就緒任務,二是實現任務的切換。比如說,當一個任務釋放cpu控制權后,進行一次任務調度,這個時候任務調度器首先要去任務就緒表查詢優先級最高的就緒任務,查到之后,進行一次任務切換,轉而去執行下一個任務。
下面是幾個關于任務的函數:
1)任務建立函數
一般使用OSTaskCreate
定義為
解釋如下
該函數包括4個參數:task:是指向任務代碼的指針;p_arg:是任務開始執行時,傳遞給任務參數的指針;ptos:是分配給任務的堆棧的棧頂指針;prio是分配給任務的優先級。
2)任務刪除函數
任務刪除,是把任務置于睡眠狀態。一般使用OSTaskDel
原型為
其中參數prio就是要刪除任務的優先級
特別注意:任務不能隨便刪除,必須在確保被刪除任務的資源被釋放的前提下才能刪除!
3)請求任務刪除函數
向被刪除任務發送刪除請求,實現任務釋放自身占用的資源并刪除。函數為OSTaskDelReq,原型如下
其中參數prio就是要刪除任務的優先級
4)優先級更改函數
函數為OSTaskChangePrio,其原型如下
其中參數oldprio是要任務的之前的優先級,newprio是更改之后的優先級。
5)任務掛起函數
任務掛起函數把任務的就緒標志刪除,做好任務掛起記錄,被掛起的任務,在解掛之后可以繼續運行。任務觀其函數為OsTaskSuspend
定義為
解釋如下
/* ********************************************************************************************************* * SUSPEND A TASK * * Description: This function is called to suspend a task. The task can be the calling task if the * priority passed to OSTaskSuspend() is the priority of the calling task or OS_PRIO_SELF. * * Arguments : prio is the priority of the task to suspend. If you specify OS_PRIO_SELF, the * calling task will suspend itself and rescheduling will occur. * * Returns : OS_ERR_NONE if the requested task is suspended * OS_ERR_TASK_SUSPEND_IDLE if you attempted to suspend the idle task which is not allowed. * OS_ERR_PRIO_INVALID if the priority you specify is higher that the maximum allowed * (i.e. >= OS_LOWEST_PRIO) or, you have not specified OS_PRIO_SELF. * OS_ERR_TASK_SUSPEND_PRIO if the task to suspend does not exist * OS_ERR_TASK_NOT_EXITS if the task is assigned to a Mutex PIP * * Note : You should use this function with great care. If you suspend a task that is waiting for * an event (i.e. a message, a semaphore, a queue ...) you will prevent this task from * running when the event arrives. ********************************************************************************************************* */6)任務恢復函數
有任務掛起函數,就有任務恢復函數,通過該函數將被掛起的任務恢復,讓調度器能夠重新調度該函數。函數為OSTaskResume
原型為
UCOSII信號量和郵箱
任務間的同步依賴于任務間的通信。在UCOSII 中,是使用信號量、郵箱(消息郵箱)和消息隊列這些被稱作事件的中間環節來實現任務之間的通信。使用事件控制塊來描述具體的信息,事件控制塊結構體
信號量
信號量是一類事件,它是為了給共享資源設立一個標志,該標志表示該共享資源的占用情況。這樣,當一個任務在訪問共享資源之前,就可以先對這個標志進行查詢,從而在了解資源被占用的情況之后,再來決定自己的行為。
信號量可以分為兩種:一種是二值型信號量(也稱為互斥信號量),另外一種是N 值信號量。
郵箱
在多任務操作系統中,常常需要在任務與任務之間通過傳遞“消息”的方式來進行通信。為此需要在內存中創建一個存儲空間作為該消息的緩沖區,稱為消息緩沖區,這樣在任務間傳遞數據(消息)的最簡單辦法就是傳遞消息緩沖區的指針。把用來傳遞消息緩沖區指針的數據結構叫做郵箱(消息郵箱)。
在UCOSII中,通過事件控制塊的OSEventPrt來傳遞消息緩沖區指針,同時使事件控制塊的成員OSEventType為常數OS_EVENT_TYPE_MBOX,則該事件控制塊就叫做消息郵箱。
下面看一下關于信號量的函數
1)創建信號量
函數為OSSemCreate
該函數返回值為已創建的信號量的指針,而參數cnt則是信號量計數器(OSEventCnt)的初始值。
2)請求信號量函數
函數為OSSemPend
其中,參數pevent是被請求信號量的指針,timeout為等待時限,err為錯誤信息。
為防止任務因得不到信號量而處于長期的等待狀態,函數OSSemPend允許用參數timeout設置一個等待時間的限制,當任務等待的時間超過timeout時可以結束等待狀態而 進入就緒狀態。如果參數timeout被設置為0,則表明任務的等待時間為無限長。
3)發送信號量函數
任務獲得信號量,并在訪問共享資源結束以后要釋放信號量,釋放信號量也叫做發送信號量,發送信號通過OSSemPost函數實現 。OSSemPost 函數在對信號量的計數器操作之前,首先要檢查是否還有等待該信號量的任務。如果沒有,就把信號量計數器OSEventCnt加一;如果有,則調用調度器OS_Sched( )去運行等待任務中優先級別最高的任務。函數OSSemPost的原型為:
其中,pevent為信號量指針,該函數在調用成功后,返回值為OS_ON_ERR,否則會根據具體錯誤返回OS_ERR_EVENT_TYPE、OS_SEM_OVF。
4)刪除信號量
應用程序如果不需要某個信號量,可以調用函數OSSemDel來刪除該信號量,該函數的原型為:
其中,pevent為要刪除的信號量指針,opt為刪除條件選項,err為錯誤信息。
下面是關于郵箱的函數
1)創建郵箱
創建郵箱通過函數OSMboxCreate實現,該函數原型為:
函數中的參數msg為消息的指針,函數的返回值為消息郵箱的指針。
調用函數OSMboxCreate需先定義msg的初始值。在一般的情況下,這個初始值為NULL;但也可以事先定義一個郵箱,然后把這個郵箱的指針作為參數傳遞到函數OSMboxCreate 中,使之一開始就指向一個郵箱。
2) 向郵箱發送消息函數
任務可以通過調用函數OSMboxPost向消息郵箱發送消息,這個函數的原型為:
其中pevent為消息郵箱的指針,msg為消息指針。
3) 請求郵箱函數
當一個任務請求郵箱時需要調用函數OSMboxPend,這個函數的主要作用就是查看郵箱指針OSEventPtr是否為NULL,如果不是NULL就把郵箱中的消息指針返回給調用函數的任務,同時用OS_NO_ERR通過函數的參數err通知任務獲取消息成功;如果郵箱指針OSEventPtr是NULL,則使任務進入等待狀態,并引發一次任務調度。
函數OSMboxPend的原型為:
其中pevent為請求郵箱指針,timeout為等待時限,err為錯誤信息。
4) 查詢郵箱狀態函數
任務可以通過調用函數OSMboxQuery查詢郵箱的當前狀態。該函數原型為:
其中pevent為消息郵箱指針,pdata為存放郵箱信息的結構。
5) 刪除郵箱函數
在郵箱不再使用的時候,我們可以通過調用函數OSMboxDel來刪除一個郵箱,該函數原型為:
其中pevent為消息郵箱指針,opt為刪除選項,err為錯誤信息。
2.UCOSII實驗代碼
下面是一個STM32mini板實例:
主要功能:程序初始時,LED0燈閃爍,表示任務1在運行;串口顯示表示任務2在運行。
通過信號量和郵箱,實現KEY0控制LED0任務(任務1)的掛起,KEY1實現串口顯示任務(任務2)的刪除,并且實現LED1燈的亮滅;WK_UP實現LED0任務(任務1)的恢復。
實驗結果:只有窗口顯示部分
main.c文件
#include "led.h" #include "delay.h" #include "sys.h" #include "usart.h" #include "includes.h" #include "key.h" #include "usart.h" //START 任務 //設置任務優先級 #define START_TASK_PRIO 10 ///開始任務的優先級為最低 //設置任務堆棧大小 #define START_STK_SIZE 128 //任務任務堆棧 OS_STK START_TASK_STK[START_STK_SIZE]; //任務函數 void start_task(void *pdata);//LED0任務 //設置任務優先級 #define LED0_TASK_PRIO 7 //設置任務堆棧大小 #define LED0_STK_SIZE 64 //任務堆棧 OS_STK LED0_TASK_STK[LED0_STK_SIZE]; //任務函數 void led0_task(void *pdata);//LED1任務 //設置任務優先級 #define LED1_TASK_PRIO 6 //設置任務堆棧大小 #define LED1_STK_SIZE 64 //任務堆棧 OS_STK LED1_TASK_STK[LED1_STK_SIZE]; //任務函數 void led1_task(void *pdata);//key傳遞函數 //設置任務優先級 #define KEY_TASK_PRIO 5 //設置任務堆棧大小 #define KEY_STK_SIZE 64 //任務堆棧 OS_STK KEY_TASK_STK[KEY_STK_SIZE]; //任務函數 void key_task(void *pdata);//按鍵掃描任務 //設置任務優先級 #define SCAN_TASK_PRIO 4 //設置任務堆棧大小 #define SCAN_STK_SIZE 64 //任務堆棧 OS_STK SCAN_TASK_STK[SCAN_STK_SIZE]; //任務函數 void scan_task(void *pdata); //串口顯示任務 #define FLOAT_TASK_PRIO 8 //設置任務堆棧大小 #define FLOAT_STK_SIZE 128 //任務堆棧 //如果任務中使用printf來打印浮點數據的話一點要8字節對齊 __align(8) OS_STK FLOAT_TASK_STK[FLOAT_STK_SIZE]; //任務函數 void float_task(void *pdata);OS_EVENT * msg_key; //按鍵郵箱時間塊指針 OS_EVENT * sem_led0; //LED0信號量指針 OS_EVENT * sem_led1; //LED1信號量指針 OS_EVENT * sem_print;//WK_UP信號量指針int main(void) {delay_init(); //延時初始化NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中斷分組配置uart_init(115200); //串口波特率設置LED_Init(); //LED初始化KEY_Init();OSInit(); //UCOS初始化OSTaskCreate(start_task,(void*)0,(OS_STK*)&START_TASK_STK[START_STK_SIZE-1],START_TASK_PRIO); //創建開始任務OSStart(); //開始任務 }//開始任務 void start_task(void *pdata) {OS_CPU_SR cpu_sr=0;pdata = pdata; msg_key=OSMboxCreate((void *)0);//創建消息郵箱sem_led0=OSSemCreate(0);sem_led1=OSSemCreate(0);//創建信號量sem_print=OSSemCreate(0);OS_ENTER_CRITICAL(); //進入臨界區(無法被中斷打斷) OSTaskCreate(led0_task,(void *)0,(OS_STK*)&LED0_TASK_STK[LED0_STK_SIZE-1],LED0_TASK_PRIO); OSTaskCreate(led1_task,(void *)0,(OS_STK*)&LED1_TASK_STK[LED1_STK_SIZE-1],LED1_TASK_PRIO);OSTaskCreate(key_task,(void *)0,(OS_STK*)&KEY_TASK_STK[KEY_STK_SIZE-1],KEY_TASK_PRIO);OSTaskCreate(scan_task,(void *)0,(OS_STK*)&SCAN_TASK_STK[SCAN_STK_SIZE-1],SCAN_TASK_PRIO);OSTaskCreate(float_task,(void *)0,(OS_STK*)&FLOAT_TASK_STK[FLOAT_STK_SIZE],FLOAT_TASK_PRIO);OSTaskSuspend(START_TASK_PRIO); //掛起起始任務.OS_EXIT_CRITICAL(); //退出臨界區(可以被中斷打斷) }//LED0任務 void led0_task(void *pdata) {//u8 err; while(1){// OSSemPend(sem_led0,0,&err);LED0=0;delay_ms(500);LED0=1;delay_ms(500);}; }//LED1任務 void led1_task(void *pdata) { u8 err;while(1){OSSemPend(sem_led1,0,&err);LED1=0;delay_ms(500);LED1=1;delay_ms(500);} }//按鍵掃描任務 void key_task(void *pdata) {int key=0;u8 err;while(1){key=(int)OSMboxPend(msg_key,10,&err);switch(key){case KEY0_PRES://發送信號量0OSSemPost(sem_led0);OSTaskSuspend(LED0_TASK_PRIO);//led0任務掛起break;case KEY1_PRES://發送信號量1OSSemPost(sem_led1);OSTaskDel(FLOAT_TASK_PRIO);//刪除串口顯示任務break;case WKUP_PRES://OSSemPost(sem_print);// OSSemPost(sem_led1);OSTaskResume(LED0_TASK_PRIO);//led1任務掛起后恢復break;}} }//串口顯示任務 void float_task(void *pdata) {//u8 err;OS_CPU_SR cpu_sr=0;while(1){// OSSemPend(sem_print,0,&err);OS_ENTER_CRITICAL(); //進入臨界區(關閉中斷)printf("串口顯示程序正在運行:\r\n\n"); //串口打印結果printf("這是任務2:\r\n\n"); //串口打印結果OS_EXIT_CRITICAL(); //退出臨界區(開中斷)delay_ms(500);} }//按鍵掃描任務 void scan_task(void *pdata) {u8 key;while(1){key=KEY_Scan(0);if(key)OSMboxPost(msg_key,(void*)key);//發送消息delay_ms(10);} }key.c文件
#include "key.h" #include "delay.h"//按鍵初始化函數 //PA0.15和PC5 設置成輸入 void KEY_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOC,ENABLE);//使能PORTA,PORTC時鐘GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);//關閉jtag,使能SWD,可以用SWD模式調試GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;//PA15GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //設置成上拉輸入GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA15GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;//PC5GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //設置成上拉輸入GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIOC5GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//PA0GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0設置成輸入,默認下拉 GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.0} //按鍵處理函數 //返回按鍵值 //mode:0,不支持連續按;1,支持連續按; //返回值: //0,沒有任何按鍵按下 //KEY0_PRES,KEY0按下 //KEY1_PRES,KEY1按下 //WKUP_PRES,WK_UP按下 //注意此函數有響應優先級,KEY0>KEY1>WK_UP!! u8 KEY_Scan(u8 mode) { static u8 key_up=1;//按鍵按松開標志if(mode)key_up=1; //支持連按 if(key_up&&(KEY0==0||KEY1==0||WK_UP==1)){delay_ms(10);//去抖動 key_up=0;if(KEY0==0)return KEY0_PRES;else if(KEY1==0)return KEY1_PRES;else if(WK_UP==1)return WKUP_PRES; }else if(KEY0==1&&KEY1==1&&WK_UP==0)key_up=1; return 0;// 無按鍵按下 }led.c文件
#include "led.h"//初始化PA8和PD2為輸出口.并使能這兩個口的時鐘 //LED IO初始化 void LED_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOD, ENABLE); //使能PA,PD端口時鐘GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8; //LED0-->PA.8 端口配置GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽輸出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度為50MHzGPIO_Init(GPIOA, &GPIO_InitStructure); //根據設定參數初始化GPIOA.8GPIO_SetBits(GPIOA,GPIO_Pin_8); //PA.8 輸出高GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //LED1-->PD.2 端口配置, 推挽輸出GPIO_Init(GPIOD, &GPIO_InitStructure); //推挽輸出 ,IO口速度為50MHzGPIO_SetBits(GPIOD,GPIO_Pin_2); //PD.2 輸出高 }工程目錄
參考博客STM32上使用UCOSII–信號量和郵箱
總結
以上是生活随笔為你收集整理的STM32mini使用UCOSII信号量和邮箱实现任务挂起和恢复的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: STM32迷你板UCOSII系统移植
- 下一篇: SpringBoot面向切面编程-用AO