一些初學(xué)單片機的同學(xué),剛剛?cè)胧肿?a href="http://wenjunhu.com/v/tag/586/" target="_blank">單片機開發(fā),還沒有涉及到使用RTOS,且剛?cè)胧种苯由蟁TOS可能會有些難度,有的使用的相對較老單片機資源還有限,也不適合跑RTOS。
或者使用RTOS,在整體思路上比較迷茫,不知從何入手,所以本文來聊聊我對單片機程序的整體框架設(shè)計的一些思路體會。
為啥要討論架構(gòu)
單片機系統(tǒng)開發(fā)人員的目標(biāo)之一是在編程環(huán)境中創(chuàng)建固件,以實現(xiàn)低成本系統(tǒng)、軟件可靠性以及快速的開發(fā)迭代時間。實現(xiàn)這種編程環(huán)境的最佳方法實踐是使用統(tǒng)一的固件架構(gòu)體系結(jié)構(gòu),該體系結(jié)構(gòu)在產(chǎn)品開發(fā)過程中充當(dāng)框架并支持“固件模塊化”,或稱為子系統(tǒng)。
如果不采用統(tǒng)一的設(shè)計架構(gòu),那么其業(yè)務(wù)需求耦合關(guān)系復(fù)雜,不采用先設(shè)計-后開發(fā)的方法論,想到哪里寫到哪里,則程序后期維護將變得異常艱辛,而引入潛在bug/缺陷的風(fēng)險也將大大增加,且不具備多人協(xié)同開發(fā)的可能。
可以結(jié)合固件模塊化、可測試性和兼容性的正確組合的設(shè)計體系架構(gòu)結(jié)構(gòu)應(yīng)用于任何固件開發(fā)項目,以最大程度地提高代碼可復(fù)用性,加快固件調(diào)試速度并提高固件可移植性。
模塊化架構(gòu)設(shè)計?
模塊化編程將程序功能分解為固件模塊/子系統(tǒng),每個模塊執(zhí)行一個功能,并包含完成該功能所需的所有源代碼和變量。
模塊化/子系統(tǒng)化有助于協(xié)調(diào)團隊中許多人的并行工作,管理項目各個部分之間的相互依賴關(guān)系,并使設(shè)計人員、系統(tǒng)集成人員能夠以可靠的方式組裝復(fù)雜的系統(tǒng)。具體來說,它可以幫助設(shè)計人員實現(xiàn)和管理復(fù)雜性。
隨著應(yīng)用程序的大小和功能的增長,需要模塊化才能將它們分成單獨的部分(無論是作為“組件”,“模塊”還是“子系統(tǒng)”)。然后,每個這樣分離的部分就成為模塊化體系結(jié)構(gòu)的一個元素。這樣,可以使用定義明確的界面隔離和訪問每個組件。此外,模塊化編程可提高固件的可讀性,同時簡化固件的調(diào)試,測試和維護。
即便是一個人獨立開發(fā)一個項目,這樣做依然在代碼的調(diào)試、可讀性、可移植性方面是最佳實踐的整體策略。如果代碼設(shè)計良好,則在其他項目可以輕松應(yīng)用。而且模塊經(jīng)過上一項目的測試驗證,在新的項目中再次應(yīng)用其缺陷風(fēng)險將大幅降低。
所以每做一個項目,以這種策略不斷積累模塊“輪子”組件,隨著經(jīng)驗的增長,積累的“輪子”就越來越多,也越來越好。所以其優(yōu)點是顯而易見的,否則每做一個項目,都從輪子造起,開發(fā)時間長不說,開發(fā)水平也得不到提高,重復(fù)性工作也很枯燥。比如前文中談到的非易失存儲管理子系統(tǒng),如設(shè)計良好,就變成一個可靠的可移植的輪子。這段話請深入理解,并拿走不謝!
固件模塊原理
固件開發(fā)中模塊化編程的基本概念是創(chuàng)建固件模塊。從概念上講,模塊代表關(guān)注點分離。在計算機科學(xué)中,關(guān)注點分離是將計算機程序分解為功能很少重疊的獨特功能的過程。關(guān)注點是程序的任何關(guān)注點或功能,并且與功能或行為同義。關(guān)注點分離的發(fā)展傳統(tǒng)上是通過模塊化和封裝來實現(xiàn)的,其實也就是解耦思想。
固件模塊可以分為幾種類型:
與很多上層用戶模塊都有關(guān)的代碼被實現(xiàn)為單獨的固件模塊。常見的如底層硬件相關(guān)的抽象實現(xiàn)。例如,hal_adc.c 是ADC用戶模塊的固件模塊,而hal_timer.c是Timer用戶模塊的固件模塊。
用于特定純軟件算法的代碼被實現(xiàn)為單獨的固件模塊。例如,alg_filter.c是執(zhí)行軟件過濾器(例如中值過濾器,均值過濾器或加權(quán)均值過濾器、IIR/FIR濾波)的固件模塊。
特定應(yīng)用程序的代碼實現(xiàn)為單獨的固件模塊。例如,app_battery.c是電池充電器應(yīng)用程序的固件模塊。特定工具的代碼實現(xiàn)為單獨的固件模塊。例如,debug_print.c是用于實現(xiàn)日志打印功能的固件模塊。
。。。。。。
實施估計模塊化設(shè)計的一些規(guī)則:
所有與模塊相關(guān)的功能都應(yīng)集成到單個源文件中,這是高內(nèi)聚的體現(xiàn)。
模塊對外提供一個頭文件,該文件聲明了該模塊的所有資源(硬件依賴/宏/常量/變量/函數(shù))。盡量用struct將緊密相關(guān)的變量進行集總封裝。
在源文件中包括自檢代碼部分,以實現(xiàn)該模塊模塊的所有自檢功能。
固件模塊的接口應(yīng)經(jīng)過精心設(shè)計和定義。
由于固件取決于硬件,因此需要在源文件頭中明確提及硬件的相關(guān)性。比如利用宏將硬件依賴轉(zhuǎn)定義,或者利用函數(shù)將基本操作進行封裝。則在新的架構(gòu)體系,僅僅需要移植這部分實現(xiàn)即可使用。
通常,固件模塊可供其他團隊成員在其他項目中使用。可能涉及到管理更改,缺陷修復(fù)、所有者應(yīng)維護模塊。源文件頭應(yīng)包含“作者”和“版本”信息。
固件在某種程度上取決于編譯器。源文件頭中應(yīng)聲明基于什么開發(fā)環(huán)境進行過驗證,以指定編譯器或與IDE相關(guān)的信息。
需要注意的是,模塊化設(shè)計會引入一些調(diào)用開銷,也可能增加固件尺寸大小。在實際實現(xiàn)時,折中考量。不要過度模塊化,所以建議采用高內(nèi)聚、低耦合的實現(xiàn)策略。在前面文章中有談到過的呼吸機PB560的設(shè)計,看過其代碼,本打算解讀一下其代碼設(shè)計,但讀下來發(fā)現(xiàn),其設(shè)計過度模塊化了,沒有實現(xiàn)高內(nèi)聚的思想。其源代碼很多源文件僅僅實現(xiàn)了一個函數(shù),而不是把一類問題集中抽象實現(xiàn),后來就放棄了其代碼解讀。
如何拆分模塊?
做工程開發(fā),一定是需求驅(qū)動的。第一件事需要對需求有比較清晰的認知,然后才能設(shè)計一個比較合理的框架。我們需要實現(xiàn)什么?大致總體設(shè)計過程策略我的基本采用如下圖所示思路(我比較喜歡繪圖,圖會讓人比較直觀)
問自己第一個問題是:這個項目要實現(xiàn)什么主要功能?這個來自哪里?如果是實際產(chǎn)品開發(fā),則可能來自市場的需求,如果是自己的DIY項目,也一定會YY出一個大致的想法?總之不管源自何方,需求總要先梳理清楚。那么需求一般意義上包含哪些呢?
哪些是硬件IO接口需求,比如開關(guān)量輸入,ADC采樣,I2C/SPI通信等等
哪些是業(yè)務(wù)邏輯需求,比如要采集一個傳感器量數(shù)據(jù),控制一個加熱裝置,那么這是高內(nèi)聚的需求。
哪些是算法相關(guān)的技術(shù)需求,比如產(chǎn)品中哪些信號需要濾波處理,哪些需要做頻域分析等等。
是否有對外的通信協(xié)議需求。
是否有業(yè)務(wù)數(shù)據(jù)需要歷史存儲,或者設(shè)備參數(shù)需要掉電保存
是否需要有日志打印需求。
。。。。。。。。
不一而足。
結(jié)合固件模塊原理以及相關(guān)指導(dǎo)原則,那么將相關(guān)性高的需求,抽象實現(xiàn)在一系列的模塊中,在由這一系列模塊配合實現(xiàn)某個相關(guān)性高的業(yè)務(wù)需求,再進一步這些模塊就變成一個子系統(tǒng)。多個子系統(tǒng)在main.c的調(diào)度下,協(xié)調(diào)完成產(chǎn)品的整體功能。
如何集成調(diào)度
對于某些不使用RTOS的應(yīng)用而言,可以使用如下的框架進行:
void main(void){ /*各模塊初始化*/ init_module_1(); init_module_2(); 。。。。 while(1) { /*實現(xiàn)一個定時調(diào)度策略*/ if(timer50ms) { timer50ms = 0; app_module_1(); } if(timer100ms) { timer100ms = 0; app_module_2(); } /*異步請求處理,如中斷后臺處理*/ if(flag1) { communication_handler(); } 。。。。。 }}
對于基于RTOS的集成實現(xiàn)舉例:
void task1(void){ /*處理子系統(tǒng)相關(guān)的初始化*/ init_task1(); while(1) { /*應(yīng)用相關(guān)調(diào)用*/ task1_mainbody(); 。。。。 }}。。。.void taskn(void){ /*處理子系統(tǒng)相關(guān)的初始化*/ init_taskn(); while(1) { /*應(yīng)用相關(guān)調(diào)用*/ taskn_mainbody(); 。。。。 }}
void main(void){ /*一些基本硬件相關(guān)初始化,比如IO,時鐘,OS tick定時器等*/ init_hal(); 。。。。。。 /*一些基本RTOS初始化*/ init_os(); /*任務(wù)創(chuàng)建*/ os_creat(“task1”,task1,棧設(shè)置,優(yōu)先級,。。。); 。。。。。。 os_creat(“taskn”,taskn,棧設(shè)置,優(yōu)先級,。。。); /*啟動OS調(diào)度器,交由OS調(diào)度管理應(yīng)用任務(wù)*/ os_start();}
具體不同的RTOS,其函數(shù)名各有不同,但大致思路一般都差不多。
總結(jié)一下
本文從為什么需要模塊化設(shè)計整體架構(gòu),到這樣做的好處,以及具體做的一些指導(dǎo)原則,再到實際中如何實現(xiàn),怎么做到高內(nèi)聚低耦合,提供了一些個人工作中的體會以及思路。
同時對于裸機程序整體框架、基于RTOS的集成框架做了兩個demo,基本能解決大部分的框架思路問題。將前文中的一些個人推崇的原則,在加粗總結(jié)下:
所有與模塊相關(guān)的功能都應(yīng)集成到單個源文件中,這是高內(nèi)聚的體現(xiàn)。
模塊對外提供一個頭文件,該文件聲明了該模塊的所有資源(硬件依賴/宏/常量/變量/函數(shù))。盡量用struct將緊密相關(guān)的變量進行集總封裝。
在源文件中包括自檢代碼部分,以實現(xiàn)該模塊模塊的所有自檢功能。
固件模塊的接口應(yīng)經(jīng)過精心設(shè)計和定義。
由于固件取決于硬件,因此需要在源文件頭中明確提及硬件的相關(guān)性。比如利用宏將硬件依賴轉(zhuǎn)定義,或者利用函數(shù)將基本操作進行封裝。則在新的架構(gòu)體系,僅僅需要移植這部分實現(xiàn)即可使用。
通常,固件模塊可供其他團隊成員在其他項目中使用。可能涉及到管理更改,缺陷修復(fù)、所有者應(yīng)維護模塊。源文件頭應(yīng)包含“作者”和“版本”信息。
固件在某種程度上取決于編譯器。源文件頭中應(yīng)聲明基于什么開發(fā)環(huán)境進行過驗證,以指定編譯器或與IDE相關(guān)的信息。
極力建議采用先設(shè)計-后開發(fā)的模式,比較忌諱逐步debug,想到哪里寫到哪里。當(dāng)然對于新手學(xué)習(xí)而言,后一種模式,可以逐步漸進迭代,也可以比較快的增長經(jīng)驗。當(dāng)然如何取舍,全憑個人意愿。相信您如深入閱讀,細細體會,應(yīng)該從設(shè)計思想上得到些領(lǐng)悟,有所提高。
編輯:jq
-
單片機
+關(guān)注
關(guān)注
6037文章
44558瀏覽量
635406 -
adc
+關(guān)注
關(guān)注
98文章
6498瀏覽量
544686 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4331瀏覽量
62630 -
RTOS
+關(guān)注
關(guān)注
22文章
813瀏覽量
119649
原文標(biāo)題:詳述單片機固件模塊化架構(gòu)設(shè)計
文章出處:【微信號:strongerHuang,微信公眾號:strongerHuang】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論