STM32F103硬件I2C驱动1602 LCD乱码问题深度解析
2026/9/13 10:50:08 网站建设 项目流程

简介:本资源是一套面向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_ClockSpeed50000(50kHz)必须 ≤100kHz;设为 100kHz 时,因 PCF8574T 响应延迟,易出现 ACK 失败;50kHz 提供更充裕的建立/保持时间
I2C_ModeI2C_Mode_I2C标准 I2C 模式,非 SMBus
I2C_DutyCycleI2C_DutyCycle_2高低电平比 2:1,适配 50kHz 时钟
I2C_OwnAddress10x00主机无需从机地址,设为 0
I2C_AckI2C_Ack_Enable必须开启,否则无法接收从机 ACK
I2C_AcknowledgedAddressI2C_AcknowledgedAddress_7bitPCF8574T 使用 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)
P0RS寄存器选择(0=指令,1=数据)bit0
P1RW读写选择(0=写,1=读)bit1
P2E使能脉冲(高电平有效)bit2
P3BL背光控制(1=开,0=关)bit3
P4–P7D4–D74 位数据总线(高 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 位数据线)。标准流程为:

  1. 等待 >15ms(上电延时);
  2. 发送0x03(高 4 位)三次(强制进入 4 位模式);
  3. 发送0x02(4 位模式、1 行、5×8 点阵);
  4. 发送0x28(4 位、2 行、5×8);
  5. 发送0x0C(显示开、光标关、不闪烁);
  6. 发送0x06(地址自增、无移屏);
  7. 发送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 >> 4ch & 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对应的控制字节应为0x09rs=0,rw=0,e=0,bl=1,data=0x01 → 0x00 | 0x00 | 0x00 | 0x08 | 0x10 = 0x18?错!重新计算:data_nibble=0x01<<40x10bl=10x08rs=0,rw=0,e=00x00,总和0x18;但实际发送0x01指令需先发高 4 位0x00,再发低 4 位0x01,故控制字节为0x080x11)。

提示:若逻辑分析仪显示地址正确但无 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。除硬件滤波外,软件层可采取:

  1. 重试机制封装:所有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; }
  2. 动态调整 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; }
  3. 总线恢复序列:当检测到 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)
全亮1kHz100%日间强光环境180mA
中亮1kHz50%室内常规使用90mA
微亮1kHz10%夜间鱼缸观察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()的封装,都是对车载环境中瞬态干扰的务实妥协。

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

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

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

立即咨询