在本文中,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[>[>由点阵表的生成机制决定的。如果点阵表不是这样生成的,那么组织的时候就得按照另一套方法了。