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