第二十三章 LCD1602液晶显示
1. 导入
LCD1602(16×2 字符型液晶)使用基于HD44780控制器的命令集,支持8位或4位数据总线,常用于菜单、状态与调试信息显示。本章以"4位总线,RW接地(只写)"方案实现稳定驱动,提供完整API:初始化、定位、打印、清屏、自定义字符(CGRAM)。
目标:
- 掌握LCD1602引脚与对比度、电源接法
- 理解4位时序与初始化流程
- 实现常用API:定位、打印、清屏、滚动/移位
- 扩展:显示时钟/温度、自定义字符"°"
2. 硬件设计
- 供电与对比度
- VSS→GND,VDD→+5V
- V0(对比度)→10k电位器中点(两端接VCC/GND)
- 背光(如有):A→+5V,K→GND(可串电阻限流)
- 数据/控制引脚(4位方式,推荐)
- RW→GND(只写,简单可靠)
- RS:0=命令,1=数据
- E(EN):上升沿锁存
- D4~D7:数据高四位,4位模式仅用这四根
示例接线(可按需改口):
P2.2 → RS,P2.3 → EP2.4 → D4,P2.5 → D5,P2.6 → D6,P2.7 → D7RW → GND
注意:若用P0口需外接上拉;P2口免上拉更稳。
3. 指令与寻址要点
- 主要命令(十六进制):
0x01清屏(>1.52ms)0x02归位(>1.52ms)0x04/0x06输入模式(增量/不移屏)0x08~0x0F显示/光标/闪烁开关(如0x0C 显示开、无光标)0x10/0x14/0x18/0x1C光标/整屏左/右移0x20/0x28数据长度与行数(0x28=4位、两行、5×8点阵)0x80 | addr设置DDRAM地址(行列定位)
- DDRAM行首地址(1602):
- 第1行:0x00
- 第2行:0x40
4. 完整驱动(4位、RW接地)
c
#include <reg52.h>
#include <intrins.h>
/* 引脚映射:按你的接线修改 */
sbit LCD_RS = P2^2;
sbit LCD_EN = P2^3;
sbit LCD_D4 = P2^4;
sbit LCD_D5 = P2^5;
sbit LCD_D6 = P2^6;
sbit LCD_D7 = P2^7;
/* 延时(粗略,不依赖忙标志) */
static void delay_us_inline() { _nop_(); _nop_(); _nop_(); _nop_(); }
void delay_ms(unsigned int ms){
unsigned int i,j;
for(i=0;i<ms;i++) for(j=0;j<125;j++);
}
/* 低级IO */
static void lcd_write4(unsigned char nib){ // 发送高4位或低4位
LCD_D4 = (nib & 0x01) ? 1 : 0;
LCD_D5 = (nib & 0x02) ? 1 : 0;
LCD_D6 = (nib & 0x04) ? 1 : 0;
LCD_D7 = (nib & 0x08) ? 1 : 0;
delay_us_inline();
LCD_EN = 1; delay_us_inline();
LCD_EN = 0; delay_us_inline();
}
/* 发送一个字节(4位总线):rs=0命令,rs=1数据 */
static void lcd_send(unsigned char value, bit rs){
LCD_RS = rs;
// 先发高四位
lcd_write4((value >> 4) & 0x0F);
// 再发低四位
lcd_write4(value & 0x0F);
// 大多数命令37us足够,清屏/归位需更长
if (!rs && (value==0x01 || value==0x02)) {
delay_ms(2);
} else {
// 约40us
_nop_(); _nop_(); _nop_(); _nop_();
}
}
static void lcd_cmd(unsigned char cmd){ lcd_send(cmd, 0); }
static void lcd_data(unsigned char dat){ lcd_send(dat, 1); }
/* 初始化(4位,2行,5x8,显示开,光标关,自增) */
void lcd_init(void){
LCD_RS = 0; LCD_EN = 0;
delay_ms(40); // 上电等待>30ms
// 强制进入4位模式的固定序列(参考HD44780)
lcd_write4(0x03); delay_ms(5);
lcd_write4(0x03); delay_ms(5);
lcd_write4(0x03); delay_ms(1);
lcd_write4(0x02); // 现在进入4位模式
lcd_cmd(0x28); // 功能设置:4位、2行、5x8点阵
lcd_cmd(0x08); // 显示关闭
lcd_cmd(0x01); // 清屏
delay_ms(2);
lcd_cmd(0x06); // 输入模式:写入后地址+1,屏不移
lcd_cmd(0x0C); // 显示开,光标关,闪烁关
}
/* 常用API */
void lcd_clear(void){ lcd_cmd(0x01); delay_ms(2); }
void lcd_home(void){ lcd_cmd(0x02); delay_ms(2); }
/* 设置光标:行row=0/1,列col=0..15 */
void lcd_set_cursor(unsigned char row, unsigned char col){
unsigned char addr = (row ? 0x40 : 0x00) + (col & 0x0F);
lcd_cmd(0x80 | addr);
}
/* 打印字符串(以 '\0' 结束) */
void lcd_print(const char* s){
while(*s) lcd_data((unsigned char)*s++);
}
/* 指定位置打印 */
void lcd_print_at(unsigned char row, unsigned char col, const char* s){
lcd_set_cursor(row, col);
lcd_print(s);
}
/* 自定义字符(CGRAM)。loc:0~7,对应字符码0..7;pattern[8]每行低5位有效 */
void lcd_define_char(unsigned char loc, const unsigned char pattern[8]){
unsigned char i;
loc &= 0x07;
lcd_cmd(0x40 | (loc << 3)); // 设置CGRAM地址
for(i=0;i<8;i++) lcd_data(pattern[i] & 0x1F);
lcd_cmd(0x80); // 返回DDRAM(可选)
}
/* 示例:主程序 */
void main(void){
unsigned int cnt = 0;
const unsigned char deg_sym[8] = {
0x04,0x0A,0x04,0x00,0x00,0x00,0x00,0x00 // 简易"°"
};
lcd_init();
lcd_define_char(0, deg_sym); // 自定义字符0号为"°"
lcd_print_at(0, 0, "Hello, LCD1602!");
lcd_print_at(1, 0, "Cnt: ");
while(1){
char buf[6];
unsigned int v = cnt;
// 将数字转字符串
buf[0] = (v/10000)%10 + '0';
buf[1] = (v/1000)%10 + '0';
buf[2] = (v/100)%10 + '0';
buf[3] = (v/10)%10 + '0';
buf[4] = (v%10) + '0';
buf[5] = '\0';
lcd_print_at(1, 5, buf); // 在第2行第6列更新计数
// 示范自定义符号(在末尾显示"°C")
lcd_set_cursor(1, 11);
lcd_data(0); // 打印自定义"°"
lcd_data('C');
cnt++;
delay_ms(500);
}
}
要点:
- 初始化中四次
lcd_write4(...)的固定序列是切入4位模式的关键。 RW接地避开忙标志读取,使用"足够延时"保证可靠。- 清屏/归位需要>1.52ms,其余指令约37μs,代码已分别处理。
5. 常用拓展
- 光标与移屏
lcd_cmd(0x0E):显示开、有光标lcd_cmd(0x0F):显示开、光标闪烁lcd_cmd(0x18):整屏左移;0x1C:右移
- 快速覆盖行尾空白
- 打印后用空格填满剩余列,避免残留字符
- 数值/时间显示(与前章结合)
- DS1302时间:
lcd_print_at(0,0,"HH:MM:SS"); - DS18B20温度:使用
lcd_define_char自定义"°",显示"23.5°C"
- DS1302时间:
6. 故障排查
- 无显示/黑方块一行:对比度V0未调;初始化时序错误;E脚未正确翻转。
- 乱码:4位高低半字节顺序错;数据线接错;延时不足。
- 显示位置错乱:行首地址理解错误;
lcd_set_cursor计算错误。 - 闪烁严重:频繁清屏;改用覆盖更新局部字符。
7. 进阶(可选)
- 8位总线:将D0~D7全部接入,初始化用
0x38(8位、2行、5×8),写入一次8位即可(速度快,线多)。 - 读忙标志:RW接MCU,切换D口方向,读BF(D7=1忙);但硬件与代码更复杂,通常延时法已足够。
- I²C转接模块:很多1602带PCF8574 I/O扩展,I²C仅占两线(与第17章I²C兼容),命令需映射到PCF8574位序