C语言嵌入式系统编程修炼
?
?
C語言嵌入式系統(tǒng)編程修煉之內(nèi)存操作篇
數(shù)據(jù)指針
???? 在嵌入式系統(tǒng)的編程中,常常要求在特定的內(nèi)存單元讀寫內(nèi)容,匯編有對(duì)應(yīng)的MOV指令,而除C/C++以外的其它編程語言基本沒有直接訪問絕對(duì)地址的能力。在嵌入式系統(tǒng)的實(shí)際調(diào)試中,多借助C語言指針?biāo)哂械膶?duì)絕對(duì)地址單元內(nèi)容的讀寫能力。以指針直接操作內(nèi)存多發(fā)生在如下幾種情況:
???? (1) 某I/O芯片被定位在CPU的存儲(chǔ)空間而非I/O空間,而且寄存器對(duì)應(yīng)于某特定地址;
???? (2) 兩個(gè)CPU之間以雙端口RAM通信,CPU需要在雙端口RAM的特定單元(稱為mail box)書寫內(nèi)容以在對(duì)方CPU產(chǎn)生中斷;
???? (3) 讀取在ROM或FLASH的特定單元所燒錄的漢字和英文字模。
??? 譬如:
??? unsigned char *p = (unsigned char *)0xF000FF00;
??? *p="11";
???? 以上程序的意義為在絕對(duì)地址0xF0000+0xFF00(80186使用16位段地址和16位偏移地址)寫入11。
???? 在使用絕對(duì)地址指針時(shí),要注意指針自增自減操作的結(jié)果取決于指針指向的數(shù)據(jù)類別。上例中p++后的結(jié)果是p= 0xF000FF01,若p指向int,即:
??? int *p = (int *)0xF000FF00;
???? p++(或++p)的結(jié)果等同于:p = p+sizeof(int),而p-(或-p)的結(jié)果是p = p-sizeof(int)。
??? 記住:CPU以字節(jié)為單位編址,而C語言指針以指向的數(shù)據(jù)類型長(zhǎng)度作自增和自減。理解這一點(diǎn)對(duì)于以指針直接操作內(nèi)存是相當(dāng)重要的。
函數(shù)指針
???? 首先要理解以下三個(gè)問題:
???? (1)C語言中函數(shù)名直接對(duì)應(yīng)于函數(shù)生成的指令代碼在內(nèi)存中的地址,因此函數(shù)名可以直接賦給指向函數(shù)的指針;
???? (2)調(diào)用函數(shù)實(shí)際上等同于"調(diào)轉(zhuǎn)指令+參數(shù)傳遞處理+回歸位置入棧",本質(zhì)上最核心的操作是將函數(shù)生成的目標(biāo)代碼的首地址賦給CPU的PC寄存器;
???? (3)因?yàn)楹瘮?shù)調(diào)用的本質(zhì)是跳轉(zhuǎn)到某一個(gè)地址單元的code去執(zhí)行,所以可以"調(diào)用"一個(gè)根本就不存在的函數(shù)實(shí)體,暈?請(qǐng)往下看:
???? 請(qǐng)拿出你可以獲得的任何一本大學(xué)《微型計(jì)算機(jī)原理》教材,書中講到,186 CPU啟動(dòng)后跳轉(zhuǎn)至絕對(duì)地址0xFFFF0(對(duì)應(yīng)C語言指針是0xF000FFF0,0xF000為段地址,0xFFF0為段內(nèi)偏移)執(zhí)行,請(qǐng)看下面的代碼:
??? typedef void (*lpFunction) ( ); /* 定義一個(gè)無參數(shù)、無返回類型的 */
??? /* 函數(shù)指針類型 */
??? lpFunction lpReset = (lpFunction)0xF000FFF0; /* 定義一個(gè)函數(shù)指針,指向*/
??? /* CPU啟動(dòng)后所執(zhí)行第一條指令的位置 */
??? lpReset(); /* 調(diào)用函數(shù) */
???? 在以上的程序中,我們根本沒有看到任何一個(gè)函數(shù)實(shí)體,但是我們卻執(zhí)行了這樣的函數(shù)調(diào)用:lpReset(),它實(shí)際上起到了"軟重啟"的作用,跳轉(zhuǎn)到CPU啟動(dòng)后第一條要執(zhí)行的指令的位置。
???? 記住:函數(shù)無它,唯指令集合耳;你可以調(diào)用一個(gè)沒有函數(shù)體的函數(shù),本質(zhì)上只是換一個(gè)地址開始執(zhí)行指令!
數(shù)組vs.動(dòng)態(tài)申請(qǐng)
???? 在嵌入式系統(tǒng)中動(dòng)態(tài)內(nèi)存申請(qǐng)存在比一般系統(tǒng)編程時(shí)更嚴(yán)格的要求,這是因?yàn)榍度胧较到y(tǒng)的內(nèi)存空間往往是十分有限的,不經(jīng)意的內(nèi)存泄露會(huì)很快導(dǎo)致系統(tǒng)的崩潰。
???? 所以一定要保證你的malloc和free成對(duì)出現(xiàn),如果你寫出這樣的一段程序:
??? char * function(void)
??? {
???? char *p;
???? p = (char *)malloc(…);
???? if(p==NULL)
???? …;
???? … /* 一系列針對(duì)p的操作 */
???? return p;
??? }
???? 在某處調(diào)用function(),用完function中動(dòng)態(tài)申請(qǐng)的內(nèi)存后將其free,如下:
??? char *q = function();
??? …
??? free(q);
???? 上述代碼明顯是不合理的,因?yàn)檫`反了malloc和free成對(duì)出現(xiàn)的原則,即"誰申請(qǐng),就由誰釋放"原則。不滿足這個(gè)原則,會(huì)導(dǎo)致代碼的耦合度增大,因?yàn)橛脩粼谡{(diào)用function函數(shù)時(shí)需要知道其內(nèi)部細(xì)節(jié)!
???? 正確的做法是在調(diào)用處申請(qǐng)內(nèi)存,并傳入function函數(shù),如下:
??? char *p="malloc"(…);
??? if(p==NULL)
??? …;
??? function(p);
??? …
??? free(p);
??? p="NULL";
???? 而函數(shù)function則接收參數(shù)p,如下:
??? void function(char *p)
??? {
???? … /* 一系列針對(duì)p的操作 */
??? }
???? 基本上,動(dòng)態(tài)申請(qǐng)內(nèi)存方式可以用較大的數(shù)組替換。對(duì)于編程新手,筆者推薦你盡量采用數(shù)組!嵌入式系統(tǒng)可以以博大的胸襟接收瑕疵,而無法"海納"錯(cuò)誤。畢竟,以最笨的方式苦練神功的郭靖勝過機(jī)智聰明卻范政治錯(cuò)誤走反革命道路的楊康。
???? 給出原則:
???? (1)盡可能的選用數(shù)組,數(shù)組不能越界訪問(真理越過一步就是謬誤,數(shù)組越過界限就光榮地成全了一個(gè)混亂的嵌入式系統(tǒng));
???? (2)如果使用動(dòng)態(tài)申請(qǐng),則申請(qǐng)后一定要判斷是否申請(qǐng)成功了,并且malloc和free應(yīng)成對(duì)出現(xiàn)!
??? const在C++語言中則包含了更豐富的含義,而在C語言中僅意味著:"只能讀的普通變量",可以稱其為"不能改變的變量"(這個(gè)說法似乎很拗口,但卻最準(zhǔn)確的表達(dá)了C語言中const的本質(zhì)),在編譯階段需要的常數(shù)仍然只能以#define宏定義!故在C語言中如下程序是非法的:
關(guān)鍵字const
???? const意味著"只讀"。區(qū)別如下代碼的功能非常重要,也是老生長(zhǎng)嘆,如果你還不知道它們的區(qū)別,而且已經(jīng)在程序界摸爬滾打多年,那只能說這是一個(gè)悲哀:
??? const int a;
??? int const a;
??? const int *a;
??? int * const a;
??? int const * a const;
??? const int SIZE = 10;
??? char a[SIZE]; /* 非法:編譯階段不能用到變量 */
關(guān)鍵字volatile
??? volatile變量可能用于如下幾種情況:
???? (1) 并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器,例中的代碼屬于此類);
???? (2) 一個(gè)中斷服務(wù)子程序中會(huì)訪問到的非自動(dòng)變量(也就是全局變量);
???? (3) 多線程應(yīng)用中被幾個(gè)任務(wù)共享的變量。
C語言嵌入式系統(tǒng)編程修煉之屏幕操作篇
漢字處理
???? 現(xiàn)在要解決的問題是,嵌入式系統(tǒng)中經(jīng)常要使用的并非是完整的漢字庫,往往只是需要提供數(shù)量有限的漢字供必要的顯示功能。例如,一個(gè)微波爐的LCD上沒有必要提供顯示"電子郵件"的功能;一個(gè)提供漢字顯示功能的空調(diào)的LCD上不需要顯示一條"短消息",諸如此類。但是一部手機(jī)、小靈通則通常需要包括較完整的漢字庫。
???? 如果包括的漢字庫較完整,那么,由內(nèi)碼計(jì)算出漢字字模在庫中的偏移是十分簡(jiǎn)單的:漢字庫是按照區(qū)位的順序排列的,前一個(gè)字節(jié)為該漢字的區(qū)號(hào),后一個(gè)字節(jié)為該字的位號(hào)。每一個(gè)區(qū)記錄94個(gè)漢字,位號(hào)則為該字在該區(qū)中的位置。因此,漢字在漢字庫中的具體位置計(jì)算公式為:94*(區(qū)號(hào)-1)+位號(hào)-1。減1是因?yàn)閿?shù)組是以0為開始而區(qū)號(hào)位號(hào)是以1為開始的。只需乘上一個(gè)漢字字模占用的字節(jié)數(shù)即可,即:(94*(區(qū)號(hào)-1)+位號(hào)-1)*一個(gè)漢字字模占用字節(jié)數(shù),以16*16點(diǎn)陣字庫為例,計(jì)算公式則為:(94*(區(qū)號(hào)-1)+(位號(hào)-1))*32。漢字庫中從該位置起的32字節(jié)信息記錄了該字的字模信息。
??? 對(duì)于包含較完整漢字庫的系統(tǒng)而言,我們可以以上述規(guī)則計(jì)算字模的位置。但是如果僅僅是提供少量漢字呢?譬如幾十至幾百個(gè)?最好的做法是:
???? 定義宏:
??? # define EX_FONT_CHAR(value)
??? # define EX_FONT_UNICODE_VAL(value) (value),
??? # define EX_FONT_ANSI_VAL(value) (value),
??? 定義結(jié)構(gòu)體:
??? typedef struct _wide_unicode_font16x16
??? {
???? WORD value; /* 內(nèi)碼 */
???? BYTE data[32]; /* 字模點(diǎn)陣 */
??? }Unicode;
??? #define CHINESE_CHAR_NUM … /* 漢字?jǐn)?shù)量 */
???? 字模的存儲(chǔ)用數(shù)組:
??? Unicode chinese[CHINESE_CHAR_NUM] =
??? {
??? {
??? EX_FONT_CHAR("業(yè)")
??? EX_FONT_UNICODE_VAL(0x4e1a)
??? {0x04, 0x40, 0x04, 0x40, 0x04, 0x40, 0x04, 0x44, 0x44, 0x46, 0x24, 0x4c, 0x24, 0x48, 0x14, 0x50, 0x1c, 0x50, 0x14, 0x60, 0x04, 0x40, 0x04, 0x40, 0x04, 0x44, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00}
??? },
??? {
??? EX_FONT_CHAR("中")
??? EX_FONT_UNICODE_VAL(0x4e2d)
??? {0x01, 0x00, 0x01, 0x00, 0x21, 0x08, 0x3f, 0xfc, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08, 0x21, 0x08,
??? 0x3f, 0xf8, 0x21, 0x08, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00}
??? },
??? {
??? EX_FONT_CHAR("云")
??? EX_FONT_UNICODE_VAL(0x4e91)
??? {0x00, 0x00, 0x00, 0x30, 0x3f, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0xff, 0xfe, 0x03, 0x00, 0x07, 0x00,
??? 0x06, 0x40, 0x0c, 0x20, 0x18, 0x10, 0x31, 0xf8, 0x7f, 0x0c, 0x20, 0x08, 0x00, 0x00}
??? },
??? {
??? EX_FONT_CHAR("件")
??? EX_FONT_UNICODE_VAL(0x4ef6)
??? {0x10, 0x40, 0x1a, 0x40, 0x13, 0x40, 0x32, 0x40, 0x23, 0xfc, 0x64, 0x40, 0xa4, 0x40, 0x28, 0x40, 0x2f, 0xfe,
??? 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40, 0x20, 0x40}
??? }
??? }
???? 要顯示特定漢字的時(shí)候,只需要從數(shù)組中查找內(nèi)碼與要求漢字內(nèi)碼相同的即可獲得字模。如果前面的漢字在數(shù)組中以內(nèi)碼大小順序排列,那么可以以二分查找法更高效的查找到漢字的字模。
???? 這是一種很有效的組織小漢字庫的方法,它可以保證程序有很好的結(jié)構(gòu)。
系統(tǒng)時(shí)間顯示
???? 從NVRAM中可以讀取系統(tǒng)的時(shí)間,系統(tǒng)一般借助NVRAM產(chǎn)生的秒中斷每秒讀取一次當(dāng)前時(shí)間并在LCD上顯示。關(guān)于時(shí)間的顯示,有一個(gè)效率問題。因?yàn)闀r(shí)間有其特殊性,那就是60秒才有一次分鐘的變化,60分鐘才有一次小時(shí)變化,如果我們每次都將讀取的時(shí)間在屏幕上完全重新刷新一次,則浪費(fèi)了大量的系統(tǒng)時(shí)間。
???? 一個(gè)較好的辦法是我們?cè)跁r(shí)間顯示函數(shù)中以靜態(tài)變量分別存儲(chǔ)小時(shí)、分鐘、秒,只有在其內(nèi)容發(fā)生變化的時(shí)候才更新其顯示。
??? extern void DisplayTime(…)
??? {
???? static BYTE byHour,byMinute,bySecond;
???? BYTE byNewHour, byNewMinute, byNewSecond;
???? byNewHour = GetSysHour();
???? byNewMinute = GetSysMinute();
???? byNewSecond = GetSysSecond();
???? if(byNewHour!= byHour)
???? {
???? … /* 顯示小時(shí) */
???? byHour = byNewHour;
???? }
???? if(byNewMinute!= byMinute)
???? {
???? … /* 顯示分鐘 */
???? byMinute = byNewMinute;
???? }
???? if(byNewSecond!= bySecond)
???? {
???? … /* 顯示秒鐘 */
???? bySecond = byNewSecond;
???? }
??? }
???? 這個(gè)例子也可以順便作為C語言中static關(guān)鍵字強(qiáng)大威力的證明。當(dāng)然,在C++語言里,static具有了更加強(qiáng)大的威力,它使得某些數(shù)據(jù)和函數(shù)脫離"對(duì)象"而成為"類"的一部分,正是它的這一特點(diǎn),成就了軟件的無數(shù)優(yōu)秀設(shè)計(jì)。
動(dòng)畫顯示
???? 動(dòng)畫是無所謂有,無所謂無的,靜止的畫面走的路多了,也就成了動(dòng)畫。隨著時(shí)間的變更,在屏幕上顯示不同的靜止畫面,即是動(dòng)畫之本質(zhì)。所以,在一個(gè)嵌入式系統(tǒng)的LCD上欲顯示動(dòng)畫,必須借助定時(shí)器。沒有硬件或軟件定時(shí)器的世界是無法想像的:
???? (1) 沒有定時(shí)器,一個(gè)操作系統(tǒng)將無法進(jìn)行時(shí)間片的輪轉(zhuǎn),于是無法進(jìn)行多任務(wù)的調(diào)度,于是便不再成其為一個(gè)多任務(wù)操作系統(tǒng);
???? (2) 沒有定時(shí)器,一個(gè)多媒體播放軟件將無法運(yùn)作,因?yàn)樗恢篮螘r(shí)應(yīng)該切換到下一幀畫面;
???? (3) 沒有定時(shí)器,一個(gè)網(wǎng)絡(luò)協(xié)議將無法運(yùn)轉(zhuǎn),因?yàn)槠錈o法獲知何時(shí)包傳輸超時(shí)并重傳之,無法在特定的時(shí)間完成特定的任務(wù)。
???? 因此,沒有定時(shí)器將意味著沒有操作系統(tǒng)、沒有網(wǎng)絡(luò)、沒有多媒體,這將是怎樣的黑暗?所以,合理并靈活地使用各種定時(shí)器,是對(duì)一個(gè)軟件人的最基本需求!
菜單操作
???? 無數(shù)人為之絞盡腦汁的問題終于出現(xiàn)了,在這一節(jié)里,我們將看到,在C語言中哪怕用到一丁點(diǎn)的面向?qū)ο笏枷?#xff0c;軟件結(jié)構(gòu)將會(huì)有何等的改觀!
???? 筆者曾經(jīng)是個(gè)笨蛋,被菜單搞暈了,給出這樣的一個(gè)系統(tǒng):
???? 要求以鍵盤上的"← →"鍵切換菜單焦點(diǎn),當(dāng)用戶在焦點(diǎn)處于某菜單時(shí),若敲擊鍵盤上的OK、CANCEL鍵則調(diào)用該焦點(diǎn)菜單對(duì)應(yīng)之處理函數(shù)。我曾經(jīng)傻傻地這樣做著:
??? /* 按下OK鍵 */
??? void onOkKey()
??? {
???? /* 判斷在什么焦點(diǎn)菜單上按下Ok鍵,調(diào)用相應(yīng)處理函數(shù) */
???? Switch(currentFocus)
???? {
???? case MENU1:
???? menu1OnOk();
???? break;
???? case MENU2:
???? menu2OnOk();
???? break;
???? …
???? }
??? }
??? /* 按下Cancel鍵 */
??? void onCancelKey()
??? {
???? /* 判斷在什么焦點(diǎn)菜單上按下Cancel鍵,調(diào)用相應(yīng)處理函數(shù) */
???? Switch(currentFocus)
???? {
???? case MENU1:
???? menu1OnCancel();
???? break;
???? case MENU2:
???? menu2OnCancel();
???? break;
???? …
???? }
??? }
???? 終于有一天,我這樣做了:
??? /* 將菜單的屬性和操作"封裝"在一起 */
??? typedef struct tagSysMenu
??? {
???? char *text; /* 菜單的文本 */
???? BYTE xPos; /* 菜單在LCD上的x坐標(biāo) */
???? BYTE yPos; /* 菜單在LCD上的y坐標(biāo) */
???? void (*onOkFun)(); /* 在該菜單上按下ok鍵的處理函數(shù)指針 */
???? void (*onCancelFun)(); /* 在該菜單上按下cancel鍵的處理函數(shù)指針 */
??? }SysMenu, *LPSysMenu;
???? 當(dāng)我定義菜單時(shí),只需要這樣:
??? static SysMenu menu[MENU_NUM] =
??? {
???? {
???? "menu1", 0, 48, menu1OnOk, menu1OnCancel
???? }
???? ,
???? {
???? " menu2", 7, 48, menu2OnOk, menu2OnCancel
???? }
???? ,
???? {
???? " menu3", 7, 48, menu3OnOk, menu3OnCancel
???? }
???? ,
???? {
???? " menu4", 7, 48, menu4OnOk, menu4OnCancel
???? }
???? …
??? };
???? OK鍵和CANCEL鍵的處理變成:
??? /* 按下OK鍵 */
??? void onOkKey()
??? {
???? menu[currentFocusMenu].onOkFun();
??? }
??? /* 按下Cancel鍵 */
??? void onCancelKey()
??? {
???? menu[currentFocusMenu].onCancelFun();
??? }
???? 程序被大大簡(jiǎn)化了,也開始具有很好的可擴(kuò)展性!我們僅僅利用了面向?qū)ο笾械姆庋b思想,就讓程序結(jié)構(gòu)清晰,其結(jié)果是幾乎可以在無需修改程序的情況下在系統(tǒng)中添加更多的菜單,而系統(tǒng)的按鍵處理函數(shù)保持不變。
???? 面向?qū)ο?#xff0c;真神了!
C語言嵌入式系統(tǒng)編程修煉之鍵盤操作篇
??? 功能鍵的問題在于,用戶界面并非固定的,用戶功能鍵的選擇將使屏幕畫面處于不同的顯示狀態(tài)下。
處理功能鍵
???? 功能鍵的問題在于,用戶界面并非固定的,用戶功能鍵的選擇將使屏幕畫面處于不同的顯示狀態(tài)下。例如,主畫面如圖1:
??? 圖1 主畫面
???? 當(dāng)用戶在設(shè)置XX上按下Enter鍵之后,畫面就切換到了設(shè)置XX的界面,如圖2:
??? 圖2 切換到設(shè)置XX畫面
???? 程序如何判斷用戶處于哪一畫面,并在該畫面的程序狀態(tài)下調(diào)用對(duì)應(yīng)的功能鍵處理函數(shù),而且保證良好的結(jié)構(gòu),是一個(gè)值得思考的問題。
???? 讓我們來看看WIN32編程中用到的"窗口"概念,當(dāng)消息(message)被發(fā)送給不同窗口的時(shí)候,該窗口的消息處理函數(shù)(是一個(gè)callback函數(shù))最終被調(diào)用,而在該窗口的消息處理函數(shù)中,又根據(jù)消息的類型調(diào)用了該窗口中的對(duì)應(yīng)處理函數(shù)。通過這種方式,WIN32有效的組織了不同的窗口,并處理不同窗口情況下的消息。
???? 我們從中學(xué)習(xí)到的就是:
???? (1)將不同的畫面類比為WIN32中不同的窗口,將窗口中的各種元素(菜單、按鈕等)包含在窗口之中;
???? (2)給各個(gè)畫面提供一個(gè)功能鍵"消息"處理函數(shù),該函數(shù)接收按鍵信息為參數(shù);
???? (3)在各畫面的功能鍵"消息"處理函數(shù)中,判斷按鍵類型和當(dāng)前焦點(diǎn)元素,并調(diào)用對(duì)應(yīng)元素的按鍵處理函數(shù)。
??? /* 將窗口元素、消息處理函數(shù)封裝在窗口中 */
??? struct windows
??? {
???? BYTE currentFocus;
???? ELEMENT element[ELEMENT_NUM];
???? void (*messageFun) (BYTE keyValue);
???? …
??? };
??? /* 消息處理函數(shù) */
??? void messageFunction(BYTE keyValue)
??? {
???? BYTE i = 0;
???? /* 獲得焦點(diǎn)元素 */
???? while ( (element .ID!= currentFocus)&& (i >"移位操作效率更高,我們僅是為了說明問題的方便。試想,如果用戶輸入是十進(jìn)制的,power函數(shù)或許是唯一的選擇了。
總結(jié)
???? 本篇給出了鍵盤操作所涉及的各個(gè)方面:功能鍵處理、數(shù)字鍵處理及用戶輸入整理,基本上提供了一個(gè)全套的按鍵處理方案。對(duì)于功能鍵處理方法,將LCD屏幕與Windows窗口進(jìn)行類比,提出了較新穎地解決屏幕、鍵盤繁雜交互問題的方案。
???? 計(jì)算機(jī)學(xué)的許多知識(shí)都具有相通性,因而,不斷追趕時(shí)髦技術(shù)而忽略基本功的做法是徒勞無意的。我們最多需要"精通"三種語言(精通,一個(gè)在如今的求職簡(jiǎn)歷里泛濫成災(zāi)的詞語),最佳拍檔是匯編、C、C++(或JAVA),很顯然,如果你"精通"了這三種語言,其它語言你應(yīng)該是可以很快"熟悉"的,否則你就沒有"精通"它們。
C語言嵌入式系統(tǒng)編程修煉之性能優(yōu)化篇
使用宏定義
???? 在C語言中,宏是產(chǎn)生內(nèi)嵌代碼的唯一方法。對(duì)于嵌入式系統(tǒng)而言,為了能達(dá)到性能要求,宏是一種很好的代替函數(shù)的方法。
???? 寫一個(gè)"標(biāo)準(zhǔn)"宏MIN ,這個(gè)宏輸入兩個(gè)參數(shù)并返回較小的一個(gè):
???? 錯(cuò)誤做法:
??? #define MIN(A,B) ( A 外部同步RAM > 外部異步RAM > FLASH/ROM
???? 對(duì)于程序代碼,已經(jīng)被燒錄在FLASH或ROM中,我們可以讓CPU直接從其中讀取代碼執(zhí)行,但通常這不是一個(gè)好辦法,我們最好在系統(tǒng)啟動(dòng)后將FLASH或ROM中的目標(biāo)代碼拷貝入RAM中后再執(zhí)行以提高取指令速度;
???? 對(duì)于UART等設(shè)備,其內(nèi)部有一定容量的接收BUFFER,我們應(yīng)盡量在BUFFER被占滿后再向CPU提出中斷。例如計(jì)算機(jī)終端在向目標(biāo)機(jī)通過RS-232傳遞數(shù)據(jù)時(shí),不宜設(shè)置UART只接收到一個(gè)BYTE就向CPU提中斷,從而無謂浪費(fèi)中斷處理時(shí)間;
???? 如果對(duì)某設(shè)備能采取DMA方式讀取,就采用DMA讀取,DMA讀取方式在讀取目標(biāo)中包含的存儲(chǔ)信息較大時(shí)效率較高,其數(shù)據(jù)傳輸?shù)幕締挝皇菈K,而所傳輸?shù)臄?shù)據(jù)是從設(shè)備直接送入內(nèi)存的(或者相反)。DMA方式較之中斷驅(qū)動(dòng)方式,減少了CPU 對(duì)外設(shè)的干預(yù),進(jìn)一步提高了CPU與外設(shè)的并行操作程度。
活用位操作
???? 使用C語言的位操作可以減少除法和取模的運(yùn)算。在計(jì)算機(jī)程序中數(shù)據(jù)的位是可以操作的最小數(shù)據(jù)單位,理論上可以用"位運(yùn)算"來完成所有的運(yùn)算和操作,因而,靈活的位操作可以有效地提高程序運(yùn)行的效率。舉例如下:
??? /* 方法1 */
??? int i,j;
??? i = 879 / 16;
??? j = 562 % 32;
??? /* 方法2 */
??? int i,j;
??? i = 879 >> 4;
??? j = 562 - (562 >> 5 >"通常可以提高算法效率。因?yàn)槌顺\(yùn)算指令周期通常比移位運(yùn)算大。
???? C語言位運(yùn)算除了可以提高運(yùn)算效率外,在嵌入式系統(tǒng)的編程中,它的另一個(gè)最典型的應(yīng)用,而且十分廣泛地正在被使用著的是位間的與(&)、或(|)、非(~)操作,這跟嵌入式系統(tǒng)的編程特點(diǎn)有很大關(guān)系。我們通常要對(duì)硬件寄存器進(jìn)行位設(shè)置,譬如,我們通過將AM186ER型80186處理器的中斷屏蔽控制寄存器的第低6位設(shè)置為0(開中斷2),最通用的做法是:
??? #define INT_I2_MASK 0x0040
??? wTemp = inword(INT_MASK);
??? outword(INT_MASK, wTemp &~INT_I2_MASK);
???? 而將該位設(shè)置為1的做法是:
??? #define INT_I2_MASK 0x0040
??? wTemp = inword(INT_MASK);
??? outword(INT_MASK, wTemp | INT_I2_MASK);
???? 判斷該位是否為1的做法是:
??? #define INT_I2_MASK 0x0040
??? wTemp = inword(INT_MASK);
??? if(wTemp & INT_I2_MASK)
??? {
??? … /* 該位為1 */
??? }
???? 上述方法在嵌入式系統(tǒng)的編程中是非常常見的,我們需要牢固掌握。
總結(jié)
???? 在性能優(yōu)化方面永遠(yuǎn)注意80-20準(zhǔn)備,不要優(yōu)化程序中開銷不大的那80%,這是勞而無功的。
???? 宏定義是C語言中實(shí)現(xiàn)類似函數(shù)功能而又不具函數(shù)調(diào)用和返回開銷的較好方法,但宏在本質(zhì)上不是函數(shù),因而要防止宏展開后出現(xiàn)不可預(yù)料的結(jié)果,對(duì)宏的定義和使用要慎而處之。很遺憾,標(biāo)準(zhǔn)C至今沒有包括C++中inline函數(shù)的功能,inline函數(shù)兼具無調(diào)用開銷和安全的優(yōu)點(diǎn)。
???? 使用寄存器變量、內(nèi)嵌匯編和活用位操作也是提高程序效率的有效方法。
???? 除了編程上的技巧外,為提高系統(tǒng)的運(yùn)行效率,我們通常也需要最大可能地利用各種硬件設(shè)備自身的特點(diǎn)來減小其運(yùn)轉(zhuǎn)開銷,例如減小中斷次數(shù)、利用DMA傳輸方式等。
?
轉(zhuǎn)載于:https://blog.51cto.com/1572091hyl10/479143
總結(jié)
以上是生活随笔為你收集整理的C语言嵌入式系统编程修炼的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 利用PHP的Popen实现RRDTOOL
- 下一篇: PowerShell2.0之Window