1. i2c-tools简介与环境准备

i2c-tools是Linux系统下非常实用的I2C调试工具集,包含i2cdetect、i2cget、i2cset、i2cdump和i2ctransfer等实用工具。在MTK Android平台上,这些工具能帮助我们快速检测I2C总线上的设备、读写寄存器数据,是硬件调试的必备利器。

我在MTK平台开发过程中发现,很多工程师第一次接触i2c-tools时都会遇到各种编译和运行问题。其实只要掌握正确的编译方法和调试技巧,这些工具用起来非常顺手。接下来我会分享在MTK平台编译和使用i2c-tools的完整实战经验,包括我踩过的一些坑和解决方案。

首先需要准备编译环境。我推荐使用Ubuntu 18.04或20.04作为编译主机,确保已经安装Android源码编译所需的依赖包。MTK平台通常使用Android 9.0或更高版本,需要先配置好编译环境变量,包括设置Java版本、Android工具链等。这些都是基础工作,但往往决定了后续编译能否顺利进行。

下载i2c-tools源码时,建议选择4.2或4.3版本,这两个版本在MTK平台上兼容性最好。可以从官方地址https://www.kernel.org/pub/software/utils/i2c-tools/ 下载,也可以使用git clone命令获取最新代码。我一般会选择稳定版本,避免使用过于新的版本可能带来的兼容性问题。

2. Android.mk文件配置详解

在i2c-tools源码根目录创建Android.mk文件是整个编译过程的关键步骤。这个文件告诉Android构建系统如何编译我们的工具。让我详细解释每个配置项的含义,这样你遇到问题时就知道如何调整了。

先看基础配置部分:

LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng

这里LOCAL_MODULE_TAGS设置为eng表示只在工程版本中编译。如果遇到编译错误提示模块标签问题,可以改为optional。我在MTK Android 10上就遇到过这个问题,改成optional后就编译通过了。

接下来是静态库的编译配置:

LOCAL_MODULE := i2c-tools
LOCAL_SRC_FILES := \
    tools/i2cbusses.c \
    tools/util.c \
    lib/smbus.c
LOCAL_C_INCLUDES += \
    $(LOCAL_PATH) \
    $(LOCAL_PATH)/include
include $(BUILD_STATIC_LIBRARY)

这个静态库包含了i2c-tools的核心功能,其他可执行文件都会链接这个库。注意源文件路径一定要正确,否则编译时会提示找不到文件。

对于每个工具的可执行文件配置,以i2cdetect为例:

include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := eng
LOCAL_MODULE:=i2cdetect
LOCAL_SRC_FILES:= \
    tools/i2cdetect.c
LOCAL_C_INCLUDES += \
    $(LOCAL_PATH) \
    $(LOCAL_PATH)/include
LOCAL_SHARED_LIBRARIES:= \
    libc
LOCAL_STATIC_LIBRARIES := \
    i2c-tools
LOCAL_CPPFLAGS += -DANDROID
include $(BUILD_EXECUTABLE)

这里有几个关键点:LOCAL_SHARED_LIBRARIES指定需要链接的动态库,至少需要libc;LOCAL_STATIC_LIBRARIES指定链接我们刚才编译的静态库;LOCAL_CPPFLAGS += -DANDROID是必须的,它定义了Android平台的宏。

其他工具(i2cget、i2cset、i2cdump、i2ctransfer)的配置类似,只需要修改LOCAL_MODULE和LOCAL_SRC_FILES为对应的工具名称和源文件即可。我建议一次配置好所有工具,毕竟在调试时可能都会用到。

3. 编译流程与集成方法

配置好Android.mk后,接下来就是编译环节。MTK平台有两种主要的编译方式,根据你的需求选择合适的方法。

第一种方法是使用mmm命令单独编译:

source build/envsetup.sh
lunch                # 选择对应的工程配置
mmm i2c-tools-4.2/

编译完成后,生成的工具会在out/target/product/xxx/system/bin/目录下。但这里有个关键步骤:需要重新编译整个系统镜像(make -j24)并将新镜像烧录到设备中。如果只编译不重新制作系统镜像,工具可能不会包含在最终的系统镜像中。

第二种方法更彻底,直接修改设备配置文件: 在device/mediateksample/xxx/device.mk文件中添加:

PRODUCT_PACKAGES += \
    i2cdetect \
    i2cget \
    i2cdump \
    i2cset \
    i2ctransfer

然后直接编译整个系统:

make -j24

这种方法的好处是每次编译系统都会自动包含这些工具,不需要单独处理。我在项目开发中通常使用这种方法,特别是需要频繁编译不同版本时。

编译过程中可能会遇到各种错误,最常见的是权限问题和路径问题。确保源码目录有正确的读写权限,并且放置的位置合适。我一般放在与kernel目录同级的位置,这样不容易出现路径问题。

4. 内核配置与常见错误解决

编译成功只是第一步,真正运行时经常遇到各种问题。最多见的就是提示"Could not open file /dev/i2c-1' or /dev/i2c/1'`"错误。这是因为内核没有开启I2C字符设备支持。

解决方法是在内核配置中开启CONFIG_I2C_CHARDEV选项。具体操作如下:

首先找到对应的配置文件,MTK平台的内核配置文件通常在kernel-4.9/arch/arm64/configs/xxx_64_bsp_defconfig路径下。用文本编辑器打开这个文件,添加或修改一行:

CONFIG_I2C_CHARDEV=y

然后重新编译内核并烧录到设备中。这个选项确保内核创建/dev/i2c-*字符设备节点,i2c-tools需要通过这些节点访问I2C总线。

另一个常见问题是权限问题。即使有了设备节点,普通用户可能没有访问权限。可以通过修改ueventd.rc文件来设置权限,在device/mediateksample/xxx/ueventd.rc中添加:

/dev/i2c-*              0666   root       root

这样所有用户都有读写权限,方便调试。当然在生产版本中应该设置更严格的权限。

我还遇到过工具执行时报"Permission denied"即使权限设置正确的情况。这通常是SELinux策略限制导致的。需要修改SELinux策略文件,添加相应的allow规则。这是一个比较深入的话题,如果遇到可以暂时将SELinux设置为permissive模式调试。

5. i2cdetect使用技巧与实战

i2cdetect是使用最频繁的工具,用于检测I2C总线上的设备。掌握它的使用技巧能大大提高调试效率。

首先使用i2cdetect -l列出所有I2C总线:

i2c-1   i2c             STM32F7 I2C(0x40013000)          I2C adapter
i2c-2   i2c             STM32F7 I2C(0x5c002000)          I2C adapter
i2c-0   i2c             STM32F7 I2C(0x40012000)          I2C adapter

这个命令帮助确认系统中有哪些I2C总线,以及它们的编号。在MTK平台上,I2C总线编号通常从0开始,但具体取决于硬件设计。

查看某条总线的功能支持:

i2cdetect -F 0

这个命令显示总线0支持的功能,比如是否支持SMBus快速命令、字节读写、字读写等。不同硬件支持的功能可能不同,了解这些信息有助于选择正确的读写方式。

最重要的功能是扫描设备:

i2cdetect -y -a 0

输出结果中,"--"表示该地址没有设备,"UU"表示有设备且已被驱动占用,数字表示有设备但无驱动。这个信息非常有用,可以快速确认设备是否正常连接、地址是否正确。

在实际调试中,我经常遇到设备地址冲突或者设备无响应的情况。这时候可以结合使用不同的扫描参数。比如使用-q参数进行快速扫描,或者使用-r参数进行读扫描。不同扫描方式可能得到不同结果,有助于判断问题所在。

记得在使用时加上-y参数避免确认提示,特别是在脚本中使用时。但要注意强制访问可能带来的风险,最好在了解硬件情况的前提下使用。

6. i2cget和i2cset寄存器读写操作

i2cget和i2cset是进行寄存器读写的主要工具,掌握它们的使用方法对硬件调试至关重要。

i2cget用于读取寄存器值,基本用法如下:

# 读取设备地址0x50的寄存器0x10的值
i2cget -f -y 1 0x50 0x10

这里的参数含义:-f表示强制访问(即使设备忙),-y表示自动确认提示,1是I2C总线编号,0x50是设备地址,0x10是寄存器地址。

读取16位数据需要指定模式:

i2cget -f -y 1 0x50 0x10 w

模式参数w表示以字(16位)为单位读取。有些设备支持块读取,可以一次读取多个字节。

i2cset用于写入寄存器值,用法类似:

# 向设备地址0x50的寄存器0x10写入值0xAB
i2cset -f -y 1 0x50 0x10 0xAB

写入16位数据:

i2cset -f -y 1 0x50 0x10 0xABCD w

还支持块写入模式,一次写入多个字节:

i2cset -f -y 1 0x50 0x10 0x11 0x22 0x33 i

模式参数i表示I2C块写入。如果需要使用SMBus块写入,则用s模式。

在实际使用中,有几个实用技巧:首先总是使用-f和-y参数避免交互问题;其次在写入前最好先读取确认当前值;另外要注意字节序问题,有些设备使用大端字节序,有些使用小端字节序。

我经常遇到写入后读取值不变的情况,这可能是写保护位没有解除,或者需要特定的写入序列。仔细查阅芯片手册,了解正确的配置序列很重要。

7. i2cdump和i2ctransfer高级功能

i2cdump可以批量读取寄存器内容,非常适合快速查看设备状态。基本用法:

i2cdump -f -y 1 0x50

这个命令会读取设备0x50的所有寄存器值并以十六进制格式显示。如果需要限制读取范围,可以使用-r参数:

i2cdump -f -y -r 0x10-0x20 1 0x50

这样只读取0x10到0x20的寄存器。对于大量寄存器的设备,这个功能很实用。

i2cdump支持多种读取模式:b(字节模式,默认)、w(字模式)、i(I2C块模式)、c(连续字节模式)。根据设备特性选择合适的模式很重要。

i2ctransfer功能最强大,支持复杂的传输序列。它可以在一次传输中混合读写操作,适合需要特定访问序列的设备。

基本语法:

i2ctransfer -f -y 1 w2@0x50 0x10 0x00 r3

这个命令先向设备0x50写入两个字节(0x10和0x00),然后读取3个字节。@符号前的数字表示写入的字节数,@符号后是设备地址。

更复杂的例子:

i2ctransfer -f -y 1 w3@0x50 0x20 0x00 0x01 r4 w2@0x50 0x30 0xAA r2

这个命令执行三个操作:先写入3个字节并读取4个字节,然后写入2个字节并读取2个字节。这种复杂序列对于一些需要特定初始化流程的设备非常有用。

在使用i2ctransfer时,要注意传输时序。有些设备对操作之间的延迟很敏感,可能需要添加适当的延时。另外,复杂的传输序列如果设计不当可能导致设备状态异常,所以最好在理解设备工作原理的前提下使用。

8. 实际调试案例与问题排查

通过几个实际案例来说明如何使用这些工具解决实际问题。这些案例都来自我的实际项目经验,希望能给你提供参考。

案例一:设备无响应 现象:i2cdetect扫描不到设备,但硬件连接正常。 排查步骤:首先确认I2C总线是否启用(检查/sys/class/i2c-dev/);然后检查电源和时钟是否正常;最后用示波器检查SCL和SDA信号。最终发现是上拉电阻值过大导致信号上升时间太长。

案例二:读写数据错误 现象:能检测到设备,但读写数据与预期不符。 排查步骤:先用i2cdump查看所有寄存器值,确认设备是否处于正常状态;然后检查地址是否正确(7位地址还是8位地址);最后确认字节序问题。最终发现是设备使用大端字节序而工具默认使用小端字节序。

案例三:随机性失败 现象:有时操作成功,有时失败。 排查步骤:检查电源稳定性;检查信号质量(过冲、振铃等);检查是否有其他设备干扰。最终发现是电源噪声导致,增加滤波电容后问题解决。

在问题排查时,我通常遵循以下流程:先软后硬(先确认软件配置正确再检查硬件)、先简单后复杂(从简单读写开始逐步复杂)、先静态后动态(先检查静态配置再检查动态信号)。

另外,一些调试技巧:使用strace命令跟踪系统调用,可以了解工具底层的操作;使用cat /proc/interrupts查看中断情况,判断I2C控制器是否正常工作;使用示波器或逻辑分析仪观察实际信号波形。

记得在调试过程中做好记录,包括操作步骤、观察结果、修改尝试等。这些记录不仅有助于当前问题的解决,也为以后类似问题提供参考。调试是一个需要耐心和经验的过程,多实践就能掌握其中的技巧。

更多推荐