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码的点阵。 要显示co
nten 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是个标志位(0
x00-str所指向的全部内容不反显,0
xff-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不可能为0
xff。 该函数的功能是: 根据汉字的国标码确定该汉字的点阵在汉字点阵表中的起始位置。并将它的点阵拷贝到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[>[>由点阵表的生成机制决定的。如果点阵表不是这样生成的,那么组织的时候就得按照另一套方法了。