C語言在嵌入式系統(tǒng)編程時(shí)的注意事項(xiàng)(三)

姓名:徐嬌 ? ?學(xué)號:17011210547

轉(zhuǎn)自http://mp.weixin.qq.com/s/YUXrJbin_rnwModTY2Ds_A

【嵌牛導(dǎo)讀】

C語言是一門通用計(jì)算機(jī)編程語言,應(yīng)用廣泛。C語言的設(shè)計(jì)目標(biāo)是提供一種能以簡易的方式編譯、處理低級存儲器、產(chǎn)生少量的機(jī)器碼以及不需要任何運(yùn)行環(huán)境支持便能運(yùn)行的編程語言。

盡管C語言提供了許多低級處理的功能,但仍然保持著良好跨平臺的特性,以一個(gè)標(biāo)準(zhǔn)規(guī)格寫出的C語言程序可在許多電腦平臺上進(jìn)行編譯,甚至包含一些嵌入式處理器(單片機(jī)或稱MCU)以及超級電腦等作業(yè)平臺。

20世紀(jì)80年代,為了避免各開發(fā)廠商用的C語言語法產(chǎn)生差異,由美國國家標(biāo)準(zhǔn)局為C語言訂定了一套完整的國際標(biāo)準(zhǔn)語法,稱為ANSI C,作為C語言最初的標(biāo)準(zhǔn)。

【嵌牛鼻子】C語言、嵌入式系統(tǒng)編程、注意事項(xiàng)之屏幕操作

【嵌牛提問】C語言在嵌入式系統(tǒng)編程時(shí)的注意事項(xiàng)

【嵌牛正文】

C語言嵌入式系統(tǒng)編程注意事項(xiàng)之屏幕操作

現(xiàn)在要解決的問題是,嵌入式系統(tǒng)中經(jīng)常要使用的并非是完整的漢字庫,往往只是需要提供數(shù)量有限的漢字供必要的顯示功能

漢字處理

現(xiàn)在要解決的問題是,嵌入式系統(tǒng)中經(jīng)常要使用的并非是完整的漢字庫,往往只是需要提供數(shù)量有限的漢字供必要的顯示功能。例如,一個(gè)微波爐的LCD上沒有必要提供顯示“電子郵件”的功能;一個(gè)提供漢字顯示功能的空調(diào)的LCD上不需要顯示一條“短消息”,諸如此類。但是一部手機(jī)、小靈通則通常需要包括較完整的漢字庫。

如果包括的漢字庫較完整,那么,由內(nèi)碼計(jì)算出漢字字模在庫中的偏移是十分簡單的:漢字庫是按照區(qū)位的順序排列的,前一個(gè)字節(jié)為該漢字的區(qū)號,后一個(gè)字節(jié)為該字的位號。每一個(gè)區(qū)記錄94個(gè)漢字,位號則為該字在該區(qū)中的位置。因此,漢字在漢字庫中的具體位置計(jì)算公式為:94*(區(qū)號-1)+位號-1。減1是因?yàn)閿?shù)組是以0為開始而區(qū)號位號是以1為開始的。只需乘上一個(gè)漢字字模占用的字節(jié)數(shù)即可,即:(94*(區(qū)號-1)+位號-1)*一個(gè)漢字字模占用字節(jié)數(shù),以16*16點(diǎn)陣字庫為例,計(jì)算公式則為:(94*(區(qū)號-1)+(位號-1))*32。漢字庫中從該位置起的32字節(jié)信息記錄了該字的字模信息。

對于包含較完整漢字庫的系統(tǒng)而言,我們可以以上述規(guī)則計(jì)算字模的位置。但是如果僅僅是提供少量漢字呢?譬如幾十至幾百個(gè)?最好的做法是:

定義宏:

# define EX_FONT_CHAR()

# define EX_FONT_UNICODE_VAL() (),

# define EX_FONT_ANSI_VAL() (),

定義結(jié)構(gòu)體:

typedef struct _wide_unicode_font16x16

{

WORD ; /*內(nèi)碼*/

BYTE data[32]; /*字模點(diǎn)陣*/

}Unicode;

#define CHINESE_CHAR_NUM … /*漢字?jǐn)?shù)量*/

字模的存儲用數(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è)較好的辦法是我們在時(shí)間顯示函數(shù)中以靜態(tài)變量分別存儲小時(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ù)脫離“對象”而成為“類”的一部分,正是它的這一特點(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í)器,是對一個(gè)軟件人的最基本需求!

在80186為主芯片的嵌入式系統(tǒng)中,我們需要借助硬件定時(shí)器的中斷來作為軟件定時(shí)器,在中斷發(fā)生后變更畫面的顯示內(nèi)容。在時(shí)間顯示“xx:xx”中讓冒號交替有無,每次秒中斷發(fā)生后,需調(diào)用ShowDot:

void ShowDot()

{

static BOOL bShowDot = TRUE; /*再一次領(lǐng)略static關(guān)鍵字的威力*/

if(bShowDot)

{

showChar(’:’,xPos,yPos);

}

else

{

showChar(’ ’,xPos,yPos);

}

bShowDot =!bShowDot;

}

菜單操作

無數(shù)人為之絞盡腦汁的問題終于出現(xiàn)了,在這一節(jié)里,我們將看到,在C語言中哪怕用到一丁點(diǎn)的面向?qū)ο笏枷?,軟件結(jié)構(gòu)將會有何等的改觀!

要求以鍵盤上的“← →”鍵切換菜單焦點(diǎn),當(dāng)用戶在焦點(diǎn)處于某菜單時(shí),若敲擊鍵盤上的OK、CANCEL鍵則調(diào)用該焦點(diǎn)菜單對應(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();

}

程序被大大簡化了,也開始具有很好的可擴(kuò)展性!我們僅僅利用了面向?qū)ο笾械姆庋b思想,就讓程序結(jié)構(gòu)清晰,其結(jié)果是幾乎可以在無需修改程序的情況下在系統(tǒng)中添加更多的菜單,而系統(tǒng)的按鍵處理函數(shù)保持不變。

面向?qū)ο螅嫔窳耍?/p>

模擬MessageBox函數(shù)

MessageBox函數(shù),這個(gè)Windows編程中的超級猛料,不知道是多少入門者第一次用到的函數(shù)。還記得我們第一次在Windows中利用MessageBox輸出“Hello,World!”對話框時(shí)新奇的感覺嗎?無法統(tǒng)計(jì),這個(gè)世界上究竟有多少程序員學(xué)習(xí)Windows編程是從MessageBox(“Hello,World!”,…)開始的。在我本科的學(xué)校,廣泛流傳著一個(gè)詞匯,叫做“’Hello,World’級程序員”,意指入門級程序員,但似乎“’Hello,World’級”這個(gè)說法更搞笑而形象。

嵌入式系統(tǒng)中沒有給我們提供MessageBox,但是鑒于其功能強(qiáng)大,我們需要模擬之,一個(gè)模擬的MessageBox函數(shù)為:

/******************************************

/*函數(shù)名稱:MessageBox

/*功能說明: 彈出式對話框,顯示提醒用戶的信息

/*參數(shù)說明:lpStr ---提醒用戶的字符串輸出信息

/* TYPE ---輸出格式(ID_OK = 0,ID_OKCANCEL = 1)

/*返回值: 返回對話框接收的鍵值,只有兩種KEY_OK,KEY_CANCEL

/******************************************

typedef enum TYPE { ID_OK,ID_OKCANCEL }MSG_TYPE;

extern BYTE MessageBox(LPBYTE lpStr,BYTE TYPE)

{

BYTE key = -1;

ClearScreen(); /*清除屏幕*/

DisplayString(xPos,yPos,lpStr,TRUE); /*顯示字符串*/

/*根據(jù)對話框類型決定是否顯示確定、取消*/

switch(TYPE)

{

case ID_OK:

DisplayString(13,yPos+High+1,“確定”,0);

break;

case ID_OKCANCEL:

DisplayString(8,yPos+High+1,“確定”,0);

DisplayString(17,yPos+High+1,“取消”,0);

break;

default:

break;

}

DrawRect(0,0,239,yPos+High+16+4); /*繪制外框*/

/* MessageBox是模式對話框,阻塞運(yùn)行,等待按鍵*/

while( (key!= KEY_OK)||(key!= KEY_CANCEL) )

{

key = getSysKey();

}

/*返回按鍵類型*/

if(key== KEY_OK)

{

return ID_OK;

}

else

{

return ID_CANCEL;

}

}

上述函數(shù)與我們平素在VC++等中使用的MessageBox是何等的神似?。繉?shí)現(xiàn)這個(gè)函數(shù),你會看到它在嵌入式系統(tǒng)中的妙用是無窮的。

總結(jié)

本篇是本系列文章中技巧性最深的一篇,它提供了嵌入式系統(tǒng)屏幕顯示方面一些很巧妙的處理方法,靈活使用它們,我們將不再被LCD上凌亂不堪的顯示內(nèi)容所困擾。

屏幕乃嵌入式系統(tǒng)生存之重要輔助,面目可憎之顯示將另用戶逃之夭夭。屏幕編程若處理不好,將是軟件中最不系統(tǒng)、最混亂的部分,筆者曾深受其害。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容