隨筆-144  評論-80  文章-1  trackbacks-0

          C語言嵌入式系統(tǒng)編程修煉之四:屏幕操作

          作者:宋寶華   更新日期:2005-07-22

          漢字處理

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

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

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

            定義宏:

          # 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]; /* 字模點陣 */
          }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ù)組中查找內(nèi)碼與要求漢字內(nèi)碼相同的即可獲得字模。如果前面的漢字在數(shù)組中以內(nèi)碼大小順序排列,那么可以以二分查找法更高效的查找到漢字的字模。

            這是一種很有效的組織小漢字庫的方法,它可以保證程序有很好的結(jié)構(gòu)。

            系統(tǒng)時間顯示

             從NVRAM中可以讀取系統(tǒng)的時間,系統(tǒng)一般借助NVRAM產(chǎn)生的秒中斷每秒讀取一次當(dāng)前時間并在LCD上顯示。關(guān)于時間的顯示,有一個效率問題。因為 時間有其特殊性,那就是60秒才有一次分鐘的變化,60分鐘才有一次小時變化,如果我們每次都將讀取的時間在屏幕上完全重新刷新一次,則浪費(fèi)了大量的系統(tǒng) 時間。

            一個較好的辦法是我們在時間顯示函數(shù)中以靜態(tài)變量分別存儲小時、分鐘、秒,只有在其內(nèi)容發(fā)生變化的時候才更新其顯示。

          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關(guān)鍵字強(qiáng)大威力的證明。當(dāng)然,在C++語言里,static具有了更加強(qiáng)大的威力,它使得某些數(shù)據(jù)和函數(shù)脫離"對象"而成為"類"的一部分,正是它的這一特點,成就了軟件的無數(shù)優(yōu)秀設(shè)計。
          動畫顯示

            動畫是無所謂有,無所謂無的,靜止的畫面走的路多了,也就成了動畫。隨著時間的變更,在屏幕上顯示不同的靜止畫面,即是動畫之本質(zhì)。所以,在一個嵌入式系統(tǒng)的LCD上欲顯示動畫,必須借助定時器。沒有硬件或軟件定時器的世界是無法想像的:

            (1) 沒有定時器,一個操作系統(tǒng)將無法進(jìn)行時間片的輪轉(zhuǎn),于是無法進(jìn)行多任務(wù)的調(diào)度,于是便不再成其為一個多任務(wù)操作系統(tǒng);

            (2) 沒有定時器,一個多媒體播放軟件將無法運(yùn)作,因為它不知道何時應(yīng)該切換到下一幀畫面;

            (3) 沒有定時器,一個網(wǎng)絡(luò)協(xié)議將無法運(yùn)轉(zhuǎn),因為其無法獲知何時包傳輸超時并重傳之,無法在特定的時間完成特定的任務(wù)。

            因此,沒有定時器將意味著沒有操作系統(tǒng)、沒有網(wǎng)絡(luò)、沒有多媒體,這將是怎樣的黑暗?所以,合理并靈活地使用各種定時器,是對一個軟件人的最基本需求!

            在80186為主芯片的嵌入式系統(tǒng)中,我們需要借助硬件定時器的中斷來作為軟件定時器,在中斷發(fā)生后變更畫面的顯示內(nèi)容。在時間顯示"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語言中哪怕用到一丁點的面向?qū)ο笏枷耄浖Y(jié)構(gòu)將會有何等的改觀!

            筆者曾經(jīng)是個笨蛋,被菜單搞暈了,給出這樣的一個系統(tǒng):


          圖1 菜單范例

            要求以鍵盤上的"← →"鍵切換菜單焦點,當(dāng)用戶在焦點處于某菜單時,若敲擊鍵盤上的OK、CANCEL鍵則調(diào)用該焦點菜單對應(yīng)之處理函數(shù)。我曾經(jīng)傻傻地這樣做著:

          /* 按下OK鍵 */
          void onOkKey()
          {
           /* 判斷在什么焦點菜單上按下Ok鍵,調(diào)用相應(yīng)處理函數(shù) */
           Switch(currentFocus)
           {
            case MENU1:
             menu1OnOk();
             break;
            case MENU2:
             menu2OnOk();
             break;
            …
           }
          }
          /* 按下Cancel鍵 */
          void onCancelKey()
          {
           /* 判斷在什么焦點菜單上按下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)我定義菜單時,只需要這樣:

          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ū)ο螅嫔窳耍?br>模擬MessageBox函數(shù)

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

            
          圖2 經(jīng)典的Hello,World!

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

            嵌入式系統(tǒng)中沒有給我們提供MessageBox,但是鑒于其功能強(qiáng)大,我們需要模擬之,一個模擬的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 keyValue = -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( (keyValue != KEY_OK) || (keyValue != KEY_CANCEL) )
           {
            keyValue = getSysKey();
           }
           /* 返回按鍵類型 */
           if(keyValue== KEY_OK)
           {
            return ID_OK;
           }
           else
           {
            return ID_CANCEL;
           }
          }

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

            總結(jié)

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

            屏幕乃嵌入式系統(tǒng)生存之重要輔助,面目可憎之顯示將另用戶逃之夭夭。屏幕編程若處理不好,將是軟件中最不系統(tǒng)、最混亂的部分,筆者曾深受其害。
          posted on 2005-09-21 17:32 小力力力 閱讀(1029) 評論(1)  編輯  收藏 所屬分類: C/C++

          評論:
          # re: C語言嵌入式系統(tǒng)編程修煉之四:屏幕操作 2006-08-14 12:58 | guest
          你真厲害!懂得很多。  回復(fù)  更多評論
            
          主站蜘蛛池模板: 汽车| 图片| 琼结县| 吴川市| 河津市| 涞源县| 民权县| 遂昌县| 文化| 蓬莱市| 昌邑市| 冷水江市| 井冈山市| 耒阳市| 桓仁| 满城县| 故城县| 合肥市| 大连市| 宣恩县| 定结县| 高邮市| 铜山县| 牟定县| 尤溪县| 阳东县| 峡江县| 宝鸡市| 五常市| 涟源市| 西贡区| 黄大仙区| 延庆县| 贵阳市| 汉源县| 兰考县| 江油市| 泾川县| 慈利县| 宁晋县| 赣榆县|