STM32通过IIC驱动LCD1602:PCF8574转接板实战与调试指南
1. 项目概述与核心价值
最近在整理一个老项目,需要用到LCD1602来显示一些简单的状态信息。手头正好有一块STM32F103C8T6的核心板,但IO口资源已经比较紧张了。直接驱动LCD1602需要至少6个IO(4位模式)或11个IO(8位模式),这显然不划算。翻找元件盒时,发现了之前买LCD屏时附赠的一块蓝色小转接板,上面印着PCF8574T。这玩意儿就是个IIC转8位并口的芯片,理论上用两根线(SDA, SCL)就能控制LCD1602,简直是IO资源紧张时的救星。但实际用起来,从硬件连接到软件驱动,再到调试显示乱码,每一步都有不少细节需要注意。网上资料虽然多,但往往只给代码,不讲清楚背后的时序逻辑和硬件原理,导致移植起来各种不顺利。今天我就把用STM32通过IIC控制LCD1602的完整过程,结合软硬IIC的选择、PCF8574的地址配置、LCD1602的初始化序列以及实际调试中遇到的坑,系统地梳理一遍。无论你是用的是标准库还是HAL库,是软件模拟IIC还是硬件IIC,这篇文章都能给你一个清晰、可复现的参考方案。
2. 核心硬件解析:为什么是PCF8574?
2.1 LCD1602的接口困境与PCF8574的破局思路
LCD1602液晶屏本身是一个并行接口设备。标准的8位数据模式需要DB0-DB7共8根数据线,外加RS(数据/命令选择)、RW(读写选择)、E(使能信号)3根控制线,最少需要11个IO口。即使用4位数据模式(分两次发送一个字节),也需要7个IO口。对于很多以STM32F103C8T6为代表的“小钢炮”型MCU来说,IO口是非常宝贵的资源,尤其是当项目还需要连接传感器、通信模块时,IO口捉襟见肘。
PCF8574芯片的出现,完美地解决了这个矛盾。它是一颗由NXP生产的远程8位I/O扩展芯片,通过IIC总线与主控(如STM32)通信。主控只需要两根线(SDA和SCL),就可以读写PCF8574的8个IO口的状态。我们可以将这8个IO口灵活地映射到LCD1602所需的控制线和数据线上,从而实现“串行转并行”的控制。市面上常见的蓝色转接板,就是集成了PCF8574芯片、上拉电阻和16Pin排母,将复杂的连线简化为一个4Pin接口(VCC, GND, SDA, SCL)。
2.2 PCF8574转接板硬件详解与地址配置
拿到一块PCF8574转接板,首先要解决的是IIC设备地址问题。PCF8574的7位IIC地址由芯片本身的固定部分和A0、A1、A2三个地址引脚的电平决定。固定部分为0100,加上A2、A1、A0,组成7位地址 0100 A2 A1 A0。
关键点在于:市面上绝大多数转接板为了节省空间和成本,默认将A0, A1, A2三个引脚直接接地(GND)。这意味着A2=A1=A0=0。因此,最常见的7位设备地址就是 0100 000,换算成8位写地址(最低位为0表示写操作)是 0x40,8位读地址(最低位为1)是 0x41。在编程时,我们通常使用8位地址。
注意:务必用万用表确认或查看转接板背面电路。如果发现地址不对,通信会完全失败。有些转接板会留有焊盘,可以通过焊接0欧电阻或飞线来改变A0-A2的接法,从而改变地址。
转接板的引脚连接通常已固定,其8个IO(P0-P7)与LCD1602的引脚对应关系如下(这是最常见的一种接法,务必与你手中的板子核对):
- P0 连接 LCD1602 的 RS (数据/命令寄存器选择)
- P1 连接 LCD1602 的 RW (读写选择,通常我们只写,所以可以接地或置低)
- P2 连接 LCD1602 的 E (使能信号,高电平脉冲触发)
- P3 连接 LCD1602 的 背光控制 (如果支持,通常高电平点亮)
- P4-P7 分别连接 LCD1602 的 DB4-DB7 (4位数据总线的高4位)
这里有一个非常重要的细节:LCD1602的DB0-DB3(低4位)在4线模式下是不接的,必须悬空! 转接板只用了P4-P7这4个IO,正是为了配合LCD1602的4位数据模式。这种模式下,一个字节的数据需要分两次(先高4位,后低4位)发送。
3. 通信基础:软IIC与硬IIC的抉择与实现
3.1 IIC协议核心时序与STM32的两种实现方式
IIC(Inter-Integrated Circuit)总线是一种简单、双向、二线制、同步串行总线。它由数据线(SDA)和时钟线(SCL)构成,支持多主多从,每个设备都有唯一的地址。
关键时序包括:
- 起始条件(S):SCL为高电平时,SDA出现一个下降沿。
- 停止条件(P):SCL为高电平时,SDA出现一个上升沿。
- 数据有效性:在SCL高电平期间,SDA必须保持稳定。数据的变化只能发生在SCL为低电平期间。
- 应答(ACK):发送器每发送完一个字节(8位),在第9个时钟脉冲期间释放SDA线,接收器需要将SDA拉低以表示应答。
在STM32上,我们有硬件IIC和软件模拟IIC两种方式来实现这个协议。
硬件IIC:利用STM32片上的I2C外设(如I2C1, I2C2)。由硬件自动生成时序,效率高,不占用CPU时间,但配置相对复杂,且STM32F1系列的硬件IIC早年有“bug”的传闻(实际上在标准库和HAL库的合理配置下已很稳定),调试不当时容易卡死(总线锁死)。 软件模拟IIC:任意指定两个GPIO口,通过代码控制其高低电平变化来模拟出IIC时序。优点是移植性极强,不依赖特定外设,引脚可任意换;缺点是时序由延时函数控制,占用CPU资源,速度较慢。
对于驱动LCD1602这种低速设备,两种方式都绰绰有余。我的选择建议是:如果你是初学者,或者项目需要快速验证,优先使用软件模拟IIC,因为它简单、排错直观。如果你对STM32的HAL库或硬件外设比较熟悉,且追求代码的“正统”和效率,可以选择硬件IIC。
3.2 软件模拟IIC的代码实现与精准延时
软件模拟IIC的核心在于GPIO的操作和精准的延时。下面给出一个基于STM32标准库的模拟IIC底层驱动关键代码,并解释延时的重要性。
首先,定义SDA和SCL对应的GPIO端口和引脚:
然后,实现基本的引脚操作宏和延时函数:
接下来是时序模拟:
实操心得:软件IIC的稳定性极度依赖
IIC_Delay_us的准确性。如果延时太短,可能违反LCD1602或PCF8574的时序要求;如果延时太长,会影响整体速度。最好的校准方法是使用逻辑分析仪或示波器抓取波形,对照PCF8574数据手册的时序图(如t_{HD,STA},t_{LOW},t_{HIGH}等参数)进行调整。如果没有仪器,可以尝试将延时调大到10us甚至20us来保证通信的可靠性,对于显示应用来说速度完全足够。
3.3 硬件IIC配置要点与避坑指南(以HAL库为例)
如果你选择硬件IIC,以STM32CubeMX配置I2C1为例,需要注意以下关键点:
-
模式选择:选择 I2C,模式为 Standard Mode(标准模式,100kHz)。驱动LCD1602完全够用,高速模式可能不稳定。
-
引脚配置:查看数据手册,找到I2C1的SDA(如PB7)和SCL(如PB6)引脚,将其配置为 Alternate Function Open Drain(复用开漏输出)。这一点至关重要,因为IIC总线要求是“线与”,必须使用开漏模式,并依赖外部上拉电阻。
-
参数配置:
- Clock Speed: 100000 Hz (100kHz)
- Duty Cycle: 2:1 (标准模式忽略此项)
- Addressing Mode: 7-bit (PCF8574是7位地址)
- Dual Address: Disable
- General Call: Disable
- No Stretch: Disable (时钟延展禁用,简化处理)
-
生成代码与关键API: 使用HAL库的
HAL_I2C_Master_Transmit()函数来发送数据。这个函数会完整处理起始、地址发送、数据传输和停止条件。C// 向PCF8574(地址0x40)发送一个字节数据uint8_t data_to_send = 0xFF;HAL_StatusTypeDef status = HAL_I2C_Master_Transmit(&hi2c1, 0x40, &data_to_send, 1, 100);if(status != HAL_OK) {// 错误处理,例如重试或点亮错误LED}
避坑指南:
- 上拉电阻必须接:硬件IIC的SDA和SCL线必须连接外部上拉电阻(通常4.7kΩ到10kΩ),即使MCU引脚内部有上拉,也建议使用外部电阻,以保证总线电平的稳定。
- 超时处理:
HAL_I2C_Master_Transmit的最后一个参数是超时时间(毫秒)。如果总线卡死(例如从设备无应答),这个超时机制可以防止程序永远卡住。合理设置超时时间(如100ms)并检查返回值是良好的编程习惯。- 总线锁死(Bus Lock):这是硬件IIC最让人头疼的问题。通常发生在通信意外中断(如从设备掉电)时。解决方法包括:1) 在初始化IIC前,尝试发送几个时钟脉冲(SCL)来“解锁”总线;2) 切换IIC引脚为普通GPIO,模拟产生停止条件;3) 最粗暴但有效的方法是,在程序开始时先对IIC外设进行反初始化(DeInit),再重新初始化(Init)。
4. 驱动层构建:打通STM32、PCF8574与LCD1602
4.1 数据发送函数:将字节拆解为4位模式
PCF8574转接板使用4位数据模式连接LCD1602。这意味着我们不能直接发送一个8位字节到PCF8574,而需要将一个字节拆分成高4位(data & 0xF0)和低4位((data & 0x0F) << 4),分两次发送。
每次发送的“数据包”不仅仅包含这4位数据,还必须包含控制信号(RS, RW, E)的状态。我们可以定义一个函数,来组装并发送这个8位数据到PCF8574。
这个函数是驱动LCD1602的基石。它清晰地展示了如何将数据、控制信号和使能脉冲组合起来,通过PCF8574发送出去。
4.2 LCD1602初始化序列:不可省略的“开机密码”
LCD1602模块上电后处于一个不确定的状态,必须按照严格的规定序列进行初始化,才能进入4位数据模式正常工作。这个序列是固定的,任何步骤的遗漏或顺序错误都可能导致初始化失败(表现为显示乱码、方块或完全不显示)。
标准的4位模式初始化流程如下:
- 上电后等待:LCD内部电路需要时间稳定,至少等待40ms以上。
- 发送三次功能设置命令(0x30):这是为了确保模块进入8位模式,为切换到4位模式做准备。注意,此时还是按8位模式发送,但只发高4位(0x3),因为低4位还没连接。
- 发送功能设置命令,进入4位模式:发送
0x20(即0010 0000,高4位是0010)。这个命令告诉LCD,后续我们将使用4位数据总线。 - 正式设置显示模式:现在开始,所有命令/数据都通过
LCD_Write_4bit函数发送。发送命令0x28,表示:4位数据线、2行显示、5x8点阵字体。 - 设置显示开关控制:发送命令
0x0C,表示:显示开、光标关、光标闪烁关。 - 设置输入模式:发送命令
0x06,表示:读写后地址指针自动加1、文字不动。
对应的初始化代码:
注意事项:初始化序列中的延时(
Delay_ms和Delay_us)绝对不能随意缩短或省略。它们是为了满足LCD1602内部控制器(通常是HD44780或其兼容芯片)的时序要求。尤其是清屏命令(0x01)后的延时,手册要求至少1.52ms,建议给2ms以上,否则后续操作可能无效。
4.3 上层应用函数封装
在LCD_Write_4bit和初始化函数的基础上,我们可以封装出易于使用的上层函数,让驱动变得友好。
有了这些函数,在主程序中显示内容就变得非常简单:
5. 调试实战与常见问题排查
5.1 上电无任何显示(背光也不亮)
这是最令人沮丧的情况。请按照以下步骤排查:
- 检查电源:用万用表测量转接板VCC和GND之间的电压,确保在5V左右(部分LCD1602兼容3.3V,但5V最保险)。注意:STM32的IO口是3.3V电平,而PCF8574和LCD1602通常工作在5V。 确保你的转接板逻辑电平是兼容的(多数转接板已集成电平转换或PCF8574本身支持宽电压)。如果直接连接,最好使用带电平转换的转接板,或者确认你的PCF8574型号支持3.3V供电。
- 检查背光:很多LCD1602的背光是独立的。如果屏幕有显示但很暗,检查背光引脚(LED+和LED-)。通常LED+接VCC,LED-通过一个限流电阻(如100Ω)接地。有些转接板的背光由PCF8574的P3控制,需要在代码中将其置高(
control_bits |= LCD_BL_PIN;)。 - 检查IIC通信:这是问题的核心。使用逻辑分析仪或示波器连接到SDA和SCL线,观察上电后STM32是否有发起IIC通信(起始信号)。如果没有,检查:
- 软件IIC:GPIO初始化是否正确?引脚模式是否设置为推挽输出(SDA在输入/输出间切换)?延时函数是否有效?
- 硬件IIC:引脚是否配置为复用开漏(AF_OD)?外部上拉电阻(4.7kΩ)是否接上?地址是否正确(默认0x40)?
- 检查地址:如果IIC有起始信号和地址发送,但没有应答(ACK),大概率是地址错误。用万用表测量转接板上PCF8574芯片的A0, A1, A2引脚对地电平,确认地址。尝试扫描IIC总线上的设备地址。
5.2 屏幕显示乱码或黑色方块
如果屏幕有背光,但显示的是乱码、固定的黑色方块或者错位的字符,问题通常出在初始化序列或时序上。
- 初始化序列不完整或错误:这是最常见的原因。请严格对照本文第4.2节的初始化步骤和代码,确保每一步的指令和延时都准确无误。 特别是前三次发送
0x30和后续发送0x28的步骤。 - 时序过快:软件IIC的延时太短,或者硬件IIC速度设置过快(比如用了400kHz)。将速度降到100kHz或以下,软件IIC的延时适当加长(如高低电平延时增加到5us以上)。
- 对比度调节:LCD1602有一个VO引脚(对比度调节),通常接一个电位器到VCC和GND。如果对比度电压不合适,也可能显示全黑方块或浅到看不见。调节电位器,观察显示变化。
- 数据线接反或错位:虽然转接板固定了连接,但请再次确认代码中
LCD_D4_PIN到LCD_D7_PIN的位定义是否与转接板PCB上的丝印或原理图一致。例如,你的板子可能是P4接DB4,也可能是P7接DB4。如果接反,显示必然乱码。
5.3 显示内容错位或光标乱跳
- DDRAM地址设置错误:LCD1602内部有显示数据RAM(DDRAM)。第一行地址从
0x80开始,第二行从0xC0开始。LCD_Set_Cursor函数必须正确计算地址。 - 输入模式设置错误:初始化命令
0x06设置了“地址指针自动加一”和“显示不移位”。如果错设为0x04或0x05,会导致写入一个字符后光标左移或显示内容移位,看起来就像错位。 - 未及时清屏或复位:在程序开始频繁发送数据前,确保执行了清屏命令
0x01并给予了足够的延时。
5.4 使用逻辑分析仪进行深度调试
逻辑分析仪是调试IIC通信的利器。将通道连接到SDA和SCL,设置触发条件为IIC起始信号。抓取一次完整的初始化或写数据过程,你可以清晰地看到:
- **起始信号(S)和停止信号(P)**是否完整。
- **设备地址(0x40)**是否正确,后面是否跟了ACK(低电平)。
- 发送的每一个数据字节是否正确,特别是高4位和低4位拆分是否正确。
- E使能信号(在PCF8574的数据中)是否产生了正确的脉冲(先高后低)。
通过对比抓取到的波形和你代码中期望发送的数据,可以精准定位是底层IIC通信问题,还是上层LCD指令组装问题。
6. 项目优化与扩展思路
6.1 减少延时提升刷新率
在确保稳定的前提下,可以尝试优化延时。软件IIC中,IIC_Delay_us和LCD_Write_4bit中的Delay_us是主要优化对象。通过逻辑分析仪确认波形满足PCF8574和LCD1602的最小时序要求后,可以逐步减小延时。对于静态显示,速度不是问题;如果需要动态刷新(如显示不断变化的传感器数据),优化延时能带来更流畅的体验。
6.2 实现滚动显示与自定义字符
LCD1602支持创建最多8个5x8像素的自定义字符(CGRAM)。你可以通过发送特定命令(地址0x40起)和像素数据,来定义比如温度符号“℃”、箭头、简单图标等。这需要计算像素对应的字节数据,是一个很好的练习。
滚动显示可以通过软件控制实现。例如,先向DDRAM写入一段超出16个字符的字符串,然后通过发送“显示移位”命令(0x18左移或0x1C右移)来实现硬件滚动,效率更高。
6.3 多设备IIC总线与地址扩展
一个IIC总线可以挂载多个设备。如果你还需要连接其他IIC传感器(如OLED、BMP280等),只需将它们和PCF8574转接板一起挂到STM32的同一组IIC引脚上,并确保地址不冲突。如果地址冲突,可以通过修改PCF8574转接板上A0-A2的接法来改变其地址(从0x40改为例如0x42、0x44等),并在代码中相应修改设备地址常量。
6.4 移植到其他平台
这套驱动逻辑(PCF8574 + 4位模式初始化 + 字节拆分发送)是通用的。你可以轻松地将其移植到Arduino、ESP32、树莓派Pico等其他平台上,只需要替换底层的IIC通信函数(如Arduino的Wire库)和延时函数即可。这种跨平台性正是使用标准协议(IIC)和通用芯片(PCF8574)带来的巨大优势。