☰
PCF8574T四针LCD1602驱动详解:从I2C时序到避坑指南
2026/10/14 16:31:39 网站建设 项目流程

简介:这份压缩包是一套基于51单片机的四针LCD1602 I2C驱动解决方案,面向嵌入式入门者和单片机开发者。方案利用PCF8574T将I2C总线扩展为并行GPIO,仅凭SCL、SDA及两根电源线即可控制LCD1602,显著节省IO资源,适合STC89C51等常见51芯片。包内共25个文件,包括main.c、1602_bsp.c、delay_bsp.c及对应头文件等C源码,完整Keil工程文件(uvproj/uvopt/lnp),可直接编译的hex烧录文件,以及编译过程生成的lst、obj、map、m51等辅助文件,总共约55KB,结构清晰。代码涵盖I2C启动/停止、PCF8574T地址读写、LCD初始化序列、清屏与字符显示等关键模块,便于学习移植。已有2065人学习,尤其适合需要快速上手I2C驱动液晶显示或优化引脚资源的开发者;在Keil下导入工程后,修改main.c中的显示内容即可烧录验证。

1. 四针LCD1602到底省了什么:IIC转并行方案的落地价值

很多第一次拿到四针LCD1602模块的人都会愣一下:屏幕明明是16个引脚的液晶面板,怎么板上只引出VCC、GND、SDA、SCL四根线?答案就在背面那颗黑色八角芯片PCF8574T上。它把LCD1602需要的6根控制线(RS、RW、E、D4到D7)压缩成2根I2C数据线,让单片机只用两个GPIO就能点亮一块字符屏。这个方案在省引脚、走线距离、扩展多块屏幕这三个场景里,几乎是成本最低的解法。

适合谁?手里GPIO快用光的Arduino玩家、用ESP32做小仪表盘又要省引脚的人、以及想在一块主板上同时挂多块屏幕的开发者。坑也明显:时序被I2C包了一层,出错时不像直接并口驱动那么直观。这篇文章就是把PCF8574T的底层逻辑、接线、最小驱动代码和常见翻车点一次讲透,让新手照着做能亮,熟手能避开暗坑。

2. PCF8574T到底干了什么:四针控制的底层逻辑

2.1 引脚映射:一个芯片怎么顶替六根线

LCD1602本身是并行接口,数据线D0到D7加上RS、RW、E,最简工作模式也要6根线。PCF8574T做的事很简单:它是I2C转8位并行IO的扩展芯片,单片机通过I2C总线往它内部一个8位寄存器写值,这个值就直接体现在P0到P7八个引脚的电平上。硬件上把LCD的D4到D7接PCF8574T的P0到P3,RS接P4,RW接P5,E接P6,背光接P7,一个字节就把LCD所需的所有信号塞满了。

常见的四针LCD1602模块,出厂时已经把这套映射固定死在PCB上,用户不需要自己飞线。但不同厂家模块的映射略有差异,比如有的模块把背光接在P3而不是P7,有的把RW在板上直接拉低省掉了这根控制线。拿到模块先看背面的丝印,标注了P0到P7对应关系的,按丝印来;没标的,按P0-D4、P1-D5、P2-D6、P3-D7、P4-RS、P5-RW、P6-E、P7-背光的通用映射来。这个映射表决定了后面所有代码里的位操作顺序,一定要确认。

PCF8574T的地址由A0、A1、A2三个引脚的电平决定,这也是四针模块上偶尔能看到三个焊盘短接点的原因。常见默认地址是0x27,但有的模块出厂就把A0到A2拉成了不同组合,地址变成0x3F。地址不对,扫描不到设备,后续一切免谈。这个细节排在避坑清单第一位,后面专门讲。

2.2 点屏最核心的时序:一个字节怎么变成两次E脉冲

LCD1602在4位模式下,每个字符或指令的高4位和低4位是分两次送进去的。每次送4位数据时,E引脚必须先拉高再拉低,形成一个下降沿,LCD在下降沿那一刻锁存数据。在直接并口驱动时,这个E脉冲由单片机GPIO直接翻转,时序非常直观。但用PCF8574T时,E引脚是P6,它和D4到D7挤在同一个字节里,这就产生了一个关键约束:必须用两次I2C写入来模拟一次E脉冲。

具体做法是:第一次写入的数据里,E位(P6)置1,这时LCD的数据线上已经摆好了高4位,E处于高电平;第二次写入的数据里,E位置0,数据线维持同样的高4位,E由高变低产生下降沿,LCD锁存这4位。然后再用同样的两步送低4位。所以显示一个字符,实际要执行4次I2C写操作:高4位E高、高4位E低、低4位E高、低4位E低。这就是很多人用库时感觉"显示有点慢"的根本原因——I2C时钟100kHz时,4次写入加上地址帧和应答位,一个字符要耗时几百微秒,连续刷新大量字符时能明显感知到。

理解了这一步,后面调试花屏就有方向了。很多库函数封装了这套流程,比如write4bits和 pulseEnable,但底层原理就是上面这个字节里E位的翻转。自己写驱动时,I2C发送函数里每个字节的组装顺序,完全按照这个逻辑来。背光位P7则另有用处:它不参与LCD时序,只是控制背光三极管或MOS管的开关,写入时单独置1或清0即可,不影响数据位。

3. 硬件接线与地址探测:拿到四针模块先做的三件事

3.1 四根线怎么接:VCC-GND-SDA-SCL不是全部顺序怼上

四针模块的排针顺序,不同厂家不完全一致,常见顺序是GND-VCC-SDA-SCL,但也有VCC-GND-SCL-SDA的排列。直接按顺序怼上之前,先看模块丝印或卖家文档确认引脚定义,这一步省掉后面大部分排查时间。接线时注意SDA和SCL要接对单片机的I2C引脚,Arduino Uno上是A4和A5,ESP32上通常是GPIO21和GPIO22,STM32则看使用的是哪个I2C外设的对应引脚。

供电电压是第一个隐藏坑。PCF8574T的供电范围是2.5V到6V,看似兼容3.3V和5V,但LCD1602的液晶驱动电压和背光LED压降,在3.3V下对比度会明显偏低。如果单片机是3.3V供电的ESP32,建议给模块单独供5V,I2C引脚之间做好电平匹配。常见做法是VCC接5V,再把SDA和SCL通过上拉电阻接到3.3V,或者选择支持5V输入的引脚。供电电压不足时,最常见的现象是屏幕能亮但字符极淡,调对比度电位器也救不回来。

上拉电阻这块也值得一提。I2C总线需要上拉电阻才能正常工作,大多数四针模块板上已经焊了4.7k或10k的上拉电阻。如果模块上没有,就需要在SDA和SCL上各接一个上拉电阻到VCC,阻值4.7k到10k之间都可以。单片机的内部上拉太弱,不建议依赖内部上拉驱动I2C,尤其在总线长度超过20厘米时,信号边沿会明显变缓,导致通信不稳定。

3.2 扫描I2C地址:写三段小程序确认模块在总线上的身份

地址是四针模块最常见的不确定因素。手册上写PCF8574T的默认地址是0x27,但模块厂家不一定照做,A0到A2的跳线组合可能把地址改成0x3F甚至0x26。与其翻文档猜,不如在总线上做一次扫描。Arduino环境里I2C扫描器是最短的小程序,它遍历0x08到0x77的所有地址,逐个发送探测帧,有设备应答就打印出来。这个程序只有十几行,却能一锤定音。

#include <Wire.h> void setup() { Serial.begin(115200); Wire.begin(); Serial.println("I2C Scanner"); for (byte addr = 0x08; addr < 0x78; addr++) { Wire.beginTransmission(addr); byte error = Wire.endTransmission(); if (error == 0) { Serial.print("Found device at 0x"); if (addr < 0x10) Serial.print("0"); Serial.println(addr, HEX); } } } void loop() { delay(10000); }

这段代码的逻辑很简单:Wire.beginTransmission启动一次I2C通信,写入目标地址后立即结束传输。如果从设备存在,会返回应答,endTransmission的返回值是0;如果无人应答,返回值为2(地址发送失败NACK)。就靠这个返回值判断设备是否存在。注意一次只能打印一个地址,如果扫描出来两个地址,说明总线上串了其他I2C设备,需要根据设备类型区分哪个是LCD模块。

扫描器的结果在换模块、换单片机、换接法后,都建议重新跑一遍。我自己经历过项目跑到一半屏幕突然不亮,排查半天发现是模块背面一颗电容虚焊导致地址不稳定,重新扫描后才确认地址已经变了。养成习惯:新模块上电先扫描,把地址记在模块背面标签上,能省掉后面大量重复劳动。

3.3 初始化参数:对比度旋钮和背光跳线

四针模块上通常有一个蓝色电位器,这是调节LCD1602液晶对比度的,不是背光亮度。它控制的是Vo引脚的电压,调的是字符显示的黑度。很多新手以为屏幕淡是背光不够,拼命调这个旋钮,其实背光亮度由P7位的电平决定。调试顺序应该是:先确认背光亮,再调对比度电位器让字符清晰,最后才是软件层面的参数调整。

背光跳线在部分模块上存在,通常是一个标注了BLK和BL的跳线帽或者0欧电阻位。这个跳线决定背光LED的电源是从PCF8574T的P7走,还是从VCC直接走。从P7走的好处是可以用软件控制背光开关和亮度(通过PWM),代价是背光电流要经过PCF8574T的IO引脚,最大只能到25mA左右,LED背光全亮时会有点吃紧。从VCC直接走则恒定亮,软件管不了。调试期间建议先让背光常亮,减少一个变量。

对比度电位器还有个特点:温度变化时最佳位置会漂移。夏天调好的位置,冬天可能字符变淡。量产设备如果要长期稳定运行,建议在代码里把Vo电压做成可软件调节的,或者直接选带温度补偿的模块。不过对大多数实验和桌面项目来说,电位器调一次就够用了。

4. 最小可跑代码:从初始化到显示一行字符

4.1 用现成库在Arduino上点亮:最小代码

Arduino生态里最常用的LCD I2C库是LiquidCrystal_I2C,底层就是通过Wire库往PCF8574T写字节。它的构造参数依次是地址、列数、行数,初始化后调用begin和print就能显示。下面的代码是完整可运行的最小示例,我把注释写到每一行关键动作上。

#include <Wire.h> #include <LiquidCrystal_I2C.h> // 地址0x27来源于I2C扫描结果,如果你的模块是0x3F就改这里 LiquidCrystal_I2C lcd(0x27, 16, 2); void setup() { lcd.init(); // 初始化LCD:等待上电稳定后发送初始化指令序列 lcd.backlight(); // 置位PCF8574T的P7,打开背光 lcd.setCursor(0, 0); // 光标移到第1行第1列 lcd.print("Hello, 4pin LCD!"); // 显示一行字符 lcd.setCursor(0, 1); // 光标移到第2行第1列 lcd.print("I2C via PCF8574T"); } void loop() { // 空循环保持显示内容,实际项目中可在这里刷新动态数据 }

这段代码的核心动作只有四个:init负责往LCD发送那串著名的初始化时序,包括多次0x30唤醒、功能设置、显示开关、清屏;backlight通过置位P7点亮背光;setCursor换算行列到DDRAM地址;print逐字符调用内部write函数完成高位低位的两次锁存。库已经帮我们把上一章讲的E脉冲模拟封装好了,业务代码只需要关心显示内容本身。

实际使用中要注意两点。第一,init内部有一段专用延时,等待LCD上电稳定,在5V供电时至少需要15毫秒,代码里如果自己写初始化,这段延时不能省。第二,如果lcd.init()之后屏幕没有反应,不一定是接线错,也可能是地址不对——构造参数里的0x27要改成扫描得到的真实地址。开局第一步永远是跑一遍扫描器,而不是盯着显示效果猜。

4.2 手动模拟I2C时序的驱动核心:理解每一位的作用

现成库虽然方便,但出了问题就是黑匣子。我建议至少手写一遍底层字节拼装逻辑,才能真正理解四针驱动的工作原理。下面这段代码展示了核心的writeByte函数:它接收一个字节,这个字节已经按PCF8574T的引脚映射排布好,函数负责产生E脉冲。

// 底层写一个字节到PCF8574T,bit6是E引脚,bit7是背光 void lcdWriteByte(uint8_t data) { // E高电平:数据引脚已经稳定,准备锁存 uint8_t highPhase = data | 0x40; // 置E位为1 wireWriteByte(highPhase); delayMicroseconds(2); // E高电平保持时间,至少450ns // E低电平:下降沿触发LCD锁存 uint8_t lowPhase = data & ~0x40; // 清E位为0 wireWriteByte(lowPhase); delayMicroseconds(2); // E低电平保持时间 } // 往PCF8574T写一个字节的实际I2C操作 void wireWriteByte(uint8_t value) { Wire.beginTransmission(0x27); // 目标地址,按实际修改 Wire.write(value); Wire.endTransmission(); }

lcdWriteByte的入参data,其中bit0到bit3是LCD的D4到D7,bit4是RS,bit5是RW,bit6是E,bit7是背光。调用方在发送高4位时,把数据放到bit0到bit3的对应位置,然后调用lcdWriteByte。函数内部先让E变高,再让E变低,两次I2C写入之间隔了2微秒,这个延时保证了LCD的建立时间和保持时间都满足数据手册要求。实际项目中,如果I2C时钟频率较高或者线路较长,2微秒可能需要加大到5微秒。

拼装数据更完整的过程是:先取出字符的高四位,放进data的低四位,再加上RS位表示数据或指令,再调用lcdWriteByte发送;随后取低四位重复同样步骤。RS位在发送数据时为1,发送指令时为0。RW引脚在四针模块上大多被固定接地,所以bit5通常写0。背光bit7在整个过程中保持不变,否则背光会跟着每个字符闪烁。

4.3 参数怎么调:地址、背光、清屏速度

三个参数最常调。地址前面已经反复强调,扫描后填入构造参数即可。背光控制,LiquidCrystal_I2C库里就是backlight和noBacklight两个方法,底层是把bit7置1或清0。如果模块的背光控制脚不是P7而是其他引脚,需要改用自定义初始化或者修改库源码里的byte值。第三个参数是清屏速度,lcd.clear()会往LCD发送清屏指令,LCD1602的清屏指令执行时间最长要1.52毫秒,库里的clear会等待这个时间。在循环里频繁clear会导致明显的屏幕闪烁,优化的做法是只更新变化的字符,而不是每轮循环清屏重写。

还有一个容易被忽略的构造参数,有的库构造函数里带了第四个参数,用于指定负向显示还是正向显示,以及是否启用光标。默认情况下库会关闭光标,但如果你在调试时想看到光标位置,可以调用lcd.cursor()显示下划线光标,或者lcd.blink()让光标闪烁。这两个方法本身不涉及PCF8574T的特殊处理,只是往LCD发送对应的显示控制指令,但调试阶段非常有用,可以直观确认光标是否在预期位置。

I2C时钟频率也值得提一句。Wire库默认跑100kHz标准模式,这个速度对LCD1602足够。有人图快把时钟调到400kHz,虽然PCF8574T支持,但LCD侧的数据建立时间可能不够,偶发花屏概率大增。我的建议是保持在100kHz,显示性能瓶颈不在I2C总线,而在LCD本身的DDRAM读写速度,调高时钟收益极小,风险却不小。

5. 四针LCD1602避坑清单:从地址翻车到背光失控

5.1 地址翻车:0x27还是0x3F,扫描器说了算

现象:新模块上电,代码用的是网上抄来的0x27地址,lcd.init()后屏幕毫无反应,背光有时亮有时不亮。换了好几个库都一样。

原因:PCF8574T的地址由A0、A1、A2决定,不同厂家模块默认接法不同。0x27对应A0=A1=A2全部接地,0x3F对应全部接高。市面上两类模块都存在,还有少数模块把地址设成了0x26或0x20。

解决:先跑I2C扫描器,得到真实地址再改代码。如果你跑扫描器后一个地址都扫不到,大概率是接线问题或者总线被拉死了,优先检查SDA/SCL是否接反,VCC是否为模块要求的电压。扫描器是定位此类问题的第一手段,不要靠猜或者论坛帖子。

5.2 花屏乱码:E脉冲宽度与电源纹波的博弈

现象:屏幕能亮,但显示的不是预期字符,第一行出现一个个实心方块,或者字符中夹杂乱码。重新上电后乱码位置随机变化。

原因:常见的有三种。第一种是E脉冲宽度不足,I2C连续两次写入间隔太短,LCD没有足够时间在下降沿锁存数据;第二种是电源纹波大,PCF8574T在电平翻转瞬间产生毛刺,LCD误读数据;第三种是初始化时序被干扰,单片机复位瞬间总线上有噪波信号。

解决:先排查电源,VCC和GND的杜邦线不要飞得太长,最好加一个100nF去耦电容在模块电源引脚附近。再检查初始化代码,确保init前有至少50毫秒的上电稳定延时。如果自己手写驱动,把E高低电平之间的延时从2微秒加到5微秒试试。裸机的LCD1602模块相比四针模块更容易花屏,因为四针模块的时序完全由I2C字节流决定,反而更稳定——前提是I2C通信本身没有位错误。

5.3 背光失控:P7位被数据操作误伤

现象:屏幕显示正常,但背光会随着每次字符刷新轻微闪烁,或者调用backlight()没有反应,背光恒定不亮。

原因:PCF8574T的8位输出在每次写入时都会被整体更新。如果你的初始化过程或者自写驱动中,某个函数在拼装字节时把bit7清了0,而后续写数据时没有恢复bit7,背光就在那一瞬间被关掉。闪烁的原因是显示刷新函数每次都写入一个背光位为0的字节,背光在每次字符写入时经历关-开的抖动。背光彻底不亮的原因则可能是模块背光控制没接P7,或者三极管驱动电路损坏。

解决:自写驱动时,维护一个全局变量保存当前背光状态,每次拼装数据字节时,先把bit7(或模块实际的背光位)按这个变量置位,再叠加数据位和RS位。使用现成库时,确认库版本对backlight的实现是否在init之后被正确调用,有些裁剪版的库对noBacklight支持不完整。判断背光电路好坏的快速方法:把模块VCC和背光控制引脚短接一下,如果背光亮了说明电路正常,问题在驱动逻辑。

5.4 芯片发烫与上电失败:供电顺序和电平匹配的门道

现象:PCF8574T芯片摸上去烫手,但屏幕不亮。或者3.3V单片机供电时屏幕有时能亮有时不亮,字符极淡。

原因:芯片发烫通常是电源反接或者过压,PCF8574T虽然标称耐压6V,但有些劣质模块的LDO或稳压二极管失效后会把高压直接灌入芯片。字符极淡则是LCD1602的液晶驱动电压不足,3.3V供电时Vo偏置达不到对比度要求,这不是芯片的问题,是LCD面板的问题。

解决:先断电检查VCC和GND是否接反,四针排针的顺序混乱是很多翻车的起点。然后确认供电电压:5V供电下用万用表量PCF8574T的VCC引脚,应该是4.8V到5.2V之间;3.3V系统则建议模块单独供5V,SDA和SCL做电平转换或串电阻分压。芯片发烫且电流超过100mA,基本判定损坏,直接换模块。另外上电瞬间的浪涌电流,会通过背光LED和三极管冲击PCF8574T的输出引脚,长期看建议在VCC入口加一个10欧姆限流电阻或磁珠。

5.5 第二行始终空白:DDRAM地址和行起始地址的坑

现象:第一行显示正常,setCursor(0, 1)后第二行没有任何输出。

原因:LCD1602的行地址不是连续的。第1行从0x00开始,第2行从0x40开始。大多数库的setCursor会帮你做这个换算,但有些软件模拟I2C的实现里,setCursor直接调用了LCD指令,把列号当成地址发送,忽略了行偏移。另一个原因是PCF8574T的P5(RW位)没有被拉低,LCD处于读状态,写操作被忽略。

解决:先用最简单的lcd.setCursor(0, 1)确认库里行偏移是否正确。然后检查模块上RW引脚是否接地,很多四针模块为了省事,把RW直接接地了,但也有模块把RW通过PCF8574T的P5控制。如果P5悬空,LCD读写状态不确定,表现就是某个功能时好时坏。解决办法是把RW引脚在模块上飞线接地,或者确保P5位始终为0。

6. 进阶验证:用逻辑分析仪给四针驱动拍一张时序照片

如果你已经能正常显示字符,下一步值得做的是用逻辑分析仪抓一次写入过程,亲眼看一看E脉冲长什么样。哪怕是十几块钱的8通道逻辑分析仪,配合开源软件,就能看到SDA和SCL上的波形,以及PCF8574T输出侧的E信号变化。抓一次你能直观理解:I2C地址帧长什么样、应答位在哪、两次写入之间的E脉冲是不是符合LCD数据手册的要求。

抓取的技巧是在循环里反复调用lcd.print("A"),然后暂停程序,这样逻辑分析仪能稳定捕获重复的波形。重点看SCL时钟频率、SDA数据是否在SCL低电平期间切换、E高电平宽度是否大于450纳秒。如果发现E脉冲宽度不够,回到代码里加大delayMicroseconds的参数,或者降低I2C时钟频率。这个验证方法对冲掉自写驱动时"看着对但实际不工作"的玄学问题,比靠眼睛盯着屏幕猜有效得多。

再进一步,可以把驱动改成非阻塞方式。上面所有代码里的delayMicroseconds都是阻塞等待,在实时性要求高的项目里,几百微秒的等待可能不可接受。思路是用定时器分时产生I2C时序,或者把要显示的字符做成队列,后台任务逐个发送。但我的实际经验是:LCD1602本身刷新率就不高,人眼也察觉不出阻塞带来的差异,除非你在同一个循环里还要处理高优先级任务,否则没必要牺牲代码简洁性。

最后说一个习惯:我在每块四针LCD模块的背面贴一张小标签,写上实测I2C地址和供电电压备注。模块用久了或换板子重用时,直接看标签就能确认参数,不用每次重新扫描。这个不起眼的习惯,在同时维护五六块模块的项目里,帮我避开了好几次"这模块是不是坏了"的误判。希望帮到你。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询