很多人在刚接触 LCD1602 时,能跑通例程,却不清楚“字符怎么从字模表变成屏幕上的两行内容”。这篇文章直接围绕一个可验证的目标展开:在 LCD1602 上显示 Experiment is done! 和 2024-06-18。你可以顺着代码看清 51 单片机如何映射 LCD 寄存器、何时检查忙状态,以及两行文本分别是怎样被写入显示缓冲区的。
先明确这次要显示什么
本次任务很直接:根据字模表准备字符数据,并让 LCD1602 最终显示以下两行内容:
Experiment is done!
2024-06-18
LCD1602 是一个 16 列、2 行的字符液晶模块,适合这类固定文本显示。原例中先在仿真软件里完成连线,再在编程环境中写入控制代码;真正决定屏幕内容的关键,是把目标字符串对应的字符编码整理进数组,再按 LCD 的写入时序送到指定地址。
换句话说,这个例子的核心不是“如何画仿真图”,而是三件事:准备显示数据、建立对 LCD 控制寄存器的访问、按初始化和写入顺序把字符送上屏。
字符数据和 LCD 寄存器如何准备
代码首先引入了与 51 单片机开发相关的头文件:

#include <reg51.h>
#include <stdio.h>
接下来定义了一组字符数组和 LCD 对应的存储器映射地址。这里的 str1 并不是直接写字符串,而是提前把要显示的字符按字模表对应的编码填入数组中。数组前 20 个字节用于第一段显示数据,后 20 个字节用于第二段显示数据。
unsigned char str1[]={0x45,0x78,0x70,0x65,0x72,0x69,0x6D,0x65,0x6E,0x74,0x20,0x69,0x73,0x20,0x64,0x6F,0x6E,0x65,0x21,0x20,
0x20,0x20,0x20,0x20,0x20,0x32,0x30,0x32,0x34,0x2D,0x30,0x36,0x2D,0x31,0x38,0x20,0x20,0x20,0x20,0x20};
xdata unsigned char LCD_CMD_WR _at_ 0x9000;
xdata unsigned char LCD_DATA_WR _at_ 0x9002;
xdata unsigned char LCD_BUSY_RD _at_ 0x9004;
xdata unsigned char LCD_DATA_RD _at_ 0x9006;
这里有两个要点值得单独看清:
字符数组为什么要手动写成十六进制
因为这个例子是按字模表思路组织数据的。像 0x45、0x78、0x70 这些值,最终会被当作 LCD 字符编码送入显示模块,对应组成 Experiment is done!;后半段的 0x32 0x30 0x32 0x34 等则对应 2024-06-18。
其中还刻意加入了多个 0x20,也就是空格。这么做的作用是控制两行文字在显示中的留白,让日期出现在第二行合适的位置,而不是紧贴前一段内容。
四个地址分别负责什么
这几个 xdata 变量通过 _at_ 关键字直接映射到外部数据存储器地址,等于把 LCD 控制器寄存器“起了变量名”。

LCD_CMD_WR映射到0x9000,用于写命令LCD_DATA_WR映射到0x9002,用于写显示数据LCD_BUSY_RD映射到0x9004,用于读取忙状态LCD_DATA_RD映射到0x9006,用于读取数据
需要注意的是,这个示例里实际用到的是写命令、写数据和读取忙状态,LCD_DATA_RD 定义出来了,但后续并没有参与显示流程。
写命令和写数据前为什么都要检查忙状态
LCD1602 不是任何时刻都能立即接收新内容。为了避免上一个操作还没完成就写入下一条命令,代码把“等待 LCD 空闲”放进了两个基础函数里:
void LCD_WriteCommand(unsigned char c)
{
while(LCD_BUSY_RD & 0x80);
LCD_CMD_WR = c;
}
void LCD_WriteData(unsigned char d)
{
while(LCD_BUSY_RD & 0x80);
LCD_DATA_WR = d;
}
这里通过读取 LCD_BUSY_RD 的最高位来判断 LCD 是否忙碌:
- 最高位为
1:LCD 正忙,继续等待 - 最高位为
0:LCD 空闲,可以写入
这一步非常关键。无论是初始化命令,还是后面真正显示的字符数据,都会经过这个判断。也就是说,代码把 LCD 的基础时序控制收口到了两个函数里,主函数只负责决定“写什么”和“写到哪一行”,逻辑会更清晰。
从工程角度看,这种写法也更稳妥。后面如果你要扩展为显示别的字符串,甚至做光标移动、清行、滚动显示,也都可以继续复用这两个底层接口。
主函数怎样把两行文本写到 LCD1602
真正完成显示的是 main 函数。它先初始化 LCD1602,再分别定位到两行起始地址,把数组中的内容按顺序送出去。
void main(void)
{
unsigned int i;
//LCD1602初始化
LCD_WriteCommand(0x30);
LCD_WriteCommand(0x38);
LCD_WriteCommand(0x0C);
LCD_WriteCommand(0x01);
LCD_WriteCommand(0x06);
LCD_WriteCommand(0x01);
// 写第一行字符
LCD_WriteCommand(0x80);
for(i=0;i<20;i++)
LCD_WriteData(str1[i]);
//写第二行字符
LCD_WriteCommand(0xC0);
for(i=20;i<40;i++){
LCD_WriteData(str1[i]);
}
while(1);
}
初始化命令分别在做什么
示例里依次发送了 0x30、0x38、0x0C、0x01、0x06、0x01。结合原文说明,它们用于完成 LCD1602 的基础初始化,包括功能设置、显示模式设置、显示开关控制、清屏以及地址自动递增。
对入门者来说,现阶段不用强行死记每一条命令的全部位定义,先抓住执行顺序更重要:LCD 必须先进入可工作的显示状态,后续写字符才会稳定生效。
为什么是 0x80 和 0xC0
初始化完成后,程序先发送 0x80,把显示地址定位到第一行起始位置;然后用循环将 str1[0] 到 str1[19] 依次写入,因此第一行会显示数组前半段内容。
接着发送 0xC0,把地址切换到第二行起始位置,再把 str1[20] 到 str1[39] 写入 LCD,于是第二行显示数组后半段内容,也就是整理过间距后的日期。
最后的 while(1); 让程序停留在无限循环中,屏幕内容因此会持续保持显示状态,而不会在执行完后退出。
最后效果与可直接复用的思路
按这个流程运行后,LCD1602 就会稳定显示预设好的两行文本。这个例子虽然简单,但已经覆盖了字符 LCD 最常见的一条路径:准备字符编码、建立寄存器映射、通过忙检测写命令和数据、按行地址输出内容。
如果你后面想改显示内容,最直接的方式就是替换 str1 中对应的编码;如果想改布局,则重点调整空格 0x20 的数量,以及第一行和第二行各自写入的数据范围。
对于刚接触 LCD1602 的读者,这段程序最值得掌握的并不是某个单独命令,而是整体结构:底层函数负责通信时序,主函数负责初始化和显示流程。把这条线看明白,后续再扩展菜单、状态提示或传感器读数显示时,就会顺手很多。













