別被野路子帶偏了,說說正經嵌入式工程師開發常用的3種架構

2022-04-27     大方老師單片機

原標題:別被野路子帶偏了,說說正經嵌入式工程師開發常用的3種架構

別被野路子帶偏了,說說正經嵌入式工程師開發常用3種架構

摘要:對於單片機程序來說,大家都不陌生,但是真正使用架構,考慮架構的恐怕並不多,隨著程序開發的不斷增多,架構是非常必要的。

應用程式的架構大致有三種:

1簡單的前後台順序執行程序,這類寫法是大多數人使用的方法,不需用思考程序的具體架構,直接通過執行順序編寫應用程式即可。

2時間片輪詢法,此方法是介於順序執行與作業系統之間的一種方法。

3作業系統,此法應該是應用程式編寫的最高境界。

///插播一條:我自己在今年年初錄製了一套還比較系統的入門單片機教程,想要的同學找我拿就行了免費的,私信我就可以~點我頭像黑色字體加我地球呺也能領取哦。最近比較閒,帶做畢設,帶學生參加省級或以上比///

正文開始:

一、程序框架設計

1、前後台順序執行法

這是初學者們常用的程序框架設計方案,不用考慮太多東西,代碼簡單,或者對系統的整體實時性和並發性要求不高;初始化後通過while(1){}for(;;){}`循環不斷調用自己編寫完成的函數,也基本不考慮每個函數執行所需要的時間,大部分情況下函數中或多或少都存在毫秒級別的延時等待。

優點:對於初學者來說,這是最容易也是最直觀的程序架構,邏輯簡單明了,適用於邏輯簡單,複雜度比較低的軟體開發。

缺點:實時性低,由於每個函數或多或少存在毫秒級別的延時,即使1ms,也會造成其他函數間隔執行時間的不同,雖然可通過定時器中斷的方式,但是前提是中斷執行函數花的時間必須短。當程序邏輯複雜度提升時,會導致後來維護人員的大腦混亂,很難理清楚該程序的運行狀態。

以下是在校期間做的寢室防盜系統的部分代碼(當時也存在部BUG,沒有解決。現在再看,其實很多問題,而且比較嚴重,比如中斷服務函數內竟然3000ms延時,這太可怕了,還有串口發送等等;由於實時性要求不算太高,因此主函數中的毫秒級別延時對系統運行沒有多大影響,當然BUG外;若是後期需要維護,那就是一個大工程,還不如推翻重寫)

int main(void)

{

u8 temperature;

u8 humidity;

int a;

delay_init();

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

I2c_init();

uart2_Init(9600);

uart_init(9600);//串口初始化115200

TIM3_Int_Init(4999,7199);

ds1302_init();

while(DHT11_Init())//DHT11初始

{

led2=0;

}

a1602_init();

Ds1302Init();

EXTIX_Init();

GPIOX_Init();

lcd12864_INIT();

LcdInit();

beep_init();

RED_Init();

led1=1;

beep=0;

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); //讀取鬧鐘保存的數據

delay_ms(10);

fen=At24c02Read(1); //讀取鬧鐘保存的數

usart2_scan(); //藍牙數據掃描

usart2_bian(); //藍牙處理數據

usart2_gai();

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(); //顯示防盜鬧鐘狀

}

}

//定時3中斷服務程序

void TIM3_IRQHandler(void)//TIM3中斷

{

int i;

if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //TIM3更新中斷髮生與否

{

TIM_ClearITPendingBit(TIM3, TIM_IT_Update);//TIMx更新中斷標

if(key1==1&&FEN-fen==0&&SHI-shi==0)//時間一到鬧鐘響起

{

f=1;

}

if(key1==0||FEN-fen!=0||SHI-shi!=0)

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發送數據

while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待發送結束

USART_RX_STA=0;

}

delay_ms(3000);

for(i=0;i<3;i++)

{

USART_SendData(USART1, num1[i]);//向串1發送數據

while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待發送結束

USART_RX_STA=0;

}

}

}

}

二、時間片論法

介於前後台順序執行法作業系統之間的一種程序架構設計方案。該設計方案需能幫助嵌入式軟體開發者更上一層樓,在嵌入式軟體開發過程中,若遇到以下幾點,那麼該設計方案可以說是最優選擇,適用於程序較複雜的嵌入式系統;

·目前的需求設計需要完全沒有必要上作業系統。

·任務函數無需時刻執行,存在間隔時(比如按鍵,一般情況下,都需要軟體防抖,初學者的做法通常是延10ms左右再去判斷,10ms極大浪費CPU的資源,在這段時間CPU完全可以處理很多其他事)

·實時性有一定的要求。

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

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

以下介紹兩種不同的實現方案,分別針對無函數指針概念的朋友和想進一步學習的朋友。

1、無函數指針的設計方式

/**

* @brief 主函.

* @param None.

* @return None.

*/

int main(void)

{

System_Init();

while (1)

{

if (TIM_1msFlag)// 1ms

{

CAN_CommTask(); // CAN/接收通信任務

TIM_1msFlag = 0;

}

if (TIM_10msFlag) // 10ms

{

KEY_ScanTask(); //按鍵掃描處理任務

TIM_10msFlag = 0;

}

if (TIM_20msFlag) // 20ms

{

LOGIC_HandleTask();//邏輯處理任務

TIM_20msFlag = 0;

}

if (TIM_100msFlag) // 100ms

{

LED_CtrlTask(); //指示燈控制任務

TIM_100msFlag = 0;

}

if (TIM_500msFlag)// 500ms

{

TIM_500msFlag = 0;

}

if (TIM_1secFlag) // 1s

{

WDog_Task(); //喂狗任

TIM_1secFlag = 0;

}

}

}

/**

* @brief 定時3中斷服務函.

* @param None.

* @return None.

*/

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、含函數指針的設計方式

/**

* @brief任務函數相關信息結構體定.

*/

typedefstruct{

uint8 m_runFlag; /*!<程序運行標記0-不運行1 */

uint16 m_timer; /*!<計時 */

uint16 m_itvTime; /*!<任務運行間隔時 */

void (*m_pTaskHook)(void); /*!<要運行的任務函 */

} TASK_InfoType;

#define TASKS_MAX 5 //定義任務數目

/**任務函數相關信 */

static TASK_InfoType sg_tTaskInfo[TASKS_MAX] = {

{0, 1, 1, CAN_CommTask}, // CAN通信任務

{0, 10, 10, KEY_ScanTask}, //按鍵掃描任務

{0, 20, 20, LOGIC_HandleTask}, //邏輯處理任務

{0, 100, 100, LED_CtrlTask}, //指示燈控制任務

{0, 1000, 1000, WDog_Task}, //喂狗任務

};

/**

* @brief 任務函數運行標誌處.

* @note 該函數1ms定時器中斷調用

* @param None.

* @return None.

*/

void TASK_Remarks(void)

{

uint8 i;

for (i = 0; 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 任務函數運行處.

* @note 該函數由主循環調用

* @param None.

* @return None.

*/

void TASK_Process(void)

{

uint8 i;

for (i = 0; i

{

if (sg_tTaskInfo[i].m_runFlag)

{

sg_tTaskInfo[i].m_pTaskHook(); //運行任務

sg_tTaskInfo[i].m_runFlag = 0; //標誌0

}

}

}

/**

* @brief 主函.

* @param None.

* @return None.

*/

int main(void)

{

System_Init();

while (1)

{

TASK_Process();

}

}

/**

* @brief 定時3中斷服務函.

* @param None.

* @return None.

*/

void TIM3_IRQHandler(void)

{

if(TIM_GetITStatus(TIM3,TIM_IT_Update) == SET) //溢出中斷

{

TASK_Remarks();

}

TIM_ClearITPendingBit(TIM3,TIM_IT_Update);//清除中斷標誌位

}

三、作業系統

嵌入式操作系EOS(Embedded OperatingSystem)是一種用途廣泛的系統軟體,過去它主要應用於工業控制和國防系統領域,而對於單片機來說,比較常用的UCOSFreeRTOSRT-ThreadNanoRTX等多種搶占式操作系(其他Linux等作業系統不適用於單片)

作業系統時間片論法,在任務執行方面來說,作業系統對每個任務的耗時沒有過多的要求,需要通過設置每個任務的優先級,在高優先級的任務就緒時,會搶占低優先級的任務;作業系統相對複雜,因此這裡沒有詳細介紹了。

關於如何選擇合適的操作系(uCOSFreeRTOSRTThreadRTXRTOS的對比之特點:

·uCOS:網上資料豐富,非常適合學習,但是在產品上使用則需要收費。

·FreeRTOS:使用免費,因此很多產品都在用。

·RT-Thread:國產物聯網作業系統,有著十分豐富的組件,也免費,資料RT-Thread文檔中心。

·RTX:ARMCortex-M設備設計的免版稅,確定性的實時作業系統。

借網上一張對比圖:

四、總結

從上述的對比中可以看出,時間片輪詢法的優勢還是比較大的,它既有前後台順序執行法的優點,也有作業系統的優點。結構清晰,簡單,非常容易理解,所以這種是比較常用的單片機設計框架。

END

文章來源: https://twgreatdaily.com/zh-mo/e35433cb6eabd8276681d71ab3b9aac1.html