模塊化編程
模塊化編程是開發(fā)者首先會掌握的一種編程思想,就像前面我們多次提到的把一些特定功能的代碼大打包成一個(gè)函數(shù),這么一來以后在其他項(xiàng)目中就可以通過復(fù)制、粘貼輕松的移植了。這就是最簡單的模塊化思想了,當(dāng)然如果要規(guī)范一點(diǎn)的話我們需要在函數(shù)前做一些必要的注釋說明,方便自己日后重新使用這段代碼時(shí)能很快就可以知道代碼的功能。比如下面這段代碼:
/*
- 函數(shù)名:led_display
- 描 述:****
- 輸 入:****
- 輸 出:****
- 返 回:****
- 調(diào) 用:****
- 備 注:****
*/
void led_display(uint16_t duty ,uint8_t up_rate,uint8_t down_rate)
{
****
}
當(dāng)然也可以不用寫這么多注釋,注釋內(nèi)容只要能保證可以看懂函數(shù)的意思就行,內(nèi)容根據(jù)自己的喜好來確定,比如你也可以這樣些:
/**
- @brief :****
- @param : ****
- @retval : ****
**/
甚至你也可以直接就用一句注釋說明,比如:
/* **** */
或者直接
// ****
只要能保證可以一眼看懂你的說明就行,當(dāng)然最好是自形成一個(gè)統(tǒng)一的格式注釋格式,讓程序看起來更加美觀大方,以后別人讀你的程序心情也會好很多,畢竟項(xiàng)目開發(fā)不一定是你一個(gè)人完成特別是互聯(lián)網(wǎng)項(xiàng)目,比如一個(gè)大型項(xiàng)目就涉及到前端開發(fā)者,后端開發(fā)者,移動端(或桌面端)開發(fā)者,甚至還需要測試人員配合,這種情況雖然不會每個(gè)人都需要看你的代碼但至少會有人來跟你協(xié)同完成應(yīng)用功能。所以很多公司都會規(guī)定注釋規(guī)范或編程規(guī)范。
平時(shí)編程時(shí)除了善于打包函數(shù)將程序模塊化,但項(xiàng)目中很多時(shí)候我們的程序不止是幾個(gè)函數(shù),我們項(xiàng)目可能使用了大量的外設(shè),程序中需要大量的函數(shù)來完成功能,這時(shí)候如果只是打包函數(shù),我們的代碼也會變得非常龐大,如果一個(gè)文件里面有幾千甚至上萬行代碼,你日后維護(hù)起來是不是會崩潰,可能找一個(gè)bug都能找半天,這時(shí)候我們需要怎么辦呢?其實(shí)C語言庫文件就已經(jīng)告訴我們做法了,當(dāng)然其他語言也是一樣。這時(shí)候我們就可以進(jìn)行分文件打包程序,就是將相同功能或控制同一個(gè)外設(shè)的代碼放在同一個(gè)文件里進(jìn)行模塊化,這樣做是不是比單純使用的函數(shù)會更有優(yōu)勢,以后我們?nèi)绻貜?fù)使用這些代碼都不用在代碼中查找這些函數(shù)的了,直接將整個(gè)文件拷貝就可以了,現(xiàn)在我們一起來看看這是怎么實(shí)現(xiàn)的。
以上圖片中展示的就是這種模塊化的做法,在項(xiàng)目中將不同作用的代碼放在不同的文件夾下,將不同模塊的代碼放在不同的***.c文件里面,這樣后續(xù)修改,調(diào)試,移植代碼的效率就會大大提高。那這種做法是怎么實(shí)現(xiàn)的呢,之前我們在介紹C語言文件時(shí)說過C語言文件主要有兩種,即.c和.h文件,.c文件我們稱它問源文件,.h文件我們稱為頭文件,在源文件中我們存放各種變量或功能函數(shù)的定義等內(nèi)容,在頭文件中聲明對應(yīng)的變量、函數(shù)或宏定義等等,所以一般情況下源文件和頭文件都是成對出現(xiàn)的。若某個(gè)文件需要使用另一個(gè)文件中定義的函數(shù)時(shí)就在該文件頭部添加另一個(gè)文件對應(yīng)的頭文件,這樣就可以實(shí)現(xiàn)各部模塊程序相互調(diào)用了。通過源文件和頭文件分離,當(dāng)你遇到有一些代碼文件你不想給別人知道,但又要發(fā)布給別人,這種情況下也有對應(yīng)的處理辦法,具體的做法我們后面再介紹。
一般情況下在不同的源文件中我們也會像之前些函數(shù)一樣對該文件做一些注釋描述,比如:
/**** (C) COPYRIGHT *********
- 文件名 :****
- 描述 :****
- 操作系統(tǒng) :****
- 軟件平臺 :****
- 硬件基礎(chǔ) :****
- 庫版本 :****
- 作者 :****
- 版本編號 :****
- 修改時(shí)間 :****
- 修改說明 :****
**********************************/
當(dāng)然你也可以不用寫這么多內(nèi)容,以你能看懂為準(zhǔn)。
接下來
多任務(wù)編程
一般情況下我們在使用 51、AVR、STM32等單片機(jī)編程的時(shí)候都是在main函數(shù)里面用while(1)做一個(gè)大循環(huán)來完成所有的處理,即應(yīng)用程序是一個(gè)無限的循環(huán),循環(huán)中調(diào)用相應(yīng)的函數(shù)完成所需的處理。有時(shí)候我們也需要用到中斷來完成一些功能操作。相對于多任務(wù)系統(tǒng)而言,這個(gè)就是單任務(wù)系統(tǒng),也稱作前后臺系統(tǒng),中斷服務(wù)函數(shù)作為前臺程序,大循環(huán)while(1)作為后臺程序。
類似一下的做法:
void IRQHandler_fun()
{
flag1 = 1;
}
int main (void)
{
while(1)
{
if (1 = flag1)
{
func1();
flag1 = 0;
}
}
}
這種做法在一般項(xiàng)目中是完全沒有問題的,但是這種程序結(jié)構(gòu)的一個(gè)缺陷是它的前后臺系統(tǒng)的實(shí)時(shí)性不強(qiáng),前后臺系統(tǒng)各個(gè)任務(wù)(應(yīng)用程序)都是排隊(duì)等著輪流執(zhí)行,不管你這個(gè)程序現(xiàn)在有多緊急,沒輪到你就只能等著!相當(dāng)于所有任務(wù)(應(yīng)用程序)的優(yōu)先級都是一樣的。在小項(xiàng)目中前后臺系統(tǒng)簡單,資源消耗也少,單片機(jī)處理起來還得心應(yīng)手!但在稍微大一點(diǎn)的嵌入式應(yīng)用中前后臺系統(tǒng)對實(shí)時(shí)性要求較高的應(yīng)用中就明顯力不從心了。這時(shí)候就需要進(jìn)行多任務(wù)處理了,就像一下的做法:
void first_task()
{
while (1)
{
if(has_data())
put_data();
}
}
void second_task()
{
while (1)
{
if(get_data())
do_something();
}
}
int main(void)
{
create_task(first_task);
create_task(second_task);
start_task();
}
相信細(xì)心的朋友一眼就能發(fā)現(xiàn),這種做法很明顯的不一樣就是每個(gè)任務(wù)函數(shù)中都有一個(gè)while (1)死循環(huán),是不是我們只要在程序中多使用一些while (1)死循環(huán)就OK了呢?簡單說可以這么認(rèn)為吧,當(dāng)然實(shí)際情況肯定不會這么簡單。
那多任務(wù)編程是怎么實(shí)現(xiàn)程序功能的呢?多任務(wù)系統(tǒng)把一個(gè)大問題“分而治之”,把大任務(wù)劃分成很多個(gè)小問題,逐步的把小任務(wù)解決掉,大任務(wù)也就隨之解決了,這些任務(wù)是并發(fā)處理的。注意,并不是說同一時(shí)刻一起執(zhí)行很多個(gè)任務(wù),畢竟我們單片機(jī)項(xiàng)目都是使用單核芯片,任意時(shí)刻它也只能存在一個(gè)任務(wù)占用其內(nèi)核,它是由于每個(gè)任務(wù)執(zhí)行的時(shí)間很短,導(dǎo)致看起來像是同一時(shí)刻執(zhí)行了很多個(gè)任務(wù)一樣。
看了以上內(nèi)容是不是感覺多任務(wù)編程非常的難?其實(shí)難肯定是難,但也沒有那么難,因?yàn)楝F(xiàn)在有非常多開源多任務(wù)系統(tǒng)供我們選擇,比如freeRTOS,RT-Thread,UC/OS等等,所以就不用我們自己從零寫一個(gè)多任務(wù)系統(tǒng)了,使用時(shí)選擇一個(gè)合適的系統(tǒng)進(jìn)行移植就能減少很多工作量了。
像以上這個(gè)項(xiàng)目,調(diào)用freeRTOS來實(shí)現(xiàn)程序功能。當(dāng)然這種編程思維不是一兩天就能掌握,需要一定的項(xiàng)目經(jīng)驗(yàn),所以初學(xué)者理解起來困難并不要緊,要緊的是自己需要把C語言基礎(chǔ)掌握好,基礎(chǔ)牢固了編程就有感覺了。
面向?qū)ο缶幊?/p>
前面介紹C語言基礎(chǔ)時(shí)我們說過編程語言主要有面向過程和面向?qū)ο髢煞N,C語言是典型的面向過程的一種編程語言。你們你可能就會想為什么我們使用C語言開發(fā)單片機(jī)程序還需要面向?qū)ο蟮木幊趟季S呢,是要使用面向?qū)ο蟮木幊陶Z言來開發(fā)了嗎?當(dāng)然不是,我們還是使用C語言來編程,只是呢,在編程時(shí)我們按面向?qū)ο蟮姆绞絹硖幚砥湎嚓P(guān)功能,簡單的說就是把C語言通過一定的處理技巧封裝成像面向?qū)ο蟮木幊陶Z言一樣來執(zhí)行程序功能。
對于流程清晰的簡單程序,一般只有一條流程主線,很容易被劃分成順序執(zhí)行的幾個(gè)步驟,面向?qū)ο缶幊毯兔嫦蜻^程編程沒有太大差別,并且面向過程編程相對比面向?qū)ο缶幊谈又庇^高效。
當(dāng)我們面對一個(gè)大型的復(fù)雜程序,由于其錯(cuò)綜復(fù)雜的流程和交互關(guān)系,很難將其簡單地拆分成一條主線串成的簡單步驟,而通常表現(xiàn)為一個(gè)網(wǎng)狀關(guān)系結(jié)構(gòu)。這個(gè)時(shí)候,面向過程編程的這種流程化和線性化的思維方式就會顯得比較吃力,而面向?qū)ο缶幊痰膬?yōu)勢就比較明顯了。
這也是為什么說面向?qū)ο蟮木幊陶Z言是更高級的語言的原因之一,面向?qū)ο缶幊田L(fēng)格的代碼更容易復(fù)用、擴(kuò)展和維護(hù)、更高級、更人性化、更適合大規(guī)模復(fù)雜程序的開發(fā)。在C語言編程的一些操作系統(tǒng)(包括Unix,linux以及Windows等等)中就會用到的面向?qū)ο蟮木幊谭绞?,里面有很多的結(jié)構(gòu)體、指針、鏈表等內(nèi)容。如果還沒有接觸到面向?qū)ο缶幊讨荒苷f明你做的東西還不夠復(fù)雜。當(dāng)然也不是非要掌握這個(gè)技能你才能進(jìn)行項(xiàng)目開發(fā),只是你掌握了這些技能你就能開發(fā)更復(fù)雜的工程了。你的薪酬肯定也會比其他人高。
我們想來看看一些簡單的面向?qū)ο缶幊檀a:
以上是STM32某系列芯片官方標(biāo)準(zhǔn)庫函數(shù)中的部分片段代碼。
這是freeRTOS中任務(wù)創(chuàng)建的函數(shù),對于初學(xué)者來說這些咋看都是很深奧的內(nèi)容,所以這里也不需要你現(xiàn)在就掌握它,以后你有機(jī)會接觸它們,現(xiàn)在知道有這么回事就好了。現(xiàn)在總不至于哪天面試的時(shí)候被人問你一句你是否嘗試過面向?qū)ο箝_發(fā)單片機(jī)程序時(shí)自己啥話都說不上來。
這是微軟某個(gè)版本的某文件的某段源碼,看起來很復(fù)雜吧,說實(shí)話這段代碼我也沒細(xì)看,細(xì)看也一時(shí)不可能看明白,只是剛好看到就這個(gè)就貼上來跟大家一起分享一下。
評論