STM32 I2C 通信 HAL 库踩坑记录
问题HAL 库的 I2C 用起来感觉比 SPI 麻烦——有时候卡死在HAL_I2C_Master_Transmit有时候第一次通信正常第二次就超时。下面是最常见的几个坑和对应的解决办法。坑一总线锁死SCL 被拉低I2C 通信中断时比如刚发完地址突然复位从机可能把 SCL 或 SDA锁死在低电平后续所有 I2C 操作都超时。现象第一次调用HAL_I2C_Master_Transmit就超时返回。解决——复位 I2C 总线void I2C_ResetBus(I2C_HandleTypeDef *hi2c) { GPIO_InitTypeDef gpio {0}; // 将 SCL 和 SDA 临时设成普通 GPIO 输出 gpio.Mode GPIO_MODE_OUTPUT_OD; gpio.Pull GPIO_PULLUP; gpio.Speed GPIO_SPEED_FREQ_MEDIUM; // SCL 引脚查你的原理图这里是 PB6 gpio.Pin GPIO_PIN_6; HAL_GPIO_Init(GPIOB, gpio); // SDA 引脚PB7 gpio.Pin GPIO_PIN_7; HAL_GPIO_Init(GPIOB, gpio); // 产生 9 个时钟脉冲让从机释放总线 for (int i 0; i 9; i) { HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET); // SCL Low HAL_Delay(1); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); // SCL High HAL_Delay(1); } // 产生 STOP 条件SDA 在 SCL 高时上升沿 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); HAL_Delay(1); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_RESET); // SDA Low HAL_Delay(1); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); // SDA High → STOP // 重新初始化 I2C 外设 HAL_I2C_DeInit(hi2c); HAL_I2C_Init(hi2c); }坑二未使用超时参数// ❌ 超时设太大卡死在这里 10 秒 HAL_I2C_Master_Transmit(hi2c1, addr, data, len, 10000); // ✅ 设一个合理的超时超时就返回错误 if (HAL_I2C_Master_Transmit(hi2c1, addr, data, len, 100) ! HAL_OK) { printf(I2C Error!\r\n); I2C_ResetBus(hi2c1); // 自动恢复 }坑三地址左移I2C 的 7 位地址和 HAL 库传的地址关系// 假设器件地址是 0x3C7 位 // HAL 库的 addr 参数要左移 1 位即 0x78 // 但 CubeMX 生成的 MX_I2C1_Init 用的是 HAL_I2C_Init 结构体中的 AddressingMode // 大部分示例直接传 0x78 而不是 0x3C // 推荐直接传左移后的地址 #define DEV_ADDR (0x3C 1) // 0x78 HAL_I2C_Master_Transmit(hi2c1, DEV_ADDR, data, len, 100);坑四I2C 时序配置不对I2C 时钟 APB1 时钟42MHz/ 分频系数。重要F407 使用的是旧版 I2C 外设寄存器叫 CCR 和 TRISE不是 TimingTiming 是 F0/F3/F7/L0/L4 等新版 I2C 外设的寄存器名。最可靠的方式是让 HAL 库的I2C_Init()自动计算而不是手动改寄存器。I2C_InitTypeDef I2C_InitStruct; I2C_InitStruct.ClockSpeed 400000; // 快速模式 400kHz I2C_InitStruct.DutyCycle I2C_DUTYCYCLE_2; // DUTY0 或 DUTY1 // ... 其他参数 ... HAL_I2C_Init(hi2c1); // HAL 自动计算 CCR/TRISE手动计算公式仅供理解CCR快速模式 DUTY0CCR 42MHz / (3 × 400kHz) ≈ 35CCR快速模式 DUTY1CCR 42MHz / (25 × 400kHz) ≈ 4TRISETRISE (最大上升时间 / T_PCLK1) 1100kHz 模式 TRISE≈43400kHz 模式 TRISE≈13CubeMX 中选择 Standard Mode / Fast Mode 它会自动算。坑五没有上拉电阻I2C 的 SCL 和 SDA 是开漏输出必须外部接上拉电阻4.7kΩ 典型。如果你的模块是单独买的通常自带。但如果用杜邦线直连芯片引脚就可能忘记加上拉。现象有时能通信有时不能波形上升沿很缓。测用示波器看 SCL/SDA 的上升沿——如果 1μs说明上拉电阻太大或没有。// 紧急补救STM32 内部有上拉电阻约 40kΩ // 在 CubeMX GPIO 设置中把 SCL/SDA 的 Pull 改成 Pull-up // 但 40kΩ 只适合低速短距离标准还是用外部 4.7kΩ总结坑一句话解决总线锁死软件复位 I2C 总线超时卡死设短超时 错误处理地址不对7 位地址左移 1 位时序不对用 CubeMX 自动计算波形不好外加 4.7kΩ 上拉电阻