C語言嵌入式系統編程修鍊之螢幕操作

來源:互聯網
上載者:User
漢文書處理

  現在要解決的問題是,嵌入式系統中經常要使用的並非是完整的漢字型檔,往往只是需要提供數量有限的漢字供必要的顯示功能。例如,一個微波爐的LCD上沒有必要提供顯示"電子郵件"的功能;一個提供漢字顯示功能的空調的LCD上不需要顯示一條"短訊息",諸如此類。但是一部手機、小靈通則通常需要包括較完整的漢字型檔。

  如果包括的漢字型檔較完整,那麼,由內碼計算出漢字字模在庫中的位移是十分簡單的:漢字型檔是按照區位的順序排列的,前一個位元組為該漢字的區號,後一個位元組為該字的位號。每一個區記錄94個漢字,位號則為該字在該區中的位置。因此,漢字在漢字型檔中的具體位置計算公式為:94*(區號-1)+位號-1。減1是因為數組是以0為開始而區號位號是以1為開始的。只需乘上一個漢字字模佔用的位元組數即可,即:(94*(區號-1)+位號-1)*一個漢字字模佔用位元組數,以16*16點陣字型檔為例,計算公式則為:(94*(區號-1)+(位號-1))*32。漢字型檔中從該位置起的32位元組資訊記錄了該字的字模資訊。

  對於包含較完整漢字型檔的系統而言,我們可以以上述規則計算字模的位置。但是如果僅僅是提供少量漢字呢?譬如幾十至幾百個?最好的做法是:

  定義宏:

# define EX_FONT_CHAR(value)
# define EX_FONT_UNICODE_VAL(value) (value),
# define EX_FONT_ANSI_VAL(value) (value),

  定義結構體:

typedef struct _wide_unicode_font16x16
{
 WORD value; /* 內碼 */
 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語言中哪怕用到一丁點的物件導向思想,軟體結構將會有何等的改觀!

  筆者曾經是個笨蛋,被菜單搞暈了,給出這樣的一個系統:


圖1 菜單範例

  要求以鍵盤上的"← →"鍵切換菜單焦點,當使用者在焦點處於某菜單時,若敲擊鍵盤上的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’級"這個說法更搞笑而形象。

  
圖2 經典的Hello,World!

  圖2給出了兩種永恒經典的Hello,World對話方塊,一種只具有"確定",一種則包含"確定"、"取消"。是的,MessageBox的確有,而且也應該有兩類!這完全是由特定的應用需求決定的。

  嵌入式系統中沒有給我們提供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 keyValue = -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( (keyValue != KEY_OK) || (keyValue != KEY_CANCEL) )
 {
  keyValue = getSysKey();
 }
 /* 返回按鍵類型 */
 if(keyValue== KEY_OK)
 {
  return ID_OK;
 }
 else
 {
  return ID_CANCEL;
 }
}

  上述函數與我們平素在VC++等中使用的MessageBox是何等的神似啊?實現這個函數,你會看到它在嵌入式系統中的妙用是無窮的。

  總結

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

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.