硬件驱动
Protues内置12864液晶型号是AMPIRE KS0108,其中KS0108为驱动器芯片的型号。驱动器作为桥梁,为引脚输入到显示在液晶上输出提供了连接。引用网友“jiangnanzhl”在百度知道的回答:CGROM和CGRAM中存储的字模信息相当于厨房中的食品,CGROM是厨房中现成的熟食,CGRAM是用户自行制作的菜肴,这些食品都要通过托盘转移一下,才能送到餐桌上食用;类似的字模编码都要先被读取到对应的DDRAM中,经如上中转以后,屏幕的相应位置才显示出字符。
以ST7920驱动为例,这是大多数有字库版本的12864所使用的驱动器,其内部结构如下图:
驱动内部的主要结构是这些存储器。
DDRAM(Data Display RAM): 数据显示存储器,输入到DDRAM的信息将会通过普通驱动和段驱动显示在LCD上。
CGROM(Character Generation ROM): 字符发生器,只读,包含有标准的ASCII码、日文字符、和希腊文字符,包括中文字符也存在CGROM中。字库一般指的就是CGROM。
CGRAM(Character Generation RAM): 为了使用户存储自己设计的字模编码,厂家所提供的自定义字符区,一般空间不大。
HCGROM(Half height Character Generation ROM): 半宽字符发生器。
Protues中KS0108驱动版本的12864是无字库的,就是只有DDRAM,没有其他的存储器。CS1和CS2是KS0108驱动类12864的标志性引脚。因此该型号显示屏含有两个驱动器,每一块驱动器控制64×64个点,两块驱动器就控制128×64个点,由片选引脚CS1和CS2控制显示哪一块。RST复位端接单片机接口,或者接高电平,但不能不接。
字模显示
显示屏上有128×64个点,划分为64小行,4大行,8页,128列,如图所示。在编写代码的时候,我是先通过片选确定写在左半部分还是右半部分,然后选择第一页到第七页(一般为了工整不选择小行),再选择1~64列,这样光标就确定了从某一点上开始写。
关于字模,先从汉字的数字化说起吧。汉字是象形文字,其结构较英文字母复杂的多。而且英文字母的种类很少,中文的种类更是浩繁,因此汉字数字化后所占的空间自然也是要比英文字符大。因此要想使汉字也储存在计算机中,就要中国人另想办法,另起炉灶。为了使每一个汉字有一个全国统一的代码,1980年,我国颁布了第一个汉字编码的国家标准: G(国)B(标)2312。这个字符集是我国中文信息处理技术的发展基础,是推动汉字数字化的重要举措。国标码是四位十六进制,然而为了便于交流,大家常用的是四位十进制的区位码,区位码是国标码0xXXYY减去0xA0A0,再转换为十进制。常见的汉字与符号组成一个94×94的矩阵。在此方阵中,每一行称为一个“区”,每一列称为一个“位”。01-09区为682个特殊字符,16~87区为汉字区,包含6763个汉字。
ASCII码的最大值是127,转换成二进制是1111111,不足八位,存储时在最高位补0就是01111111。char类型在计算机中占用一个字节,可以囊括ASCII码上所有字符。而汉字在计算机中存储需要两个字节,区码和位码需要分别占一个字节。如28区的46位上的汉字区位码是2846,区码、位码分别转换成16进制后是0x1C和0x2E。但这样的区码和位码在存储时就和ASCII码重复了,为了区别,将区码和位码在存储时分别加0xA0,这样必定大于01111111,就和ASCII码区分开了。区位码2846对应的国标码是0xBCCE,对应的汉字都是“嘉”。(其实以上内容与字模无直接联系,不过简单了解可以避免把概念搞混)
但是,国标码也只是一串由0、1组成的数字,它该如何在屏幕上显示呢?这就要用到字模了。字模虽然也是一组数字,但它的意义却与数字的意义有了根本的变化,它是用数字的各位信息来记载英文或汉字的形状。
如此,0的含义变成了“不填充”,1的含义变成了“填充”。这样,中文的显示需要32个字节的数据,英文、ASCII上的符号的显示需要16个字节的数据。字库就是厂家已经取好了一些常用汉字的字模,放在了只读的CGROM中。
字符按照什么样的顺序存储在ROM中,就涉及到驱动的工作方式。通过实验发现, 一般是以“第x页第y列从下到上、先写低位后写高位”的方式从CGROM读出数据并写在LCD上。
以上图“A”的字模为例,往LCD上写“A”的第一个字节的顺序为:1、1、1、0、0、0、0,那么储存在CGROM的第一个字节就需要是0000 0111,即0x07。第二个字节需要是0x0F,写在第x页第y+1列。写完第八个字节后就要在命令中“page+1”,从下一页开始写,一直到写完十六个字节。所谓字模的宽和高就是这样来的,“A”的宽是8,高是16。无字库版本只不过需要人为地按这样的规则将数据写入DDRAM,因而就要借助取模软件。取模软件能够把汉字、字符等等按照顺序以数组的形式呈现出来,供我们使用。
取模软件
由于12864没有字库,因此向它直接写入ASCII码是无法显示的。如图写入ASCII码,结果屏幕只是单纯显示出一个字节。
但这样的话自由度比较高,我们可以使用任意字体、任意字号来写,粗细、倾斜等也可以设置。字模软件Zimo21界面如图。
主要想说一下参数设置中的取模方式:
想实现从左到右的显示顺序的话,在取模的时候就要先取出字符位代码的第1竖列、第1到8横行的数据,接着取出第2竖列、第1~8横行的数据的数据,最后一直取到第y+7列的数据,然后按列依次呈现出来,这种按列取模方式叫做纵向取模。再点击以C51方式取模,就可以获得十六进制的一串数字,我们把它放到数组A[ ]中,就建立了自己的字库。
想实现从上到下显示顺序的话,在取模的时候就要按照从上到下的顺序了,这个叫横向取模。
字节倒序是根据不同驱动器的驱动方式来勾选,看其先写低字节或是先写高字节,如果不知道不妨勾选然后试两次。
此外,字模所占内存的大小与字体和字号都有关。宋体、小四对应的汉字点阵宽×高=16×16,英文点阵宽×高=8×16,所以汉字占了LCD的一大行和16列;英文也占了一大行,但比较窄,占了8列。其他字体和大小也都可以,考虑美观的话可以多实践几次,找到更合适的字体。在控制面板中搜索字体,可以在里面开启已安装但未显示的字体。
主要函数代码
以上是各个指令的控制代码。包括读、写、初始化的各个函数在网上已经有很多人编写了,我们能看懂即可。我觉得重要的是在理解原理的基础上,调用这些模块化的东西来实现自己想要的功能。先写这么多了,以上只是一些个人拙见,我也在不断地学习更深入的应用。如有错误,还望各位不吝指正!
void selectscreen(unsigned int i) //选屏幕
{
switch (i)
{
case 0: CS1 = 0; CS2 = 1; break; //片选左屏
case 1: CS1 = 1; CS2 = 0; break; //片选右屏
case 2: CS1 = 0; CS2 = 0; break; //选两块屏
default: break; //退出
}
}
void chekbusy12864(void)
{
unsigned int dat;
RS = 0; //指令模式
RW = 1; //读数据
do{
P2 = 0;
EN = 1; //E端口置1
delay_us(1);
dat = P2 & 0x80; //dat取总线上最高位的值
EN = 0; //E端口置0
}while (dat != 0); //dat若为1意味着LCD“忙”,系统将停在这里,等到“不忙”的时候再运行下去
}
void writeinstr(unsigned char x) //写入命令
{
chekbusy12864();
RS=0; //写指令
RW=0; //低电平是Write
EN=0; //保险起见,将E先置0。其实没有必要
P2=x; //总线写入
// delay_us(1); //仿真时可以不用延时,实际电气连接后就需要调试了
EN=1;
// delay_us(1);
EN = 0; //在R/W=0时,E=“1→0”会在下降沿锁存DB7∽DB0,从而数据被写到IR或DR
}
void writedata(unsigned char y) //写入数据,一系列写函数的最小单元
{
chekbusy12864();
RS=1; //写数据
RW=0; //低电平是Write,高电平是Read
EN=0;
P2=y;
// delay_us(1);
EN=1;
// delay_us(1);
EN=0;
}
void setpage(unsigned char page)
{
page=0xb8|page; //1011 1XXX,XXX为0~7,对应第一页到第8页
writeinstr(page); //写入“页设置”指令
}
void setcolumn(unsigned char column)
{
column=column&0x3f; //这里限制column取值最大为0x3f,即63。
column=0x40|column; //01XX XXXX,XXXXXX为0~63,对应1~64列
writeinstr(column); //写入“列设置”指令
}
void clear12864(unsigned char screen) //12864清零
{
unsigned char i,j;
selectscreen(screen); //选择清哪一个屏幕
for(i=0;i<8;i++)
{
setpage(i);
setcolumn(0);
for(j=0;j<64;j++)
{
writedata(0x00); //逐个点清空
}
}
}
void initlcd(void)
{
chekbusy12864();
clear12864(2);
writeinstr(0xc0); //从第0行开始显示
}