问题现象
项目里用了两颗 INA226(U3 和 U7)做电流检测,驱动是自己写的软件 I2C。测试时发现一个奇怪的现象:
不调用
INA226_Scan()直接初始化时,INA226_Init_U3()/INA226_Init_U7()读取 Manufacturer ID 返回0xFFFF,芯片识别失败。必须先执行一次
INA226_Scan(),后续的 Init 和读取才能正常工作。
问题来了:Scan 和 Init 明明是两回事,为什么必须先 Scan 才能 Init?
根因分析
1. 两套 I2C 初始化逻辑
系统中存在两套 I2C 初始化代码,都操作 PB6/PB7,但配置的模式完全不同:
| 初始化来源 | 配置模式 | 目的 |
|---|---|---|
wk_i2c1_init()(src/wk_i2c.c) |
GPIO_MODE_MUX(复用模式) |
硬件 I2C1 外设 |
swi2c_gpio_init()(drivers/ina226/ina226.c) |
GPIO_MODE_OUTPUT(通用输出) |
软件 I2C bit-bang |
wk_i2c1_init() 的代码:
|
|
swi2c_gpio_init() 的代码:
|
|
2. 软件 I2C 到底怎么操作引脚?
当前驱动使用软件 I2C(INA226_USE_HW_I2C = 0),通过直接操作 GPIO 寄存器实现时序:
|
|
scr(set output data register):写 1 输出高电平clr(clear output data register):写 1 输出低电平idt(input data register):读取引脚输入电平
关键发现:scr/clr/idt 这些寄存器只有在引脚处于 通用 GPIO 模式 时才有效。当引脚配置为 复用模式(MUX) 时,GPIO 寄存器操作无法影响引脚电平,软件 I2C 的 START、STOP、ACK/NACK 全部失效。
3. 为什么先 Scan 就能工作?
把两条路径画出来就清楚了:
正常路径(有 Scan):
|
|
问题路径(无 Scan):
|
|
真相:INA226_Scan() 内部调用了 swi2c_gpio_init(),把 PB6/PB7 从 MUX 模式切回了 OUTPUT 模式。所以先 Scan 再 Init 是"歪打正着",真正起作用的是 swi2c_gpio_init()。
修复方案
思路
把 swi2c_gpio_init() 的调用从 INA226_Scan() 中提取出来,封装为公共函数,确保 任何 需要进行软件 I2C 通信的地方都先初始化 GPIO。
代码改动
1. 新增 INA226_Swi2cInit()
|
|
2. 三个调用点全部加上
INA226_Scan()(替换原有的直接调用):
|
|
INA226_Init_U3()(新增):
|
|
INA226_Init_U7()(新增):
|
|
修复后的路径
|
|
现在不需要先 Scan 也能直接 Init 了。
延伸思考
硬件 I2C 会碰到这个问题吗?
不会。如果 INA226_USE_HW_I2C = 1,驱动使用 AT32 I2C1 外设,通过标准库函数(i2c_start_generate()、i2c_data_send() 等)通信。此时 PB6/PB7 保持 MUX 模式即可,硬件外设自动控制引脚,不存在 GPIO 模式切换问题。
软件 I2C 和硬件 I2C 能混用吗?
不能同时工作。 两套机制共用 PB6/PB7,软件 I2C 要求 OUTPUT 模式,硬件 I2C 要求 MUX 模式。切换时必须先调用对应的 GPIO 初始化函数。
当前项目使用软件 I2C(INA226_USE_HW_I2C = 0),因此 wk_i2c1_init() 实际上只起到"总线解锁"的作用(发送 9 个 SCL 脉冲),后续 INA226 驱动完全走软件 I2C。
怎么避免以后再踩这个坑?
核心原则:软件 I2C 的读写函数自身应该保证 GPIO 模式正确,不能依赖外部先调用某个初始化函数。
更彻底的修复方式:在 i2c1_write() / i2c1_read() 的开头调用 swi2c_gpio_init(),这样任何 I2C 操作都自动确保 GPIO 模式正确。GPIO 初始化本身很快(几条寄存器写),实际性能影响可忽略。
总结
| 问题 | 原因 | 解决 |
|---|---|---|
读取返回 0xFFFF |
PB6/PB7 处于 MUX 模式,软件 I2C 寄存器操作无效 | 在 Init 前调用 swi2c_gpio_init() 切回 OUTPUT 模式 |
| 必须先 Scan 才能 Init | INA226_Scan() 内部调了 swi2c_gpio_init() |
提取为公共函数 INA226_Swi2cInit(),所有 I2C 操作前都调用 |
这个 bug 最坑的地方在于:它不是每次必现。如果你的代码恰好先调了 Scan,一切正常;一旦跳过 Scan,问题才暴露。这种"隐式依赖"是嵌入式开发中最容易踩的坑之一。
环境:AT32F403A + INA226 + 软件 I2C (bit-bang)