姓名:徐嬌 ? ?學號:17011210547
轉自http://mp.weixin.qq.com/s/YUXrJbin_rnwModTY2Ds_A
【嵌牛導讀】
C語言是一門通用計算機編程語言,應用廣泛。C語言的設計目標是提供一種能以簡易的方式編譯、處理低級存儲器、產生少量的機器碼以及不需要任何運行環境支持便能運行的編程語言。
盡管C語言提供了許多低級處理的功能,但仍然保持著良好跨平臺的特性,以一個標準規格寫出的C語言程序可在許多電腦平臺上進行編譯,甚至包含一些嵌入式處理器(單片機或稱MCU)以及超級電腦等作業平臺。
20世紀80年代,為了避免各開發廠商用的C語言語法產生差異,由美國國家標準局為C語言訂定了一套完整的國際標準語法,稱為ANSI C,作為C語言最初的標準。
【嵌牛鼻子】C語言、嵌入式系統編程、注意事項之屏幕操作
【嵌牛提問】C語言在嵌入式系統編程時的注意事項
【嵌牛正文】
C語言嵌入式系統編程注意事項之屏幕操作
現在要解決的問題是,嵌入式系統中經常要使用的并非是完整的漢字庫,往往只是需要提供數量有限的漢字供必要的顯示功能
漢字處理
現在要解決的問題是,嵌入式系統中經常要使用的并非是完整的漢字庫,往往只是需要提供數量有限的漢字供必要的顯示功能。例如,一個微波爐的LCD上沒有必要提供顯示“電子郵件”的功能;一個提供漢字顯示功能的空調的LCD上不需要顯示一條“短消息”,諸如此類。但是一部手機、小靈通則通常需要包括較完整的漢字庫。
如果包括的漢字庫較完整,那么,由內碼計算出漢字字模在庫中的偏移是十分簡單的:漢字庫是按照區位的順序排列的,前一個字節為該漢字的區號,后一個字節為該字的位號。每一個區記錄94個漢字,位號則為該字在該區中的位置。因此,漢字在漢字庫中的具體位置計算公式為:94*(區號-1)+位號-1。減1是因為數組是以0為開始而區號位號是以1為開始的。只需乘上一個漢字字模占用的字節數即可,即:(94*(區號-1)+位號-1)*一個漢字字模占用字節數,以16*16點陣字庫為例,計算公式則為:(94*(區號-1)+(位號-1))*32。漢字庫中從該位置起的32字節信息記錄了該字的字模信息。
對于包含較完整漢字庫的系統而言,我們可以以上述規則計算字模的位置。但是如果僅僅是提供少量漢字呢?譬如幾十至幾百個?最好的做法是:
定義宏:
# define EX_FONT_CHAR()
# define EX_FONT_UNICODE_VAL() (),
# define EX_FONT_ANSI_VAL() (),
定義結構體:
typedef struct _wide_unicode_font16x16
{
WORD ; /*內碼*/
BYTE data[32]; /*字模點陣*/
}Unicode;
#define CHINESE_CHAR_NUM … /*漢字數量*/
字模的存儲用數組:
Unicode chinese[CHINESE_CHAR_NUM]=
{
{
EX_FONT_CHAR(“業”)
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}
}
}
要顯示特定漢字的時候,只需要從數組中查找內碼與要求漢字內碼相同的即可獲得字模。如果前面的漢字在數組中以內碼大小順序排列,那么可以以二分查找法更高效的查找到漢字的字模。
這是一種很有效的組織小漢字庫的方法,它可以保證程序有很好的結構。
系統時間顯示
從NVRAM中可以讀取系統的時間,系統一般借助NVRAM產生的秒中斷每秒讀取一次當前時間并在LCD上顯示。關于時間的顯示,有一個效率問題。因為時間有其特殊性,那就是60秒才有一次分鐘的變化,60分鐘才有一次小時變化,如果我們每次都將讀取的時間在屏幕上完全重新刷新一次,則浪費了大量的系統時間。
一個較好的辦法是我們在時間顯示函數中以靜態變量分別存儲小時、分鐘、秒,只有在其內容發生變化的時候才更新其顯示。
extern void DisplayTime(…)
{
static BYTE byHour,byMinute,bySecond;
BYTE byNewHour,byNewMinute,byNewSecond;
byNewHour = GetSysHour();
byNewMinute = GetSysMinute();
byNewSecond = GetSysSecond();
if(byNewHour!= byHour)
{
… /*顯示小時*/
byHour = byNewHour;
}
if(byNewMinute!= byMinute)
{
… /*顯示分鐘*/
byMinute = byNewMinute;
}
if(byNewSecond!= bySecond)
{
… /*顯示秒鐘*/
bySecond = byNewSecond;
}
}
這個例子也可以順便作為C語言中static關鍵字強大威力的證明。當然,在C++語言里,static具有了更加強大的威力,它使得某些數據和函數脫離“對象”而成為“類”的一部分,正是它的這一特點,成就了軟件的無數優秀設計。
動畫顯示
動畫是無所謂有,無所謂無的,靜止的畫面走的路多了,也就成了動畫。隨著時間的變更,在屏幕上顯示不同的靜止畫面,即是動畫之本質。所以,在一個嵌入式系統的LCD上欲顯示動畫,必須借助定時器。沒有硬件或軟件定時器的世界是無法想像的:
(1) 沒有定時器,一個操作系統將無法進行時間片的輪轉,于是無法進行多任務的調度,于是便不再成其為一個多任務操作系統;
(2) 沒有定時器,一個多媒體播放軟件將無法運作,因為它不知道何時應該切換到下一幀畫面;
(3) 沒有定時器,一個網絡協議將無法運轉,因為其無法獲知何時包傳輸超時并重傳之,無法在特定的時間完成特定的任務。
因此,沒有定時器將意味著沒有操作系統、沒有網絡、沒有多媒體,這將是怎樣的黑暗?所以,合理并靈活地使用各種定時器,是對一個軟件人的最基本需求!
在80186為主芯片的嵌入式系統中,我們需要借助硬件定時器的中斷來作為軟件定時器,在中斷發生后變更畫面的顯示內容。在時間顯示“xx:xx”中讓冒號交替有無,每次秒中斷發生后,需調用ShowDot:
void ShowDot()
{
static BOOL bShowDot = TRUE; /*再一次領略static關鍵字的威力*/
if(bShowDot)
{
showChar(’:’,xPos,yPos);
}
else
{
showChar(’ ’,xPos,yPos);
}
bShowDot =!bShowDot;
}
菜單操作
無數人為之絞盡腦汁的問題終于出現了,在這一節里,我們將看到,在C語言中哪怕用到一丁點的面向對象思想,軟件結構將會有何等的改觀!
要求以鍵盤上的“← →”鍵切換菜單焦點,當用戶在焦點處于某菜單時,若敲擊鍵盤上的OK、CANCEL鍵則調用該焦點菜單對應之處理函數。我曾經傻傻地這樣做著:
/*按下OK鍵*/
void onOkKey()
{
/*判斷在什么焦點菜單上按下Ok鍵,調用相應處理函數*/
Switch(currentFocus)
{
case MENU1:
menu1OnOk();
break;
case MENU2:
menu2OnOk();
break;
…
}
}
/*按下Cancel鍵*/
void onCancelKey()
{
/*判斷在什么焦點菜單上按下Cancel鍵,調用相應處理函數*/
Switch(currentFocus)
{
case MENU1:
menu1OnCancel();
break;
case MENU2:
menu2OnCancel();
break;
…
}
}
終于有一天,我這樣做了:
/*將菜單的屬性和操作“封裝”在一起*/
typedef struct tagSysMenu
{
char *text; /*菜單的文本*/
BYTE xPos; /*菜單在LCD上的x坐標*/
BYTE yPos; /*菜單在LCD上的y坐標*/
void(*onOkFun)(); /*在該菜單上按下ok鍵的處理函數指針*/
void(*onCancelFun)(); /*在該菜單上按下cancel鍵的處理函數指針*/
}SysMenu,*LPSysMenu;
當我定義菜單時,只需要這樣:
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();
}
程序被大大簡化了,也開始具有很好的可擴展性!我們僅僅利用了面向對象中的封裝思想,就讓程序結構清晰,其結果是幾乎可以在無需修改程序的情況下在系統中添加更多的菜單,而系統的按鍵處理函數保持不變。
面向對象,真神了!
模擬MessageBox函數
MessageBox函數,這個Windows編程中的超級猛料,不知道是多少入門者第一次用到的函數。還記得我們第一次在Windows中利用MessageBox輸出“Hello,World!”對話框時新奇的感覺嗎?無法統計,這個世界上究竟有多少程序員學習Windows編程是從MessageBox(“Hello,World!”,…)開始的。在我本科的學校,廣泛流傳著一個詞匯,叫做“’Hello,World’級程序員”,意指入門級程序員,但似乎“’Hello,World’級”這個說法更搞笑而形象。
嵌入式系統中沒有給我們提供MessageBox,但是鑒于其功能強大,我們需要模擬之,一個模擬的MessageBox函數為:
/******************************************
/*函數名稱:MessageBox
/*功能說明: 彈出式對話框,顯示提醒用戶的信息
/*參數說明: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); /*顯示字符串*/
/*根據對話框類型決定是否顯示確定、取消*/
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是模式對話框,阻塞運行,等待按鍵*/
while( (key!= KEY_OK)||(key!= KEY_CANCEL) )
{
key = getSysKey();
}
/*返回按鍵類型*/
if(key== KEY_OK)
{
return ID_OK;
}
else
{
return ID_CANCEL;
}
}
上述函數與我們平素在VC++等中使用的MessageBox是何等的神似啊?實現這個函數,你會看到它在嵌入式系統中的妙用是無窮的。
總結
本篇是本系列文章中技巧性最深的一篇,它提供了嵌入式系統屏幕顯示方面一些很巧妙的處理方法,靈活使用它們,我們將不再被LCD上凌亂不堪的顯示內容所困擾。
屏幕乃嵌入式系統生存之重要輔助,面目可憎之顯示將另用戶逃之夭夭。屏幕編程若處理不好,將是軟件中最不系統、最混亂的部分,筆者曾深受其害。