目录
[3. 硬件设计](#3. 硬件设计)
[4. 软件设计](#4. 软件设计)
[4.1 lcd12864](#4.1 lcd12864)
[4.1.1 写指令函数 lcd12864_write_cmd](#4.1.1 写指令函数 lcd12864_write_cmd)
[4.1.2 写数据函数 lcd12864_write_data](#4.1.2 写数据函数 lcd12864_write_data)
[4.1.3 初始化函数 lcd12864_init](#4.1.3 初始化函数 lcd12864_init)
[4.1.4 清屏函数](#4.1.4 清屏函数)
[4.1.5 字符显示函数](#4.1.5 字符显示函数)
[4.2 main.c](#4.2 main.c)
[4.3 public](#4.3 public)
使用 LCD12864 实现的功能:在其上显示汉字字符
3. 硬件设计
带字库 12864 液晶
开发板上集中了一个 带字库 12864 液晶接口,下面来看 12864 液晶接口电路


LCD12864 的 8 位数据口 LCD_D0 -- LCD_D7 连接 P0.0 -- P0.7 管脚
LCD12864 的 RS、RW、E 脚 与单片机的 P2.6、P2.5、P2.7 管脚连接
LCD12864 的 PSB 脚与单片机的 P3.2 管脚连接,RESET 复位脚直接连接在 VCC 上

该接口是 LCD12864 与 TFTLCD 彩屏共用的,共占用 20 个管脚
MiniLCD12864 是 16 脚,带字库的 LCD12864 通常是 20 脚,这样可以兼容带字库与不带字库的
4. 软件设计
4.1 lcd12864
lcd12864.c
4.1.1 写指令函数 lcd12864_write_cmd
核心逻辑:通过RS引脚选择指令寄存器,WR选择写操作,利用E(使能脚)的上升沿完成指令锁存,是 LCD12864 指令写入的标准时序。
cpp
void lcd12864_write_cmd(u8 cmd)
{
LCD12864_RS=0;//选择命令(RS=0:指令寄存器)
LCD12864_WR=0;//选择写(WR=0:写操作,1:读操作)
LCD12864_E=0;
LCD12864_DATAPORT=cmd;//将指令放到8位数据总线上
delay_ms(1);//时序延时,确保数据稳定
LCD12864_E=1;//E上升沿:锁存指令到LCD内部
delay_ms(1);
LCD12864_E=0;//E下降沿:完成指令写入
}
delay_ms(1)需确保是 51 单片机的精准毫秒级延时 (需在lcd12864.h中实现delay_ms函数),否则时序不匹配会导致指令写入失败。
4.1.2 写数据函数 lcd12864_write_data
核心区别:仅LCD12864_RS=1(选择数据寄存器),其余时序和写指令完全一致;
cpp
void lcd12864_write_data(u8 dat)
{
LCD12864_RS=1;//选择数据(RS=1:数据寄存器)
LCD12864_WR=0;//选择写
LCD12864_E=0;
LCD12864_DATAPORT=dat;//准备显示数据(字符/汉字编码)
delay_ms(1);
LCD12864_E=1;//上升沿锁存数据
delay_ms(1);
LCD12864_E=0;
}
用途:写入显示数据(ASCII 字符码 / 汉字内码,带字库屏会自动解析显示)。
4.1.3 初始化函数 lcd12864_init
LCD12864_PSB = 1:必须设置(即单片机 P3.2 管脚),否则会进入串口模式导致 8 位并行通信失效
cpp
void lcd12864_init(void)
{
LCD12864_PSB=1;//选择8位并行接口(PSB=0为串口模式)
lcd12864_write_cmd(0x30);//8位总线、基本指令集(正确)
lcd12864_write_cmd(0x0c);//【注释错误】正确含义:显示开、光标关、闪烁关
lcd12864_write_cmd(0x06);//写入数据后光标右移、屏幕不移动(正确)
lcd12864_write_cmd(0x01);//清屏【缺少延时】
}
4.1.4 清屏函数
cpp
void lcd12864_clear(void)
{
lcd12864_write_cmd(0x01); //清屏【缺少延时】
}
4.1.5 字符显示函数
lcd12864.c
cpp
void lcd12864_show_string(u8 x,u8 y,u8 *str)
{
if(y<=0)y=0;
if(y>3)y=3;
x&=0x0f; //限制x≤15(0~15列),y≤3(0~3行)
switch(y)
{
case 0: x|=0x80;break;//第1行起始地址:0x80 + 列偏移x
case 1: x|=0x90;break;//第2行起始地址:0x90 + 列偏移x
case 2: x|=0x88;break;//第3行起始地址:0x88 + 列偏移x
case 3: x|=0x98;break;//第4行起始地址:0x98 + 列偏移x
}
lcd12864_write_cmd(x);//写入地址指令,定位显示位置
while(*str!='\0')//循环写入字符串,直到结束符'\0'
{
lcd12864_write_data(*str);
str++;
}
}
lcd12864.h
cpp
#ifndef _lcd12864_H
#define _lcd12864_H
#include "public.h"
//管脚定义
sbit LCD12864_RS=P2^6;//数据命令选择
sbit LCD12864_WR=P2^5;//读写选择
sbit LCD12864_E=P2^7;//使能信号
#define LCD12864_DATAPORT P0 //LCD12864数据端口定义
sbit LCD12864_PSB=P3^2;//8位或4并口/串口选择
//函数声明
void lcd12864_init(void);
void lcd12864_clear(void);
void lcd12864_show_string(u8 x,u8 y,u8 *str);
#endif
代码逐行解释:
cpp
if(y<=0)y=0;
作用 :行号y的最小值限制
防止用户传入负数行号 (比如 y=-1),导致后续switch分支错误,强制将负数 y 修正为 0(第 1 行)
举例:若传入 y=-2,执行后 y=0,避免地址计算出错。
cpp
if(y>3)y=3;
作用 :行号y的最大值限制(汉字字符占8*8,4行即62,正好对应 12864 的64行)
LCD12864 带字库屏每行最多显示 16 个字符,共 4 行(对应 y=0/1/2/3),若传入 y>3(比如 y=5),强制修正为 3(第 4 行);
核心目的:避免超出屏幕显示范围,导致显示乱码或指令失效。
cpp
x&=0x0f; //限制x,y不能大于显示范围
作用 :列偏移 x 的范围限制(0~15)
0x0f是 16 进制,二进制为0000 1111;
x &= 0x0f 等价于 x = x & 0x0f,按位与运算后,x 的高 4 位被清零,仅保留低 4 位,最终 x 的取值范围只能是 0~15(对应每行 16 列);
举例:若传入 x=20(二进制0001 0100),与0x0f按位与后结果为 4,强制限定在有效列范围;
注释小瑕疵:只限制了 x,y 的限制是前面两个 if,注释写 "限制 x,y" 稍不准确,但不影响功能。
cpp
switch(y)
{
case 0: x|=0x80;break;//第1行地址+x的偏移
case 1: x|=0x90;break;//第2行地址+x的偏移
case 2: x|=0x88;break;//第3行地址+x的偏移
case 3: x|=0x98;break;//第4行地址+x的偏移
}
作用:每行的地址拼接
0x80、0x90、0x88、0x98是 LCD12864 第 0-3 行的起始地址(手册规定);x |= 0x80等价于x = x | 0x80,将 x 的最高位设为 1,拼接成 "第 1 行 + 列偏移 x" 的地址指令;- 举例:x=5(第 6 列),执行后 x=0x80+5=0x85,发送该指令后,光标定位到第 1 行第 6 列;
break:跳出 switch,避免执行后续分支。
cpp
lcd12864_write_cmd(x);
作用 :发送拼接好的地址指令,完成显示位置定位(只需传 X 即可确定起始位置)
lcd12864_write_cmd是你之前定义的 "写指令函数",将拼接后的地址指令发送给 LCD12864,光标就会移动到指定的 (x 列、y 行) 位置。
cpp
while(*str!='\0')
{
lcd12864_write_data(*str);
str++;
}
作用:写入当前字符数据,显示在屏幕上
lcd12864_write_data是你之前定义的 "写数据函数"
对于带字库屏,传入字符的 ASCII 码(如 'A'=0x41)或汉字内码(如 "中"=0xD6D0),硬件会自动解析并显示对应的字符 / 汉字
str++ 等价于 str = str + 1,指针地址加 1,指向字符串的下一个字符
答疑:
调用函数时传入的是字符串(比如"A"、"中")这种 "文本形式",而非直接传0x41、0xD6D0这类编码值,但 LCD12864 带字库屏却能正确解析显示 ------ 本质是C 语言帮你完成了 "文本字面量" 到 "编码值" 的自动转换
① C 语言中 "字符 / 字符串字面量" 的本质 ------ 不是文本,是编码值的 "别名",编译器会自动把它们转换成对应的编码值(ASCII/GB2312)存储到内存中
② 函数中*str取到的不是 "文本",是内存中的编码值
在lcd12864_show_string函数里,u8 *str是字符串指针 ,指向的是字符串在内存中的 "首地址",*str取到的是内存中存储的编码值
**执行过程:**假设调用:lcd12864_show_string(2, 1, "ABC");
- y=1(≤3 且 > 0),无需修正;x=2,
x&=0x0f后仍为 2; - switch (y=1),执行
x|=0x90→ x=0x90+2=0x92; - 发送指令 0x92,定位到第 2 行第 3 列;
- 循环:
- 第 1 次:*str='A' → 写入数据 'A',str 指向 'B';
- 第 2 次:*str='B' → 写入数据 'B',str 指向 'C';
- 第 3 次:*str='C' → 写入数据 'C',str 指向 '\0';
- 循环结束,最终在第 2 行第 3 列开始显示 "ABC"。
4.2 main.c
cpp
/**************************************************************************************
实验名称:LCD12864液晶显示实验(带字库)
接线说明:
实验现象:下载程序后,LCD12864上显示汉字字符信息
注意事项:
***************************************************************************************/
#include "public.h"
#include "lcd12864.h"
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void main()
{
lcd12864_init();//LCD12864初始化
lcd12864_show_string(0,0,"Hello World!");//第1行字符串显示
lcd12864_show_string(0,1,"大家好!");//第2行字符串显示
lcd12864_show_string(0,2,"欢迎使用51开发板");//第3行字符串显示
lcd12864_show_string(0,3,"好好学习天天向上");//第4行字符串显示
while(1)
{
}
}
4.3 public
public.c
cpp
#include "public.h"
/*******************************************************************************
* 函 数 名 : delay_10us
* 函数功能 : 延时函数,ten_us=1时,大约延时10us
* 输 入 : ten_us
* 输 出 : 无
*******************************************************************************/
void delay_10us(u16 ten_us)
{
while(ten_us--);
}
/*******************************************************************************
* 函 数 名 : delay_ms
* 函数功能 : ms延时函数,ms=1时,大约延时1ms
* 输 入 : ms:ms延时时间
* 输 出 : 无
*******************************************************************************/
void delay_ms(u16 ms)
{
u16 i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
public.h
cpp
#ifndef _public_H
#define _public_H
#include "reg52.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
typedef unsigned long u32;
void delay_10us(u16 ten_us);
void delay_ms(u16 ms);
#endif