LCD1602 I2C转接板PCF8574T详解:51单片机省IO驱动方案

📅 发布时间:2026/10/14 16:32:09
LCD1602 I2C转接板PCF8574T详解:51单片机省IO驱动方案
简介一份面向51单片机学习者的四针LCD1602 IIC驱动工程代码基于PCF8574T实现I2C转并行接口仅用四根线即可完成LCD1602显示控制大幅节省IO资源适合在STC89C51等8051内核项目中使用。压缩包共25个文件、约55KB以main.c、1602_bsp.c、delay_bsp.c等C源码为主配套对应h头文件、Keil工程文件uvproj/uvopt及可直接烧录的hex文件同时包含lst、obj、map等编译辅助文件目录结构清晰。当前已有2065人学习下载。通过该工程读者可以拿到一套完整可编译的LCD1602 I2C驱动示例既能直接复用底层驱动也能对照main.c、1602_bsp.c中的代码梳理I2C启动、地址发送、控制命令与数据写入的完整时序还能结合hex文件快速验证实际显示效果是学习51单片机I2C通信与PCF8574T扩展应用的实用参考。1. LCD1602 省掉 8 根数据线I2C 转接板与 PCF8574T 到底做了什么做 51 单片机项目时最烦的事情之一就是 LCD1602 那 12 根引脚。RS、RW、EN 加 8 根数据线焊板子费劲占用 IO 也多稍微复杂点的系统 IO 就不够用了。用 I2C 转接板把 PCF8574T 芯片和 LCD1602 一拼整个显示模块只留 VCC、GND、SDA、SCL 四根针单片机这边只需要两个 IO 口就能驱动而且代码逻辑还更简单了。这套方案适合手里 IO 紧张、想精简布线、或者已经在用 I2C 总线挂传感器/存储芯片的开发者。本文从芯片原理、硬件接法、51 模拟 I2C 时序到初始化序列拆一遍完整流程把容易翻车的细节也一并点出来。2. PCF8574T 与 LCD1602 的适配逻辑先搞懂 IO 扩展和 4 线模式2.1 PCF8574T 是什么为什么它能驱动 LCDPCF8574T 是一颗 I2C 接口的 8 位准双向 IO 扩展芯片I2C 总线只占两个引脚扩展出来 8 个 IOP0~P7。LCD1602 内部控制器是 HD44780需要 11 根控制/数据线RS、RW、EN、D0~D7PCF8574T 的 P0~P7 正好可以映射到这些引脚所以转接板本质上就是把 LCD 的 11 根线在 PCB 上重新分配由单片机通过 I2C 写入 8 位数据芯片再把并行电平送到 LCD。PCF8574T 最关键的寄存器只有一个输出数据锁存器。你往 I2C 从机地址上写一个字节这个字节的每一位就对应 P0~P7 的输出电平写入后一直保持直到下一次写入。LCD1602 的 EN 脉冲就是这么产生的——软件控制某一个位先拉高再拉低完成一次命令/数据锁存。物理电平特性上PCF8574T 的输出是高电平驱动能力弱、低电平驱动能力强的准双向结构外部需要上拉电阻转接板上通常已经有 10k 排阻。LCD1602 的 HD44780 输入阻抗高逻辑电平阈值在 2.2V 以上算高电平所以典型 5V 供电场景下直接驱动没问题。某些转接板还带背光跳线帽和对比度电位器前者控制 LED 背光供电后者调节 V0 偏压。2.2 四线模式怎么省掉一半数据线HD44780 支持 4 位总线模式也就是一次只传 D4~D7 这 4 根数据线RS、RW、EN 照常控制8 位数据拆成高 4 位和低 4 位两次传输。PCF8574T 只有 8 个扩展 IO如果让 LCD 工作在 8 位模式那么 RS、RW、EN、D0~D7 就占了 11 个引脚扩展芯片根本不够用。4 位模式正好只用 RS、RW、EN、D4~D7 共 7 根线加上背光控制位和空余位8 个 IO 刚好安排下。转接板常见的接线映射是PCF8574T 引脚LCD1602 引脚功能P0RS寄存器选择0 命令 / 1 数据P1RW读写选择0 写 / 1 读接地常低P2EN使能脉冲下降沿锁存P3LED背光控制通过三极管/跳线P4D4数据位 4P5D5数据位 5P6D6数据位 6P7D7数据位 7RW 在纯写操作时直接接地所以软件里 P1 始终写 0。传输一个完整字节的过程是先发高 4 位再发低 4 位每次都是先置 RS/EN等数据稳定后拉高 EN 再拉低LCD 在 EN 下降沿时锁存数据。2.3 I2C 从机地址怎么确定PCF8574T 的 7 位从机地址由固定部分 0100 和硬件地址引脚 A0、A1、A2 共同组成。转接板一般用 0x27A0A1A21或 0x3FA0A1A21 的另一种接法取决于板子设计。实际 I2C 总线上发送的地址字节需要左移一位再加读写位写操作地址是 0x27 1 0x4E读操作是 0x27 1 | 1 0x4F。我一般会在代码里留一个地址检测函数上电先扫描 0x20~0x38 范围内的每个地址看哪个有 ACK 应答这样换板子时不用改代码。如果总线上还挂了别的 I2C 器件比如 24C02 存储器地址 0xA0/0xA1扫描时要注意避开冲突地址。这颗芯片出厂默认地址是 0x27淘宝上大部分模块也是这个值。3. 51 单片机 I2C 时序与 LCD1602 初始化序列核心代码逐段拆解3.1 软件模拟 I2C 主机的读写函数51 单片机没有硬件 I2C 外设部分增强型 51 除外所以用普通 IO 口模拟 I2C 总线协议。I2C 的时序核心是三件事起始条件SCL 高时 SDA 下降沿、停止条件SCL 高时 SDA 上升沿、数据位SDA 在 SCL 低时变化SCL 高时保持稳定。速率控制在 100kHz 以内比较稳51 的 12MHz 晶振下用软件延时即可满足。// 模拟 I2C 底层函数sbit 定义 SCL 和 SDA sbit I2C_SCL P2^1; sbit I2C_SDA P2^0; void I2C_Delay(void) // 延时约 5us12MHz 晶振下实际约 5~10us { _nop_(); _nop_(); _nop_(); _nop_(); } void I2C_Start(void) { I2C_SDA 1; I2C_SCL 1; I2C_Delay(); I2C_SDA 0; // SCL 高电平期间 SDA 拉低 → 起始条件 I2C_Delay(); I2C_SCL 0; // 释放时钟线准备传输数据 } void I2C_Stop(void) { I2C_SDA 0; I2C_SCL 1; I2C_Delay(); I2C_SDA 1; // SCL 高电平期间 SDA 拉高 → 停止条件 I2C_Delay(); } void I2C_WriteByte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { I2C_SDA (dat 0x80) ? 1 : 0; dat 1; I2C_Delay(); I2C_SCL 1; // SCL 高电平从机锁存 SDA 上的位 I2C_Delay(); I2C_SCL 0; // SCL 拉低允许 SDA 变化 } I2C_SDA 1; // 释放 SDA等待从机 ACK I2C_Delay(); I2C_SCL 1; I2C_Delay(); // 此时读 SDA 引脚低电平表示 ACK I2C_SCL 0; }这个代码的关键细节在于时序顺序不能乱。比如 I2C_Start 必须是先 SCL1 再拉低 SDA如果 SDA 先变低再拉高 SCL 就是非法状态每个 SCL 高电平期间 SDA 必须保持稳定否则从机采到的位是错的。ACK 检测那一步SDA 必须释放写 1之后再把 SCL 拉高读取 SDA 引脚电平因为 PCF8574T 是开漏结构只有从机拉低和外部上拉两个状态不释放会读到自身输出。3.2 向 PCF8574T 写一个字节怎么控制 EN 下降沿LCD1602 的 EN 引脚要求在数据稳定后产生一个下降沿来完成锁存。因为 PCF8574T 是锁存型输出你写进去的 P2 位会一直保持所以每发一个 LCD 字节需要至少写两次 PCF8574T第一次输出数据 EN1第二次输出同样数据 EN0。这两次之间的时间差就是 EN 高电平持续时间要大于 HD44780 要求的最小脉宽约 450ns实际做几百微秒毫秒级延时都没问题。// 向 LCD 发送一个完整字节4 位模式分两次送 // 参数 dat要发送的 8 位数据mode0命令1数据 void LCD_WriteByte(unsigned char dat, bit mode) { unsigned char high, low; high dat 0xF0; // 取高 4 位D4~D7 对应 P4~P7 low (dat 4) 0xF0; // 取低 4 位移动到高位位置 // 发送高 4 位 PCF8574_Write(high | (mode ? 0x01 : 0x00) | 0x04); // RSmode, EN1 PCF8574_Write(high | (mode ? 0x01 : 0x00)); // EN0锁存高 4 位 // 发送低 4 位 PCF8574_Write(low | (mode ? 0x01 : 0x00) | 0x04); // RSmode, EN1 PCF8574_Write(low | (mode ? 0x01 : 0x00)); // EN0锁存低 4 位 } // 直接操作 PCF8574T 输出口 void PCF8574_Write(unsigned char dat) { I2C_Start(); I2C_WriteByte(0x4E); // 0x27 1写地址 I2C_WriteByte(dat); I2C_Stop(); }注意这里的 0x04 是 EN 位P2的掩码0x01 是 RS 位P0的掩码。背光控制位 P3 如果要常亮应该每次写数据时都带上 0x08不然背光会随数据变化闪烁或熄灭。比如我上面的写法就不够严谨——PCF8574_Write 里应该统一写成dat | 0x08。很多教程代码里会出现背光不亮或者闪屏基本都是漏掉了背光位。3.3 LCD1602 初始化序列不按这个顺序就会花屏HD44780 初始化有严格时序要求尤其是 4 位模式命令顺序错了屏幕直接不显示。4 位模式下要先发 0x03 三次等内部初始化完成后再切到 4 位模式这是很多人忽略的地方。// LCD1602 初始化必须在主程序上电后调用 void LCD_Init(void) { delay_ms(50); // 等待内部电源稳定 // 强制 8 位模式三次 LCD_WriteCmd(0x03); // 第一次 0x03 delay_ms(5); LCD_WriteCmd(0x03); // 第二次 0x03 delay_ms(1); LCD_WriteCmd(0x03); // 第三次 0x03 delay_ms(1); // 切换到 4 位模式 LCD_WriteCmd(0x02); // 0x02 的低 4 位 0010对应 4 位模式 delay_ms(1); // 功能设置8 位→4 位2 行显示5x7 点阵 LCD_WriteCmd(0x28); // 0b00101000DL0(4位)、N1(2行)、F0(5x7) // 显示开关光标不显示不闪烁 LCD_WriteCmd(0x0C); // 0b00001100D1 显示开、C0 光标关、B0 闪烁关 // 清屏 LCD_WriteCmd(0x01); // 清显示光标回首页 delay_ms(2); // 清屏需要 1.64ms 左右 // 显示模式写入后光标右移画面不移动 LCD_WriteCmd(0x06); // 0b00000110I/D1 递增、S0 不移位 }最坑的是第一次 0x03。因为此时 LCD 还在 8 位模式0x03 的高 4 位是 0000D70、D60、D50、D40但 LCD 内部在 8 位模式下只看 D0~D7 的高 4 位D4~D7所以你要在 PCF8574T 的 P4~P7 上输出 0011。这个函数在文章后面会给出。三次 0x03 是为了确保 LCD 在总线上电后不管处于什么状态都能收到正确命令。如果省略或顺序不对最常见的现象是屏幕亮但有黑块或者字符错乱。// 发送命令4 位模式 void LCD_WriteCmd(unsigned char cmd) { LCD_WriteByte(cmd, 0); // mode0RS 置低 delay_ms(1); // 命令执行时间至少 40us }3.4 显示字符和字符串先等忙标志还是用延时HD44780 内部有忙标志BF读取它需要把 RW 置 1 再读 D7但 PCF8574T 转接板一般直接接地读不了忙标志所以纯写模式下只能用延时函数等待 LCD 内部操作完成。命令执行时间参考 HD44780 手册清屏和光标复位要 1.52ms其他命令 37us 到 40us。我习惯清屏后延时 2ms普通命令后延时 50us 到 100us 保证余量。// 在第 page 行第 col 列显示字符串 str void LCD_ShowString(unsigned char row, unsigned char col, unsigned char *str) { unsigned char addr; if (row 0) { addr 0x80 col; // 第一行 DDRAM 地址从 0x00 开始 } else { addr 0xC0 col; // 第二行 DDRAM 地址从 0x40 开始 } LCD_WriteCmd(addr); // 设置 DDRAM 地址 while (*str ! \0) { LCD_WriteData(*str); // 写入字符数据ASCII 直接对应字模 } } // 发送数据显示字符用 void LCD_WriteData(unsigned char dat) { LCD_WriteByte(dat, 1); // mode1RS 置高 delay_us(50); }DDRAM 地址那个 0x80 是命令标志位HD44780 规定地址设置命令的高位必须是 1低 7 位才是实际地址。第一行地址范围 0x00~0x0F第二行是 0x40~0x4F所以命令码就是 0x80 偏移。这个地址关系几乎折磨过每个第一次写 LCD 驱动的人——直接往 0x00 写命令是不显示的必须加 0x80。4. 实际接线与上电流程从杜邦线到万用表测量4.1 单片机与转接板的四种接线方式最常见的接法是用 51 单片机的 P2^0 和 P2^1 分别模拟 SDA 和 SCL。注意 51 的 P1/P2/P3 口内部有上拉电阻输出高电平能力足够但复位期间 IO 口输出高电平这个状态会导致 I2C 总线上出现类似启动条件的电平变化所以最好等单片机复位完成后初始化代码里把 SDA 和 SCL 先拉低再通信。供电方面LCD1602 转接板一般直接吃 5V因为 HD44780 的工作电压是 5V 等级3.3V 下对比度会变差且背光昏暗。但 51 单片机有的是 5V有的是 3.3V 版本如果是 3.3V 的单片机最好用 5V 供电给转接板再让 SDA/SCL 走电平转换或者串小电阻耦合。实际上很多 3.3V 单片机直接驱动 5V 转接板也能工作因为 I2C 是开漏结构电平由外部上拉决定只要上拉电阻接的是 5VSDA 释放时就是 5V 高电平3.3V 单片机强行输出高电平时是 3.3V从机识别这个电压通常也没问题但这不是可靠方案长时间跑有隐患。// 初始化 IO 口方向 void I2C_Init(void) { I2C_SCL 1; // SCL 默认高空闲状态 I2C_SDA 1; // SDA 默认高空闲状态 }4.2 上电不显示的排查路径上电后现象分三类完全没反应、屏幕亮但无字符、有乱码。前两类的排查路径是通过万用表先量转接板的 VCC 和 GND 电压用万用表二极管档测一下 SDA/SCL 是否有上拉到 VCC通常有 4.7k 或 10k 排阻接着确认对比度电位器位置LCD1602 的 V0 电压如果调到极端屏幕上就全黑或全白。第三类乱码大多是 4 位模式没切换成功或初始化顺序错了。还有一类很隐蔽的问题是 I2C 地址不对。有的转接板背面焊盘是 A0/A1/A2 的跳线电容出厂默认是 0x27但如果你收的板子被改动过地址可能是 0x3F 甚至 0x26扫描地址比猜快得多。我写了一个简单的 I2C 扫描函数在 main 里先调用一遍把有应答的地址通过串口打印出来这样就能锁定正确地址再往下画。这个函数就借用 PCF8574_Write 的底层逐个地址发写命令等待 ACK。// I2C 地址扫描返回第一个有 ACK 的地址找不到返回 0xFF unsigned char I2C_ScanAddr(void) { unsigned char addr; for (addr 0x20; addr 0x38; addr) { I2C_Start(); if (I2C_WriteByte((addr 1) 0xFE)) { // 写地址判断 ACK I2C_Stop(); return addr; } I2C_Stop(); } return 0xFF; }注意这个函数里 I2C_WriteByte 需要在返回前顺便把 ACK 位通过函数返回值带回上面的版本里我为了让流程简单返回了 ACK 状态。实际写的时候要在 I2C_WriteByte 里加一个返回值处理把 SDA 读取状态变成 0/1。4.3 背光和对比度调节的隐藏细节转接板上的电位器通常是一个蓝色的多圈可调是调液晶偏压 V0 的顺时针拧到某个位置字符最清晰拧过头会全黑。背光控制看转接板设计有些是直接用 P3 驱动三极管有些是跳线帽短接背光电源和 VCC。用软件控制背光开关时每次写 PCF8574T 都要注意同时设置 P3 位——如果你在显示期间把 P3 写成 0背光就灭了。另一个细节是第一次拿到转接板时上面的电位器被拧到中间屏幕可能显示不清晰这很正常不是代码问题。把代码调通后边调电位器边看字符找到一个对比度临界点字刚出来且没有背景黑影就固定住。这个电位器别调太猛多圈电位器转起来需要耐心。5. 常见问题与避坑记录五个真实踩过的坑5.1 屏幕全黑无字符现象LCD1602 背光亮屏幕整个显示一排黑块发任何命令都没反应。原因断电情况下检查接线发现 SDA 和 SCL 正好接反了。更隐蔽的原因是 PCF8574T 的 SDA 和 SCL 上有焊接短路万用表量不出来但 I2C 通信就是失败。解决先用 I2C 地址扫描函数确认是否能收到 ACK收不到就交换 SDA/SCL 再扫一次。如果还不行把转接板拔下来单独供电用万用表量 P4~P7 对应引脚的电平手动给 PCF8574T 写一个测试字节看 LED 有没有反应。5.2 字符有重影或乱码现象能显示但字符不对劲比如显示的数字每一位都带一个重影或者显示的内容和预期完全不符。原因这是 4 位模式下高 4 位和低 4 位发送速度太快LCD 来不及锁存导致数据错位。另一种可能是 I2C 通信速率太高PCF8574T 不支持那么高的速率。解决在两次 PCF8574_Write 之间加 1ms 延时尤其是第一次拉高 EN 后、拉低 EN 前要给 LCD 一个稳定时间。具体做法是把 LCD_WriteByte 里的 PCF8574_Write 函数调用之间都加delay_ms(1)不要省这个时间。实测 12MHz 晶振不加延时偶尔能跑但环境温度一变或者线长了就翻车加延时一劳永逸。5.3 屏幕上只有第一行有显示现象第一行显示正常第二行全是黑色方块或者空白写第二行数据没反应。原因第二行的 DDRAM 基地址写错了。很多人把第二行地址误写成 0xC0 之后忘了加 0x80实际正确是 0xC0已经在地址里了。更常见的错误是第一行和第二行的 col 偏移没有区分比如从第二行的第 8 列开始写但是显示出来却从头开始。解决确认 LCD_ShowString 里第一行地址是 0x80 col第二行地址是 0xC0 col。如果第二行完全不亮先手动写一条命令 0xC0再写几个字符试试。如果这样能显示说明是地址计算的问题检查 15 的列偏移。5.4 I2C 地址不同导致无响应现象代码里写死了 0x27但屏幕不响应看起来像是转接板烧了。原因转接板上的地址跳线焊盘被改动比如 A0 焊到了高电平地址变成 0x3F 或者其他组合。实测中遇到过三块一模一样的板子两块是 0x27一块是 0x3F最初怀疑是芯片问题扫描后才发现是地址差异。解决写一个扫描函数上文给出上电后先扫描地址把返回值打印出来或者直接填充到通信代码里然后永久锁死。扫描函数不要用串口直接用 LCD 显示省得两个外设互相干扰。5.5 上电瞬间屏幕闪烁现象单片机上电后LCD 先是闪一下然后正常显示。原因上电瞬间 PCF8574T 的 P0~P7 输出状态不确定可能是低电平也可能是高电平导致 LCD 收到随机命令。等到单片机程序跑起来后I2C 初始化把这些引脚写成了确定值才恢复正常。解决在 main 函数最开始就调用 LCD_Init并且在 I2C_Init 里把 SDA/SCL 都设置为高上电后尽快把 PCF8574T 的所有输出写入安全值比如背光开、EN 低、RS 低也就是写一次 0x08。这个动作能显著减少闪烁。如果还是闪可以在转接板 VCC 和 GND 之间加一个 100uF 电解电容滤波复位噪声。6. 进阶技巧用一个定时器实现任意 IO 模拟 I2C 与 4 位模式调试把 I2C 时序的延时改成基于定时器会让代码在某些场景下更稳定比如主程序里已经用定时器做毫秒 tick可以顺便把 I2C 延时的精确性提升一个档次。但你只要遵循最低电平持续时间即可——12MHz 晶振下最简单的nop延时就能用不必上定时器。这里我提供另一个实用技巧用逻辑分析仪看 PCF8574T 的输出波形来调试 LCD 初始化流程。// 用定时器产生 10us 延时替代 _nop_ 延时 void TimerDelay_10us(void) { TH0 0xFC; // 12MHz 晶振定时器 0 工作在方式 110us 定时 TL0 0x18; TR0 1; while (!TF0); TR0 0; TF0 0; }这个延时函数的目的是让 I2C 时序的时序余量更稳定尤其当你的系统里中断比较多、主循环时序不稳时用定时器定时的效果比nop好很多。不过如果只是驱动 LCD1602上面 3.1 节里的 I2C_Delay 也已经足够用了别过度设计。另一个调试技巧是把 PCF8574T 的 P4~P7 单独拿出来看在 LCD_Init 开始前用 PCF8574_Write(0x0F) 点亮 P4~P7 对应的 LED如果接在转接板上会看到确认 PCF8574T 本身输出工作正常。这种断开式的调试在没逻辑分析仪的时候很有效。我最终在自己板子上跑通的流程习惯是上电后先调对比度再跑地址扫描然后按顺序初始化 LCD最后滚动显示几行长字符串测试 DDRAM 地址边界。从那以后每拿到一块新的 LCD 转接板我都强制走一遍“扫描地址 → 单字节测试 → 初始化 → 显示测试”四步整个过程不超过两分钟但能挡住九成故障。希望帮到你。本文还有配套的精品资源点击获取