詳論單片機(jī)固件模塊化架構(gòu)設(shè)計(jì)

[導(dǎo)讀] 為什么寫(xiě)本文恢氯?做公號(hào)兩月,遇到一些初學(xué)單片機(jī)的同學(xué)阱州,剛剛?cè)胧肿鰡纹瑱C(jī)開(kāi)發(fā)挑秉,還沒(méi)有涉及到使用RTOS,且剛?cè)胧种苯由蟁TOS可能會(huì)有些難度苔货,有的使用的相對(duì)較老單片機(jī)資源還有限犀概,也不適合跑RTOS∫共眩或者使用RTOS姻灶,在整體思路上比較迷茫,不知從何入手诈茧,所以本文來(lái)聊聊我對(duì)單片機(jī)程序的整體框架設(shè)計(jì)的一些思路體會(huì)产喉。

為啥要討論架構(gòu)

單片機(jī)系統(tǒng)開(kāi)發(fā)人員的目標(biāo)之一是在編程環(huán)境中創(chuàng)建固件,以實(shí)現(xiàn)低成本系統(tǒng)敢会、軟件可靠性以及快速的開(kāi)發(fā)迭代時(shí)間曾沈。 實(shí)現(xiàn)這種編程環(huán)境的最佳方法實(shí)踐是使用統(tǒng)一的固件架構(gòu)體系結(jié)構(gòu),該體系結(jié)構(gòu)在產(chǎn)品開(kāi)發(fā)過(guò)程中充當(dāng)框架并支持“固件模塊化”鸥昏,或稱為子系統(tǒng)塞俱。

如果不采用統(tǒng)一的設(shè)計(jì)架構(gòu),那么其業(yè)務(wù)需求耦合關(guān)系復(fù)雜互广,不采用先設(shè)計(jì)-后開(kāi)發(fā)的方法論敛腌,想到哪里寫(xiě)到哪里卧土,則程序后期維護(hù)將變得異常艱辛,而引入潛在bug/缺陷的風(fēng)險(xiǎn)也將大大增加像樊,且不具備多人協(xié)同開(kāi)發(fā)的可能尤莺。

可以結(jié)合固件模塊化、可測(cè)試性和兼容性的正確組合的設(shè)計(jì)體系架構(gòu)結(jié)構(gòu)應(yīng)用于任何固件開(kāi)發(fā)項(xiàng)目生棍,以最大程度地提高代碼可復(fù)用性颤霎,加快固件調(diào)試速度并提高固件可移植性。

模塊化架構(gòu)設(shè)計(jì)涂滴?

模塊化編程將程序功能分解為固件模塊/子系統(tǒng)友酱,每個(gè)模塊執(zhí)行一個(gè)功能,并包含完成該功能所需的所有源代碼和變量柔纵。

模塊化/子系統(tǒng)化有助于協(xié)調(diào)團(tuán)隊(duì)中許多人的并行工作缔杉,管理項(xiàng)目各個(gè)部分之間的相互依賴關(guān)系,并使設(shè)計(jì)人員搁料、系統(tǒng)集成人員能夠以可靠的方式組裝復(fù)雜的系統(tǒng)或详。 具體來(lái)說(shuō),它可以幫助設(shè)計(jì)人員實(shí)現(xiàn)和管理復(fù)雜性郭计。 隨著應(yīng)用程序的大小和功能的增長(zhǎng)霸琴,需要模塊化才能將它們分成單獨(dú)的部分(無(wú)論是作為“組件”,“模塊”還是“子系統(tǒng)”)昭伸。 然后梧乘,每個(gè)這樣分離的部分就成為模塊化體系結(jié)構(gòu)的一個(gè)元素。 這樣庐杨,可以使用定義明確的界面隔離和訪問(wèn)每個(gè)組件选调。 此外,模塊化編程可提高固件的可讀性灵份,同時(shí)簡(jiǎn)化固件的調(diào)試学歧,測(cè)試和維護(hù)。

**即便是一個(gè)人獨(dú)立開(kāi)發(fā)一個(gè)項(xiàng)目各吨,這樣做依然在代碼的調(diào)試、可讀性袁铐、可移植性方面是最佳實(shí)踐的整體策略揭蜒。如果代碼設(shè)計(jì)良好,則在其他項(xiàng)目可以輕松應(yīng)用剔桨。而且模塊經(jīng)過(guò)上一項(xiàng)目的測(cè)試驗(yàn)證屉更,在新的項(xiàng)目中再次應(yīng)用其缺陷風(fēng)險(xiǎn)將大幅降低。所以每做一個(gè)項(xiàng)目洒缀,以這種策略不斷積累模塊"輪子"組件瑰谜,隨著經(jīng)驗(yàn)的增長(zhǎng)欺冀,積累的“輪子”就越來(lái)越多,也越來(lái)越好萨脑。所以其優(yōu)點(diǎn)是顯而易見(jiàn)的隐轩,否則每做一個(gè)項(xiàng)目,都從輪子造起渤早,開(kāi)發(fā)時(shí)間長(zhǎng)不說(shuō)职车,開(kāi)發(fā)水平也得不到提高,重復(fù)性工作也很枯燥鹊杖。**比如前文中談到的非易失存儲(chǔ)管理子系統(tǒng)悴灵,如設(shè)計(jì)良好,就變成一個(gè)可靠的可移植的輪子骂蓖。這段話請(qǐng)深入理解积瞒,并拿走不謝!

固件模塊原理

固件開(kāi)發(fā)中模塊化編程的基本概念是創(chuàng)建固件模塊登下。 從概念上講茫孔,模塊代表關(guān)注點(diǎn)分離。 在計(jì)算機(jī)科學(xué)中庐船,關(guān)注點(diǎn)分離是將計(jì)算機(jī)程序分解為功能很少重疊的獨(dú)特功能的過(guò)程银酬。 關(guān)注點(diǎn)是程序的任何關(guān)注點(diǎn)或功能,并且與功能或行為同義筐钟。關(guān)注點(diǎn)分離的發(fā)展傳統(tǒng)上是通過(guò)模塊化和封裝來(lái)實(shí)現(xiàn)的揩瞪,其實(shí)也就是解耦思想。

固件模塊可以分為幾種類型:

與很多上層用戶模塊都有關(guān)的代碼被實(shí)現(xiàn)為單獨(dú)的固件模塊篓冲。 常見(jiàn)的如底層硬件相關(guān)的抽象實(shí)現(xiàn)李破。例如,hal_adc.c 是ADC用戶模塊的固件模塊壹将,而hal_timer.c是Timer用戶模塊的固件模塊嗤攻。

用于特定純軟件算法的代碼被實(shí)現(xiàn)為單獨(dú)的固件模塊。例如诽俯,alg_filter.c是執(zhí)行軟件過(guò)濾器(例如中值過(guò)濾器妇菱,均值過(guò)濾器或加權(quán)均值過(guò)濾器、IIR/FIR濾波)的固件模塊暴区。

特定應(yīng)用程序的代碼實(shí)現(xiàn)為單獨(dú)的固件模塊闯团。例如,app_battery.c是電池充電器應(yīng)用程序的固件模塊仙粱。特定工具的代碼實(shí)現(xiàn)為單獨(dú)的固件模塊房交。 例如,debug_print.c是用于實(shí)現(xiàn)日志打印功能的固件模塊伐割。

......

實(shí)施估計(jì)模塊化設(shè)計(jì)的一些規(guī)則:

所有與模塊相關(guān)的功能都應(yīng)集成到單個(gè)源文件中候味,這是高內(nèi)聚的體現(xiàn)刃唤。

模塊對(duì)外提供一個(gè)頭文件,該文件聲明了該模塊的所有資源(硬件依賴/宏/常量/變量/函數(shù))白群。盡量用struct將緊密相關(guān)的變量進(jìn)行集總封裝尚胞。

在源文件中包括自檢代碼部分,以實(shí)現(xiàn)該模塊模塊的所有自檢功能川抡。

固件模塊的接口應(yīng)經(jīng)過(guò)精心設(shè)計(jì)和定義辐真。

由于固件取決于硬件,因此需要在源文件頭中明確提及硬件的相關(guān)性崖堤。比如利用宏將硬件依賴轉(zhuǎn)定義侍咱,或者利用函數(shù)將基本操作進(jìn)行封裝。則在新的架構(gòu)體系密幔,僅僅需要移植這部分實(shí)現(xiàn)即可使用楔脯。

通常,固件模塊可供其他團(tuán)隊(duì)成員在其他項(xiàng)目中使用胯甩。 可能涉及到管理更改昧廷,缺陷修復(fù)、所有者應(yīng)維護(hù)模塊偎箫。 源文件頭應(yīng)包含“作者”和“版本”信息木柬。

固件在某種程度上取決于編譯器。 源文件頭中應(yīng)聲明基于什么開(kāi)發(fā)環(huán)境進(jìn)行過(guò)驗(yàn)證淹办,以指定編譯器或與IDE相關(guān)的信息眉枕。

需要注意的是,模塊化設(shè)計(jì)會(huì)引入一些調(diào)用開(kāi)銷怜森,也可能增加固件尺寸大小速挑。在實(shí)際實(shí)現(xiàn)時(shí),折中考量副硅。不要過(guò)度模塊化姥宝,所以建議采用高內(nèi)聚、低耦合的實(shí)現(xiàn)策略恐疲。在前面文章中有談到過(guò)的呼吸機(jī)PB560的設(shè)計(jì)腊满,看過(guò)其代碼,本打算解讀一下其代碼設(shè)計(jì)培己,但讀下來(lái)發(fā)現(xiàn)糜烹,其設(shè)計(jì)過(guò)度模塊化了,沒(méi)有實(shí)現(xiàn)高內(nèi)聚的思想漱凝。其源代碼很多源文件僅僅實(shí)現(xiàn)了一個(gè)函數(shù),而不是把一類問(wèn)題集中抽象實(shí)現(xiàn)诸迟,后來(lái)就放棄了其代碼解讀茸炒。

如何拆分模塊愕乎?

做工程開(kāi)發(fā),一定是需求驅(qū)動(dòng)的壁公。第一件事需要對(duì)需求有比較清晰的認(rèn)知感论,然后才能設(shè)計(jì)一個(gè)比較合理的框架。我們需要實(shí)現(xiàn)什么紊册?大致總體設(shè)計(jì)過(guò)程策略我的基本采用如下圖所示思路(我比較喜歡繪圖比肄,圖會(huì)讓人比較直觀)


問(wèn)自己第一個(gè)問(wèn)題是:這個(gè)項(xiàng)目要實(shí)現(xiàn)什么主要功能?這個(gè)來(lái)自哪里囊陡?如果是實(shí)際產(chǎn)品開(kāi)發(fā)芳绩,則可能來(lái)自市場(chǎng)的需求,如果是自己的DIY項(xiàng)目撞反,也一定會(huì)YY出一個(gè)大致的想法妥色?總之不管源自何方,需求總要先梳理清楚遏片。那么需求一般意義上包含哪些呢嘹害?

哪些是硬件IO接口需求,比如開(kāi)關(guān)量輸入吮便,ADC采樣笔呀,I2C/SPI通信等等

哪些是業(yè)務(wù)邏輯需求,比如要采集一個(gè)傳感器量數(shù)據(jù)髓需,控制一個(gè)加熱裝置许师,那么這是高內(nèi)聚的需求。

哪些是算法相關(guān)的技術(shù)需求授账,比如產(chǎn)品中哪些信號(hào)需要濾波處理枯跑,哪些需要做頻域分析等等。

是否有對(duì)外的通信協(xié)議需求白热。

是否有業(yè)務(wù)數(shù)據(jù)需要?dú)v史存儲(chǔ)敛助,或者設(shè)備參數(shù)需要掉電保存

是否需要有日志打印需求。

........

不一而足屋确。

結(jié)合固件模塊原理以及相關(guān)指導(dǎo)原則纳击,那么將相關(guān)性高的需求,抽象實(shí)現(xiàn)在一系列的模塊中攻臀,在由這一系列模塊配合實(shí)現(xiàn)某個(gè)相關(guān)性高的業(yè)務(wù)需求焕数,再進(jìn)一步這些模塊就變成一個(gè)子系統(tǒng)。多個(gè)子系統(tǒng)在main.c的調(diào)度下刨啸,協(xié)調(diào)完成產(chǎn)品的整體功能堡赔。

如何集成調(diào)度

對(duì)于某些不使用RTOS的應(yīng)用而言,可以使用如下的框架進(jìn)行:

voidmain(void)

{

/*各模塊初始化*/

init_module_1();

init_module_2();

....

while(1)

{

/*實(shí)現(xiàn)一個(gè)定時(shí)調(diào)度策略*/

if(timer50ms)

{

timer50ms?=0设联;

app_module_1();

}

if(timer100ms)

{

timer100ms?=0善已;

app_module_2();

}

/*異步請(qǐng)求處理,如中斷后臺(tái)處理*/

if(flag1)

{

communication_handler();

}

.....

}

}

對(duì)于基于RTOS的集成實(shí)現(xiàn)舉例:

voidtask1(void)

{

/*處理子系統(tǒng)相關(guān)的初始化*/

init_task1();

while(1)

{

/*應(yīng)用相關(guān)調(diào)用*/

task1_mainbody();

....

}

}

....

voidtaskn(void)

{

/*處理子系統(tǒng)相關(guān)的初始化*/

init_taskn();

while(1)

{

/*應(yīng)用相關(guān)調(diào)用*/

taskn_mainbody();

....

}

}

voidmain(void)

{

/*一些基本硬件相關(guān)初始化,比如IO,時(shí)鐘灼捂,OS?tick定時(shí)器等*/

init_hal();

......

/*一些基本RTOS初始化*/

init_os();

/*任務(wù)創(chuàng)建*/

os_creat("task1",task1,棧設(shè)置,優(yōu)先級(jí)换团,...);

......

os_creat("taskn",taskn,棧設(shè)置悉稠,優(yōu)先級(jí),...);

/*啟動(dòng)OS調(diào)度器艘包,交由OS調(diào)度管理應(yīng)用任務(wù)*/

os_start();

}

具體不同的RTOS的猛,其函數(shù)名各有不同,但大致思路一般都差不多想虎。

總結(jié)一下

本文從為什么需要模塊化設(shè)計(jì)整體架構(gòu)卦尊,到這樣做的好處,以及具體做的一些指導(dǎo)原則磷醋,再到實(shí)際中如何實(shí)現(xiàn)猫牡,怎么做到高內(nèi)聚低耦合,提供了一些個(gè)人工作中的體會(huì)以及思路邓线。同時(shí)對(duì)于裸機(jī)程序整體框架淌友、基于RTOS的集成框架做了兩個(gè)demo,基本能解決大部分的框架思路問(wèn)題骇陈。將前文中的一些個(gè)人推崇的原則震庭,在加粗總結(jié)下:

所有與模塊相關(guān)的功能都應(yīng)集成到單個(gè)源文件中,這是高內(nèi)聚的體現(xiàn)你雌。

模塊對(duì)外提供一個(gè)頭文件器联,該文件聲明了該模塊的所有資源(硬件依賴/宏/常量/變量/函數(shù))。盡量用struct將緊密相關(guān)的變量進(jìn)行集總封裝婿崭。

在源文件中包括自檢代碼部分拨拓,以實(shí)現(xiàn)該模塊模塊的所有自檢功能谊惭。

固件模塊的接口應(yīng)經(jīng)過(guò)精心設(shè)計(jì)和定義暂雹。

由于固件取決于硬件,因此需要在源文件頭中明確提及硬件的相關(guān)性罢猪。比如利用宏將硬件依賴轉(zhuǎn)定義授瘦,或者利用函數(shù)將基本操作進(jìn)行封裝醋界。則在新的架構(gòu)體系,僅僅需要移植這部分實(shí)現(xiàn)即可使用提完。

通常形纺,固件模塊可供其他團(tuán)隊(duì)成員在其他項(xiàng)目中使用。 可能涉及到管理更改徒欣,缺陷修復(fù)逐样、所有者應(yīng)維護(hù)模塊。 源文件頭應(yīng)包含“作者”和“版本”信息。

固件在某種程度上取決于編譯器官研。 源文件頭中應(yīng)聲明基于什么開(kāi)發(fā)環(huán)境進(jìn)行過(guò)驗(yàn)證秽澳,以指定編譯器或與IDE相關(guān)的信息。

極力建議采用先設(shè)計(jì)-后開(kāi)發(fā)的模式戏羽,忌諱逐步debug,想到哪里寫(xiě)到哪里楼吃。當(dāng)然對(duì)于新手學(xué)習(xí)而言始花,后一種模式,可以逐步漸進(jìn)迭代孩锡,也可以比較快的增長(zhǎng)經(jīng)驗(yàn)酷宵。當(dāng)然如何取舍,全憑個(gè)人意愿躬窜。

相信您如深入閱讀浇垦,細(xì)細(xì)體會(huì),應(yīng)該從設(shè)計(jì)思想上得到些領(lǐng)悟荣挨,有所提高男韧。如果能幫助到您,則我心甚慰默垄,也不枉辛苦碼了這么多字此虑。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市口锭,隨后出現(xiàn)的幾起案子朦前,更是在濱河造成了極大的恐慌,老刑警劉巖鹃操,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件韭寸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡荆隘,警方通過(guò)查閱死者的電腦和手機(jī)恩伺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)臭胜,“玉大人莫其,你說(shuō)我怎么就攤上這事∷嗜” “怎么了乱陡?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)仪壮。 經(jīng)常有香客問(wèn)我憨颠,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任爽彤,我火速辦了婚禮养盗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘适篙。我一直安慰自己往核,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布嚷节。 她就那樣靜靜地躺著聂儒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪硫痰。 梳的紋絲不亂的頭發(fā)上衩婚,一...
    開(kāi)封第一講書(shū)人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音效斑,去河邊找鬼非春。 笑死,一個(gè)胖子當(dāng)著我的面吹牛缓屠,可吹牛的內(nèi)容都是我干的奇昙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼藏研,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼敬矩!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蠢挡,我...
    開(kāi)封第一講書(shū)人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤弧岳,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后业踏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體禽炬,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年勤家,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腹尖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡伐脖,死狀恐怖热幔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情讼庇,我是刑警寧澤绎巨,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站蠕啄,受9級(jí)特大地震影響场勤,放射性物質(zhì)發(fā)生泄漏戈锻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一和媳、第九天 我趴在偏房一處隱蔽的房頂上張望格遭。 院中可真熱鬧,春花似錦留瞳、人聲如沸拒迅。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)坪它。三九已至,卻和暖如春帝牡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蒙揣。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工靶溜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人懒震。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓罩息,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親个扰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瓷炮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348