• 正文
    • 1、前言
    • 2、程序框架設(shè)計(jì)
    • 3、總結(jié)
  • 相關(guān)推薦
申請入駐 產(chǎn)業(yè)圖譜

嵌入式軟件架構(gòu)設(shè)計(jì)-任務(wù)調(diào)度

04/02 13:01
237
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點(diǎn)資訊討論

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è)計(jì)方案:

  • 前后臺順序執(zhí)行法
  • 時(shí)間片論法
  • 操作系統(tǒng)

2、程序框架設(shè)計(jì)

前后臺順序執(zhí)行法

這是初學(xué)者們常用的程序框架設(shè)計(jì)方案,不用考慮太多東西,代碼簡單,或者對系統(tǒng)的整體實(shí)時(shí)性和并發(fā)性要求不高;初始化后通過 while(1){ } 或 for(;;){ } 循環(huán)不斷調(diào)用自己編寫完成的函數(shù),也基本不考慮每個(gè)函數(shù)執(zhí)行所需要的時(shí)間,大部分情況下函數(shù)中或多或少都存在毫秒級別的延時(shí)等待。

優(yōu)點(diǎn):對于初學(xué)者來說,這是最容易也是最直觀的程序架構(gòu),邏輯簡單明了,適用于邏輯簡單,復(fù)雜度比較低的軟件開發(fā)。

缺點(diǎn):實(shí)時(shí)性低,由于每個(gè)函數(shù)或多或少存在毫秒級別的延時(shí),即使是1ms,也會(huì)造成其他函數(shù)間隔執(zhí)行時(shí)間的不同,雖然可通過定時(shí)器中斷的方式,但是前提是中斷執(zhí)行函數(shù)花的時(shí)間必須短。當(dāng)程序邏輯復(fù)雜度提升時(shí),會(huì)導(dǎo)致后來維護(hù)人員的大腦混亂,很難理清楚該程序的運(yùn)行狀態(tài)。

以下是我在學(xué)校期間做的寢室防盜系統(tǒng)的主函數(shù)代碼(當(dāng)時(shí)也存在部分BUG,沒有解決。現(xiàn)在再看,其實(shí)很多問題,而且比較嚴(yán)重,比如中斷服務(wù)函數(shù)內(nèi)竟然有3000ms延時(shí),這太可怕了,還有串口發(fā)送等等;由于實(shí)時(shí)性要求不算太高,因此主函數(shù)中的毫秒級別延時(shí)對系統(tǒng)運(yùn)行沒有多大影響,當(dāng)然除BUG外;若是后期需要維護(hù),那就是一個(gè)大工程,還不如推翻重寫):

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的日期時(shí)間
shi=At24c02Read(0);????????????????//讀取鬧鐘保存的數(shù)據(jù)
delay_ms(10);
fen=At24c02Read(1);????????????????//讀取鬧鐘保存的數(shù)據(jù)
usart2_scan();????????????????????????//藍(lán)牙數(shù)據(jù)掃描
usart2_bian();????????????????????????//藍(lán)牙處理數(shù)據(jù)
nao_scan();
k++;
if(k<20)
{
if(k==1)
LcdWriteCom(0x01);??//清屏
LcdDisplay();????????????????????????????//顯示日期時(shí)間
}
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)

}
}

//定時(shí)器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更新中斷標(biāo)志
if(key1==1&&FEN-fen==0&&SHI-shi==0)????????????????//時(shí)間一到鬧鐘響起
{
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;
}
}
}
}

時(shí)間片論法

介于“前后臺順序執(zhí)行法”和“操作系統(tǒng)”之間的一種程序架構(gòu)設(shè)計(jì)方案。該設(shè)計(jì)方案需能幫助嵌入式軟件開發(fā)者更上一層樓,在嵌入式軟件開發(fā)過程中,若遇到以下幾點(diǎn),那么該設(shè)計(jì)方案可以說是最優(yōu)選擇,適用于程序較復(fù)雜的嵌入式系統(tǒng);

1.目前的需求設(shè)計(jì)需要完全沒有必要上操作系統(tǒng)

2.任務(wù)函數(shù)無需時(shí)刻執(zhí)行,存在間隔時(shí)間(比如按鍵,一般情況下,都需要軟件防抖,初學(xué)者的做法通常是延時(shí)10ms左右再去判斷,但10ms極大浪費(fèi)了CPU的資源,在這段時(shí)間內(nèi)CPU完全可以處理很多其他事情)

3.實(shí)時(shí)性有一定的要求

該設(shè)計(jì)方案需要使用一個(gè)定時(shí)器,一般情況下定時(shí)1ms即可(定時(shí)時(shí)間可隨意定,但中斷過于頻繁效率就低,中斷太長,實(shí)時(shí)性差),因此需要考慮到每個(gè)任務(wù)函數(shù)的執(zhí)行時(shí)間,建議不能超過1ms(能通過程序優(yōu)化縮短執(zhí)行時(shí)間則最好優(yōu)化,如果不能優(yōu)化的,則必須保證該任務(wù)的執(zhí)行周期必須遠(yuǎn)大于任務(wù)所執(zhí)行的耗時(shí)時(shí)間),同時(shí)要求主循環(huán)或任務(wù)函數(shù)中不能存在毫秒級別的延時(shí)。

如何確定每個(gè)函數(shù)的任務(wù)周期呢?根據(jù)任務(wù)的耗時(shí)和效果決定、如按鍵掃描任務(wù)周期為 10ms(為了提高響應(yīng)),指示燈控制任務(wù)周期為 100ms(通常情況下最高100ms的閃爍頻率正好,特殊需求除外),LCD/OLED 顯示周期為 100ms(通過這種通過SPI/IIC等接口的方式耗時(shí)大約在 1~10ms,甚至更長,所以任務(wù)周期必須遠(yuǎn)大于耗時(shí),同時(shí)為了滿足人眼所能接受的刷屏效果,也不能太長,100ms 的任務(wù)周期比較合適)等

以下介紹兩種不同的實(shí)現(xiàn)方案,分別針對無函數(shù)指針概念的朋友和想進(jìn)一步學(xué)習(xí)的朋友。

① 無函數(shù)指針的設(shè)計(jì)方式

1.首先定義計(jì)時(shí)標(biāo)志變量,以1毫秒為時(shí)間片,在定時(shí)器中斷函數(shù)中累計(jì)計(jì)時(shí),同時(shí)將對應(yīng)時(shí)間標(biāo)識置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);
}

2.然后在主函數(shù)循環(huán)中判斷時(shí)間標(biāo)識,標(biāo)識被置1則代表時(shí)間已到,可以執(zhí)行相應(yīng)的函數(shù),全部執(zhí)行完畢后將標(biāo)識置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();//?人機(jī)交互任務(wù)

TIM_10msFlag?=?0;
}

if?(TIM_100msFlag)
{
LED_CtrlTask();//?指示燈任務(wù)

TIM_100msFlag?=?0;
}

if?(TIM_1secFlag)
{
WDog_Task();//?喂狗任務(wù)

TIM_1secFlag?=?0;
}
}
}

② 含函數(shù)指針的設(shè)計(jì)方式

1.定義含有函數(shù)指針的結(jié)構(gòu)體,用來指向需要周期執(zhí)行的函數(shù)。

typedef?struct{
uint8?m_runFlag;/*!<?程序運(yùn)行標(biāo)記:0-不運(yùn)行,1運(yùn)行?*/
uint16?m_timer;/*!<?計(jì)時(shí)器?*/
uint16?m_itvTime;/*!<?任務(wù)運(yùn)行間隔時(shí)間?*/
void?(*m_pTaskHook)(void);/*!<?要運(yùn)行的任務(wù)函數(shù)?*/
}?TASK_InfoType;

2.實(shí)現(xiàn)函數(shù)指針任務(wù)基本調(diào)度功能:任務(wù)調(diào)度管理和任務(wù)調(diào)度執(zhí)行??。

/**
*?@brief??????任務(wù)函數(shù)運(yùn)行調(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();?//?運(yùn)行任務(wù)
sg_tTaskInfo[i].m_runFlag?=?0;?//?標(biāo)志清0
}
}
}

3.分別在定時(shí)器中斷函數(shù)和主函數(shù)中調(diào)用任務(wù)調(diào)度管理和任務(wù)調(diào)度執(zhí)行??。

int?main(void)
{
System_Init();

while?(1)
{
TASK_Process();
}
}

/**
*?@brief??????定時(shí)器3中斷服務(wù)函數(shù).
*/
void?TIM3_IRQHandler(void)
{
if?(TIM_GetITStatus(TIM3,TIM_IT_Update)?==?SET)
{
TASK_Remarks();
}

TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}

4.添加需要執(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)域,而對于單片機(jī)來說,比較常用的有 UCOS、FreeRTOS、RT-Thread Nano 和 RTX 等多種搶占式操作系統(tǒng)(其他如 Linux 等操作系統(tǒng)不適用于單片機(jī))

操作系統(tǒng)和“時(shí)間片論法”,在任務(wù)執(zhí)行方面來說,操作系統(tǒng)對每個(gè)任務(wù)的耗時(shí)沒有過多的要求,需要通過設(shè)置每個(gè)任務(wù)的優(yōu)先級,在高優(yōu)先級的任務(wù)就緒時(shí),會(huì)搶占低優(yōu)先級的任務(wù);操作系統(tǒng)相對復(fù)雜,因此這里沒有詳細(xì)介紹了。

關(guān)于如何選擇合適的操作系統(tǒng)([RTOS]uCOS、FreeRTOS、RTThread、RTX等RTOS的對比之特點(diǎn)):

UCOS:網(wǎng)上資料豐富,非常適合學(xué)習(xí),但是在產(chǎn)品上使用則需要收費(fèi)

FreeRTOS:使用免費(fèi),因此很多產(chǎn)品都在用

RT-Thread:國產(chǎn)物聯(lián)網(wǎng)操作系統(tǒng),有著十分豐富的組件,也免費(fèi),資料:RT-Thread 文檔中心

RTX:為ARM和Cortex-M設(shè)備設(shè)計(jì)的免版稅,確定性的實(shí)時(shí)操作系統(tǒng)

借網(wǎng)上一張對比圖:

3、總結(jié)

從上述的對比中可以看出,時(shí)間片輪詢法的優(yōu)勢還是比較大的,它既有前后臺順序執(zhí)行法的優(yōu)點(diǎn),也有操作系統(tǒng)的優(yōu)點(diǎn)。結(jié)構(gòu)清晰,簡單,非常容易理解,所以這種是比較常用的單片機(jī)設(shè)計(jì)框架。

相關(guān)推薦