嵌入式軟件架構(gòu)設(shè)計-任務(wù)調(diào)度
掃描二維碼
隨時隨地手機看文章
1、前言
在嵌入式MCU軟件開發(fā)過程中,程序任務(wù)調(diào)度架構(gòu)的搭建尤為重要,直接關(guān)系到該程序能支持多少功能(隨著功能越多系統(tǒng)響應(yīng)能力越弱,好的任務(wù)調(diào)度架構(gòu)能夠在保持相同的系統(tǒng)響應(yīng)能力前提下支持更多的功能),下面介紹三種常用的程序任務(wù)調(diào)度框架設(shè)計方案:
-
前后臺順序執(zhí)行法
-
時間片論法
-
操作系統(tǒng)
2、程序框架設(shè)計
前后臺順序執(zhí)行法
這是初學(xué)者們常用的程序框架設(shè)計方案,不用考慮太多東西,代碼簡單,或者對系統(tǒng)的整體實時性和并發(fā)性要求不高;初始化后通過 while(1){ } 或 for(;;){ } 循環(huán)不斷調(diào)用自己編寫完成的函數(shù),也基本不考慮每個函數(shù)執(zhí)行所需要的時間,大部分情況下函數(shù)中或多或少都存在毫秒級別的延時等待。
優(yōu)點:對于初學(xué)者來說,這是最容易也是最直觀的程序架構(gòu),邏輯簡單明了,適用于邏輯簡單,復(fù)雜度比較低的軟件開發(fā)。
缺點:實時性低,由于每個函數(shù)或多或少存在毫秒級別的延時,即使是1ms,也會造成其他函數(shù)間隔執(zhí)行時間的不同,雖然可通過定時器中斷的方式,但是前提是中斷執(zhí)行函數(shù)花的時間必須短。當(dāng)程序邏輯復(fù)雜度提升時,會導(dǎo)致后來維護人員的大腦混亂,很難理清楚該程序的運行狀態(tài)。
以下是我在學(xué)校期間做的寢室防盜系統(tǒng)的主函數(shù)代碼(當(dāng)時也存在部分BUG,沒有解決?,F(xiàn)在再看,其實很多問題,而且比較嚴重,比如中斷服務(wù)函數(shù)內(nèi)竟然有3000ms延時,這太可怕了,還有串口發(fā)送等等;由于實時性要求不算太高,因此主函數(shù)中的毫秒級別延時對系統(tǒng)運行沒有多大影響,當(dāng)然除BUG外;若是后期需要維護,那就是一個大工程,還不如推翻重寫):
int main(void) { u8 temperature; u8 humidity; int a; delay_init(); uart2_Init(9600); TIM3_Int_Init(4999,7199); ds1302_init(); while(DHT11_Init()); //DHT11初始化 a1602_init(); lcd12864_INIT(); LcdInit(); while(1) { for(a=0;a<11;a++) { num[a+3]=At24c02Read(a+2)-208; delay_us(10); } for(a=0;a<6;a++) { shuru[a]=At24c02Read(a+13)-208; delay_us(10); } delay_ms(10); RED_Scan(); Ds1302ReadTime(); //讀取ds1302的日期時間 shi=At24c02Read(0); //讀取鬧鐘保存的數(shù)據(jù) delay_ms(10); fen=At24c02Read(1); //讀取鬧鐘保存的數(shù)據(jù) usart2_scan(); //藍牙數(shù)據(jù)掃描 usart2_bian(); //藍牙處理數(shù)據(jù) nao_scan(); k++; if(k<20) { if(k==1) LcdWriteCom(0x01); //清屏 LcdDisplay(); //顯示日期時間 } if(RED==0) RED_Scan(); if(k>=20&&k<30) { if(k==20) LcdWriteCom(0x01); //清屏 Lcddisplay(); //顯示溫濕度 LcdWriteCom(0x80+6); DHT11_Read_Data(&temperature,&humidity); //讀取溫濕度值 Temp=temperature;Humi=humidity; LcdWriteData('0'+temperature/10); LcdWriteData('0'+temperature%10); LcdWriteCom(0x80+0X40+6); LcdWriteData('0'+humidity/10); LcdWriteData('0'+humidity%10); } if(k==30) k=0; lcd12864(); //顯示防盜鬧鐘狀態(tài) } } //定時器3中斷服務(wù)程序 void TIM3_IRQHandler(void) //TIM3中斷 { int i; if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //檢查TIM3更新中斷發(fā)生與否 { TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIMx更新中斷標志 if(key1==1&&FEN-fen==0&&SHI-shi==0) //時間一到鬧鐘響起 { f=1; } else { f=0; } if(USART_RX_BUF[0]=='R'&&USART_RX_BUF[1]=='I'&&USART_RX_BUF[2]=='N'&&USART_RX_BUF[3]=='G') { key0=1; for(i=0;i<17;i++) { USART_SendData(USART1, num[i]);//向串口1發(fā)送數(shù)據(jù) while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待發(fā)送結(jié)束 USART_RX_STA=0; } delay_ms(3000); for(i=0;i<3;i++) { USART_SendData(USART1, num1[i]);//向串口1發(fā)送數(shù)據(jù) while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待發(fā)送結(jié)束 USART_RX_STA=0; } } } }
時間片論法
介于“前后臺順序執(zhí)行法”和“操作系統(tǒng)”之間的一種程序架構(gòu)設(shè)計方案。該設(shè)計方案需能幫助嵌入式軟件開發(fā)者更上一層樓,在嵌入式軟件開發(fā)過程中,若遇到以下幾點,那么該設(shè)計方案可以說是最優(yōu)選擇,適用于程序較復(fù)雜的嵌入式系統(tǒng);
目前的需求設(shè)計需要完全沒有必要上操作系統(tǒng)
任務(wù)函數(shù)無需時刻執(zhí)行,存在間隔時間(比如按鍵,一般情況下,都需要軟件防抖,初學(xué)者的做法通常是延時10ms左右再去判斷,但10ms極大浪費了CPU的資源,在這段時間內(nèi)CPU完全可以處理很多其他事情)
實時性有一定的要求
該設(shè)計方案需要使用一個定時器,一般情況下定時1ms即可(定時時間可隨意定,但中斷過于頻繁效率就低,中斷太長,實時性差),因此需要考慮到每個任務(wù)函數(shù)的執(zhí)行時間,建議不能超過1ms(能通過程序優(yōu)化縮短執(zhí)行時間則最好優(yōu)化,如果不能優(yōu)化的,則必須保證該任務(wù)的執(zhí)行周期必須遠大于任務(wù)所執(zhí)行的耗時時間),同時要求主循環(huán)或任務(wù)函數(shù)中不能存在毫秒級別的延時。
如何確定每個函數(shù)的任務(wù)周期呢?根據(jù)任務(wù)的耗時和效果決定、如按鍵掃描任務(wù)周期為 10ms(為了提高響應(yīng)),指示燈控制任務(wù)周期為 100ms(通常情況下最高100ms的閃爍頻率正好,特殊需求除外),LCD/OLED 顯示周期為 100ms(通過這種通過SPI/IIC等接口的方式耗時大約在 1~10ms,甚至更長,所以任務(wù)周期必須遠大于耗時,同時為了滿足人眼所能接受的刷屏效果,也不能太長,100ms 的任務(wù)周期比較合適)等
以下介紹兩種不同的實現(xiàn)方案,分別針對無函數(shù)指針概念的朋友和想進一步學(xué)習(xí)的朋友。
① 無函數(shù)指針的設(shè)計方式
-
首先定義計時標志變量,以1毫秒為時間片,在定時器中斷函數(shù)中累計計時,同時將對應(yīng)時間標識置1 。
void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3,TIM_IT_Update) == SET) { sg_1msTic++; sg_1msTic % 1 == 0 ? TIM_1msFlag = 1 : 0; sg_1msTic % 10 == 0 ? TIM_10msFlag = 1 : 0; sg_1msTic % 20 == 0 ? TIM_20msFlag = 1 : 0; sg_1msTic % 100 == 0 ? TIM_100msFlag = 1 : 0; sg_1msTic % 500 == 0 ? TIM_500msFlag = 1 : 0; sg_1msTic % 1000 == 0 ? (TIM_1secFlag = 1, sg_1msTic = 0) : 0; } TIM_ClearITPendingBit(TIM3,TIM_IT_Update); }
-
然后在主函數(shù)循環(huán)中判斷時間標識,標識被置1則代表時間已到,可以執(zhí)行相應(yīng)的函數(shù),全部執(zhí)行完畢后將標識置0 ,等待下次觸發(fā)。
int main(void) { System_Init(); while (1) { if (TIM_1msFlag) { CAN_CommTask();// CAN通信任務(wù) TIM_1msFlag = 0; } if (TIM_10msFlag) { KEY_ScanTask();// 按鍵掃描任務(wù) Hmi_Task();// 人機交互任務(wù) TIM_10msFlag = 0; } if (TIM_100msFlag) { LED_CtrlTask();// 指示燈任務(wù) TIM_100msFlag = 0; } if (TIM_1secFlag) { WDog_Task();// 喂狗任務(wù) TIM_1secFlag = 0; } } }
② 含函數(shù)指針的設(shè)計方式
-
定義含有函數(shù)指針的結(jié)構(gòu)體,用來指向需要周期執(zhí)行的函數(shù)。
typedef struct{ uint8 m_runFlag;/*!< 程序運行標記:0-不運行,1運行 */ uint16 m_timer;/*!< 計時器 */ uint16 m_itvTime;/*!< 任務(wù)運行間隔時間 */ void (*m_pTaskHook)(void);/*!< 要運行的任務(wù)函數(shù) */ } TASK_InfoType;
-
實現(xiàn)函數(shù)指針任務(wù)基本調(diào)度功能:任務(wù)調(diào)度管理和任務(wù)調(diào)度執(zhí)行 。
/** * @brief 任務(wù)函數(shù)運行調(diào)度管理. */ void TASK_Remarks(void) { uint8 i; for (i = 0; i < TASKS_MAX; i++) { if (sg_tTaskInfo[i].m_timer) { sg_tTaskInfo[i].m_timer--; if (0 == sg_tTaskInfo[i].m_timer) { sg_tTaskInfo[i].m_timer = sg_tTaskInfo[i].m_itvTime; sg_tTaskInfo[i].m_runFlag = 1; } } } } /** * @brief 任務(wù)函數(shù)調(diào)度執(zhí)行. */ void TASK_Process(void) { uint8 i; for (i = 0; i < TASKS_MAX; i++) { if (sg_tTaskInfo[i].m_runFlag) { sg_tTaskInfo[i].m_pTaskHook(); // 運行任務(wù) sg_tTaskInfo[i].m_runFlag = 0; // 標志清0 } } }
-
分別在定時器中斷函數(shù)和主函數(shù)中調(diào)用任務(wù)調(diào)度管理和任務(wù)調(diào)度執(zhí)行 。
int main(void) { System_Init(); while (1) { TASK_Process(); } } /** * @brief 定時器3中斷服務(wù)函數(shù). */ void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3,TIM_IT_Update) == SET) { TASK_Remarks(); } TIM_ClearITPendingBit(TIM3,TIM_IT_Update); }
-
添加需要執(zhí)行的任務(wù)函數(shù)即可。
#define TASKS_MAX 5 // 定義任務(wù)數(shù)目 /** 任務(wù)函數(shù)相關(guān)信息 */ static TASK_InfoType sg_tTaskInfo[TASKS_MAX] = { {0, 1, 1, CAN_CommTask}, // CAN通信任務(wù) {0, 10, 10, KEY_ScanTask}, // 按鍵掃描任務(wù) {0, 10, 10, LOGIC_HandleTask}, // 邏輯處理任務(wù) {0, 100, 100, LED_CtrlTask},// 指示燈控制任務(wù) {0, 1000, 1000, WDog_Task}, // 喂狗任務(wù) };
操作系統(tǒng)
嵌入式操作系統(tǒng)EOS(Embedded OperatingSystem)是一種用途廣泛的系統(tǒng)軟件,過去它主要應(yīng)用于工業(yè)控制和國防系統(tǒng)領(lǐng)域,而對于單片機來說,比較常用的有 UCOS、FreeRTOS、RT-Thread Nano 和 RTX 等多種搶占式操作系統(tǒng)(其他如 Linux 等操作系統(tǒng)不適用于單片機)
操作系統(tǒng)和“時間片論法”,在任務(wù)執(zhí)行方面來說,操作系統(tǒng)對每個任務(wù)的耗時沒有過多的要求,需要通過設(shè)置每個任務(wù)的優(yōu)先級,在高優(yōu)先級的任務(wù)就緒時,會搶占低優(yōu)先級的任務(wù);操作系統(tǒng)相對復(fù)雜,因此這里沒有詳細介紹了。
關(guān)于如何選擇合適的操作系統(tǒng)([RTOS]uCOS、FreeRTOS、RTThread、RTX等RTOS的對比之特點):
UCOS:網(wǎng)上資料豐富,非常適合學(xué)習(xí),但是在產(chǎn)品上使用則需要收費
FreeRTOS:使用免費,因此很多產(chǎn)品都在用
RT-Thread:國產(chǎn)物聯(lián)網(wǎng)操作系統(tǒng),有著十分豐富的組件,也免費,資料:RT-Thread 文檔中心
RTX:為ARM和Cortex-M設(shè)備設(shè)計的免版稅,確定性的實時操作系統(tǒng)
借網(wǎng)上一張對比圖:
3、總結(jié)
從上述的對比中可以看出,時間片輪詢法的優(yōu)勢還是比較大的,它既有前后臺順序執(zhí)行法的優(yōu)點,也有操作系統(tǒng)的優(yōu)點。結(jié)構(gòu)清晰,簡單,非常容易理解,所以這種是比較常用的單片機設(shè)計框架。





