资讯动态

LCD1602接PCF8574:I2C转并口、准双向口与4位模式时序

发布时间:2026/10/30 3:42:24 来源:尧图企业网站定制
简介面向51单片机初学者与电子制作爱好者这份四针LCD1602 IIC驱动PCF8574T资料聚焦以两根I2C线加电源线控制字符液晶、缓解IO资源紧张的问题可用于STC89C51等8051内核平台的显示模块实验与小项目移植。压缩包共25个文件、约55KB包含C源码与头文件、Keil工程及项目配置文件以及hex、lst、obj、map等编译与链接产物另有构建日志和说明页面便于对照源码、工程设置与最终烧录结果。已有2066人学习下载说明其在I2C扩展LCD1602这一常见需求中具有较高参考度。资料围绕PCF8574T的GPIO映射、LCD1602初始化序列、指令与数据写入、延时处理和工程组织展开读者可借助1602_bsp、delay_bsp、main等模块理解四针接线下的通信流程并结合map、lst排查编译与资源占用问题快速搭建可验证的显示系统。1. 十一根线压成四根LCD1602 挂上 PCF8574 之后到底省在哪51 单片机上点一块 LCD1602最反直觉的地方在于真正难的不是让它亮而是让它稳定地不花屏。并行接法要吃掉 11 根 IO8 数据 RS/RW/EN对只有 32 根口的 51 来说太奢侈。于是 PCF8574T 这类 IIC 转 8 位并口的芯片被贴到屏幕背面把两根线变成 RS、RW、EN 和 D4~D7四针接法就此流行。可它并不是少接几根线这么简单——它把问题从电平对不对换成了时序咬不咬得合而后者才是黑块、乱码、第二行不亮的真正来源。2. 准双向口的脾气为什么读之前必须先写 12.1 一个引脚内部的四件套拆开 PCF8574 的单个 IO内部结构其实只有四样东西一个输出锁存器、一个强下拉 NMOS、一个弱上拉电流源、一个输入缓冲器。厂商把这种结构叫准双向口意思是它没有真正的方向寄存器方向是靠你写进去什么隐式决定的。写 1 的时候下拉管关断引脚只靠那个大约 100µA 的弱上拉电流源往上拽。这个电流非常小所以任何外部器件都能轻松把它拉低——这正是它能当输入用的物理基础。写 0 的时候下拉管导通灌电流能力能到 25mA直接点一颗 LED 都没问题。有意思的是51 单片机自己的 P1、P2 口也是同样的结构。所以用 51 去驱 PCF8574本质上是两个准双向口在对话双方都遵守想读先写 1的规矩谁先释放总线谁就掌握主动。2.2 读引脚还是读锁存器I2C 读操作返回的是引脚的实际电平不是锁存器里的值。这个区别决定了整个驱动能不能工作。假设锁存器里某一位是 0下拉管就处于导通状态引脚被硬按在低电平上。这时候哪怕外面的 LCD 拼命把 DB7 往上拉读回来永远是 0因为那颗 25mA 的下拉管比 LCD 的输出强得多。所以读之前必须先把这一位写成 1让下拉管关断把引脚让出来。这也是读忙标志函数里那句pcf_write(base | 0xF0 | BIT_EN)的真正含义它不是随便写个 0xF0而是在把 P4~P7 四个锁存器置 1好让 LCD 的 DB7~DB4 能驱动这几根线。上电复位后锁存器默认全是 1所以第一次读不用管但只要你在某个时刻把它们写成过 0下一次读之前就必须写回来。有个容易被忽略的细节写 1 之后到真正读之间需要建立时间。引脚从 0 变 1 靠的是弱上拉给线电容充电如果排线长、分布电容大这个上升沿会变缓。100kHz 的 IIC 下一般感觉不到但把速率提到 400kHz 又用了长排线就可能读到中间态。稳妥的做法是在写 1 和读之间插一小段延时几微秒就够。2.3 整字节写入带来的影子寄存器问题PCF8574 没有寄存器地址的概念一次 I2C 事务就是往 8 个锁存器里写一个字节。它不支持位操作你想改一个引脚另外 7 个的状态必须一起写进去。这就带来一个很实际的麻烦。新手常写的代码是pcf_write(0x04)来拉 EN觉得我只操作 EN 这一位。实际上这一笔把 RS、RW、背光全清零了RS 变 0 意味着 LCD 把接下来的数据当命令背光变 0 意味着屏幕当场暗一下RW 变 0 倒还好但整体状态已经乱了。常见的解法是维护一个影子变量代码里叫g_ctrl专门存那些常驻位比如背光。每次写之前用按位或把常驻位拼进去写完再算数据位。这样背光不会闪RS 也不会在两次写之间莫名其妙跳变。这个习惯看着啰嗦但它能省掉后面一堆为什么屏幕偶尔抖一下的排查时间。3. 八根口线对十六根引脚映射表是怎么被定死的3.1 主流模块的 P0~P7 分配市面上带 PCF8574 的 LCD1602 模块背面那颗芯片的 8 根口线基本按同一套逻辑分配PCF8574 位接到 LCD1602作用P0RS0 命令1 数据P1RW0 写1 读P2EN使能脉冲下降沿采样P3背光控制一般经三极管注意极性P4D4数据 bit4P5D5数据 bit5P6D6数据 bit6P7D7数据 bit7这套分配不是随便定的低 4 位放控制信号高 4 位放数据正好对应 4 位模式下数据从 DB4~DB7 进出的硬件设计。高 4 位和低 4 位在字节里天然分开写 nibble 的时候一个左移就到位不用来回搬。但也不是所有模块都这么排。有少数板子把 D4~D7 放在 P0~P3控制信号放到 P4~P6。所以驱动代码里这几个位必须做成宏不能写死成 0x01、0x02、0x04。换一块模块就全乱了这是血泪经验。3.2 背光位为什么必须参与每一次写背光这一位看着无关紧要实际上它是整个驱动里最容易翻车的地方。背光通常不直接挂在 PCF8574 的引脚上中间串了一颗三极管做电流放大因为 LCD 背光动辄几十毫安PCF8574 单脚 25mA 灌电流扛不住。三极管的接法决定了极性有的是 P3 输出低电平点亮有的是输出高电平点亮。写代码之前得先量一下或者干脆两种都试。更关键的是因为 PCF8574 只能整字节写背光位必须出现在每一次写操作里。写命令要带上它写数据要带上它拉 EN 更要带上它。漏掉一次屏幕就闪一下。人眼对这种几十微秒的暗变其实很敏感尤其是连续刷字符的时候看起来就像屏幕在抖。用影子寄存器能彻底解决这个问题初始化时把背光位设好之后所有写操作都从影子变量出发只在局部临时拼上 EN 和数据位写完不修改影子。这样背光位永远在不会因为某次疏忽被清掉。3.3 4 位模式的物理原因HD44780 这类控制器本来设计了 8 位和 4 位两种总线模式。8 位模式一次送一个完整字节速度快4 位模式分两次送先高 4 位后低 4 位速度减半但省 4 根线。PCF8574 只有 8 位口线如果 8 位数据全接RS、RW、EN 三根控制线就没地方放了背光更别提。所以 4 位模式不是可选项是唯一解。8 根口线刚好切成 4 数据 3 控制 1 背光一位不浪费。代价是每次写一个字节要做两次 EN 脉冲、两次 I2C 事务。按 100kHz 的 IIC 算一次事务大约 200~300µs写一个字符要 4 次事务两个 nibble每个 nibble 三次 pcf_write实际更多一屏 32 个字符刷下来要几十毫秒。对于显示温度、时间这类低速场景完全够用但想刷动画就别指望了。4. 时序咬合一个字节为什么要拆成两次 EN 脉冲4.1 一次 EN 脉冲能送进去几位HD44780 的写入时序其实很简单数据在 DB 线上就位EN 拉高保持至少 450ns然后 EN 拉低下降沿那一刻控制器把数据取走。在 4 位模式下它只认 DB7~DB4 这四根线所以一次 EN 脉冲只能送进去半个字节。这就意味着写一个完整字节要两个 EN 脉冲第一个送高 4 位第二个送低 4 位。顺序不能反反了就是乱码。而且这两个脉冲之间RS 和 RW 必须保持不变否则 LCD 会把第二个 nibble 当成另一个字节的高 4 位整个字节流就错位了。在 PCF8574 的框架下一个 EN 脉冲至少对应三次写操作先把数据摆好、EN 保持低再拉高 EN最后拉低 EN。三次 I2C 事务每次都要带上正确的 RS、RW 和背光位。这就是为什么一个字符写下去要花掉一千多微秒。4.2 写字节、写命令、写数据的三层封装代码上最好分三层最底层是 nibble中间是字节最上面是命令和数据。分层之后上层完全不用关心 EN 怎么拉、数据怎么移位。#include reg52.h sbit SCL P1^0; sbit SDA P1^1; #define LCD_ADDR 0x27 // PCF8574T 的 7 位地址A2/A1/A0 全接高 #define BIT_RS 0x01 #define BIT_RW 0x02 #define BIT_EN 0x04 #define BIT_BL 0x08 static unsigned char g_ctrl BIT_BL; // 影子寄存器常驻背光位 static void i2c_delay(void) { unsigned char i 8; // 11.0592MHz 下约几微秒 while (i--) ; } static void i2c_start(void) { SDA 1; SCL 1; i2c_delay(); SDA 0; i2c_delay(); SCL 0; } static void i2c_stop(void) { SDA 0; SCL 1; i2c_delay(); SDA 1; i2c_delay(); } static void i2c_send(unsigned char b) { unsigned char i; for (i 0; i 8; i) { SDA (b 0x80) ? 1 : 0; b 1; SCL 1; i2c_delay(); SCL 0; i2c_delay(); } SDA 1; // 释放 SDA交给从机应答 SCL 1; i2c_delay(); SCL 0; // 第 9 个时钟ACK 位这里不检查 } static void pcf_write(unsigned char data) { i2c_start(); i2c_send((unsigned char)(LCD_ADDR 1)); // 写方向最低位为 0 i2c_send(data); i2c_stop(); }i2c_send里有两个细节值得说。第一数据位在 SCL 低电平期间改变在高电平期间被采样这是 IIC 的基本规则所以移位必须在拉高 SCL 之前完成。第二第 9 个时钟把 SDA 释放成 1让从机有机会把它拉低表示 ACK这段代码没检查 ACK因为 LCD 模块一般不会掉线省掉判断能快一点。真要做健壮性可以把这个位读回来判断。nibble 层的封装static void lcd_nibble(unsigned char nib, unsigned char is_data) { unsigned char base g_ctrl | (is_data ? BIT_RS : 0); unsigned char d (unsigned char)((nib 0x0F) 4); pcf_write(base | d); // EN0先把数据摆到 P4~P7 pcf_write(base | d | BIT_EN); // EN1LCD 准备采样 pcf_write(base | d); // EN0下降沿写入 } static void lcd_byte(unsigned char v, unsigned char is_data) { lcd_nibble((unsigned char)(v 4), is_data); // 先高 4 位 lcd_nibble((unsigned char)(v 0x0F), is_data); // 再低 4 位 } void lcd_command(unsigned char cmd) { lcd_byte(cmd, 0); } void lcd_data(unsigned char dat) { lcd_byte(dat, 1); }lcd_nibble里三次写是刻意为之的。第一次写只摆数据不拉 EN是为了让 D4~D7 在 EN 上升之前就稳定如果数据和 EN 同时变化LCD 可能在上升沿采到还没稳定的电平。中间那次拉高 EN 之后没有额外延时是因为pcf_write本身要走一次完整 I2C 事务耗时远超 HD44780 要求的 450ns 最小脉宽。真要抠时序可以在拉高和拉低之间插一句i2c_delay()但实测没必要。lcd_byte里的移位方向是硬规矩先v 4再v 0x0F先高后低。搞反了就是典型的能显示但全是乱码。4.3 忙标志 BF 与 AC要不要读HD44780 有个忙标志位 BF在 DB7 上。BF 为 1 表示控制器正在执行上一条指令比如清屏要 1.52ms这时候写进去的东西会被丢掉。读 BF 的方法是 RS0、RW1然后读两次 nibble第一次读到 BF 加上地址计数器的高 3 位第二次读到地址计数器的低 4 位。static unsigned char pcf_read(void) { unsigned char v 0, i; i2c_start(); i2c_send((unsigned char)((LCD_ADDR 1) | 0x01)); // 读方向 SDA 1; // 释放 SDA for (i 0; i 8; i) { SCL 1; i2c_delay(); v (unsigned char)((v 1) | (SDA ? 1 : 0)); SCL 0; i2c_delay(); } SDA 1; SCL 1; i2c_delay(); SCL 0; // 主机回 NACK i2c_stop(); return v; } unsigned char lcd_read_bf(void) { unsigned char hi, lo; unsigned char base g_ctrl | BIT_RW; // RS0, RW1 pcf_write(base | 0xF0 | BIT_EN); // P4~P7 写成 1 当输入同时 EN1 hi (unsigned char)(pcf_read() 0xF0); pcf_write(base | 0xF0); // EN0 pcf_write(base | 0xF0 | BIT_EN); lo (unsigned char)(pcf_read() 4); pcf_write(base | 0xF0); return (unsigned char)(hi | lo); }这段代码里0xF0不是随便写的。它把 P4~P7 四个锁存器置 1关掉下拉管DB7~DB4 才能被 LCD 驱动。如果这里写成 0x00读回来永远是 0BF 判断就失效了程序会一直认为不忙然后写进去一堆被丢弃的指令。但实际做 51 项目时读 BF 往往不划算。一次读 BF本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑