起因

数码管和 LED 能表达的信息量太少,设备要做中文菜单、状态界面,就需要一块能显示汉字的点阵屏。12864 是最常见的选择之一,而且分“带字库”和“不带字库”两种:不带字库的屏显示汉字要自己生成字模、逐字节搬点阵(一套字库工具链的活);带字库的屏控制芯片里已经烧好了汉字字库,发两个编码字节就出一个汉字,开发量小一大截。本篇使用带 ST7920 字库芯片、8 位并口驱动的 12864。

需求

  • 51 单片机用 8 位并口驱动 12864(ST7920 主控)
  • 能初始化、清屏、在指定位置显示中文和英文字符
  • 弄清文本显示和绘图刷新两套用法

技术实现原理

带字库为什么“发编码就能出字”

ST7920 内置 2M 位 CGROM,存着 8192 个 16×16 汉字字模,按 GB2312 编码索引。往 DDRAM 连续写两个字节(一个汉字的 GB2312 编码),芯片自动查出字模点亮对应的点阵——字库在芯片里、检索在芯片里做,单片机只管发码。代价是文本只能按字符网格排版:4 行 × 16 个半角字符位(8 个全角汉字位),行首地址 0x80 / 0x90 / 0x88 / 0x98,这就是后面代码里 addresses[] 数组的来历。

汉字必须“连续写”的根因

一个汉字占两个编码字节,芯片按字节流顺序配对解析。如果写完高字节后插进一条命令,配对就被拆散,轻则错字、重则整行乱码——所以显示汉字的循环里除了数据什么都不能夹。

两套坐标系统:文本坐标与绘图坐标

ST7920 有两套指令集,对应两种显示方式,坐标体系完全不同:

  • 基本指令(0x30):文本模式,坐标是 4×16 的字符格;
  • 扩充指令(0x34/0x36):绘图模式,操作 128×64 的 GDRAM 点阵,分上下两个半屏(行 0-31 / 32-63),每行 16 字节,写完一个字节列地址自动加一。

0x36 与 0x34 的差别只在一个“绘图显示开”位——忘了这个位,写再多 GDRAM 屏也不亮。同一对 (x,y) 在两套模式里位置不一样,这就是“坐标变了,一定要注意”的根因。清屏函数走绘图模式(按点清),显示字符走文本模式(按格写),用前各自切指令集。

读写时序:与 LCD1602 同族

RS(1 数据 / 0 命令)、RW(0 写)、E 上升沿锁存数据。这套时序和 LCD1602 几乎一致(同一时代的指令风格),所以两个函数写好,初始化代码就完成一大半——所有上层函数都是反复调它们。

一、LCD12864液晶显示屏简介

在我们常用的人机交互显示界面中,除了数码管、LED,有一种12864液晶屏用的比较多。 顾名思义,12864表示其横向可以显示128个点,纵向可以显示64个点。

LCD12864图形点阵液晶显示模块(LCM)就是由128*64个液晶显示点组成的一个128列*64行的阵列, 它的每一行横向一共有128个可显示点,每一列纵向有64个, 这些“点”其实也都是一个个发光二极管,每个显示点对应一位二进制数,1表示亮,0表示灭.

我这里使用的就是带字库的,直接可以显示中文,不需要自己去编码,发送数据使用8位并行模式。

我们常用的12864液晶模块中有带字库的,也有不带字库的,其控制芯片也有很多种, 如KS0108、T6963C,ST7920等等。

本产品是以ST7920为主控芯片的12864液晶屏的一种。

lcd12864基本资料

常见的12864LCD使用的控制芯片是ST7920。

ST7920一般和ST7921(列驱动芯片)配合使用,做成显示2行每行16个汉字的显示屏25632LCD,或者是做成4行每行8个汉字的显示屏12864LCD 。

12864液晶模块中有带字库可以显示字母、数字符号、中文字型及图形,具有绘图及文字画面混合显示功能。

提供三种控制接口,分别是

  • 8 位微处理器接口,
  • 4 位微处理器接口
  • 串行接口。

所有的功能,包含显示RAM,字型产生器,都包含在一个芯片里面,只要一个最小的微处理系统,就可以方便操作模块。 内置2M-位中文字型ROM (CGROM) 总共提供8192 个中文字型(16x16 点阵), 16K-位半宽字型ROM (HCGROM) 总共提供126 个符号字型(16x8 点阵),64 x 16-位字型产生RAM (CGRAM), 另外绘图显示画面提供一个64x256 点的绘图区域(GDRAM),可以和文字画面混和显示。 提供多功能指令:

  • 画面清除(Display clear)
  • 光标归位(Return home)
  • 显示打开/关闭(Display on/off)
  • 光标显示/隐藏(Cursor on/off)
  • 显示字符闪烁(Display character blink)
  • 光标移位(Cursor shift)
  • 显示移位(Display shift)
  • 垂直画面旋转(VerTIcal line scroll)
  • 反白显示(By_line reverse display)
  • 待命模式(Standby mode)

lcd12864主要参数

1、工作电压(VDD):3.3或5.5V(只支持一种电压)

2、逻辑电平:2.7~5.5V

3、LCD 驱动电压(Vo):0~7V

4、工作温度(TOP):0~55℃(常温)/-20~70℃(宽温)

保存温度(TST):-10~65℃(常温)/-30~80℃(宽温)

3.lcd12864引脚功能

二、引脚介绍

1.一共有20个引脚,重要的只有那么几个 3VO就是调节对比度的,相当于调节每个点显示出来的深浅,虽然只有亮与不亮两种选择,但亮的模式下相当于可以调节每个点到底多亮 4RS,为1的时候表示发送的发送的数据,为0表示发送命令 5RW,就是控制我们是往LCD12864读数据(1)还是写数据(0),我们要控制LCD,当然是往里面写数据(0) 6E,使能信号 17~RST复位脚

接下来的DB0~DB7就是用来传输我们的数据,一个字节八个位刚好 2.官方的引脚图

三、时序图

LCD12864和LCD1602的简直一模一样

四、初始化LCD的代码

1.两个最重要的函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/*
	值得注意的是:
		(1).这两个函数(向LCD写入命令和向LCD写入数据)能够写好正常
			整个LCD的初始化代码基本上就写好一大半了,因为后面所有的函数都是不断调用这两个函数
		(2).这两个函数可以根据自己的需求合并成一个,两个函数就一个地方不一样CD12864_RS = 0/1
		(3).这是根据上面时序图写出来的,和LCD1602的代码也简直一模一样
			只需要稍微改变一下,延时函数我也没显示出来,根据自己开发平台来自己写
*/
	//向12864中写入数据
	void LCD12864_Write_Data(unsigned char dat)
	{
		LCD12864_E = 0;
		LCD12864_WR = 0;//选择写模式 
		LCD12864_RS = 1;//选择数据模式

		//延时
		delay_10us(1);

		//将数据准备
		LCD12864_Data = dat;

		delay_10us(1);

		LCD12864_E = 1;//表示开始写入数据,上升沿写入数据

		delay_10us(20); //延时等待数据全部发送完成
	}



	//向12864中写入命令
	void LCD12864_Write_Com(unsigned char com)
	{
		//首先我们设置片选信号
		LCD12864_E = 0;//片选信号一般设置为0表示片选
		LCD12864_WR = 0;//选择写模式 
		LCD12864_RS = 0;//选择命令模式

		//延时
		delay_10us(1);

		//将数据准备
		LCD12864_Data = com;

		delay_10us(1);

		LCD12864_E = 1;//表示开始写入数据,上升沿写入数据

		delay_10us(20);
	}

2.初始化函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
        注意:
            (1).这些指令具体代表什么暂时不用管,如果你想快速看到你的LCD效果
                你有时间再去研究,这些指令基本就是一些约束校准的工作
        */
        //对LCD12864进行初始化
        void LCD12864_Init()
        {
            //首先对LCD进行复位
            LCD12864_REST = 0;
            delay_ms(30);
            LCD12864_REST = 1;
            delay_ms(20);

            /* 开始初始化的步骤 */
            LCD12864_Write_Com(0x30);
            LCD12864_Write_Com(0x08);
            LCD12864_Write_Com(0x01);
            LCD12864_Write_Com(0x06);
            LCD12864_Write_Com(0x0c);

        }

3.基本刷屏函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
        /*  
        1.总共分为上下两个屏幕,其中第0->31行和第32->63行的行地址相同

        (上 0->31)第一个屏幕的首地址(0x80,0x80) -> ... -> (0x80,0x87)
                                   ....
                                  (0xa0,0x80) -> ... -> (0xa0,0x87)
        (下 32->63)第二个屏幕的首地址(0x80,0x88) -> ... -> (0x80,0x90)
                                    ...
                                 (0xa0,0x88) -> ... -> (0xa0,0x90)

        2.每个地址就是控制 一行16个小点,发送两次数据分别控制前8点和后8个点,
            例写入的01010101 01010101来控制屏幕的点 , 1显示 0不显示

        3.先写入行地址,后写入列地址,连续写列地址会自动增加
        */

        //进行清屏操作 LCD12864_Clear_Screen(0xFF)全亮
            //LCD12864_Clear_Screen(0xFF)全灭
        void LCD12864_Clear_Screen(unsigned char value)
        {
            unsigned char i,j;

            LCD12864_Write_Com(0x34);
            LCD12864_Write_Com(0x36);


            for(i=0;i<64;i++){
                    if(i<32){
                    LCD12864_Write_Com(0x80+i);
                    LCD12864_Write_Com(0x80);
                    }else {
                    LCD12864_Write_Com(0x80+(i-32));
                    LCD12864_Write_Com(0x88);
                    }

                    for(j=0;j<16;j++)
                        LCD12864_Write_Data(value);
            }
        }

4.显示中英文字符函数 显示非中文字符 和 中文字符都差不多,因为自带汉字库,只是注意写汉字的时候要连续写! 注意这里的坐标变了,一定要注意!!!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

    /*  
    实际坐标:
        0x80 -> 0x87
        0x90 -> 0x97
        0x88 -> 0x8f
        0x98 -> 0x9f

        //这个坐标我们自己规定的,再换算成上面的实际坐标就可以了
        指出坐标(1,1) -> (1,8)
                (4,1) -> (4,8)
        再指出需要显示的字符
        */
        unsigned char addresses[] = {0x80,0x90,0x88,0x98};
        void LCD12864_Display_Char(unsigned char x,unsigned char y,unsigned char dat){


                LCD12864_Write_Com(0x30);
                LCD12864_Write_Com(0x06);

                //写入地址
                LCD12864_Write_Com(addresses[x-1]+(y-1));

                //写入数据
                LCD12864_Write_Data(dat);

        }

        //显示汉字
        void LCD12864_Display_Chars(unsigned char x,unsigned char y,unsigned char *dat){


                LCD12864_Write_Com(0x30);
                LCD12864_Write_Com(0x06);

                //写入地址
                LCD12864_Write_Com(addresses[x-1]+(y-1));

                //写入数据
                while(*dat != '\0'){
                    LCD12864_Write_Data(*dat);
                    dat++;
                }
        }

5.主函数调用一下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
void main(){
    unsigned char *datas ="LCD液晶显示";
    //液晶初始化
    LCD12864_Init();

    LCD12864_Clear_Screen(0x00); //清屏

    while(1){

        LCD12864_Display_Char(1,2,'A');
        LCD12864_Display_Chars(2,1,datas);
    }

}

代码说明

  • LCD12864_Write_Data / Write_Com:唯一差别是 RS 电平(1 数据 / 0 命令),按“拉低 E → 设 RS/RW → 放数据 → E 上升沿”的时序走;
  • LCD12864_Init:复位后 0x30 进基本指令集,0x08/0x01/0x06/0x0c 依次是显示关、清屏、光标移位方向、开显示;
  • LCD12864_Clear_Screen:0x34→0x36 进绘图模式并打开显示,下半屏(行 32-63)的列基址从 0x88 起补上偏移,两半屏各清 32 行 × 16 字节;
  • Display_Char / Display_Chars:先回 0x30 文本模式,按 addresses[] 行基址加偏移写坐标,再写编码;汉字循环里只发数据字节,不夹任何命令。

踩坑记录与注意事项

  • 显示乱码先查源码编码:CGROM 按 GB2312 索引,源文件若存成 UTF-8,字符串字节和字库码表对不上必乱码——文件编码与字库编码要一致;
  • 绘图写了不少屏不亮:0x34 只进扩充指令,0x36 才打开绘图显示,差一个位的事;文本操作前记得切回 0x30;
  • 下半屏没刷到:行 32 以后的列基址是 0x88 起(不是 0x80),忘了加偏移就只有上半屏变化;
  • 汉字中间别插命令:两字节配对被拆散就出错字,连续写完一段再操作;
  • VO 对比度先调好:电位器没调,整屏全黑或全白,很容易误判成屏坏了、代码错了;
  • 高速 MCU 上时序不稳:E 前后的延时别省,太快数据锁存不完整,51 的 IO 速度慢一般碰不到。