第4章 I2C驱动调试实战:从子系统到波形

I2C这玩意儿,说简单也简单,就两根线:SCL和SDA。但说复杂吧,我这些年遇到的I2C问题,十个手指头都数不过来。从设备树配错地址,到从机不回ACK,再到时钟延展卡死——每个坑我都踩过。

这一章,咱们就围绕RK3568平台,把I2C子系统扒开来看。我会结合i2c-tools和逻辑分析仪,带你一步步定位那些让人头疼的ACK/NACK问题。

本章核心目标: 掌握I2C驱动框架、设备树配置要点、时序分析方法,以及用工具定位通信故障的实战技巧。

4.1 I2C子系统架构概览

Linux内核的I2C子系统分三层,我习惯这么记:

  • I2C Core(核心层):提供总线注册、设备枚举、驱动匹配等通用机制。你不需要动它。
  • I2C Adapter(适配器层):对应SoC的I2C控制器硬件。RK3568有5个I2C控制器,每个都是一个adapter。
  • I2C Client(设备层):挂载在总线上的从设备,比如温度传感器、音频Codec、触摸屏。

说白了,适配器负责发数据,设备层负责解析数据。驱动开发主要关注设备层,但调试时三层都可能出问题。

Linux I2C 子系统架构 用户空间 (i2c-tools / 应用) I2C Core (i2c-core.c) I2C Adapter (rk3x_i2c.c) I2C Client (e.g. mpu6050.c) 硬件:RK3568 I2C控制器 + 外部从设备 适配器驱动由SoC厂商提供(RK3568使用rk3x_i2c.c),设备驱动由开发者编写 调试时先确认适配器层工作正常,再排查设备层

4.2 设备树配置——别小看这几行

RK3568的设备树里,I2C节点长这样。我见过太多人把地址写错,或者忘了配时钟频率。

&i2c1 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&i2c1m0_xfer>;
    clock-frequency = <100000>;  /* 100KHz标准模式 */

    /* 挂载一个温度传感器 */
    temperature_sensor: tmp102@48 {
        compatible = "ti,tmp102";
        reg = <0x48>;
        #address-cells = <1>;
        #size-cells = <0>;
    };
};
避坑指南: 我曾经把reg地址写成0x48,但实际芯片地址是0x49。结果内核报错"i2c i2c-1: client 0x48 not found"。查了半小时才发现是地址搞反了。记住:reg是7位地址左移1位后的值,不是原始地址。

这里有几个关键点:

  • clock-frequency:RK3568支持100KHz、400KHz和1MHz。但实际能不能跑上去,取决于PCB走线和从设备能力。我建议先跑100KHz,稳定了再提频。
  • pinctrl:必须选对复用功能。RK3568的I2C1有m0和m1两组引脚,配错了SCL/SDA就没波形。
  • reg:7位从机地址,注意对齐方式。内核会帮你左移,所以设备树里写的就是7位地址本身。

4.3 通信时序分析——用逻辑分析仪看真相

软件层面调通了,不代表硬件没问题。我习惯先挂逻辑分析仪,抓一段波形看看。

标准的I2C写时序是这样的:

  1. START条件:SCL高电平时,SDA从高变低。
  2. 发送从机地址+写位:7位地址 + 0(写),等待ACK。
  3. 发送寄存器地址:告诉从机你要写哪个寄存器。
  4. 发送数据:一个字节一个字节发,每字节后等ACK。
  5. STOP条件:SCL高电平时,SDA从低变高。

为什么会NACK?我总结了几种常见情况:

现象 可能原因 排查方法
地址阶段NACK 从机地址错误、从机未上电、SCL/SDA接反 核对设备树reg、测量VDD、检查原理图
数据阶段NACK 从机忙、寄存器不存在、写保护 查看从机datasheet、降低频率重试
时钟延展卡死 从机拉低SCL、适配器超时设置过短 抓波形看SCL低电平时间、增大超时值
波形畸变 上拉电阻太小/太大、容性负载过重 检查PCB走线、调整上拉电阻(典型4.7KΩ)
注意: RK3568的I2C控制器内部有滤波和毛刺抑制,但外部上拉电阻必须加。我遇到过一块板子没加上拉电阻,偶尔能通信,但大部分时间NACK。逻辑分析仪一看,SDA上升沿慢得像蜗牛。

4.4 使用i2c-tools快速验证

在调试阶段,我几乎不用写测试程序。i2c-tools这套工具足够搞定90%的问题。

常用命令一览:

命令 作用 示例
i2cdetect -l 列出所有I2C总线 查看RK3568的5个adapter
i2cdetect -y 1 扫描总线1上的设备 看0x48位置有没有UU或数字
i2cget -y 1 0x48 0x00 读取设备0x48的寄存器0x00 读温度传感器ID寄存器
i2cset -y 1 0x48 0x01 0x3C 向设备0x48的寄存器0x01写0x3C 配置传感器工作模式
i2ctransfer -y 1 w2@0x48 0x00 0x01 发送2字节写数据 更灵活的传输方式

举个例子,我调试一个音频Codec时,用i2cdetect扫不到设备。但逻辑分析仪显示有波形,从机也回了ACK。后来发现是Codec的地址线A0接了高电平,实际地址是0x1B而不是默认的0x1A。用i2cdetect -y 2 0x1b指定地址扫描,一下就出来了。

我的习惯: 拿到一块新板子,先跑一遍i2cdetect -y扫所有总线。如果扫不到设备,别急着怀疑驱动,先拿示波器或逻辑分析仪看波形。硬件问题占I2C故障的60%以上。

4.5 实战:定位ACK/NACK问题

假设你遇到了一个典型的NACK问题。从机是MPU6050六轴传感器,地址0x68。你写驱动时发现i2c_master_send返回-5(即-EREMOTEIO,表示NACK)。

我的排查步骤:

  1. 确认设备树:检查reg = <0x68>是否正确。MPU6050的AD0引脚接地时地址是0x68,接VCC是0x69。
  2. 测量供电:用万用表量VDD和VLOGIC引脚,确保电压正常。
  3. 抓波形:逻辑分析仪挂SCL和SDA,触发条件设为START条件。看地址阶段有没有ACK。
  4. 分析波形:如果地址阶段SDA在第9个时钟周期被拉低,说明有ACK。否则就是NACK。
  5. 检查上拉:如果波形上升沿很缓,可能是上拉电阻太大。RK3568内部有弱上拉,但建议外部加4.7KΩ。

我曾经遇到一个奇葩问题:MPU6050在低温下偶尔NACK。抓波形发现时钟延展时间超过了内核默认的1ms超时。解决办法是在设备树里加i2c-scl-rising-time-ns和i2c-scl-falling-time-ns属性,或者增大adapter的超时值。

/* 在设备树中调整时序参数 */
&i2c1 {
    clock-frequency = <100000>;
    i2c-scl-rising-time-ns = <175>;
    i2c-scl-falling-time-ns = <15>;
    /* 这些值可以从逻辑分析仪测得的波形计算 */
};
关键经验: 逻辑分析仪是I2C调试的"照妖镜"。别靠猜,直接看波形。地址、数据、ACK/NACK、时钟延展,一目了然。我团队里新来的同事总喜欢改代码试,我告诉他:先抓波形,再改代码,效率能提升3倍。

4.6 内核调试开关

如果手头没有逻辑分析仪,可以打开内核的I2C调试日志。RK3568的内核默认编译了I2C动态调试支持。

# 打开I2C核心调试
echo 'file i2c-core.c +p' > /sys/kernel/debug/dynamic_debug/control

# 打开RK3X适配器调试
echo 'file i2c-rk3x.c +p' > /sys/kernel/debug/dynamic_debug/control

# 查看日志
dmesg | tail -50

日志会打印每次传输的细节,包括地址、数据、返回值。但说实话,这只能看到软件层面的信息。硬件波形问题,还是得上逻辑分析仪。

嗯,这一章的内容就到这里。I2C调试说难不难,说简单也不简单。关键是掌握方法:设备树配对、工具用好、波形看懂。下次遇到NACK,你应该知道从哪里下手了。