總結(jié)
本篇是本系列文章中技巧性最深的一篇,它提供了嵌入式系統(tǒng)屏幕顯示方面一些很巧妙的處理方法,靈活使用它們,我們將不再被LCD上凌亂不堪的顯示內(nèi)容所困擾。
屏幕乃嵌入式系統(tǒng)生存之重要輔助,面目可憎之顯示將另用戶逃之夭夭。屏幕編程若處理不好,將是軟件中最不系統(tǒng)、最混亂的部分,筆者曾深受其害。
C語言嵌入式系統(tǒng)編程注意事項(xiàng)之鍵盤操作
處理功能鍵
讓我們來看看WIN32編程中用到的“窗口”概念,當(dāng)消息(message)被發(fā)送給不同窗口的時(shí)候,該窗口的消息處理函數(shù)(是一個(gè)callback函數(shù))最終被調(diào)用,而在該窗口的消息處理函數(shù)中,又根據(jù)消息的類型調(diào)用了該窗口中的對應(yīng)處理函數(shù)。通過這種方式,WIN32有效的組織了不同的窗口,并處理不同窗口情況下的消息。
我們從中學(xué)習(xí)到的就是:
(1)將不同的畫面類比為WIN32中不同的窗口,將窗口中的各種元素(菜單、按鈕等)包含在窗口之中;
?。?)給各個(gè)畫面提供一個(gè)功能鍵“消息”處理函數(shù),該函數(shù)接收按鍵信息為參數(shù);
?。?)在各畫面的功能鍵“消息”處理函數(shù)中,判斷按鍵類型和當(dāng)前焦點(diǎn)元素,并調(diào)用對應(yīng)元素的按鍵處理函數(shù)。
/* 將窗口元素、消息處理函數(shù)封裝在窗口中 */
struct windows
{
BYTE currentFocus;
ELEMENT element[ELEMENT_NUM];
void (*messageFun) (BYTE key);
…
};
/* 消息處理函數(shù) */
void message(BYTE key)
{
BYTE i = 0;
/* 獲得焦點(diǎn)元素 */
while ( (element .ID!= currentFocus)&& (i 《 ELEMENT_NUM) )
{
i++;
}
/* “消息映射” */
if(i 《 ELEMENT_NUM)
{
switch(key)
{
case OK:
element.OnOk();
break;
…
}
}
}
在窗口的消息處理函數(shù)中調(diào)用相應(yīng)元素按鍵函數(shù)的過程類似于“消息映射”,這是我們從WIN32編程中學(xué)習(xí)到的。編程到了一個(gè)境界,很多東西都是相通的了。其它地方的思想可以拿過來為我所用,是為編程中的“拿來主義”。
在這個(gè)例子中,如果我們還想玩得更大一點(diǎn),我們可以借鑒MFC中處理MESSAGE_MAP的方法,我們也可以學(xué)習(xí)MFC定義幾個(gè)精妙的宏來實(shí)現(xiàn)“消息映射”。
處理數(shù)字鍵
用戶輸入數(shù)字時(shí)是一位一位輸入的,每一位的輸入都對應(yīng)著屏幕上的一個(gè)顯示位置(x坐標(biāo),y坐標(biāo))。此外,程序還需要記錄該位置輸入的值,所以有效組織用戶數(shù)字輸入的最佳方式是定義一個(gè)結(jié)構(gòu)體,將坐標(biāo)和數(shù)值捆綁在一起:
/* 用戶數(shù)字輸入結(jié)構(gòu)體 */
typedef struct tagInputNum
{
BYTE byNum; /* 接收用戶輸入賦值 */
BYTE xPos; /* 數(shù)字輸入在屏幕上的顯示位置x坐標(biāo) */
BYTE yPos; /* 數(shù)字輸入在屏幕上的顯示位置y坐標(biāo) */
}InputNum, *LPInputNum;
那么接收用戶輸入就可以定義一個(gè)結(jié)構(gòu)體數(shù)組,用數(shù)組中的各位組成一個(gè)完整的數(shù)字:
InputNum inputElement[NUM_LENGTH]; /* 接收用戶數(shù)字輸入的數(shù)組 */
/* 數(shù)字按鍵處理函數(shù) */
extern void onNumKey(BYTE num)
{
if(num==0|| num==1) /* 只接收二進(jìn)制輸入 */
{
/* 在屏幕上顯示用戶輸入 */
DrawText(inputElement[currentElementInputPlace].xPos, inputElement[currentElementInputPlace].yPos, “%1d”, num);
/* 將輸入賦值給數(shù)組元素 */
inputElement[currentElementInputPlace].byNum = num;
/* 焦點(diǎn)及光標(biāo)右移 */
moveToRight();
}
}
將數(shù)字每一位輸入的坐標(biāo)和輸入值捆綁后,在數(shù)字鍵處理函數(shù)中就可以較有結(jié)構(gòu)的組織程序,使程序顯得很緊湊。
整理用戶輸入
繼續(xù)第2節(jié)的例子,在第2節(jié)的onNumKey函數(shù)中,只是獲取了數(shù)字的每一位,因而我們需要將其轉(zhuǎn)化為有效數(shù)據(jù),譬如要轉(zhuǎn)化為有效的XXX數(shù)據(jù),其方法是:
/* 從2進(jìn)制數(shù)據(jù)位轉(zhuǎn)化為有效數(shù)據(jù):XXX */
void convertToXXX()
{
BYTE i;
XXX = 0;
for (i = 0; i 《 NUM_LENGTH; i++)
{
XXX += inputElement.byNum*power(2, NUM_LENGTH - i - 1);
}
}
反之,我們也可能需要在屏幕上顯示那些有效的數(shù)據(jù)位,因?yàn)槲覀円残枰軌蚍聪蜣D(zhuǎn)化:
/* 從有效數(shù)據(jù)轉(zhuǎn)化為2進(jìn)制數(shù)據(jù)位:XXX */
void convertFromXXX()
{
BYTE i;
XXX = 0;
for (i = 0; i 《 NUM_LENGTH; i++)
{
inputElement.byNum = XXX / power(2, NUM_LENGTH - i - 1) % 2;
}
}
當(dāng)然在上面的例子中,因?yàn)閿?shù)據(jù)是2進(jìn)制的,用power函數(shù)不是很好的選擇,直接用“《《 》》”移位操作效率更高,我們僅是為了說明問題的方便。試想,如果用戶輸入是十進(jìn)制的,power函數(shù)或許是唯一的選擇了。
總結(jié)
本篇給出了鍵盤操作所涉及的各個(gè)方面:功能鍵處理、數(shù)字鍵處理及用戶輸入整理,基本上提供了一個(gè)全套的按鍵處理方案。對于功能鍵處理方法,將LCD屏幕與Windows窗口進(jìn)行類比,提出了較新穎地解決屏幕、鍵盤繁雜交互問題的方案。
計(jì)算機(jī)學(xué)的許多知識(shí)都具有相通性,因而,不斷追趕時(shí)髦技術(shù)而忽略基本功的做法是徒勞無意的。我們最多需要“精通”三種語言(精通,一個(gè)在如今的求職簡歷里泛濫成災(zāi)的詞語),最佳拍檔是匯編、C、C++(或JAVA),很顯然,如果你“精通”了這三種語言,其它語言你應(yīng)該是可以很快“熟悉”的,否則你就沒有“精通”它們。
C語言嵌入式系統(tǒng)編程注意事項(xiàng)之性能優(yōu)化
在C語言中,宏是產(chǎn)生內(nèi)嵌代碼的唯一方法。對于嵌入式系統(tǒng)而言,為了能達(dá)到性能要求,宏是一種很好的代替函數(shù)的方法
使用宏定義
在C語言中,宏是產(chǎn)生內(nèi)嵌代碼的唯一方法。對于嵌入式系統(tǒng)而言,為了能達(dá)到性能要求,宏是一種很好的代替函數(shù)的方法。
寫一個(gè)“標(biāo)準(zhǔn)”宏MIN ,這個(gè)宏輸入兩個(gè)參數(shù)并返回較小的一個(gè):
錯(cuò)誤做法:
#define MIN(A,B) ?。?A 《= B ? A : B )
正確做法:
#define MIN(A,B) ((A)《= (B) ? (A) : (B) )
對于宏,我們需要知道三點(diǎn):
(1)宏定義“像”函數(shù);
(2)宏定義不是函數(shù),因而需要括上所有“參數(shù)”;
?。?)宏定義可能產(chǎn)生副作用。
下面的代碼:
least = MIN(*p++, b);
將被替換為:
?。?(*p++) 《= (b) ?(*p++):(b) )
發(fā)生的事情無法預(yù)料。
因而不要給宏定義傳入有副作用的“參數(shù)”。
使用寄存器變量
當(dāng)對一個(gè)變量頻繁被讀寫時(shí),需要反復(fù)訪問內(nèi)存,從而花費(fèi)大量的存取時(shí)間。為此,C語言提供了一種變量,即寄存器變量。這種變量存放在CPU的寄存器中,使用時(shí),不需要訪問內(nèi)存,而直接從寄存器中讀寫,從而提高效率。寄存器變量的說明符是register。對于循環(huán)次數(shù)較多的循環(huán)控制變量及循環(huán)體內(nèi)反復(fù)使用的變量均可定義為寄存器變量,而循環(huán)計(jì)數(shù)是應(yīng)用寄存器變量的最好候選者。
?。?) 只有局部自動(dòng)變量和形參才可以定義為寄存器變量。因?yàn)榧拇嫫髯兞繉儆趧?dòng)態(tài)存儲(chǔ)方式,凡需要采用靜態(tài)存儲(chǔ)方式的量都不能定義為寄存器變量,包括:模塊間全局變量、模塊內(nèi)全局變量、局部static變量;
?。?) register是一個(gè)“建議”型關(guān)鍵字,意指程序建議該變量放在寄存器中,但最終該變量可能因?yàn)闂l件不滿足并未成為寄存器變量,而是被放在了存儲(chǔ)器中,但編譯器中并不報(bào)錯(cuò)(在C++語言中有另一個(gè)“建議”型關(guān)鍵字:inline)。
下面是一個(gè)采用寄存器變量的例子:
/* 求1+2+3+….+n的值 */
WORD Addition(BYTE n)
{
register i,s=0;
for(i=1;i《=n;i++)
{
s=s+i;
}
return s;
}
本程序循環(huán)n次,i和s都被頻繁使用,因此可定義為寄存器變量。
內(nèi)嵌匯編
程序中對時(shí)間要求苛刻的部分可以用內(nèi)嵌匯編來重寫,以帶來速度上的顯著提高。但是,開發(fā)和測試匯編代碼是一件辛苦的工作,它將花費(fèi)更長的時(shí)間,因而要慎重選擇要用匯編的部分。
在程序中,存在一個(gè)80-20原則,即20%的程序消耗了80%的運(yùn)行時(shí)間,因而我們要改進(jìn)效率,最主要是考慮改進(jìn)那20%的代碼。
嵌入式C程序中主要使用在線匯編,即在C程序中直接插入_asm{ }內(nèi)嵌匯編語句:
/* 把兩個(gè)輸入?yún)?shù)的值相加,結(jié)果存放到另外一個(gè)全局變量中 */
int result;
void Add(long a, long *b)
{
_asm
{
MOV AX, a
MOV BX, b
ADD AX, [BX]
MOV result, AX
}
}
利用硬件特性
首先要明白CPU對各種存儲(chǔ)器的訪問速度,基本上是:
CPU內(nèi)部RAM 》 外部同步RAM 》 外部異步RAM 》 FLASH/ROM
對于程序代碼,已經(jīng)被燒錄在FLASH或ROM中,我們可以讓CPU直接從其中讀取代碼執(zhí)行,但通常這不是一個(gè)好辦法,我們最好在系統(tǒng)啟動(dòng)后將FLASH或ROM中的目標(biāo)代碼拷貝入RAM中后再執(zhí)行以提高取指令速度;
對于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í)間;
如果對某設(shè)備能采取DMA方式讀取,就采用DMA讀取,DMA讀取方式在讀取目標(biāo)中包含的存儲(chǔ)信息較大時(shí)效率較高,其數(shù)據(jù)傳輸?shù)幕締挝皇菈K,而所傳輸?shù)臄?shù)據(jù)是從設(shè)備直接送入內(nèi)存的(或者相反)。DMA方式較之中斷驅(qū)動(dòng)方式,減少了CPU 對外設(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 《《 5);
對于以2的指數(shù)次方為“*”、“/”或“%”因子的數(shù)學(xué)運(yùn)算,轉(zhuǎn)化為移位運(yùn)算“《《 》》”通??梢蕴岣咚惴ㄐ?。因?yàn)槌顺\(yùn)算指令周期通常比移位運(yùn)算大。
C語言位運(yùn)算除了可以提高運(yùn)算效率外,在嵌入式系統(tǒng)的編程中,它的另一個(gè)最典型的應(yīng)用,而且十分廣泛地正在被使用著的是位間的與(&)、或(|)、非(~)操作,這跟嵌入式系統(tǒng)的編程特點(diǎn)有很大關(guān)系。我們通常要對硬件寄存器進(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é)果,對宏的定義和使用要慎而處之。很遺憾,標(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傳輸方式等。
評論