简介:本资源是一套面向STM32F103初学者与嵌入式开发者的I2C接口1602液晶屏完整驱动工程,解决在Cortex-M3平台上快速实现I2C通信并驱动常用字符型LCD的核心问题,适用于课程设计、毕业设计及小型人机交互界面开发。压缩包共107个文件,含44个头文件(.h)定义寄存器、函数接口与LCD指令集,39个源文件(.c)实现GPIO复用配置、HAL_I2C底层初始化、1602初始化序列、清屏/光标定位/字符串显示等关键功能,以及10个汇编启动文件(.s)和Keil工程配置文件(.uvprojx/.uvoptx),整体仅393KB,轻量易集成。已有1679人学习下载,工程结构规范,包含keilkilll.bat一键清理脚本、hex可执行文件及调试配置(.dbgconf),预览可见system_stm32f10x.c、stm32f10x_rcc.c等标准外设库支撑文件,便于理解时钟树配置与外设依赖关系,是掌握STM32硬件I2C驱动与LCD应用的高实用性参考实现。
1. 为什么 STM32F103 驱动 I2C 接口的 1602 LCD 总是显示乱码或不亮?这不是硬件问题,而是时序与寄存器配置的双重陷阱
很多刚接触 STM32F103 的开发者,在用 GPIO 模拟 I2C 驱动 1602 LCD 成功后,转而使用硬件 I2C(如 I2C1)却频繁遇到:屏幕全黑、只亮背光、字符闪烁、首行显示异常、写入后无响应等现象。根本原因不在接线错误或电平不匹配——而是对 HD44780 控制器的初始化时序要求、I2C 从机地址映射规则、以及 STM32F103 标准外设库(v3.5.0)中 I2C 模块的 ACK/NACK 时序控制逻辑缺乏深度理解。本篇聚焦于STM32F103 + 硬件 I2C1 + PCF8574T 扩展芯片驱动 1602 字符型 LCD这一最典型组合,不依赖 HAL 库,基于标准外设库 v3.50 实现完整、可复现、带时序校验的驱动程序。适用于车载仪表辅助显示、鱼缸温控屏、工业人机界面等对稳定性与资源占用有明确要求的嵌入式场景,尤其适合已搭建好 STM32F103 最小系统(含 8MHz 晶振、3.3V 供电、BOOT0=0)并希望跳过模拟 I2C 性能瓶颈的工程师。
2. 从 HD44780 初始化流程反推 I2C 通信约束:为什么必须用 PCF8574T 而非直连?
2.1 1602 LCD 的本质是 HD44780 控制器 + 字符点阵屏,I2C 接口需桥接芯片
1602 LCD 模块本身不具备原生 I2C 接口。市面常见“I2C 1602”模块实为在传统 16 引脚并行接口(RS、RW、E、D0–D7)基础上,集成了PCF8574T(或兼容型号如 PCA8574)作为 I/O 扩展芯片。该芯片将 MCU 的 I2C 信号转换为并行控制信号,驱动 HD44780。其关键特性包括:
- 8 位准双向 I/O 口(P0–P7),默认高电平上拉;
- I2C 从机地址由 A0/A1/A2 引脚电平决定,默认地址为 0x27(7 位地址)或 0x4E(8 位写地址);
- 内部集成上拉电阻,但实际应用中仍需在 SDA/SCL 线加 4.7kΩ 外部上拉(尤其在长线或多设备时);
- 支持 100kHz 标准模式 I2C,不支持高速模式(400kHz)—— 这直接限制了 STM32F103 I2C1 的时钟配置上限。
提示:若使用地址跳线帽为 A0=A1=A2=HIGH 的模块,实际地址为 0x3F(7 位),对应 8 位写地址为 0x7E。务必用逻辑分析仪或
I2C_Scan工具确认真实地址,否则所有通信均失败。
2.2 HD44780 初始化时序是 I2C 驱动成败的核心门槛
HD44780 上电后需严格遵循“Function Set → Display ON/OFF → Entry Mode Set”三步初始化序列,且每步之间需满足最小延时(tAS ≥ 40ns,tPW ≥ 450ns,tCYCLE ≥ 1ms)。当通过 PCF8574T 间接控制时,这些时序被拆解为:
- E(使能)引脚脉冲:由 PCF8574T 的某一位(通常 P6)控制,需在数据稳定后产生一个 >450ns 的高电平脉冲;
- RS(寄存器选择)与 RW(读写)引脚:分别由 P0、P1 控制,决定写指令(RS=0)或写数据(RS=1),RW 始终为 0(写模式);
- D4–D7 数据线:4 位模式下仅使用 P4–P7,需分两次发送(高 4 位 + 低 4 位);8 位模式需全部 8 位,但会增加 I2C 传输字节数和时序复杂度。
因此,I2C 驱动的本质,是向 PCF8574T 发送一组按特定顺序排列的 8 位字节,每个字节的每一位精确对应 P0–P7 的电平状态,并确保 E 引脚在正确时刻翻转。任何一次 E 脉冲缺失、RS 错位或延时不足,都会导致 HD44780 进入未定义状态,表现为乱码或黑屏。
2.3 STM32F103 I2C1 硬件模块的关键配置参数解析
在标准外设库 v3.50 中,I2C 初始化依赖I2C_InitTypeDef结构体。针对 1602 场景,以下参数不可随意设置:
| 参数 | 推荐值 | 说明 |
|---|---|---|
I2C_ClockSpeed | 50000(50kHz) | 必须 ≤100kHz;设为 100kHz 时,因 PCF8574T 响应延迟,易出现 ACK 失败;50kHz 提供更充裕的建立/保持时间 |
I2C_Mode | I2C_Mode_I2C | 标准 I2C 模式,非 SMBus |
I2C_DutyCycle | I2C_DutyCycle_2 | 高低电平比 2:1,适配 50kHz 时钟 |
I2C_OwnAddress1 | 0x00 | 主机无需从机地址,设为 0 |
I2C_Ack | I2C_Ack_Enable | 必须开启,否则无法接收从机 ACK |
I2C_AcknowledgedAddress | I2C_AcknowledgedAddress_7bit | PCF8574T 使用 7 位地址 |
// 示例:I2C1 初始化代码(基于 STM32F103RCT6,PB6/PB7) void I2C1_GPIO_Config(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏输出 GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStruct); } void I2C1_Config(void) { I2C_InitTypeDef I2C_InitStruct; I2C_DeInit(I2C1); I2C_StructInit(&I2C_InitStruct); I2C_InitStruct.I2C_ClockSpeed = 50000; // 关键!50kHz I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; I2C_InitStruct.I2C_OwnAddress1 = 0x00; I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; I2C_Init(I2C1, &I2C_InitStruct); I2C_Cmd(I2C1, ENABLE); }注意:
GPIO_Mode_AF_OD(复用开漏)是 I2C 物理层强制要求,普通推挽模式会导致总线冲突。若使用 STM32F103 最小系统,务必确认 PB6(SCL)、PB7(SDA)已正确连接至模块对应引脚,且模块 VCC 为 5V 时需加电平转换(如 TXS0102),3.3V 系统则需确认模块支持 3.3V 逻辑电平。
3. 构建可复用的 I2C-1602 驱动层:从单字节写入到整屏控制
3.1 封装底层 I2C 通信函数:确保每次传输都校验 ACK
所有上层操作均基于I2C_Write_Byte(uint8_t addr, uint8_t data)函数。该函数需完成:起始信号 → 发送从机地址(写)→ 等待 ACK → 发送数据 → 等待 ACK → 停止信号。关键在于超时机制与 ACK 检查,避免死锁。
// I2C 写单字节,返回 1 表示成功,0 表示失败(NACK 或超时) uint8_t I2C_Write_Byte(uint8_t addr, uint8_t data) { uint16_t timeout = 0xFFFF; // 1. 发送起始条件 I2C_GenerateSTART(I2C1, ENABLE); while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) && timeout--); if (timeout == 0) return 0; // 2. 发送从机地址(7位地址左移1位,最低位置0表示写) I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter); timeout = 0xFFFF; while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) && timeout--); if (timeout == 0) return 0; // 3. 发送数据字节 I2C_SendData(I2C1, data); timeout = 0xFFFF; while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED) && timeout--); if (timeout == 0) return 0; // 4. 发送停止条件 I2C_GenerateSTOP(I2C1, ENABLE); return 1; }逻辑说明:
I2C_CheckEvent()是标准库核心函数,它轮询 I2C_SR1 寄存器的事件标志位。I2C_EVENT_MASTER_BYTE_TRANSMITTED表示数据已发出且收到从机 ACK。若超时(timeout == 0),说明从机未响应(地址错误、电源异常或硬件断开),函数立即返回失败,防止后续操作无效。
3.2 定义 PCF8574T 引脚映射与控制字节生成规则
PCF8574T 的 8 位输出(P0–P7)需映射到 1602 的控制信号。典型接线如下(以常见模块为例):
| PCF8574T 引脚 | 连接 1602 引脚 | 功能 | 位权(bit) |
|---|---|---|---|
| P0 | RS | 寄存器选择(0=指令,1=数据) | bit0 |
| P1 | RW | 读写选择(0=写,1=读) | bit1 |
| P2 | E | 使能脉冲(高电平有效) | bit2 |
| P3 | BL | 背光控制(1=开,0=关) | bit3 |
| P4–P7 | D4–D7 | 4 位数据总线(高 4 位) | bit4–bit7 |
因此,一个控制字节byte的构造公式为:
byte = (rs << 0) | (rw << 1) | (e << 2) | (bl << 3) | (data_nibble << 4)其中data_nibble是 4 位数据(0x0–0xF),rs/rw/e/bl为 0 或 1。
// 生成 PCF8574T 控制字节:rs, rw, e, bl 为 0/1,data_nibble 为 4 位数据 uint8_t PCF8574_Write_Byte(uint8_t rs, uint8_t rw, uint8_t e, uint8_t bl, uint8_t data_nibble) { uint8_t byte = 0; byte |= (rs & 0x01) << 0; byte |= (rw & 0x01) << 1; byte |= (e & 0x01) << 2; byte |= (bl & 0x01) << 3; byte |= (data_nibble & 0x0F) << 4; return byte; } // 发送一个控制字节(含 E 脉冲) void LCD_Send_Byte(uint8_t addr, uint8_t rs, uint8_t data_nibble, uint8_t bl) { uint8_t byte; // 第一步:E=0,设置 RS/RW/BL/数据,但 E 为 0(不触发) byte = PCF8574_Write_Byte(rs, 0, 0, bl, data_nibble); I2C_Write_Byte(addr, byte); // 第二步:E=1,产生上升沿,锁存数据 byte = PCF8574_Write_Byte(rs, 0, 1, bl, data_nibble); I2C_Write_Byte(addr, byte); // 第三步:E=0,产生下降沿,完成脉冲(HD44780 要求 E 高电平宽度 ≥ 450ns) byte = PCF8574_Write_Byte(rs, 0, 0, bl, data_nibble); I2C_Write_Byte(addr, byte); // 延时:确保 E 下降沿后,HD44780 完成内部操作(tCYCLE ≥ 1ms) Delay_ms(1); }参数说明:
LCD_Send_Byte()中rs=0表示发送指令(如清屏、光标设置),rs=1表示发送字符数据;bl=1开启背光;data_nibble是待发送的 4 位数据。三次I2C_Write_Byte()构成一个完整的 E 脉冲周期,中间无额外延时,依赖 I2C 传输本身的时序保障。
3.3 实现 HD44780 初始化序列与基础命令封装
初始化必须严格按 4 位模式执行(因 PCF8574T 仅暴露 4 位数据线)。标准流程为:
- 等待 >15ms(上电延时);
- 发送
0x03(高 4 位)三次(强制进入 4 位模式); - 发送
0x02(4 位模式、1 行、5×8 点阵); - 发送
0x28(4 位、2 行、5×8); - 发送
0x0C(显示开、光标关、不闪烁); - 发送
0x06(地址自增、无移屏); - 发送
0x01(清屏,耗时约 1.64ms)。
// LCD 初始化(addr 为 PCF8574T 7 位地址,如 0x27) void LCD_Init(uint8_t addr) { // 上电延时 >15ms Delay_ms(30); // 步骤1-3:三次发送 0x03(高4位),强制进入4位模式 LCD_Send_Byte(addr, 0, 0x03, 1); // rs=0, data=0x03, bl=1 Delay_ms(5); LCD_Send_Byte(addr, 0, 0x03, 1); Delay_ms(5); LCD_Send_Byte(addr, 0, 0x03, 1); Delay_ms(5); // 步骤4:发送 0x02(4位模式指令) LCD_Send_Byte(addr, 0, 0x02, 1); // 步骤5:Function Set (0x28) -> 4-bit, 2-line, 5x8 dots LCD_Send_Byte(addr, 0, 0x02, 1); // 高4位 LCD_Send_Byte(addr, 0, 0x08, 1); // 低4位 Delay_ms(1); // 步骤6:Display ON/OFF (0x0C) -> Display ON, Cursor OFF, Blink OFF LCD_Send_Byte(addr, 0, 0x00, 1); LCD_Send_Byte(addr, 0, 0x0C, 1); Delay_ms(1); // 步骤7:Entry Mode Set (0x06) -> Increment, No Shift LCD_Send_Byte(addr, 0, 0x00, 1); LCD_Send_Byte(addr, 0, 0x06, 1); Delay_ms(1); // 步骤8:Clear Display (0x01),耗时最长,必须延时 LCD_Send_Byte(addr, 0, 0x00, 1); LCD_Send_Byte(addr, 0, 0x01, 1); Delay_ms(2); } // 清屏命令 void LCD_Clear(uint8_t addr) { LCD_Send_Byte(addr, 0, 0x00, 1); LCD_Send_Byte(addr, 0, 0x01, 1); Delay_ms(2); } // 设置光标位置(line: 1 or 2, pos: 0-15) void LCD_Set_Pos(uint8_t addr, uint8_t line, uint8_t pos) { uint8_t addr_offset = (line == 1) ? 0x00 : 0x40; // 第二行起始地址为 0x40 uint8_t ddram_addr = addr_offset + pos; LCD_Send_Byte(addr, 0, 0x00, 1); // 高4位 LCD_Send_Byte(addr, 0, 0x80 | (ddram_addr >> 4), 1); // 设置地址指令高4位 LCD_Send_Byte(addr, 0, 0x00, 1); LCD_Send_Byte(addr, 0, ddram_addr & 0x0F, 1); // 低4位 }逻辑说明:
LCD_Set_Pos()中0x80 | (ddram_addr >> 4)是 HD44780 的“设置 DDRAM 地址”指令(0x80 是指令基址),需分两次发送高 4 位和低 4 位。Delay_ms(2)在清屏后是硬性要求,因 HD44780 内部清屏操作需 1.64ms,不足则后续写入可能丢失。
4. 实战:在 STM32F103 上显示温度与时间,验证驱动稳定性
4.1 构建主循环:整合初始化、字符串显示与动态刷新
假设已实现Get_Temperature()(返回 float)和Get_Time_String()(返回 "HH:MM:SS" 字符串),主函数结构如下:
int main(void) { SystemInit(); // 设置系统时钟为 72MHz Delay_Init(); // 初始化 SysTick 延时 I2C1_GPIO_Config(); I2C1_Config(); // 初始化 LCD,地址 0x27(常见模块) LCD_Init(0x27); // 显示静态标题 LCD_Set_Pos(0x27, 1, 0); LCD_Write_String(0x27, "STM32F103-I2C"); LCD_Set_Pos(0x27, 2, 0); LCD_Write_String(0x27, "Temp: C"); while (1) { float temp = Get_Temperature(); char temp_str[8]; sprintf(temp_str, "%.1f", temp); // 更新第二行温度值(覆盖原位置) LCD_Set_Pos(0x27, 2, 6); // 光标移到 "Temp: " 后 LCD_Write_String(0x27, temp_str); // 可选:显示时间(需 RTC 或软件计时器) char time_str[9]; Get_Time_String(time_str); LCD_Set_Pos(0x27, 1, 10); // 第一行末尾 LCD_Write_String(0x27, time_str); Delay_ms(500); // 刷新间隔 } }4.2 实现字符串写入与中文字符支持(GB2312 编码)
标准 1602 仅支持 ASCII 和自定义 CGRAM 字符。若需显示中文(如“温度”),需预先将 GB2312 编码的汉字点阵写入 CGRAM。此处提供 ASCII 字符串写入函数:
// 向当前光标位置写入字符串(自动处理 '\0' 终止) void LCD_Write_String(uint8_t addr, char *str) { while (*str != '\0') { LCD_Send_Byte(addr, 1, *str >> 4, 1); // 高4位 LCD_Send_Byte(addr, 1, *str & 0x0F, 1); // 低4位 str++; } } // 写入单个 ASCII 字符(0x20–0x7E) void LCD_Write_Char(uint8_t addr, char ch) { LCD_Send_Byte(addr, 1, ch >> 4, 1); LCD_Send_Byte(addr, 1, ch & 0x0F, 1); }注意:
LCD_Write_String()中ch >> 4和ch & 0x0F直接提取 ASCII 字符的高 4 位和低 4 位,符合 4 位数据总线传输要求。若字符串含非 ASCII 字符(如中文),需先查 GB2312 码表,再将对应点阵数据写入 CGRAM(本篇不展开,因涉及 64 字节 CGRAM 分配与自定义字模)。
4.3 关键调试技巧:用逻辑分析仪捕获 I2C 波形验证时序
当显示异常时,最高效的方法是用 Saleae Logic 或类似工具抓取 SDA/SCL 波形。重点关注:
- 起始/停止信号:是否符合 I2C 规范(SCL 高时 SDA 下降为 START,上升为 STOP);
- 从机地址:第 1 字节是否为
0x4E(0x27<<1)或0x7E(0x3F<<1); - E 脉冲时序:在 PCF8574T 的响应波形中,能否看到 P2(E)引脚的清晰高电平脉冲(宽度 >450ns);
- ACK 信号:每字节后第 9 个时钟周期,SDA 是否被从机拉低(ACK);
- 数据字节内容:是否与
PCF8574_Write_Byte()计算结果一致(如清屏指令0x01对应的控制字节应为0x09:rs=0,rw=0,e=0,bl=1,data=0x01 → 0x00 | 0x00 | 0x00 | 0x08 | 0x10 = 0x18?错!重新计算:data_nibble=0x01→<<4得0x10,bl=1→0x08,rs=0,rw=0,e=0→0x00,总和0x18;但实际发送0x01指令需先发高 4 位0x00,再发低 4 位0x01,故控制字节为0x08和0x11)。
提示:若逻辑分析仪显示地址正确但无 ACK,检查 PCF8574T 供电(VDD=5V 或 3.3V)、GND 是否共地、SCL/SDA 上拉电阻是否安装(4.7kΩ)、模块地址跳线是否与代码匹配。万用表测 PCF8574T 的 SDA/SCL 引脚对地电压,正常应为 3.3V(上拉)或 0V(被拉低),若为 1.8V 说明上拉不足或短路。
5. 进阶优化与抗干扰设计:让 I2C-1602 在车载与鱼缸环境中稳定运行
5.1 降低 I2C 通信失败率的 3 个硬核技巧
在电磁环境复杂的车载仪表或鱼缸水泵附近,I2C 总线易受干扰导致 NACK。除硬件滤波外,软件层可采取:
重试机制封装:所有
I2C_Write_Byte()调用均包裹重试逻辑,最多尝试 3 次。uint8_t I2C_Write_Byte_Retry(uint8_t addr, uint8_t data, uint8_t retry_times) { uint8_t i; for (i = 0; i < retry_times; i++) { if (I2C_Write_Byte(addr, data)) return 1; Delay_us(100); // 两次尝试间微小延时 } return 0; }动态调整 I2C 时钟:在初始化失败时,自动降频至 25kHz 并重试。
uint8_t LCD_Init_Adaptive(uint8_t addr) { uint32_t speeds[] = {50000, 25000, 10000}; uint8_t i; for (i = 0; i < 3; i++) { I2C_DeInit(I2C1); I2C_InitStruct.I2C_ClockSpeed = speeds[i]; I2C_Init(I2C1, &I2C_InitStruct); I2C_Cmd(I2C1, ENABLE); if (I2C_Write_Byte_Retry(addr, 0x00, 3)) // 发送任意字节测试 { LCD_Init(addr); // 成功后执行完整初始化 return 1; } } return 0; }总线恢复序列:当检测到 SCL 被从机拉低卡死时,用 GPIO 模拟 9 个 SCL 脉冲强制释放。
void I2C_Recover_Bus(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6; // SCL on PB6 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_SetBits(GPIOB, GPIO_Pin_6); for (uint8_t i = 0; i < 9; i++) { GPIO_ResetBits(GPIOB, GPIO_Pin_6); Delay_us(5); GPIO_SetBits(GPIOB, GPIO_Pin_6); Delay_us(5); } // 恢复 I2C 复用功能 GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD; GPIO_Init(GPIOB, &GPIO_InitStruct); }
5.2 背光 PWM 控制与功耗优化表格
为适配车载环境(12V 供电)或鱼缸夜间模式,需独立控制背光亮度。PCF8574T 的 P3(BL)引脚可接 MOSFET 控制 LED 电流,但更优方案是用 STM32 的 TIM3_CH2(PA7)输出 PWM:
| 背光模式 | PWM 频率 | 占空比 | 适用场景 | 电流估算(5V LED) |
|---|---|---|---|---|
| 全亮 | 1kHz | 100% | 日间强光环境 | 180mA |
| 中亮 | 1kHz | 50% | 室内常规使用 | 90mA |
| 微亮 | 1kHz | 10% | 夜间鱼缸观察 | 18mA |
| 关闭 | — | 0% | 休眠省电 | 0mA |
// 初始化 TIM3 PWM 控制背光(PA7) void TIM3_PWM_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; TIM_TimeBaseStruct.TIM_Period = 999; // 1kHz 频率(72MHz / (999+1) = 72kHz → 72kHz/72 = 1kHz) TIM_TimeBaseStruct.TIM_Prescaler = 71; // PSC=71 → 72MHz/(71+1)=1MHz 计数频率 TIM_TimeBaseStruct.TIM_ClockDivision = 0; TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStruct); TIM_OCInitTypeDef TIM_OCStruct; TIM_OCStruct.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCStruct.TIM_OutputState = TIM_OutputState_Enable; TIM_OCStruct.TIM_Pulse = 500; // 初始占空比 50%(500/1000) TIM_OCStruct.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC2Init(TIM3, &TIM_OCStruct); TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM3, ENABLE); TIM_Cmd(TIM3, ENABLE); } // 设置背光亮度(0-100) void LCD_Set_Backlight(uint8_t percent) { uint16_t pulse = (uint16_t)(percent * 10); // 0-100 → 0-1000 TIM_SetCompare2(TIM3, pulse); }逻辑说明:
TIM_SetCompare2()直接修改比较寄存器 CCR2,改变 PA7 输出的 PWM 占空比。此方案比用 PCF8574T 的 P3 引脚开关控制更精细,且不占用 I2C 总线带宽。在LCD_Init()中调用TIM3_PWM_Init(),并在需要时调用LCD_Set_Backlight(30)设置 30% 亮度。
I2C 通信的可靠性最终取决于对 HD44780 数据手册中时序参数的敬畏,而非对库函数的盲目信任。每一次Delay_ms(1)的存在,都是对 tCYCLE ≥ 1ms 这一物理定律的服从;每一个I2C_Write_Byte_Retry()的封装,都是对车载环境中瞬态干扰的务实妥协。
本文还有配套的精品资源,点击获取