1 引言
在本文中,1個元素指的是1個字符,或者是1個漢字(漢字在LCD上所占的顯示空間是字符的2倍)。字符包括英文字母、數字、英文標點。1個位置指的是1個字符在LCD上所占的顯示空間的大小。本文中的數組content[>,contentram[>,contentLCD[>都定義成INT8U(8位無符號整型)里面存的都是漢字國標碼和ASCALL碼。
所用的LCD是上海晨興電子科技有限公司的SRL-0978GB液晶模塊,1行可以顯示21個字符,1屏可以顯示5行(分別稱為row0,row1,row2,row3,row4)。這樣LCD上就有105個字符位置。從0開始編號。這樣1屏就可以顯示105個ASCALL字符,但是能顯示多少個漢字了?最多能顯示50個漢字。在顯示漢字最多的情況下,如果運氣好的話還可以顯示5個字符。這時候每行只有1個字符。如果你能把這段話弄明白,那也就知道LCD顯示的特點了。
2 菜單的顯示
(1) 怎樣顯示不同的菜單?
在用戶界面中,顯示最多的就是菜單了。那是怎樣顯示不同的菜單?
首先,我給每個狀態編個狀態號,每個狀態就對應著不同的菜單。用指向字符串的指針數組來指向不同狀態下的菜單。不同狀態下都用3個不同的全局變量來分別記錄當前顯示屏上在row1行上顯示的菜單項的序號,在最后一有效行上顯示的菜單項的序號(當該菜單的菜單項的數目小于等于4時,其值為菜單項的數目;當菜單項的數目大于4時,其值為4),和當前反顯的菜單項的序號(初始為1,表示反顯第一個菜單項。當按下確認鍵時,表示選中了反顯的菜單項)。row0始終顯示菜單的標題。
例如:
#define S_Mainmenu 1 file://主菜單的狀態號
#define MAINMENU_MAXLEN 6
file://主菜單的菜單項的數目
static char *Mainmenu[MAINMENU_MAXLEN+1>=
{ “主菜單”;
“1.家家e”;
“2.短消息”;
“3.通話記錄”;
“4.個人助理”;
“5.電話新業務”;
“6.話機設置” };
file://指向主菜單的字符串的指針數組
最開始進入該狀態,Smstart=1;
file://在row1行上顯示的菜單項的序號
Smend=4; file://在最后一有效行上顯示的菜單項的序號。
Smindex=1; file://反顯的菜單項的序號
調用Dispmenu(Smmenu,Smstart,Smend,Smindex,SM_MAXLEN) 函數就可將Smmenu的菜單在LCD上順序顯示出標題和1~4號菜單項。這樣就有這些菜單項組合方式:1234,2345,3456,4561,5612,6123
(2) Dispmenu函數的實現思路
a) 調用PutMSG(0,0,Mainmenu[0>,0),在row0顯示該菜單標題的“主菜單”;
b) 如果Smstart>Smend(就是第3種和第4種和5種菜單項組合方式),那么就調用PutMSG函數從row1行開始依次顯示從Smstart開始到SM_MAXLEN的各個菜單項,然后顯示從1到Smend的菜單項。每行顯示一個菜單項。注意反顯。
c) 如果不滿足Smstart>Smend,那就從row1行開始依次顯示從Smstart到Smend的菜單項。同樣注意反顯。
d) 調用LCDDisplay()函數。
PutMSG()函數分別調用PutHZ函數和PutZF函數將待顯示內容的代碼轉換成點陣存到disp_ram[>[>中,LCDDisplay()函數將disp_ram[>[>中的點陣寫到LCD的緩存中,就可以顯示了.具體說明見后。
3 菜單的翻轉
3.1 翻轉的操作
在LCD上顯示菜單的情況下, 翻轉的操作如下:
(1) 單擊Up鍵,菜單向上翻。
首先調用OVERFLOW(&Smstart,&Smend,&Smindex,SM_MAXLEN)
對Smstart,Smend,Smindex進行調整,然后調用Dispmenu Dispmenu(Smmenu,Smstart,Smend,Smindex,SM_MAXLEN)。
(2) 單擊Down鍵,菜單向下翻。
首先調用UNDERFLOW()對Smstart,Smend,Smindex進行調整,然后調用Dispmenu()顯示菜單。
(3) 單擊Enter鍵,就進入反顯的菜單項所對應的新的狀態(對該狀態下的3個全局變量初始化后,調用Dispmenu函數就可以顯示該狀態的菜單了)
(4) 單擊Esc鍵就返回到進入該狀態的上1個狀態(調用Dispmenu函數就顯示該狀態菜單,該狀態的的3個全局變量已經記載了有關參數。)
3.2 OVERFLOW函數的實現
(1) 如果滿足(Smstart= =Smindex&&SM_MAXLEN>4)的條件
a) 如果Smstart= =1,則Smsatrt=SM_MAXLEN;否則Smstart減1
b) 如果Smend= =1,則Smend= SM_MAXLEN,否則Smend減1
c) 將Smindex改成與Smstart相同的值
(2) 如果不滿足(Smstart= =Smindex&&SM_MAXLEN>4)的條件
Smstart和Smend不變。
如果Smindex= =1,則Smindex=SM_MAXLEN;否則Smindex減1
UNDERFLOW函數的實現類似于OVERFLOW函數
4 文本的顯示
如果我們要顯示一段內容,該內容在1屏內顯示不完。那么如何知道第1屏顯示到什么元素結束,第2屏,第3屏……顯示的內容該從哪個元素開始,該到哪個元素結束了?初看起來這個問題是很簡單的,其實不然。在LCD的每1行的最后1個位置上如果要顯示1個漢字是不可能的,這樣就需要把要顯示的漢字挪到下1行顯示。也正是這個原因導致了需要確定以后的各屏該從哪個元素開始。
所用的辦法如下:
4.1 要顯示的內容是固定不變的
若要查看某條短消息的內容,但不對內容進行修改。
假如短消息的整個內容都保存在數組content[>中。我們現在要顯示content[>的內容。
(1) 先將數組content[>中的內容拷貝到數組contentram[>中。contentram[>是1個虛擬的LCD屏,該屏的空間比實際的屏的空間大很多。contentLCD[>是1個與實際屏一樣大小的數組。注意在拷貝完后,在contentram[>末尾加個結束符。
該拷貝所做的就是調整content[>中內容的位置,讓調整后的內容符合LCD顯示的原則,該原則就是在LCD每行的最后1個位置上不可能顯示1個漢字。因此要用空格來補充該位置,該漢字放在下1行開始。
(2) 從content[>到contentram[>的拷貝完成后,根據j的值就可以知道短消息的內容一共要顯示幾屏了。一般在顯示短消息內容的第一屏的時候需要在row0上顯示一行標題。netpagemax是該短消息共占有的屏數。
(3) 用netpage表示要顯示的是哪一屏內容,從1開始計數,一開始的時候其值為1。
按Up鍵,netpage減1,如果netpage==0,則netpage變為1
按Down鍵,netpage加1,如果netpage==netpagemax+1,則netpage變為netpagemax
a) netpage= =1時,將contentram〔i〕(i從0到83)拷貝到contentLCD[>中,如果沒有拷貝到83號就提前遇到了結束符,那就不必繼續拷貝了。然后調用PutMSG函數將contentLCD[>從LCD的row1行輸出。row0用作顯示標題。
b) netpage!=1時,將*(contentram+(netpage-2)*105+84+i),(i從0到104)拷貝到contentLCD[>中,如果沒有拷貝到103號就提前遇到了結束符,那就不必繼續拷貝了。然后調用PutMSG函數將contentLCD[>中的內容從row0行輸出。
4.2 要顯示的內容是變化的
例如要編輯條短消息,且短消息的內容就是變化的。
content[>用來存編輯的短消息的內容。convertindex用來表示反顯的是幾號元素(從1號開始編號),它是個實實在在的東西,用戶看到反顯的元素就知道convertidex是多少了。cursorindex用來表示光標在幾號位置上(從1號開始編號),它是一個虛擬的東西,并不通過什么記號顯示給用戶看,它是用來輔助計算convertindex的值的。這里的反顯相當于電腦上的光標。刪除是刪除反顯的元素,插入是在反顯元素的前面插入。
由于convertindex和cursorindex所用的單位不一致,就存在相互的轉換問題,并且需要保持兩者的同步。這兩個變量是相對content[>里的內容而言的。
假如短消息的整個內容放在content[>中,且內容最多有255個字符。我們現在要顯示content[>的內容。假如其中的內容是“我最近忙著做畢業設計,你們在芒什么了?”,我現在發現寫了錯字,我將反顯移動到“芒”上,這時,cursorindex為29,convertindex為15。
現在要將content[>中的內容顯示在LCD上。
(1) 將content[>中的內容拷貝到contentram[>中。如何拷貝同上。
并且要從content[>中的cursorindex推算出contentram[>中的cursorindextemp的值。
將cursorindex加上在拷貝content[cursorindex>之前所填補的空格數就是cursorindex的值。顯示第1屏時,row0顯示標題。顯示非第1屏時,row0也用來顯示內容了。row4不顯示內容,用來在漢字輸入法下顯示供選擇的拼音組合和漢字組合。
(2) 我們在編輯短消息的內容時,cursorindex和convertindex都在不斷的變化。同時虛擬屏contentram[>中的cursorindextemp也是在不斷變化的。根據cursorindextemp可以確定應該將哪一屏內容顯示在LCD上。netpage用來記錄應該顯示的屏的序號(從1開始計數)。
(3) 用netpage表示要顯示的是哪一屏內容,從1開始計數。它由cursorindextemp確定。
a) netpage==1時,將contentram〔i〕i從0到62拷貝到contentLCD[>中,如果沒有拷貝到62號就提前遇到了結束符,那就不必繼續拷貝了。然后調用PutMSG函數將contentLCD[>從LCD的row1輸出。row0用作顯示標題。
b) netpage!=1時,將*(contentram+(netpage-2)*84+63+i),(i從0到83)拷貝到contentLCD[>中,如果沒有拷貝到103號就提前遇到了結束符,那就不必繼續拷貝了。然后調用PutMSG函數將contentLCD[>從LCD的row0顯示出來。
(4) 在顯示contentLCD[>時,反顯的位置如下確定:
a) netpage==1時,計算出contentram[>中cursorindextemp前面的元素的個數存入i中,i+1就是這一屏中要反顯的元素的序號。
b) netpage!==1時候,計算出contentram[>中cursorindextemp前面的元素的個數存入i中,再計算出cursorindextemp所在頁的前面的頁中一共有多少元素存入j中,i-j+1就是這一屏中要反顯的元素的序號。
5 顯示content LCD[>中的內容
使用的LCD是128點(橫向)×64點(縱向),坐標在左上角。我開辟與一屏LCD點陣數相同的緩存區—字符型數組disp_ram[8>[128> (它用來存LCD一屏元素的點陣)。縱向8代表一列8個字節,8×8=64個點。128代表一行128個點。只要將disp_ram[>[>中的數據寫入LCD的緩存中,就可以顯示想要顯示的字符了。這里的字符包括漢字和ASCALL碼。漢字點陣是12(橫向)×12(縱向),需要18個字節來存放一個漢字的點陣。ASCALL點陣是6(橫向)×12(縱向),需要9個字節來存放一個ASCALL碼的點陣。
要顯示conten LCD[>的內容的步驟:
(1) 首先調用PutMSG(INT8U x, INT8U y,UINT8 *str,UINT8 style)函數,str指向contentLCD[>數組的首地址。
a) PutMSG(INT8U x, INT8U y,UINT8 *str,UINT8 style)函數。x,y是str指向的字串的第一個元素在LCD上顯示的位置。這里的x是以8個點陣(就是顯示一個ASCALL碼所占用的橫向點陣數目,也就是以位置為單位的)為單位的(橫向,取值為0到21),y是以4個點陣為單位的(縱向,y的取值為0,3,6,9,12,y為0時,表示從row0開始顯示,y為3表示從row1顯示,依此類推)。str是指向要顯示的字串的指針,style是個標志位(0x00-str所指向的全部內容不反顯,0xff-str所指向的全部內容反顯,為其他數字時表示該序號元素反顯,其余字符不反顯,序號從1開始編號)。
函數功能:循環調用PutHZ()或PutZF()函數在LCD上某一指定的位置顯示str所指向各個元素。
b) PutHZ(INT8U xx,INT8U yy,UINT8 * pStr,UINT8 style)
xx是要顯示的漢字在LCD上的橫向點陣的位置,yy是要顯示的漢字在LCD上的縱向點陣的位置。兩者都是從0開始計數。pStr和style的說明同PutMSG()函數,只是這里的style不可能為0xff。
該函數的功能是:
根據漢字的國標碼確定該漢字的點陣在漢字點陣表中的起始位置。并將它的點陣拷貝到HZgroup[>中。共18個字節。如果需要反顯,則要對Hzgroup[>中的所用字節取反。然后將這18個字節填到disp_ram[>[>中的相應位置。
怎么填?參考表1,將yy/8的結果賦給page,然后根據yy%8的結果來分別處理。
附表中的1個格表示LCD上的一個點。該點對應disp_ram[>[>中的某一位(i,j)(k,m,n)—表示將Hzgroup[i>的第j位放到diap_ram[k>[m>的第n位。)c)PutZF(INT8U xx,INT8U yy,UINT8 * pStr,UINT8 style)
各參數的說明同PutHZ()函數
該函數的功能是根據字符的ASCALL碼確定該字符的點陣在字符點陣表中的起始位置。并將它的點陣拷貝到ZFgroup[>中。共9個字節。如果需要反顯,則要對ZFgroup[>中的所用字節取反。然后將這9個字節填到disp_ram[>[>中的相應位置。方法類似于對漢字的處理。
(2) 然后調用LCDDisplay(UINT8 *)函數
該函數的功能就是將disp_ram[>[>中的內容拷入LCD的存儲器中,這是用匯編語言來寫的(該匯編語言是EPSON 8位單片機的)。這樣內容就從LCD上指定的開始位置處顯示出來了。
void LCDDisplay(UINT8 *src)
{
src=src; /* src is passed in register YP:IY */
#pragma asm
LD EP,#@DPAG(SFR_MEM_WaitSet) file://保存寄存器FF02的值
LD HL, #@DOFF(SFR_MEM_WaitSet)
LD A,[HL>
PUSH A
AND A,#8FH file://清除等待狀態
OR A,#10H
LD [HL>,A file://設置等待狀態
LD BR,#0
LD L,#0_LCDWirte_1:
LD EP,#18H file://LCD控制數據存儲區頁號
LD A,L
ADD A,#0B0H file://計算顯示緩沖區頁地址
LD [0H>,A file://置顯示緩沖區頁地址
LD A,#10H
LD [0H>,A file://置顯示緩沖區列地址高四位
LD A,#0
LD [0H>,A file://置顯示緩沖區列地址低四位
LD B,#80h_LCDWrite_2:
file://共128列
LD [BR:08H>,[IY>
INC IY
DJR NZ,_LCDWrite_2
INC L
CP L,#8 file://共8頁
JR NZ,_LCDWirte_1
POP A file://恢復寄存器FF02的值
LD EP,#0
LD HL, #@DOFF(SFR_MEM_WaitSet)
LD [HL>,A
#pragma endasm
}
為什么要按照以上的方法來將元素的點陣組織到disp_ram[>[>?這是因為點陣表的生成就是上述組織的逆過程。我們這樣來組織disp_ram[>[>由點陣表的生成機制決定的。如果點陣表不是這樣生成的,那么組織的時候就得按照另一套方法了。

LD BR,#0
LD L,#0_LCDWirte_1:
LD EP,#18H file://LCD控制數據存儲區頁號
LD A,L
ADD A,#0B0H file://計算顯示緩沖區頁地址
LD [0H>,A file://置顯示緩沖區頁地址
LD A,#10H
LD [0H>,A file://置顯示緩沖區列地址高四位
LD A,#0
LD [0H>,A file://置顯示緩沖區列地址低四位
LD B,#80h_LCDWrite_2:
file://共128列
LD [BR:08H>,[IY>
INC IY
DJR NZ,_LCDWrite_2
INC L
CP L,#8 file://共8頁
JR NZ,_LCDWirte_1
POP A file://恢復寄存器FF02的值
LD EP,#0
LD HL, #@DOFF(SFR_MEM_WaitSet)
LD [HL>,A
#pragma endasm
}
為什么要按照以上的方法來將元素的點陣組織到disp_ram[>[>?這是因為點陣表的生成就是上述組織的逆過程。我們這樣來組織disp_ram[>[>由點陣表的生成機制決定的。如果點陣表不是這樣生成的,那么組織的時候就得按照另一套方法了。