大家好!我是bug菌!
今天分享的文章,主要給那些沒有軟件設(shè)計思想的MCU軟件工程師看的!隨著目前MCU的各方面性能顯著提升,一些以MCU為控制中心的嵌入式系統(tǒng)也是越來越復(fù)雜,毫無軟件設(shè)計理念的代碼真的是拖累單片機,所以對每個MCU軟件工程師在軟件設(shè)計等方面的要求也將越來越高!
這里利用一個實際發(fā)生的例子,對入行的初級軟件工程師提一些軟件設(shè)計上的建議,并分享了一些經(jīng)常走的彎路,希望可以幫到大家。這篇文章我沒有談編程的規(guī)范性的東西,如果你想讓自己的程序文件代碼更加直觀、看起來美觀、可讀性強,推薦學習一下全面的編程規(guī)范,比如網(wǎng)絡(luò)上廣為流傳的,華為“C語言編程規(guī)范”。本文主要想說一說當我們的單片機遇到多個模塊的數(shù)據(jù)需要處理,類似于“多任務(wù)”時應(yīng)該怎么去思考和處理?背景是這樣的,9月份開始安排一個工程師開始做電動汽車交流充電樁,機械設(shè)計部分由公司機械結(jié)構(gòu)部門負責。充電樁的電子部分總體上分為X個部分(用到的資源): 電阻觸摸屏(RS232),M1卡讀寫(RS232),電能計量表(RS485),語音提示(SPI),電力開關(guān)(繼電器IO),通訊接口(RS485、CAN)。工程師做的過程非常勤奮,期間也是困難重重,改了很多個版本,很多的bug,總算第二年6月把充電樁立起來了。當然此過程我并沒有過多的干涉,系統(tǒng)也沒有經(jīng)過非常嚴格的測試,結(jié)果發(fā)現(xiàn)讀卡的時候不能處理觸摸屏,播放語音的時候不能處理讀卡,語音播放不能打斷或者跳躍,反正就是所有事件必須一個一個按部就班的來,一旦操作錯誤就需要多次執(zhí)行、等待、甚至重新來過。一個工作3年多的工程師怎么會把產(chǎn)品做成這樣呢?思來想去不應(yīng)該呀,是不是程序哪里出了問題 ? 解決問題的最好辦法就是評審代碼,來review代碼瞧瞧。
不看不知道,一看嚇一跳!整個的程序是沒有邏輯的,一條線就往下寫,這不正是當年在學校剛做第一個項目的代碼嗎 ? ……//主循環(huán)
while(1)
{
//上電進入主程序 或 觸發(fā)觸摸屏
Function1();//播放提示語音
Delay();//等待播放完畢
//讀取M1卡信息
Function2();
Delay();//等待讀卡數(shù)據(jù)返回
//播放提示語音
Function3();
Delay();//等待播放完畢
//M1卡數(shù)據(jù)交互,判定下一步操作及提示
Function4();
Delay();//等待數(shù)據(jù)處理完畢
……
……
}
從代碼上可以看出這個工程師基本上對于自己設(shè)計的產(chǎn)品沒有任何軟件上的設(shè)計可言,也很少去吸收一些優(yōu)秀的代碼和思想,對自己開發(fā)的程序在產(chǎn)品上的具體表現(xiàn)也不敏感,更被說對RTOS的學習和理解了。
他犯了幾個我們在程序開發(fā)過程中幾個忌諱的問題:
1、 delay(死等)這類函數(shù)應(yīng)該只在實驗室驗證某個功能過程中用到,或許是在一些初始化時序使用到,而不會用來控制整個的程序運行架構(gòu),在實際的產(chǎn)品開發(fā)時無論是主循環(huán)while中,還是其調(diào)用的函數(shù)中,亦或是中斷服務(wù)程序中幾乎是不可能看到的。
2、 產(chǎn)品設(shè)計的各個相對比較獨立的子模塊之間的邏輯關(guān)系太強,例如:必須等待播音完畢才能讀卡進入下一步操作等。我們講,產(chǎn)品設(shè)計中只有各個事件處理模塊間的邏輯關(guān)系弱化,才能更加靈活的進行處理。例如:兩個事件A和B,如果程序開發(fā)時將A做成B事件的必要條件,B事件的觸發(fā)就必須等待A事件的發(fā)生。反之如果A事件作為B事件處理的一個特殊情況,也就是說我不執(zhí)行A也有可能執(zhí)行B,那么程序開發(fā)起來就變得靈活很多。3、 沒有考慮到單片機本身是一個單核單任務(wù)的架構(gòu),每一個事件都會獨占CPU內(nèi)核,當多個任務(wù)模塊同時存在時我們應(yīng)該對各個事件進行區(qū)分,我們應(yīng)當分情況、分事件實時性要求等區(qū)分對待。 那么針對于這樣的問題,或者是遇到類似的項目我們應(yīng)該如何處理呢? 這里提一下我的建議和想法,首先他這里是裸機開發(fā),所以就不談RTOS方面的設(shè)計建議了,僅僅只是針對前后臺架構(gòu)。1、將硬件系統(tǒng)區(qū)分為獨立單元單獨做成底層驅(qū)動函數(shù)和應(yīng)用函數(shù),并且函數(shù)正常應(yīng)該有參數(shù)和返回值,其中返回值是必要的。如何衡量這類函數(shù)呢?這類函數(shù)可移植性強,只要一個.h文件和一個或多個.c文件就可以隨意放到任何工程中,一句話吧,模塊化!例如:語音播放、M1讀卡、485處理等等。2、將1中的所有函數(shù)進行時間評估,評估點有兩個。一個是函數(shù)的執(zhí)行時間t,第二個是函數(shù)的周期性發(fā)生的時間T,一個最基本的條件是t < T,理想情況應(yīng)該是t << T。
3、建立一個集中邏輯處理函數(shù),也就是核心任務(wù)調(diào)度處理函數(shù),在這個函數(shù)中對1中的各個函數(shù)進行調(diào)度。這個函數(shù)發(fā)揮的作用相當于嵌入式系統(tǒng)中的系統(tǒng)調(diào)度。這種調(diào)度是整個硬件邏輯中所有事件處理的調(diào)度,它的目的是完成一個處理過程,但是絕不依賴于任意事件的必要處理過程。這樣就將問題2中提到的事件間的邏輯關(guān)系弱化了,處理起來變得十分靈活,使得各個關(guān)系不在相互必要。bug菌記得有一本書籍叫<時間觸發(fā)型調(diào)度系統(tǒng)>,大伙可以看看,大體思想與這里的觀點差不多!4、為了保證前面內(nèi)容的正常實施還需要針對各類事件的周期,建立一個必要的時間管理函數(shù),時間函數(shù)的基礎(chǔ)一般情況下由一個內(nèi)部定時器的中斷來完成,中斷的周期一般我們考慮5-10ms。按照實際需求將N個定時器中斷定義為一個事件處理的周期TT,這個周期應(yīng)該保證處理完最惡劣情況可能發(fā)生的所有t,且保證TT < T。
5、 這其中也有例外,一些實時性要求高的事件應(yīng)當用中斷完成。其中中斷處理函數(shù)的處理事件應(yīng)盡量短,時間要求參見2。bug菌覺得,不管你所做的項目有沒有用到RTOS,平時都需要玩玩RTOS,對該觀點的理解會有幫助。6、如果某種情況需要裸機開發(fā),大家可以看一下文末推薦閱讀中的一些比較好的架構(gòu),一些經(jīng)典的菜單,按鍵,軟件定時器,還有串口等,移植起來非常方便,效率也非常高,可以大大健壯我們的代碼。素材來源:最后一個bug公眾號發(fā)布,參考作者handong,bug菌進行相關(guān)觀點的優(yōu)化和整理版權(quán)歸原作者所有。僅供技術(shù)的傳播和學習討論,如涉及作品版權(quán)問題,請聯(lián)系我進行刪除。
軟件神器TortoiseGit,曉宇姐姐教你使用圖形化方式管理單片機程序版本!