當(dāng)前位置:首頁 > 公眾號(hào)精選 > 嵌入式微處理器
[導(dǎo)讀]C語言到底該怎么學(xué),單片機(jī)coder怎么才能順利轉(zhuǎn)型成為嵌入式programer?21ic論壇有一“鎮(zhèn)站之寶”的超長經(jīng)驗(yàn)分享貼,特此分享給所有熱愛coding的你。


C語言到底該怎么學(xué),單片機(jī)coder怎么才能順利轉(zhuǎn)型成為嵌入式programer?21ic論壇有一“鎮(zhèn)站之寶”的超長經(jīng)驗(yàn)分享貼,特此分享給所有熱愛coding的你。


之前和大家談了一點(diǎn)UML在嵌入式開發(fā)中的使用,以及鏈表、哈希表等數(shù)據(jù)結(jié)構(gòu)在實(shí)現(xiàn)對(duì)象之間的交互機(jī)制(設(shè)計(jì)模式)的一點(diǎn)簡(jiǎn)單實(shí)例。有很多朋友表示很感興趣,21ic高手云集,有點(diǎn)班門弄斧的感覺,所以還望盡情拍磚。之前的帖子很亂,除了因?yàn)樘S意沒有準(zhǔn)備外,更主要是因?yàn)楸救艘蔡幱诎肫孔与A段,所談問題題目又太大。對(duì)此我只能憑借拙見,談點(diǎn)個(gè)人的理解,由于本人是這方面的新手,憑借一己之熱情,大放厥詞,還請(qǐng)各位斧正。

其實(shí)UML就是一個(gè)工具,提供了用例圖、順序圖、活動(dòng)圖、類圖、狀態(tài)機(jī)圖、部署圖、包圖等工具,輔助工程師完成:分析->設(shè)計(jì)->實(shí)施->測(cè)試的整個(gè)過程。每個(gè)過程都有細(xì)分,例如分析階段:首先是需求分析,然后有系統(tǒng)分析,再次有對(duì)象結(jié)構(gòu)分析等。需求分析階段會(huì)使用到用例圖、順序圖、狀態(tài)機(jī)圖、順序圖等,需求分析階段的最重要的要素是軟件的外部功能視圖和使用場(chǎng)景等。其中前者使用用例圖表述,它也提供了溝通用戶和開發(fā)者的橋梁;后者用順序圖或狀態(tài)機(jī)圖等表述,提供了系統(tǒng)每個(gè)功能實(shí)現(xiàn)的可能路徑。其他過程和需求分析階段類似,這里篇幅所限就不再一一提及。UML就是這樣同我們的設(shè)計(jì)過程關(guān)聯(lián)起來的。

將面向?qū)ο蟮姆椒ㄓ糜贛CU是有必要的,也是可能的,當(dāng)然也是很有效的。這樣的努力最起碼可以拉近mcu開發(fā)者同其他領(lǐng)域的C開發(fā)者之間的距離,彌補(bǔ)那道似乎難以逾越的鴻溝,比如看到linux內(nèi)核代碼時(shí),你會(huì)發(fā)現(xiàn)原來如此親切。當(dāng)然,隨著對(duì)面向?qū)ο蠓椒ǖ纳钊肜斫?,你?huì)發(fā)現(xiàn)C++也不再那么讓你不知道如何使用,或者把C++用得像面向過程的語言一樣。當(dāng)然本人C++菜鳥,還望高手指教。

然而面向?qū)ο蟮姆椒ㄒ卜且货矶?,一朝搞定,它是一個(gè)循序漸進(jìn)的過程,特別是應(yīng)用與mcu這樣的平臺(tái),好多東西是靠摸索出來的。如何開始,先從何處下手是個(gè)問題。

21ic同仁liufb提議:“正如《重構(gòu)與模式》所說:如果想成為一名更優(yōu)秀的軟件設(shè)計(jì)師,了解優(yōu)秀軟件設(shè)計(jì)的演變過程比學(xué)習(xí)優(yōu)秀設(shè)計(jì)本身更有價(jià)值,因?yàn)樵O(shè)計(jì)的演變過程中蘊(yùn)藏著大智慧?!?br style="word-break: break-all;color: rgb(68, 68, 68);font-family: Tahoma, "Microsoft Yahei", Simsun;text-align: start;background-color: rgb(222, 240, 251);">
我決定發(fā)掘一下我近十年以來的階段性C代碼,試圖去發(fā)現(xiàn)一點(diǎn)什么,這個(gè)我之前還從未嘗試過,能找到的一起的代碼也寥寥無幾。不過我覺得值得一試,那就從此開始吧。

努力發(fā)掘,搜索N年前的郵箱,居然找到了當(dāng)時(shí)在一款A(yù)T89X52單片機(jī)上的處女作。就從它開始入手了。

時(shí)代背景:2006年,鄭州某小公司,之前的工作是修手機(jī),然后是在某氣體傳感器公司焊接維護(hù)生產(chǎn)設(shè)備,再后來在這家小公司畫電路板,然而軟件才是我的最愛。好不容易boss開恩,讓我參與到寫代碼的行列。之前的進(jìn)度讓在鄭州這種蝸牛般的工作節(jié)奏的大氛圍里面的boss也覺得忍無可忍,于是我加入了。

代碼太長,截取一部分吧。里面只有我寫的一個(gè)子函數(shù),大部分是同事寫的。?

由于做開始工作的同事不太會(huì)用多文件,所以這個(gè)項(xiàng)目的代碼只有一個(gè)文件,連頭文件都沒有,整個(gè)文件有2600行代碼。以下我將列舉它的三大部分:

? ? ? ? ? 1.全局變量部分。
? ? ? ? ? 2.部分子函數(shù)。
? ? ? ? ? 3.main函數(shù)。

最后我將會(huì)用現(xiàn)在的眼光,結(jié)合大師(Grady Booch)的經(jīng)典,分析一下這部分代碼。

全局變量部分:
bit FUN,Shift_on,Add_on,Sub_on,fun_flag;bit dspflagz1,dspflagz2,dspflagz3;unsigned char z1,z2,td3,working,DSP_m,DSP_n;unsigned char l1,l2,r,m;bitflagv,flagy,flags,flag0,flagx,beepflag1,beepflag2,flagt,flagw;bit working_on,function_on, AINTSYR_on,AINTSYW_on,BINTSYR_on,BINTSYW_on ;bitprogram_on,program_woking,up_flag,down_flag,up_on,down_on;unsigned char AINTSY_state, BINTSY_state, function_state;unsigned char tx1,tx2,tx3,tw,TX,t;unsigned char display_state ,x1,x2,w1,w2;unsigned char program_state,program_working;unsigned char clk_number;unsigned char code DS[]={0,33,63,86,100,86,63,33};unsigned chards_curtime[6]={0x05,0x03,0x02,0x01,0x07,0x06};unsigned char clk_data[6]={06,1,1,0,0,1};unsigned char set_time[6];sbit switch_work= 0xB0;sbit switch_function=0xB1;sbit switch_program=0xB2;sbit switch_up=0x90;sbit switch_down=0x91;sbit switch_AINTSYR=0x92;sbit switch_AINTSYW=0x93;sbit switch_BINTSYR=0x94;sbit switch_BINTSYW=0x95;sbit RS=0xA2;sbit RW=0xA1;sbit E=0xA0;sbit CS2=0xA3;sbit CS1=0xA4;sbit DACS1=0xA7;sbit DACS2=0xA6;sbit DACS3=0xA5;sbit ds_sclk=0xB3 ; /*初始化變量*/sbit ds_io=0xB4;sbit ds_rst=0xB5;
初評(píng):除了最后是管腳定義外,前邊都是全局的標(biāo)志位或全局變量。

這個(gè) void text(void)可是我的處女作啊
//////////////////////////////////////////////////////////////////////////////////////////////////////void text(void){
bit Flag_add; /*加一標(biāo)志*/ bit Flag_sub; /*減一標(biāo)志*/ unsigned char max_value; /*各時(shí)間單位的最大值*/ unsigned char min_value; /*各時(shí)間單位的最小值*/ /*if(FUN==1) { */ /*定義標(biāo)志位*/ if(switch_work==0) /*移位鍵*/ { if(Shift_on==0) { Shift_on=1; buzzer(); clk_number++; if(clk_number>6)clk_number=1; } } else Shift_on=0; if(switch_up==0) /*加一鍵*/ { if(Add_on==0) { Add_on=1; buzzer(); Flag_add=1; } } else Add_on=0; if(switch_down==0) /*減一鍵*/ { if(Sub_on==0) { Sub_on=1; buzzer(); Flag_sub=1; } } else Sub_on=0; switch(clk_number) { case 1: max_value=99;min_value=0;break; case 2: max_value=12;min_value=1;break; case 3: if(clk_data[1]==1|| clk_data[1]==3|| clk_data[1]==5|| clk_data[1]==7|| clk_data[1]==8|| clk_data[1]==10|| clk_data[1]==12) max_value=31; /*1,3,5,7,8,10,12*/ else if( clk_data[1]==4|| clk_data[1]==6|| clk_data[1]==9|| clk_data[1]==11) max_value=30; /*4,6,9,11*/ else if((clk_data[0]%4==0)||clk_data[0]==0) max_value=29; /*閏年*/ else max_value=28; min_value=1; break; case 4: max_value=23;min_value=0;break; case 5: max_value=59;min_value=0;break; case 6: max_value=7;min_value=1;break; } if(Flag_add==1) { clk_data[clk_number-1]++; Flag_add=0; if(clk_data[clk_number-1]>max_value) clk_data[clk_number-1]=min_value; } else if(Flag_sub==1) { clk_data[clk_number-1]--; Flag_sub=0; if(clk_data[clk_number-1]-1]==0xff) clk_data[clk_number-1]=max_value; } if(switch_function==0) { if(function_on==0) { function_on=1; FUN=0; buzzer(); function_state=1; fun0_flag=1; set_time[0]=(clk_data[4]/10)*0x10+(clk_data[4]%10); set_time[1]=(clk_data[3]/10)*0x10+(clk_data[3]%10); set_time[2]=(clk_data[2]/10)*0x10+(clk_data[2]%10); set_time[3]=(clk_data[1]/10)*0x10+(clk_data[1]%10); set_time[4]=(clk_data[5]/10)*0x10+(clk_data[5]%10); set_time[5]=(clk_data[0]/10)*0x10+(clk_data[0]%10);

} } else { function_on=0; }
}
注:上面這個(gè)函數(shù)是我在51里面跑過的第一段代碼,很有收藏價(jià)值,哈哈

//////////////////////////////////////////////////////////////////////////////////////////////////////
下邊是我同事的一個(gè)函數(shù),女工程師,女中豪杰,哈哈
//////////////////////////////////////////////////////////////////////////////////////////////////////
void check_switch(){ if(FUN==0) { if(switch_work==0) { if(working_on==0) {  working_on=1; buzzer(); if(working==1) { working=0; flag0=0; } else { working=1; flag0=1; } } } else { working_on=0; }
if(switch_function==0){ if(function_on==0){ function_on=1; buzzer(); program_state=1; if(function_state==1){ function_state=2;
} else { if(function_state==2){ function_state=3;

} else { if(function_state==3){ function_state=4; FUN=1; fun1_flag=1; working=0; flagx=1; } else { function_state=1;
} } } } } else { function_on=0; }
if(switch_program==0) { if(program_on==0) { program_on=1; buzzer(); program_working=1;
flagv=1; flagy=1; flagt=1; flagw=1; } } else { program_on=0;
} if(switch_up==0) { if(up_flag==0) { up_flag=1; up_on=1; buzzer(); } } else { up_flag=0; } if(switch_down==0) { if(down_flag==0) { down_flag=1; down_on=1; buzzer(); } } else { down_flag=0; } if(switch_AINTSYR==0) { if(AINTSYR_on==0) { AINTSYR_on=1; buzzer(); if(AINTSY_state<=63) { AINTSY_state=AINTSY_state+1; }
} } else { AINTSYR_on=0; }
if(switch_AINTSYW==0) { if(AINTSYW_on==0) { AINTSYW_on=1; buzzer(); if(AINTSY_state>=1) { AINTSY_state=AINTSY_state-1; }
} } else { AINTSYW_on=0; } if(switch_BINTSYR==0) { if(BINTSYR_on==0) { BINTSYR_on=1; buzzer(); if(BINTSY_state<=63) { BINTSY_state=BINTSY_state+1; }
} } else { BINTSYR_on=0; }
if(switch_BINTSYW==0) { if(BINTSYW_on==0) { BINTSYW_on=1; buzzer(); if(BINTSY_state>=1) { BINTSY_state=BINTSY_state-1; }
} } else { BINTSYW_on=0; } } else { text(); }
}
初評(píng):注意到?jīng)]有,幾乎沒有用到參數(shù)傳遞,整個(gè)代碼所有數(shù)據(jù)全是通過全局變量傳遞的。這個(gè)我將在后邊做出分析。

最后是main函數(shù):
void main(){  init_working();/*初始化程序*/

start_t0:check_switch();
prepare_work(); if(fun0_flag==1){ ds_settime(set_time); } display();
goto start_t0;}

站在巨人的肩上才能看得更遠(yuǎn),先找一個(gè)這樣的肩膀吧。從關(guān)于代碼演變的基礎(chǔ)知識(shí)說起:

【基礎(chǔ)知識(shí)普及】

Wegner將早期高級(jí)語言做了分類:



以上內(nèi)容摘自GradyBooch《面向?qū)ο螅悍治雠c設(shè)計(jì)》
擴(kuò)展一下Wegner的分類:



以上內(nèi)容摘自Grady Booch《面向?qū)ο螅悍治雠c設(shè)計(jì)》

接下來看一下第一代和第二代早期程序設(shè)計(jì)語言的程序結(jié)構(gòu):
程序設(shè)計(jì)語言-早期結(jié)構(gòu).jpg?(18.77 KB, 下載次數(shù): 0)

特點(diǎn):
1. 所有子程序共用全局?jǐn)?shù)據(jù)。
2. 共用數(shù)據(jù)帶來大量交叉耦合。
3. 大量的標(biāo)志位或者數(shù)據(jù)定義很難讀懂它到底代表什么意思。

從前面的分類看,C怎么也是3代以后的語言(1970-1980)。再看一下我前面的例子中我和我同事的代碼,幾乎就是第一代和第二代。

早期語言寫程序的特點(diǎn):

1.幾乎很少使用參數(shù)傳遞,所有子程序幾乎全部依靠共有全局變量來傳遞數(shù)據(jù)。
2.共享全局變量帶來的交叉耦合讓這個(gè)代碼調(diào)試起來非常費(fèi)勁,因?yàn)槊總€(gè)函數(shù)都不是獨(dú)立的,它依賴自身使用的大量全局變量。在MCU上,因?yàn)橹袛嗪瘮?shù)帶來的并發(fā)性,如果有全局變量在中斷內(nèi)外都用到,那就會(huì)帶來很多麻煩。這樣的代碼如果在多任務(wù)環(huán)境中將會(huì)更糟糕。
3.再看看可讀性,大量全局變量和全局標(biāo)志位讓代碼的可讀性非常差:首先是如此之多的變量和標(biāo)志位所要表達(dá)的意圖,再者他們分散得到處都是,即使不考慮并發(fā)性也讓閱讀的人摸不到頭腦。
4.看看擴(kuò)展性和可維護(hù)性,如果有bug被測(cè)出,你定位問題將是極其困難的,因?yàn)檫@里的子程序沒有內(nèi)聚性,功能不獨(dú)立,再加上可讀性差,情況非常糟糕。如果要添加新功能,也是一件極其麻煩的事情,牽一發(fā)而動(dòng)全身,要改的地方太多了。這樣的編程方式也無法將項(xiàng)目做大。

你有這樣的經(jīng)歷嗎?剛開始做單片機(jī)的差不多都是這樣的思維吧,除非之前有過其它領(lǐng)域的C開發(fā)經(jīng)驗(yàn)。

為什么三代以后的語言寫的程序出現(xiàn)了二代前期以前的特點(diǎn)?我們可以將它稱作“語言的返祖現(xiàn)象”(這個(gè)將在后邊專門討論,這個(gè)概念可是咱首次提出的哦,哈哈,臭美一下)。

說明一個(gè)問題:每一代語言的開發(fā)者都是針對(duì)當(dāng)時(shí)成熟而又先進(jìn)的軟件思想完成的,它里面支持他所要實(shí)現(xiàn)的那種編程思想所具備的大部分特征。但是如果使用者自己沒有理解這種新的思想,而不使用這些新的特征,就會(huì)出現(xiàn)這種“返祖”現(xiàn)象,就像我身邊很多人使用C++的時(shí)候,完全是用C的思維在編程,那些面向?qū)ο蟮奶卣魍耆闪怂麄兊南拗疲@也算是“返祖現(xiàn)象”。

既然你發(fā)現(xiàn)了它的缺陷,既然你已經(jīng)知道它的‘錯(cuò)’在哪里,那問題可以得到解決了吧!問題怎么解決,如何改進(jìn)?別著急!接下來我們將會(huì)看語言的下一次革新,還會(huì)附上下一階段的代碼做為實(shí)例。我簡(jiǎn)直就是一部活生生的發(fā)展史了。

插播【基礎(chǔ)知識(shí)普及】
軟件固有特性:復(fù)雜性等問題
【軟件固有的復(fù)雜性】
* 問題域的復(fù)雜性 :
? ?? ?? ?—— 非功能性需求(可用性,健壯性,成本,性能等)的加入。
? ?? ?? ?—— 開發(fā)人員同用戶之間溝通的困難。
? ?? ?? ?—— 設(shè)計(jì)過程中的需求變更。
* 管理開發(fā)過程的困難性
* 軟件中隨處可見的靈活性
—— 這直接導(dǎo)致了軟件開發(fā)領(lǐng)域不能像其他領(lǐng)域,可以對(duì)每個(gè)構(gòu)件給出一個(gè)行業(yè)標(biāo)準(zhǔn),比如機(jī)械制造行業(yè),軟件行業(yè)卻很少能如此。這導(dǎo)致開發(fā)者需要打造大部分模塊。
* 描述離散系統(tǒng)行為的問題
—— 無法用連續(xù)系統(tǒng)建模的方式,本身也不受物理規(guī)律支配。

【復(fù)雜系統(tǒng)的5個(gè)屬性】
* 層次性:可以逐級(jí)劃分子系統(tǒng)。
* 相對(duì)本原:不同觀察者對(duì)同系統(tǒng)組件的本原性認(rèn)識(shí)不同。
* 分離關(guān)注:根據(jù)“組件”內(nèi)部之間關(guān)聯(lián)程度高于“組件”間外部關(guān)聯(lián)程度來劃分組件。
* 共同模式:如細(xì)胞,管脈系統(tǒng)在動(dòng)物和植物中都有。
* 穩(wěn)定的中間形式:復(fù)雜系統(tǒng)是演化而來的,曾經(jīng)的‘復(fù)雜系統(tǒng)’或其他‘復(fù)雜系統(tǒng)’變成基本組成。

【復(fù)雜系統(tǒng)的規(guī)范形式】
* 復(fù)雜系統(tǒng)的兩種構(gòu)成層次:1. 組成部分(Part of) 2. 是一種(Is a)


【控制復(fù)雜性的技巧】
控制復(fù)雜性的核心技巧:分而治之
*?算法分解 (自頂向下的結(jié)構(gòu)化設(shè)計(jì))強(qiáng)調(diào)了事件的順序。
*?面向?qū)ο蟮姆纸?:強(qiáng)調(diào)了一些代理。

如果把之前的代碼比作舊石器時(shí)代,接下來就是新石器時(shí)代了。
在開始分析07年的代碼之前,先科普一下:
【普及基礎(chǔ)知識(shí)】
第二代和第三代前期程序設(shè)計(jì)語言的程序結(jié)構(gòu):

程序設(shè)計(jì)-二-三代半.jpg?(47.98 KB, 下載次數(shù): 0)

和圖2-1相比有不同嗎?對(duì)了。子程序不再那么單一,它有了嵌套的結(jié)構(gòu)??纯催@個(gè)時(shí)期引入了什么特點(diǎn):
1.子函數(shù)調(diào)用支持嵌套。
2.支持各種參數(shù)傳遞。
3.程序擁有更豐富的控制結(jié)構(gòu)。
4.聲明的可見性范圍多樣化。如全局變量和函數(shù)內(nèi)部的局部變量。
這個(gè)時(shí)候開始出現(xiàn)結(jié)構(gòu)化程序設(shè)計(jì)。

這個(gè)是07年,我管這個(gè)時(shí)期叫做:

【新石器時(shí)代】
這是07年中的一個(gè)研發(fā)項(xiàng)目,是一個(gè)電力抄表終端,下面代碼是他的液晶顯示屏(128x64)的菜單部分。

代碼有點(diǎn)長,先看頭文件:
menu.h

#include"display_leaf.h" //定義了顯示葉子的函數(shù)//字模數(shù)組extern root_canshu[],root_shuju[],root_zhuangtai[],exit[],zhongduancanshu[],fukongcanshu[],celiangdian0[], celiangdian0canshu[],celiangdian1canshu[], celiangdian1[],celiangdian2[],celiangdian3[],zhongduanzhuangtai[],zhuzhantongxin[],fuhekongzhi[] ,tongxincanshu[],gaojingcanshu[],duankoucanshu[],biaoxieyi[],jibencanshu[],gongkongcanshu[] ,diankongcanshu[],dianzijishu[],biaojitongxin[],gongkongfangan[],diankongfangan[],diannengliang[], xuliang[],shunshiliang[],huanyingshiyong[],celiangdian0shuju[],celiangdian1shuju[],celiangdian2shuju[],celiangdian3shuju[];//--------------------------------------------------------------------------//-------------------------------------------// 子界面編號(hào)數(shù)組//-------------------------------------------unsigned char Son0[1] ={1}; //對(duì)應(yīng)界面0unsigned char Son1[4] ={2,3,4,0};unsigned char Son2[5] ={5,6,7,8,1};unsigned char Son3[5] ={9,10,11,12,1};unsigned char Son4[5] ={55,56,57,58,1};unsigned char Son5[5] ={31,32,33,34,2};unsigned char Son6[5] ={35,36,37,38,2};unsigned char Son7[3] ={39,40,2};unsigned char Son8[3] ={41,42,2};unsigned char Son9[4] ={43,44,45,3};unsigned char Son10[4] ={46,47,48,3};unsigned char Son11[4] ={49,50,51,3};unsigned char Son12[4] ={52,53,54,3};//*****************//以下是增加的葉子//*****************
//-----------------------------------------------------------//各個(gè)界面顯示內(nèi)容指針數(shù)組//-----------------------------------------------------------unsigned char *menu_char0[1]={huanyingshiyong};unsigned char *menu_char1[4]={root_canshu,root_shuju,root_zhuangtai,exit}; //各元素枝指向?qū)?yīng)行顯示內(nèi)容的字模數(shù)組unsigned char *menu_char2[5]={zhongduancanshu,fukongcanshu,celiangdian0canshu,celiangdian1canshu,exit};unsigned char *menu_char3[5]={celiangdian0shuju,celiangdian1shuju,celiangdian2shuju,celiangdian3shuju,exit};unsigned char *menu_char4[5]={zhongduanzhuangtai,zhuzhantongxin,biaojitongxin,fuhekongzhi,exit};unsigned char *menu_char5[5]={tongxincanshu,gaojingcanshu,duankoucanshu,biaoxieyi,exit};unsigned char *menu_char6[5]={jibencanshu,gongkongcanshu,diankongcanshu,gongkongfangan,exit};unsigned char *menu_char7[3]={jibencanshu,gaojingcanshu,exit};unsigned char *menu_char8[3]={jibencanshu,gaojingcanshu,exit};unsigned char *menu_char9[4]={diannengliang,xuliang,shunshiliang,exit};unsigned char *menu_char10[4]={diannengliang,xuliang,shunshiliang,exit};unsigned char *menu_char11[4]={diannengliang,xuliang,shunshiliang,exit};unsigned char *menu_char12[4]={diannengliang,xuliang,shunshiliang,exit};
//-----------------------------------------------------------// 各行文字?jǐn)?shù)量//-----------------------------------------------------------unsigned char char_num0[1] = {4};unsigned char char_num1[4] = {2,2,2,2};unsigned char char_num2[5] = {4,4,6,6,2};unsigned char char_num3[5] = {6,6,6,6,2};unsigned char char_num4[5] = {4,4,4,4,2};unsigned char char_num5[5] = {4,4,4,3,2};unsigned char char_num6[5] = {4,4,4,4,2};unsigned char char_num7[3] = {4,4,2};unsigned char char_num8[3] = {4,4,2};unsigned char char_num9[4] = {3,2,3,2};unsigned char char_num10[4] = {3,2,3,2};unsigned char char_num11[4] = {3,2,3,2};unsigned char char_num12[4] = {3,2,3,2};
//-----------------------------------------------------------struct INTERFACE{//當(dāng)前行號(hào)對(duì)應(yīng)子菜單編號(hào),和反顯行,對(duì)于不需要反顯示的無意義//行號(hào)在每次進(jìn)入新的界面時(shí)清零 unsigned char MAX_ROW_NUM; //該界面的最大行數(shù) unsigned char **MENU_char; //指向一指針數(shù)組,該數(shù)組元素為指向各行顯示內(nèi)容的指針 unsigned char *Son_num; //指向當(dāng)前界面子界面編號(hào)數(shù)組的指針 unsigned char *Row_num; //指向當(dāng)前界面各行文字?jǐn)?shù)量數(shù)組的指針};
struct INTERFACE Windows[13] ={ {1,menu_char0,Son0,char_num0}, {4,menu_char1,Son1,char_num1}, {5,menu_char2,Son2,char_num2}, {5,menu_char3,Son3,char_num3}, {5,menu_char4,Son4,char_num4}, {5,menu_char5,Son5,char_num5}, {5,menu_char6,Son6,char_num6}, {3,menu_char7,Son7,char_num7}, {3,menu_char8,Son8,char_num8}, {4,menu_char9,Son9,char_num9}, {4,menu_char10,Son10,char_num10}, {4,menu_char11,Son11,char_num11}, {4,menu_char12,Son12,char_num12}};

//葉子節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)struct LEAF{ unsigned char Father_num; //該葉子要返回的父親界面號(hào) void (*Display_leaf)(); //指向該葉子的顯示函數(shù)的指針};struct LEAF leaf[28] ={ {5,display_tongxincanshu}, {5,display_gaojingcanshu}, {5,display_duankoucanshu}, {5,display_biaoxieyi}, {6,display_jibencanshu}, {6,display_gongkongcanshu}, {6,display_diankongcanshu}, {6,display_gongkongfangan}, {7,display_cljibencanshu}, {7,display_clgaojingcanshu}, {8,display_cljibencanshu}, {8,display_clgaojingcanshu}, {9,display_diannengliang}, {9,display_xuliang}, {9,display_shunshiliang}, {10,display_diannengliang}, {10,display_xuliang}, {10,display_shunshiliang}, {11,display_diannengliang}, {11,display_xuliang}, {11,display_shunshiliang}, {12,display_diannengliang}, {12,display_xuliang}, {12,display_shunshiliang}, {4,display_zhongduanzhuangtai}, {4,display_zhuzhantongxinzhuangtai}, {4,display_biaojitongxinzhuangtai},{4,display_fuhekongzhizhuangtai}};
//-----------------------------------------函數(shù)--------------------------------------------------void Display_char(unsigned char,unsigned char ,unsigned char *,unsigned char);void Display_row(unsigned char,unsigned char ,unsigned char ,unsigned char *,unsigned char);void Display_window(unsigned char,unsigned char); //

//-----------------??menu.c 部分函數(shù)??--------------

//-----------------------------------------函數(shù)--------------------------------------------------void Display_char(unsigned char,unsigned char ,unsigned char *,unsigned char);void Display_row(unsigned char,unsigned char ,unsigned char ,unsigned char *,unsigned char);void Display_window(unsigned char,unsigned char); 
//-------------------------------------------------------------------------//功能: 顯示16x16漢字//參數(shù): begneX : 行地址 beginRow:列地址 n: 漢字?jǐn)?shù)量 // s: 顯示內(nèi)容 標(biāo)志寄存器: 是否反顯//調(diào)用: display_Lf() display_Rf()//zkq 2007.06.04//-------------------------------------------------------------------------void display16x16RL(unsigned char beginX,unsigned char beginRow,unsigned char n,unsigned char *s,unsigned char 標(biāo)志寄存器){ unsigned char i; if(標(biāo)志寄存器) { LCD_Write_ComR(0xb8|beginX); LCD_Write_ComR(0x40|0); for(i=0;i<64;i++) { LCD_Write_DatR(0xff); } LCD_Write_ComL(0xb8|beginX); LCD_Write_ComL(0x40|0); for(i=0;i<64;i++) { LCD_Write_DatL(0xff); } LCD_Write_ComR(0xb8|beginX + 1); LCD_Write_ComR(0x40|0); for(i=0;i<64;i++) { LCD_Write_DatR(0xff); } LCD_Write_ComL(0xb8|beginX + 1); LCD_Write_ComL(0x40|0); for(i=0;i<64;i++) { LCD_Write_DatL(0xff); } } for(i=0;i { displayrow(i,beginX,beginRow+i,s,標(biāo)志寄存器); }}
//--------------------------------------------------------------------------------------
void Display_window(unsigned char win_num,unsigned char sel_row){
unsigned char j,f; if(Windows[win_num].MAX_ROW_NUM < 4) f = Windows[win_num].MAX_ROW_NUM - 1; else f = 3; if(sel_row<4) { for(j=0;j<=f;j++) { if(j==sel_row) display16x16RL(2*j,1,*(Windows[win_num].Row_num + j),*(Windows[win_num].MENU_char + j),1); else display16x16RL(2*j,1,*(Windows[win_num].Row_num + j),*(Windows[win_num].MENU_char + j),0); } } else if(sel_row<8) { for(j=0;j<=Windows[win_num].MAX_ROW_NUM - 5;j++) { if(j==sel_row%4) display16x16RL(2*j,1,*(Windows[win_num].Row_num + j + 4),*(Windows[win_num].MENU_char + j + 4),1); else display16x16RL(2*j,1,*(Windows[win_num].Row_num + j + 4),*(Windows[win_num].MENU_char + j + 4),0); } }}

不到一年的時(shí)間,是不是有所長進(jìn)呢?
為什么呢?

1. 06年年底研究了一下邵貝貝翻譯的那本《嵌入式實(shí)時(shí)操作系統(tǒng)uCOS-II》
???* 對(duì)操作系統(tǒng)有了一點(diǎn)了解,包括多任務(wù),任務(wù)間的同步和通信、優(yōu)先級(jí),內(nèi)存管理,移植等。
? ?* 對(duì)數(shù)據(jù)結(jié)構(gòu)的使用,對(duì)指針的使用。
? ?* 代碼風(fēng)格等。

2.我的boss老張的工作熱情深深感染了我,他是我的boss也是我的朋友,他為我提供了一個(gè)使用ucos的平臺(tái)。就在我昨天寫帖子的時(shí)候,又聽他講linux下基于QT的組態(tài)軟件的使用效果,那是他最近做的一款PLC的嵌入式平臺(tái)的一部分。有這樣的朋友我很開心。這個(gè)小菜單就是老張當(dāng)年專注于電力抄表項(xiàng)目的時(shí)候我給他做的。

3.當(dāng)然,06年后半年我不少泡鄭州大學(xué)新校區(qū)的圖書館?!渡钊肜斫鈒inux內(nèi)核》也瞟過兩眼,看著很困難。蹭過信息工程學(xué)院的《數(shù)據(jù)結(jié)構(gòu)》收獲很大,里面講的那個(gè)循環(huán)隊(duì)列至今都在使用中。那本道格拉斯的《嵌入式與實(shí)時(shí)系統(tǒng)開發(fā)》當(dāng)時(shí)候看著就像天書。

大師就是大師,Booch的總結(jié)太到位了,他簡(jiǎn)直就是在講我本人啊,我又中招了。

再回頭看一下第二代和第三代前期的程序設(shè)計(jì)語言的特點(diǎn)。這次我唯一沒有做到的是模塊化,這個(gè)時(shí)候我還沒有將C的特點(diǎn)發(fā)揮出來。繼續(xù)對(duì)上面的代碼做些分析吧。

如果從菜單的實(shí)現(xiàn)角度講,這是一個(gè)很差勁的設(shè)計(jì)。主要是沒有對(duì)顯示內(nèi)容本身做進(jìn)一步的抽象,沒有類似按鈕,文本框,復(fù)選框等,窗口只有幾行文字,被選中的行反顯。因?yàn)闆]有真正意義上的“窗體”的概念,所以也沒有真正意義上同窗口綁定的“事件”的概念,三個(gè)按鍵:上翻鍵,下翻鍵用來移動(dòng)光標(biāo),確認(rèn)鍵用來進(jìn)入該項(xiàng)關(guān)聯(lián)的窗口,關(guān)聯(lián)關(guān)系被上面頭文件里面的全局?jǐn)?shù)組和全局結(jié)構(gòu)體數(shù)組定義。

菜單本身如果改進(jìn)的話,可以通過上面提到的做進(jìn)一步抽象的方法。從窗口和窗口的組成元素(文本框,按鈕,復(fù)選框等等)的獨(dú)立實(shí)現(xiàn)上努力,再加上事件。

單單從程序設(shè)計(jì)的角度看:

1. 結(jié)構(gòu)上,最主要的邏輯關(guān)系還是通過全局變量之間的組織關(guān)系來實(shí)現(xiàn)的。
2. 可讀性很差,窗口內(nèi)容很不直觀,因?yàn)榉从炒翱冢ɑ虼翱诮M成部分)屬性的內(nèi)容分散在各個(gè)數(shù)組里面。
3. 可擴(kuò)展性差,添加或者修改一個(gè)窗口(或窗口組成部分)需要更改很多地方。
4. 沒有一個(gè)統(tǒng)一的事件處理機(jī)制,按鍵的處理結(jié)果也是通過上面數(shù)組的關(guān)系反映的。

我應(yīng)該收回對(duì)第二個(gè)階段的比喻:遠(yuǎn)古時(shí)代。我的2007應(yīng)該算是青銅時(shí)代了:雖然水平很菜,但是熱情而有想法。

而我的2008好比中世紀(jì),因?yàn)猷嵵萦行┕緦?shí)在太無所事事,除了畫幾個(gè)PCB或者做一些散碎的不知所謂的小項(xiàng)目,剩下的就是像中世紀(jì)的巫師那樣做一些不登臺(tái)面的事情:私下學(xué)點(diǎn)linux環(huán)境下工具的使用和簡(jiǎn)單開發(fā)。漫長的一年毫無事事,沒有什么明顯收獲。linux的接觸猶如地理大發(fā)現(xiàn),我對(duì)新大陸充滿了渴望。整個(gè)2009年猶如我的大航海時(shí)代。這一年我接手了一個(gè)linux的應(yīng)用項(xiàng)目,是電網(wǎng)監(jiān)測(cè)系統(tǒng)的一部分;這一年我跑遍了全國很多地方的“國家電網(wǎng)”:河南的,山東的,湖北的...知名電力電網(wǎng)公司,比如:四方、南瑞等。我拿我們的電力電網(wǎng)監(jiān)測(cè)從設(shè)備系統(tǒng)和他們的主設(shè)備對(duì)接,既維護(hù)原有系統(tǒng),又增加新的功能,對(duì)于接觸Linux應(yīng)用開發(fā)不久的我來說,這是一個(gè)很好的機(jī)會(huì)。之前的系統(tǒng)是時(shí)工做的,程序?qū)懙煤苡屑记桑瑢?duì)于當(dāng)時(shí)的我來說難度也是頗大的。對(duì)此時(shí)工常常給予我指導(dǎo)和幫助,我至今都十分感激。

所以接下來我會(huì)分析一下這部分代碼,當(dāng)然開始之前我會(huì)插入一點(diǎn)小知識(shí),就是前面提到過的對(duì)于問題的面向過程的分解(算法分解),因?yàn)檫@部分最典型的特定就是算法分解。
更多操作

算法分解:

對(duì)于接受過自頂而下的結(jié)構(gòu)化程序設(shè)計(jì)的人來說,首先想到的是用算法分解將復(fù)雜系統(tǒng)劃分成簡(jiǎn)單的部分。其中每一個(gè)模塊是某個(gè)總體過程(some overall process)的一個(gè)主要步驟(a major step)。下面例子是Booch給出的一個(gè)程序的部分設(shè)計(jì),完成更新一個(gè)主控文件的內(nèi)容。它完全就是對(duì)一個(gè)流程的逐級(jí)細(xì)化。



這樣劃分的模塊雖然可以獨(dú)立編譯,但是完全沒有封裝的概念,它只關(guān)注流程,只是功能的劃分。接下來我的實(shí)例中你會(huì)體會(huì)到這一點(diǎn),當(dāng)然前輩的功力還是很深厚的,程序一直很穩(wěn)定,但是閱讀起來很費(fèi)勁,擴(kuò)展性不好,當(dāng)然像TCP部分也做了一定的封裝。整體上主要還是按照功能而不是實(shí)體做為模塊劃分的標(biāo)準(zhǔn)。即使如此,我從中還是收益匪淺的。

接下我會(huì)把代碼的各個(gè)功能模塊做些介紹,期間插入我自己的理解,最后根據(jù)分析結(jié)果做些總結(jié)。后邊的代碼沒有那么簡(jiǎn)單了,加上多年沒有看過,以后就不再設(shè)什么前提。溫故而知新,很多新發(fā)現(xiàn)也是出乎我意料的,因?yàn)榻裉斓难酃獠煌郧?。大家有什么意見和觀點(diǎn)盡管提,感興趣的話一起開始發(fā)現(xiàn)之旅吧。

先看大概介紹一下這個(gè)程序:首先,因?yàn)楸C艿木壒?,我不能將整個(gè)系統(tǒng)的功能做出描述,設(shè)計(jì)業(yè)務(wù)方面的內(nèi)容也不會(huì)出現(xiàn),但是這不影響我們關(guān)心的問題:代碼本身的討論和分析。
這個(gè)程序運(yùn)行的平臺(tái)是:moxa的嵌入式工控計(jì)算機(jī),好像是74xx系列,平臺(tái)是xsceal(當(dāng)初是ARM架構(gòu)v5TE指令集的CPU。2006年6月,Intel將其通信及應(yīng)用處理器業(yè)務(wù)出售給Marvell公司,并作為一系列不同微架構(gòu)的處理器的品牌),當(dāng)然這個(gè)程序是平臺(tái)無關(guān)的,要求有兩個(gè)網(wǎng)卡設(shè)備和若干個(gè)串口設(shè)備即可,完全可以跑在所以類unix平臺(tái)下,只需要重新編譯即可。和它相連的下行設(shè)備也是一個(gè)ARM9+linux2.4.x的平臺(tái),它從該設(shè)備獲取前端采集數(shù)據(jù),以私有協(xié)議的形式通信。上行設(shè)備為國家電網(wǎng)控制中心主站,它響應(yīng)主站的請(qǐng)求,以電力通信規(guī)約(國標(biāo))通信。設(shè)備本身的串口支持鏈接幾路下行的485設(shè)備。整個(gè)系統(tǒng)結(jié)構(gòu)就是這樣,詳細(xì)的功能我這邊就不做描述了。希望能諒解,這也和我們討論的主題無關(guān)。
首先從內(nèi)容上對(duì)這個(gè)程序做個(gè)大概的瀏覽:

所有代碼:

頭文件:



C文件:
配置文件:


makefile:

既然主要還是算法分解型的,每個(gè)文件里面都是這些分解后的結(jié)果,粗略介紹一下這個(gè)程序的分解情況,嘗試將亮點(diǎn)呈現(xiàn)出來,同時(shí)指明改進(jìn)的想法和好處。

先看一下main函數(shù):
int main(void){ ....... init_system(); //系統(tǒng)初始化
InitPassiveSock(); //監(jiān)聽套接字初始化
pthread_create(&ReceID, NULL, (void *)&RecePross, NULL); //創(chuàng)建接收處理線程
while(1){ QuerySocketsMsg(); //處理socket ....... } .......}

所在文件:main.c,各個(gè)過程完成如下功能:

一. 系統(tǒng)初始化(init_system):

代碼:
void init_system(void){ init_para(); // 1 init_serial(); // 2 init_ethernet(); // 3 ......}

過程:

? ? ? ? 1. 讀取配置文件,結(jié)果存入全局?jǐn)?shù)據(jù)結(jié)構(gòu)。
? ? ? ? 2. 初始化串口通信相關(guān)數(shù)據(jù)結(jié)構(gòu),打開串口,創(chuàng)建串口處理線程等。
? ? ? ? 3. 初始化tcp通信相關(guān)數(shù)據(jù)結(jié)構(gòu)。(僅此而已)
? ? ? ?
這一部分比較簡(jiǎn)單,不做更詳細(xì)分解。下一部分:二. 監(jiān)聽套接字初始化
更多操作

二. 監(jiān)聽套接字初始化

開始之前,先介紹一下這個(gè)程序最主要的幾個(gè)數(shù)據(jù)結(jié)構(gòu):

/* 套接字結(jié)構(gòu) */typedef struct Sockets { fd_set readfds, writefds, exceptfds; // 1. 要被檢測(cè)的可讀、可寫、例外的套接字集合
int PmuSock; // 2. PMU規(guī)范套接字 ......
struct Sockets_Comm *Comm; // 3. 為設(shè)置中每一個(gè)通訊通道建立網(wǎng)絡(luò)接口} Sockets;

1. 做過IO復(fù)用的都知道這個(gè)。
2. 用來監(jiān)聽(listen())主站鏈接的監(jiān)聽socket。
3. 是個(gè)結(jié)構(gòu)體數(shù)組:數(shù)組長度等于通信通道數(shù)量。結(jié)構(gòu)體定義如下:


 /* COMM網(wǎng)絡(luò)結(jié)構(gòu) */ typedef struct Sockets_Comm{ int Protocol; // 1. 該鏈接使用的規(guī)約類型 int MainSock; // 2. 主套接字(即上面提到的監(jiān)聽套接字accept到的鏈接套接字) pthread_t MainThreadID; // 4. 主線程ID,該線程用于處理該鏈接通信。 pthread_cond_t MainCond; pthread_mutex_t MainMutex;
............ }Sockets_Comm;


? ? ? ? 1. 規(guī)約類型:該鏈接使用哪種通信協(xié)議。
? ? ? ? 2. (如上注釋)
? ? ? ? 3. (如上注釋)

【插播】???----知識(shí)點(diǎn)

開閉原則

在面向?qū)ο缶幊填I(lǐng)域中,開閉原則規(guī)定“軟件中的對(duì)象(類,模塊,函數(shù)等等)應(yīng)該對(duì)于擴(kuò)展是開放的,但是對(duì)于修改是封閉的”[1],這意味著一個(gè)實(shí)體是允許在不改變它的源代碼的前提下變更它的行為。該特性在產(chǎn)品化的環(huán)境中是特別有價(jià)值的,在這種環(huán)境中,改變?cè)创a需要代碼審查,單元測(cè)試以及諸如此類的用以確保產(chǎn)品使用質(zhì)量的過程。遵循這種原則的代碼在擴(kuò)展時(shí)并不發(fā)生改變,因此無需上述的過程。

開閉原則的命名被應(yīng)用在兩種方式上。這兩種方式都使用了繼承來解決明顯的困境,但是它們的目的,技術(shù)以及結(jié)果是不同的。

梅耶開閉原則

勃蘭特·梅耶一般被認(rèn)為是最早提出開閉原則這一術(shù)語的人,[來源請(qǐng)求]在他1988年發(fā)行的《面向?qū)ο筌浖?gòu)造》中給出。這一想法認(rèn)為一旦完成,一個(gè)類的實(shí)現(xiàn)只應(yīng)該因錯(cuò)誤而修改,新的或者改變的特性應(yīng)該通過新建不同的類實(shí)現(xiàn)。新建的類可以通過繼承的方式來重用原類的代碼。衍生的子類可以或不可以擁有和原類相同的接口。

梅耶的定義提倡實(shí)現(xiàn)繼承。具體實(shí)現(xiàn)可以通過繼承方式來重用,但是接口規(guī)格不必如此。已存在的實(shí)現(xiàn)對(duì)于修改是封閉的,但是新的實(shí)現(xiàn)不必實(shí)現(xiàn)原有的接口。

多態(tài)開閉原則

在20世紀(jì)90年代,開閉原則被廣泛的重新定義由于抽象化接口的使用,在這中間實(shí)現(xiàn)可以被改變,多種實(shí)現(xiàn)可以被創(chuàng)建,并且多態(tài)化的替換不同的實(shí)現(xiàn)。

相比梅耶的使用方式,多態(tài)開閉原則的定義倡導(dǎo)對(duì)抽象基類的繼承。接口規(guī)約可以通過繼承來重用,但是實(shí)現(xiàn)不必重用。已存在的接口對(duì)于修改是封閉的,并且新的實(shí)現(xiàn)必須,至少,實(shí)現(xiàn)那個(gè)接口。

羅伯特·C·馬丁1996年發(fā)表的文章《開閉原則》[2]是使用這種方法的啟發(fā)式著作。在2001年,Craig Larman把開閉原則關(guān)聯(lián)到了Alistair Cockburn的名為受護(hù)的變量的模式以及David Parnas關(guān)于信息隱藏的討論。[3]


以上內(nèi)容來源于《維基百科》,總得來說,開閉原則不僅針對(duì)面向?qū)ο笤O(shè)計(jì),即使面向過程設(shè)計(jì)的軟件,如果盡可能做到這一點(diǎn)也是很有必要的。雖然面向過程的先天性決定了,這是困難的。然而有些規(guī)模龐大的軟件:如linux內(nèi)核,雖然它本身具備面向?qū)ο笏枷?,終究不是一個(gè)完全面向?qū)ο蟮拇蠊こ?,但是它的開閉原則做得很到位,這個(gè)了解的人一定深有體會(huì)。

最近太忙,代碼分析耽誤了一陣子,恐怕一時(shí)半會(huì)兒還不能續(xù)上,最近一直在考慮一個(gè)問題:面向?qū)ο笳娴氖亲詈玫膯幔空娴耐耆m用于嵌入式開發(fā)嗎?

曾經(jīng)看到過Linus在一個(gè)帖子里痛批了面向?qū)ο笳Z言。他認(rèn)為面向?qū)ο笳Z言以對(duì)象為核心,加一些相關(guān)聯(lián)的方法,簡(jiǎn)直是囈語。重要的東西應(yīng)該是數(shù)據(jù)結(jié)構(gòu),對(duì)象本身有啥重要?真正有意思的,是在不同類型的不同對(duì)象交互而且有鎖規(guī)則的時(shí)候。但是,即使是這時(shí)候,封裝什么“對(duì)象接口”也絕對(duì)錯(cuò)誤,因?yàn)椴辉偈菃我粚?duì)象的問題了。他的結(jié)論是,面向?qū)ο蠼鉀Q的都是一些小問題。
確實(shí)有很多“大人物”一直強(qiáng)調(diào):“最重要的是數(shù)據(jù)結(jié)構(gòu)”。

關(guān)于這個(gè)問題,我最近幾天在考慮。一個(gè)基本的現(xiàn)象是:目前幾乎所有的操作系統(tǒng)都不是以對(duì)象為核心的,就拿大家熟悉的ucos來說,它最核心的是:

多任務(wù)的實(shí)現(xiàn) --- systick中斷中利用TCB(任務(wù)控制塊)鏈表和任務(wù)狀態(tài)信息(就緒、空閑、掛起等)完成對(duì)任務(wù)的調(diào)度。
優(yōu)先級(jí)的實(shí)現(xiàn) --- 有一個(gè)專門的數(shù)據(jù)結(jié)構(gòu)(優(yōu)先級(jí)數(shù)組)來實(shí)現(xiàn)優(yōu)先級(jí)。
中斷的管理 ------
內(nèi)存的管理 ------ 內(nèi)存控制塊
任務(wù)間的通信和同步等 ------ 消息隊(duì)列,互斥量,郵箱等

無不體現(xiàn)了數(shù)據(jù)結(jié)構(gòu)的重要,這里面幾乎看不到對(duì)象的存在。

到底對(duì)象的適用范圍在哪里,我們?cè)撛趺醋??其?shí)這是一個(gè)很有挑戰(zhàn)性的問題,目前還沒有看到過專門討論這個(gè)問題的論著。首先我們大致可以看到:所有“平臺(tái)部分”代碼(像操作系統(tǒng))最好不要以對(duì)象為中心,在沒有使用操作系統(tǒng)的應(yīng)用里,對(duì)CPU的管理:中斷的管理,并發(fā)的實(shí)現(xiàn),消息和同步機(jī)制等也屬于這個(gè)范疇,這部分的特點(diǎn)是:
1. 所完成功能是對(duì)CPU功能的擴(kuò)充和管理。
2. 雖然CPU平臺(tái)有差異,但是體系結(jié)構(gòu)相同或相似,處理方法大部分相同(否則就不存在跨平臺(tái)的OS)。
3. 這部分是同CPU打交道的,和人類的思維差別很大。
所有業(yè)務(wù)相關(guān)的信息最好要以對(duì)象為中心。除了上述“平臺(tái)部分”的代碼,剩下應(yīng)該都是業(yè)務(wù)(廣義)上的內(nèi)容,這部分的特點(diǎn)是:

1. 所完成的功能是系統(tǒng)具體的業(yè),不是平臺(tái)的擴(kuò)展和管理。
2. 該部分和CPU無關(guān),可以以對(duì)象為中心。

所以,關(guān)于這個(gè)問題可以給出以下結(jié)論:

在軟件領(lǐng)域,可以不用想物理學(xué)那樣,需要考慮到理論的統(tǒng)一性,比如:宏觀上用一套定理,量子尺度用另一套就會(huì)讓人不舒服 --- 憑什么同樣的宇宙給出不一樣的描述方式?這也是愛因斯坦后半輩子想解決的問題。軟件領(lǐng)域不受物理規(guī)律支配,能完成功能,解決問題就是王道。完全可以針對(duì)系統(tǒng)的不同實(shí)體部分采用不同的設(shè)計(jì)方法學(xué)。事實(shí)上我們一直都是這么做的,看看我們的個(gè)人電腦系統(tǒng)的操作系統(tǒng)部分和上面的應(yīng)用軟件。

這樣,我們的問題就變成:

1. 在那些開發(fā)過程中需要做這樣的劃分(平臺(tái)部分和應(yīng)用部分)
2. 怎么劃分兩部分的界線。
3. 怎么才能最有效的結(jié)合兩部分,有沒有一套完整統(tǒng)一的方法。
4. 兩部分各自的實(shí)現(xiàn)問題。

其中第4個(gè)問題是我們千百本書里面介紹有的。要這樣,最近一直在想這個(gè)問題:

轉(zhuǎn)一斷關(guān)于面向?qū)ο缶窒扌缘挠懻摚诲e(cuò)!

Soul:我在寫書討論“面向?qū)ο蟮木窒扌浴?/span>

我 :En.這個(gè)倒與我的意見一致。哈哈哈。

我 :“絕對(duì)可以用面向過程的方法來實(shí)現(xiàn)任意復(fù)雜的系統(tǒng)。要知道,航天飛機(jī)也是在面向過程的時(shí)代上的天。但是,為了使一切變得不是那么復(fù)雜,還是出現(xiàn)了‘面向?qū)ο蟪绦蛟O(shè)計(jì)’的方法。”

我 :——哈,我那本書里,在“面向?qū)ο蟆币徊糠智暗囊闹?。就是這樣寫的。

Soul:現(xiàn)在的程序是按照馮。諾伊曼的第一種方案做的,本來就是順序的,而不是同步的。CPU怎么說都是一條指令一條指令執(zhí)行的。

我 :面向過程是對(duì)“流程”、“結(jié)構(gòu)”和“編程方法”的高度概括。而面向?qū)ο蟊旧碇唤鉀Q了“結(jié)構(gòu)”和“編程方法”的問題,而并沒有對(duì)“流程”加以改造。

Soul:確實(shí)如此。確實(shí)如此。

我 :對(duì)流程進(jìn)一步概括的,是“事件驅(qū)動(dòng)”程序模型。而這個(gè)模型不是OO提出的,而是Windows的消息系統(tǒng)內(nèi)置的。所以,現(xiàn)在很多人迷惑于“對(duì)象”和“事件”,試圖通過OO來解決一切的想法原本就是很可笑的。

Soul:我先停下來,和你討論這個(gè)問題,順便補(bǔ)充到書里去。

我 :如果要了解事件驅(qū)動(dòng)的本質(zhì),就應(yīng)該追溯到Windows內(nèi)核。這樣就涉及到線程、進(jìn)程和窗體消息系統(tǒng)這些與OO無關(guān)的內(nèi)容。所以,整個(gè)RAD的編程模型是OO與OS一起構(gòu)建的?,F(xiàn)在很多的開發(fā)人員只知其OO的外表,而看不到OS的內(nèi)核,所以也就總是難以提高。

Soul:OO里面我覺得事件的概念是很牽強(qiáng)的,因?yàn)檎嬲膶?duì)象之間是相互作用,就好像作用力和反作用力,不會(huì)有個(gè)“順序”的延時(shí)。

我 :應(yīng)該留意到,整個(gè)的“事件”模型都是以“記錄”和“消息”的方式來傳遞的。也就是說,事件模型停留在“面向過程”編程時(shí)代使用的“數(shù)據(jù)結(jié)構(gòu)”的層面上。因此,也就不難明白,使用/不使用OO都能寫Windows程序。

我 :因?yàn)榱鞒踢€是在“面向過程”時(shí)代。

Soul:所以所謂的面向?qū)ο蟮氖录€是“順序”的。所以我們經(jīng)常要考慮一個(gè)事件發(fā)生后對(duì)其他過程的影響,所以面向?qū)ο蟋F(xiàn)在而言是牽強(qiáng)的。

我 :如果你深入OS來看SEH,來看Messages,就知道這些東西原本就不是為了“面向?qū)ο蟆倍鴾?zhǔn)備的。面向?qū)ο蠓庋b了這些,卻無法改造它們的流程和內(nèi)核。因?yàn)镺O的抽象層面并不是這個(gè)。

我 :事件的連續(xù)性并不是某種編程方法或者程序邏輯結(jié)構(gòu)所決定的。正如你前面所說的,那是CPU決定的事。

Soul:比如條件選擇,其實(shí)也可以用一種對(duì)象來實(shí)現(xiàn),而事實(shí)沒有。這個(gè)是因?yàn)閏pu的特性和面向?qū)ο筇闊?/span>

我 :可能,將CPU做成面向?qū)ο蟮目赡苓€是比較難于想象和理解。所以MS才啟動(dòng).NET Framework。我不認(rèn)為.NET在面向?qū)ο蠓椒ㄉ嫌惺裁闯?,也不認(rèn)為它的FCL庫會(huì)有什么奇特的地方?!怂鼈冏銐螨嫶蟆5俏艺J(rèn)為,如果有一天OS也是用.NET Framework來編寫的,OS一級(jí)的消息系統(tǒng)、異常機(jī)制、線程機(jī)制等等都是.NET的,都是面向?qū)ο蟮?。那么,在這個(gè)基礎(chǔ)上,將“事件驅(qū)動(dòng)”并入OO層面的模型,才有可能。

Soul:所以我發(fā)覺面向?qū)ο蟮乃季S第一不可能徹底,第二只能用在總體分析層上。在很多時(shí)候,實(shí)質(zhì)上我們只是把一個(gè)順序的流程折疊成對(duì)象。

我 :倒也不是不可能徹底。有絕對(duì)OO的模型,這樣的模型我見過。哈哈~~但說實(shí)在的,我覺得小應(yīng)用用“絕對(duì)OO”的方式來編寫,有失“應(yīng)用”的本意。我們做東西只是要“用”,而不是研究它用的是什么模型。所以,“Hello World”也用OO方式實(shí)現(xiàn),原本就只是出現(xiàn)在教科書中的Sample罷了。哈哈。

Soul:還有不可能用徹底的面向?qū)ο蠓椒▉肀磉_(dá)世界。?因?yàn)檫@個(gè)世界不是面向?qū)ο蟮摹?是關(guān)系網(wǎng)絡(luò)圖,面向?qū)ο笾皇菢洌荒芷娴谋磉_(dá)世界。所以很多時(shí)候面向?qū)ο笕ソ鉀Q問題會(huì)非常痛苦。所以編程退到數(shù)據(jù)結(jié)構(gòu)更合理,哈哈。

我 :如果內(nèi)存是“層狀存取”的,那么我們的“數(shù)據(jù)結(jié)構(gòu)”就可以基于多層來形成“多層數(shù)據(jù)結(jié)構(gòu)”體系。如果內(nèi)存是“樹狀存取”的,那么我們當(dāng)然可以用“樹”的方式來存取。——可惜我們只有順序存取的內(nèi)存。

我 :程序=數(shù)據(jù)+算法 ——這個(gè)是面向過程時(shí)代的事。?程序=數(shù)據(jù)+算法+方法 ——在OO時(shí)代,我們看到了事件驅(qū)動(dòng)和模型驅(qū)動(dòng),所以出現(xiàn)了“方法”問題。

Soul:我的經(jīng)驗(yàn)是:總體結(jié)構(gòu)->面向?qū)ο螅P(guān)系->數(shù)據(jù)結(jié)構(gòu),實(shí)現(xiàn)->算法

Soul:看來我們對(duì)面向?qū)ο蟮恼J(rèn)識(shí)還是比較一致的。


思緒如脫韁的野馬,本來是想分析一下過去的代碼,看看有什么發(fā)現(xiàn)沒有。最后變成了“漫談”,索性“發(fā)散思維”一把。

一直在思考和實(shí)踐嵌入式編程方面的問題,“前輩”們給出的忠告是:一定要“積累”,針對(duì)各種具體問題,大家分享了自己多年的經(jīng)驗(yàn),總結(jié)起來主要有一下內(nèi)容:

? ? 1. 編程技巧:可移植性問題、模塊化,C語言問題等等。
? ? 2. 功能實(shí)現(xiàn):菜單的實(shí)現(xiàn)問題、GUI
? ? 3. 工具的使用:keil,gcc,uml等。
? ? 4. 外設(shè)驅(qū)動(dòng):顯示屏,can,輸入設(shè)備等。
? ? 5. 功能模塊:加密,校驗(yàn),濾波等等。
? ? 6. 處理器相關(guān):中斷處理技巧,寄存器操作,定時(shí)器等等。

其實(shí),在我看來這個(gè)領(lǐng)域的不規(guī)范才是最大的問題,正如“抽象”是軟件實(shí)踐最有用的利器一樣,只有積累沒有總結(jié)和提煉便無法深入一樣,因?yàn)槭虑槭亲霾煌甑模@個(gè)行業(yè)涉及領(lǐng)域越來越多。而我們自己又往往把我們自己在mcu上面的工作特殊化和邊緣化了:技巧的東西給過分夸大,平臺(tái)被過分依賴,代碼越來越有“個(gè)性”。將mcu和大部分“通用處理器”(暫時(shí)這樣稱呼吧,其實(shí)除了PC部分長期被Intel的x86壟斷很久意外,世界上有多少種CPU架構(gòu)啊,他們都可以被稱為“非通用”的)對(duì)立起來,然后說我是做“51”的,他是做“arm"的。
? ?? ?
其實(shí)我們做嵌入式遇到的上面大部分問題其實(shí)都是之前已經(jīng)解決的問題,我們完全可以站在巨人的肩上,在這個(gè)基礎(chǔ)上考慮更深層次的問題,只要?jiǎng)e人已經(jīng)解決過的問題,我們何不消化利用起來而自己從新總結(jié)呢?也不奇怪,我們的教科書中單片機(jī)就是講一下cpu外設(shè),匯編的基本操作,C語言給幾個(gè)簡(jiǎn)單例子,僅此而已。工作中忙于應(yīng)付老板的催促誰還有工夫去考慮,去學(xué)習(xí),去借鑒,去整理??站在巨人的肩上不是簡(jiǎn)單拿他的代碼過來用用就算了,不是在論壇”跪求“某某問題緊急問題如何解決,得之而后就算了。要看這個(gè)問題解決了,是不是類似的問題都能這樣做;我還能拿這樣的方式去解決什么問題;這樣做還有什么不足的地方,我該如何改進(jìn)它等等。久而久之才能比別人做得更好,如果把創(chuàng)造性的想法加入進(jìn)來,或許你也會(huì)創(chuàng)造奇跡。所謂規(guī)范化就是從開發(fā)過程的每個(gè)細(xì)節(jié)都先要吸收現(xiàn)成的,比較它已經(jīng)被總結(jié)為”規(guī)范“,然后去改變和創(chuàng)造,用你的新“規(guī)范”去征服問題,征服別人。當(dāng)一個(gè)需求擺正面前的時(shí)候,我們?nèi)绾蜗率??如何分析問題,給出系統(tǒng)準(zhǔn)確的定位,如何去設(shè)計(jì)規(guī)劃,如何去實(shí)施,如何測(cè)試并改進(jìn)。特殊需求如何滿足?等等。
? ?? ?
所以說,規(guī)范才是根本的。上面5類問題其實(shí)前人都總結(jié)過很好的解決方法,一套行之有效的方法也有,我們何不拿過來用?

END

本文系21ic資深網(wǎng)友keer_zu編寫


推薦閱讀

STC16是開發(fā)者的噩夢(mèng)?

少寫點(diǎn)if-else吧,它的效率有多低你知道嗎?

早期MCU芯片是怎么加密的?


→點(diǎn)關(guān)注,不迷路←

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!

嵌入式ARM

掃描二維碼,關(guān)注更多精彩內(nèi)容

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場(chǎng)的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢(shì) 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競(jìng)爭(zhēng)力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競(jìng)爭(zhēng)優(yōu)勢(shì)...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場(chǎng) NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡(jiǎn)稱"軟通動(dòng)力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉