news 2026/10/30 3:42:23

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LCD1602接PCF8574:I2C转并口、准双向口与4位模式时序

简介:面向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 根 IO(8 数据 + RS/RW/EN),对只有 32 根口的 51 来说太奢侈。于是 PCF8574T 这类 IIC 转 8 位并口的芯片被贴到屏幕背面,把两根线变成 RS、RW、EN 和 D4~D7,四针接法就此流行。可它并不是"少接几根线"这么简单——它把问题从"电平对不对"换成了"时序咬不咬得合",而后者才是黑块、乱码、第二行不亮的真正来源。

2. 准双向口的脾气:为什么读之前必须先写 1

2.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数据 bit4
P5D5数据 bit5
P6D6数据 bit6
P7D7数据 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); // EN=0,先把数据摆到 P4~P7 pcf_write(base | d | BIT_EN); // EN=1,LCD 准备采样 pcf_write(base | d); // EN=0,下降沿写入 } 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 的方法是 RS=0、RW=1,然后读两次 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; // RS=0, RW=1 pcf_write(base | 0xF0 | BIT_EN); // P4~P7 写成 1 当输入,同时 EN=1 hi = (unsigned char)(pcf_read() & 0xF0); pcf_write(base | 0xF0); // EN=0 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,读回来永远是 0,BF 判断就失效了,程序会一直认为"不忙",然后写进去一堆被丢弃的指令。

但实际做 51 项目时,读 BF 往往不划算。一次读 BF

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/30 3:42:23

桌面刻度尺精度实现:屏幕校准、缩放换算与多屏适配

1. 屏幕上量出来的"1厘米"&#xff0c;凭什么信它去年帮一个做硬件结构的朋友改图纸&#xff0c;他截图发过来&#xff0c;说这个卡扣槽宽"大概两厘米"&#xff0c;让我在页面上直接量一下对不对。我当时随手拿屏幕当尺子比了比&#xff0c;觉得差不多&…

作者头像 李华
网站建设 2026/10/30 3:42:09

51单片机通过I2C驱动LCD1602:PCF8574T时序与避坑

简介&#xff1a;四针LCD1602的IIC驱动方案面向使用51单片机、希望在有限IO下扩展字符液晶显示的开发者与学习者&#xff0c;重点解决传统并行接口占用引脚多、接线复杂的问题。方案以PCF8574T作为I2C转8位GPIO的桥接器件&#xff0c;涉及I2C时序、从机地址、LCD1602初始化序列…

作者头像 李华
网站建设 2026/10/30 3:41:55

三屏输出技术解析与HDC203-PM24实战指南

1. 三屏输出的技术挑战与解决方案在当今多任务处理需求日益增长的办公环境中&#xff0c;单显示器已经无法满足高效工作的需求。三屏输出方案能够显著提升工作效率&#xff0c;特别是在金融交易、编程开发、视频剪辑等专业领域。然而&#xff0c;实现三屏输出并非简单的显示器堆…

作者头像 李华
网站建设 2026/10/30 3:41:54

基于CNN的猫咪疲劳状态识别技术实现

1. 项目背景与核心价值猫的疲劳状态识别是一个典型的计算机视觉分类问题&#xff0c;在宠物健康监测领域具有实际应用价值。通过分析猫咪的眼部特征、姿态变化等视觉信号&#xff0c;我们可以建立可靠的疲劳判断模型。这个毕设选题结合了当下热门的深度学习技术&#xff0c;同时…

作者头像 李华
网站建设 2026/10/30 3:41:25

Linux audit内核审计原理与生产级规则实战

1. 审计这件事&#xff0c;远不是“记个日志”那么简单很多人第一次接触 audit&#xff0c;是在某次安全检查后被要求“补上系统审计策略”&#xff0c;或者在排查一个权限异常问题时&#xff0c;发现/var/log/audit/audit.log里密密麻麻全是看不懂的typeSYSCALL记录。于是下意…

作者头像 李华
网站建设 2026/10/30 3:40:42

MATLAB工程化Nelson-Siegel国债收益率曲线拟合实现

简介&#xff1a;本资源是一套基于Matlab实现的Nelson-Siegel及Nelson-Siegel-Svensson模型完整研究代码与分析材料&#xff0c;面向金融工程、计量经济学方向的本科生、研究生及实证研究者&#xff0c;用于国债收益率曲线建模、主成分分解及宏观经济变量关联分析。包内共41个文…

作者头像 李华