4. RK I2C 驱动调试:从适配器到设备,再到时序与故障排查

I2C 总线在嵌入式系统里太常见了。传感器、触摸屏、PMIC、EEPROM……几乎每个项目都能碰到。瑞芯微 RK 系列平台的 I2C 控制器本身不复杂,但一旦出问题,排查起来往往让人头疼。

我个人习惯把 I2C 调试分成四个层面:适配器驱动、设备驱动、时序分析、故障排查。前两个是写代码,后两个是拿仪器和逻辑分析仪找问题。今天咱们就把这四个层面串起来讲。

核心观点:I2C 调试的本质是“协议对、时序对、地址对”。三者缺一不可。

4.1 I2C 适配器驱动:内核帮你做了大部分事

在 RK 平台上,I2C 适配器驱动通常由芯片厂商(Rockchip)提供,内核里已经写好了。你打开 drivers/i2c/busses/i2c-rk3x.c 就能看到。

适配器驱动负责的事情很明确:

  • 初始化 I2C 控制器的寄存器
  • 配置时钟频率(100kHz、400kHz 等)
  • 实现 master_xfer 回调函数
  • 处理中断和 DMA 传输

我建议你拿到一个新板子,第一件事就是确认适配器驱动是否正常加载。方法很简单:

# 查看 I2C 总线是否注册成功
ls /dev/i2c-*
# 或者
i2cdetect -l

# 查看具体总线的设备树节点
cat /sys/bus/i2c/devices/i2c-0/name

如果 /dev/i2c-* 下面没有设备节点,说明适配器驱动没起来。这时候先别急着写设备驱动,回头检查设备树和时钟配置。

小技巧:RK 平台的 I2C 时钟源通常是 PCLK,频率不对会导致时序完全乱掉。我遇到过好几次,设备树里 clock-frequency 配了 400kHz,但实际 PCLK 只有 24MHz,算出来的分频系数根本不对。

4.2 I2C 设备驱动:你的核心战场

适配器驱动是基础设施,设备驱动才是你真正要写的。以 RK3588 上挂载一个温度传感器为例,设备驱动通常长这样:

static const struct i2c_device_id temp_sensor_id[] = {
    { "tmp117", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, temp_sensor_id);

static const struct of_device_id temp_sensor_of_match[] = {
    { .compatible = "ti,tmp117" },
    { }
};
MODULE_DEVICE_TABLE(of, temp_sensor_of_match);

static struct i2c_driver temp_sensor_driver = {
    .driver = {
        .name = "tmp117",
        .of_match_table = temp_sensor_of_match,
    },
    .probe = temp_sensor_probe,
    .id_table = temp_sensor_id,
};

module_i2c_driver(temp_sensor_driver);

这里有几个关键点:

  • compatible 字符串必须和设备树里一致,大小写都不能错
  • probe 函数里要完成设备初始化、注册 hwmon 或 input 子系统
  • id_table 用于非设备树方式匹配,建议也写上

我见过最坑的问题是什么?设备树里 reg 地址写错了。比如传感器地址是 0x48,你写成了 0x49,内核匹配不到设备,probe 函数根本不会调用。

注意:I2C 设备地址是 7 位,但设备树里 reg 属性写的是 7 位地址左移 1 位后的值?不对!内核里 reg 直接写 7 位地址,比如 0x48。别被某些旧文档误导了。

4.3 I2C 时序分析与逻辑分析仪抓包

写代码只是第一步。代码写完了,设备不工作,这时候就要上逻辑分析仪了。

我个人的调试流程是这样的:

  1. 先看波形:用逻辑分析仪抓 SCL 和 SDA 两根线
  2. 检查起始条件:SCL 高电平时 SDA 从高到低跳变
  3. 检查地址字节:7 位地址 + R/W 位,看是否和预期一致
  4. 检查 ACK/NACK:第 9 个 SCL 时钟,SDA 是否被从设备拉低
  5. 检查数据字节:每个字节的 MSB 先发,数据是否正确
  6. 检查停止条件:SCL 高电平时 SDA 从低到高跳变

下面这张图展示了 I2C 一次完整传输的时序结构:

I2C 单次传输时序结构 SCL SDA S 起始 地址字节 (7位地址 + R/W) MSB → LSB 8 个 SCL 时钟 ACK 第9时钟 数据字节 (8位数据) MSB → LSB 8 个 SCL 时钟 ACK 第9时钟 P 停止 时序关键检查点: • 起始条件:SCL 高时 SDA 从高→低 • 地址字节:7位地址 + R/W,MSB 先发 • ACK/NACK:第9个SCL时钟,从设备拉低SDA为ACK • 停止条件:SCL 高时 SDA 从低→高

逻辑分析仪抓包时,我习惯用 Saleae 或者开源的 PulseView。采样率设到 4MHz 以上,触发条件设成 SCL 下降沿。抓到的波形直接解码,一眼就能看出问题。

4.4 常见 ACK/NACK 故障排查

ACK/NACK 问题是 I2C 调试里最常见的故障。说白了,就是从设备不回应你。

我总结了几种典型场景:

现象 可能原因 排查方法
地址阶段 NACK 设备地址错误、设备未上电、I2C 总线被拉死 核对设备树 reg、测量 VDD 电压、检查 SCL/SDA 电平
数据阶段 NACK 从设备不支持该寄存器、写保护、时序超时 查看 datasheet 寄存器列表、检查写保护引脚、降低频率
偶发 NACK 总线干扰、时钟拉伸超时、电源噪声 加长超时时间、增加重试次数、示波器看电源纹波
一直 NACK I2C 控制器配置错误、GPIO 复用不对 检查 pinctrl 配置、确认 iomux 寄存器
避坑指南:我曾经在一个项目里遇到“地址阶段 NACK”,查了两天。最后发现是设备树里 i2c-scl-rising-time-ns 配得太小,导致 SCL 上升沿太陡,从设备采样出错。把上升时间从 100ns 改成 300ns 就好了。嗯,这种坑 datasheet 里通常不会写。

4.5 调试工具链:i2c-tools 是你的好帮手

在写设备驱动之前,我强烈建议先用 i2c-tools 验证硬件通路。这是最直接的方法:

# 扫描总线上的设备
i2cdetect -y 0

# 读取设备寄存器
i2cget -y 0 0x48 0x00 w

# 写入设备寄存器
i2cset -y 0 0x48 0x01 0x3F

# 连续转储
i2cdump -y 0 0x48

如果 i2cdetect 能扫到设备,说明硬件连接和适配器驱动都没问题。如果扫不到,先别急着写驱动,回头查硬件。

个人经验:RK 平台有个坑——I2C 总线号不是固定的。设备树里配的 i2c@fec00000 可能是总线 0,也可能是总线 3,取决于内核注册顺序。用 i2cdetect -l 先确认总线号,别想当然。

4.6 调试流程总结

最后,我把整个调试流程画成一张决策图,方便你对照排查:

I2C 调试决策流程图 开始调试 1. 确认适配器驱动加载 2. i2cdetect 扫描设备 扫不到 → 检查硬件/地址 扫到 → 用 i2cget 读寄存器 上逻辑分析仪抓波形 数据正确 → 写设备驱动 分析时序/ACK/NACK

这张图是我自己调试时用的路线图。你照着走一遍,90% 的 I2C 问题都能定位到。

剩下的 10% 是什么?嗯,往往是硬件设计问题——上拉电阻阻值不对、PCB 走线过长、电平不匹配。这些就需要你拿着示波器,一根一根线去量了。