拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Linux i2c-tool调试I2C设备:从安装到寄存器读写

做嵌入式开发或者经常和传感器打交道的朋友多半遇到过这种场景驱动加载失败、设备树明明配了但/dev下面就是没有对应节点、寄存器读写上报的数据千奇百怪。这时候 Linux 下的 i2c-tool 就该出场了。这个工具集就是 I2C 总线调试的瑞士军刀扫描设备、查看寄存器、手动读写全靠它。今天我就把 i2c-tool 最常用的几个命令从安装开始到扫描、读寄存器、写寄存器再到实际调试场景和排坑经验完整过一遍。内容不整虚的每个命令都配实际用法和结果说明让你看完就能照着操作省去到处查 man page 的功夫。1. 为什么调试 I2C 设备离不开 i2c-tool1.1 先搞清楚 I2C 总线在 Linux 里长什么样I2C 总线在 Linux 系统中会被抽象成/dev/i2c-N这样的设备节点N 是总线编号。很多 ARM 开发板上默认就有好几条 I2C 总线比如 I2C-0、I2C-1、I2C-2。树莓派默认把 I2C-1 引到 40Pin 排针上这个大家应该比较熟。但你要知道总线编号并不一定是物理上的 I2C 控制器编号它由设备树或平台驱动注册顺序决定所以最好通过命令去确认而不是想当然。查看系统当前有哪些 I2C 总线直接用ls /dev/i2c-*。如果你的板子还没开 I2C可能一个节点都看不到这时候需要去设备树里确认 I2C 控制器的 status 是不是 okay或者通过内核菜单开启相应驱动。还有一种常见情况是设备节点存在但里面没有挂任何设备那是因为设备树里没有声明子节点驱动不会自动 probe。这种把硬件总线抽象成文件的思路在 Linux 里非常典型。你操作 I2C 设备本质就是通过 open、ioctl、read、write 这些文件操作去访问/dev/i2c-N。而 i2c-tool 就是把这些操作封装成了可以直接敲的命令行工具省掉自己写 C 程序的成本。所以理解了这个关系你也就明白了为什么用 i2c-tool 时经常需要 root 权限因为设备节点的权限默认是 root 所有普通用户访问不到。1.2 安装 i2c-tools 的最快路径不同发行版安装方式差别不大就是包管理器不一样。我列一下最常用的几个# Debian / Ubuntu / 树莓派系统 sudo apt install i2c-tools # CentOS / RHEL / Fedora sudo yum install i2c-tools # 或者新版系统用 dnf sudo dnf install i2c-tools # Arch / Manjaro sudo pacman -S i2c-tools国内的一些国产 Linux 发行版比如基于 Debian 的统信 UOS、麒麟这类通常也支持apt或yum装不上就先更新一下软件源再试。嵌入式板卡的交叉编译场景如果板子上空间紧张也可以从源码自己交叉编译 i2c-tools编译依赖非常少基本就是标准 glibc很好搞定。有一点要注意装好 i2c-tools 只是拿到了用户态工具内核还需要 I2C 设备驱动支持也就是 i2c-dev 这个模块。大多数发行版内核默认都编译成了模块可以用modprobe i2c-dev手动加载或者用lsmod | grep i2c_dev检查是否已经加载。如果没有加载后续执行 i2cdetect 会报 Could not open file /dev/i2c-1 之类很误导人的错误。另外Windows 的 WSL 环境里也可以装 i2c-tools但我实测过WSL 默认没有透传物理 I2C 设备装了命令也执行不了最多看看帮助文档。真要在 Windows 上调试 I2C建议老老实实走 USB-I2C 适配器加配套上位机别在 WSL 上浪费时间。2. 最常用的四个命令逐个拆解如果你只打算记住四个命令那就记这四个i2cdetect、i2cdump、i2cget、i2cset。它们分别对应扫描可见设备、整体查看寄存器、读取单字节、写入单字节。把这四个玩熟九成以上的调试场景都能覆盖。2.1 i2cdetect先找到设备在哪里i2cdetect 的作用是扫描一条总线上有哪些 I2C 设备在响应。最小用法就一句i2cdetect -y 1这里的-y是跳过交互确认批量调试时非常有用不然每次都要手动输入 y 确认特别烦。最后的1是总线编号对应/dev/i2c-1。如果你不确定有几条总线先执行i2cdetect -l列出所有总线再用i2cdetect -y 总线号去扫描对应总线。扫描结果大概长这样0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --看到 0x50 这个位置出现数字说明总线 1 上有一个设备7 位地址是 0x50。这里必须强调一个经典坑i2c-tool 采用的地址是 7 位地址也就是不包含读写位。很多芯片的数据手册里会写 8 位地址比如 EEPROM 写地址 0xA0、读地址 0xA1但你在 i2cdetect 里看到的会是 0x50。这个换算关系就是把 8 位地址右移一位。如果你照着数据手册的 0xA0 去找设备永远找不到因为 0xA0 已经超出了 7 位地址范围。这个坑我见过不止一次。i2cdetect 还有一些补充参数。-r强制使用 SMBus read byte 命令来探测-q用快速写命令探测。某些设备对这几种探测方式的响应不一样一种方式扫不到就换另一种试试。另外--address-range可以指定扫描范围比如从 0x30 扫到 0x50命令格式是i2cdetect -y 1 --address-range 0x30-0x50这在设备地址已知但不想全网扫描时比较实用。扫描结果里的 UU 表示该地址已经被一个内核驱动占用这不是故障说明驱动已经 probe 成功了。扫描结果里的 -- 表示无设备应答空位表示该地址不在扫描范围内。如果你扫到一个设备但紧接着读写又报错多半是地址对了但总线时序有问题这个后面详细说。2.2 i2cdump把寄存器的家底翻出来i2cdump 和 i2cdetect 类似也是扫描但它扫描的是指定设备的所有寄存器。典型用法i2cdump -y 1 0x50意思是读取总线 1 上地址为 0x50 的设备把所有寄存器的值按十六进制打印出来。输出格式一般是行对应寄存器地址高四位列对应低四位中间每个格子里是对应寄存器的当前值。对 EEPROM 这类存储型设备这一眼就能看出里面存了什么数据。i2cdump 默认按字节读取对大多数设备适用。但有些寄存器是 16 位宽或者总线协议要求 word 访问这时候可以加模式参数。比如i2cdump -y -r 0x00-0xff 1 0x50可以指定寄存器范围免得读太多i2cdump -y 1 0x50 w指定以 word 为单位读取。模式参数紧跟在设备地址后面格式上容易让人懵我建议先用默认模式跑一次确认设备基本通信正常再根据数据手册决定是否要加模式。有一个比较危险的操作要提醒i2cdump 默认会读取连续寄存器区间对普通传感器芯片问题不大。但如果设备是 EEPROM而且里面保存了启动配置或者校准参数读操作本身不会损坏数据放心读。真正危险的是用 i2cdump 检测一个 I2C 总线上的其他设备因为有些地址空间在物理上并不存在强行探测可能让某些设备进入异常状态。总之i2cdump 只针对你确定存在的设备地址用。2.3 i2cget指定寄存器读一个字节i2cget 是更精确的读操作不扫描全部寄存器只读指定地址的一个字节。语法是i2cget -y 1 0x50 0x00参数依次是总线号、设备 7 位地址、寄存器地址。执行后终端直接显示寄存器值比如0x40。这在你只想确认某个传感器型号 ID 或者状态标志位时非常好用比 i2cdump 整个寄存器空间更轻量。i2cget 也支持模式参数默认是读一个字节。如果你读到的结果永远都是 0xff有一种常见情况是设备本身不存在或者没上电总线被上拉电阻拉高了所以读什么都像 0xff。还有一种情况是设备地址写错了比如把 8 位地址当 7 位地址用遇到的总线应答错误和读到 0xff 的情况都有可能。这时候回头检查一下地址换算。i2cget 只适合寄存器地址是 8 位的情况。很多现代传感器芯片比如一些陀螺仪、气压计寄存器地址是 16 位的需要先发送高字节再发送低字节。i2cget 不支持直接拼 16 位地址这时候要么用 i2ctransfer要么自己写个小程序。我一般直接用 i2ctransfer后面会专门讲这个命令。2.4 i2cset指哪打哪写寄存器i2cset 和 i2cget 正好对应写入一个字节到指定寄存器。基本语法i2cset -y 1 0x50 0x00 0x55含义是把总线 1、设备 0x50、寄存器 0x00 的值写成 0x55。操作之后一般再用 i2cget 读回来确认一下。写寄存器是搞坏设备的高发环节尤其是那些“写一次就永久生效”的寄存器比如 EEPROM 的写保护位、电源管理芯片的配置位。没有把握的寄存器千万别乱写。i2cset 还支持按 word 写入加w模式参数就行。还有一个很有用的参数-m它让你只修改寄存器里指定的几个 bit。比如想把寄存器 0x10 的 bit3 改成 1其他位保持原样可以这样操作i2cset -y 1 0x50 0x10 0x08 -m 0x08这里面 0x08 是掩码表示只修改第 3 位。实际写的时候驱动会先读回原值再按掩码更新最后写回去。不过要注意这个功能依赖内核 i2c-dev 的 ioctl 支持也不是所有适配器都能正常实现实测中少数 USB 转 I2C 适配器会失效遇到这种情况就只能自己先 i2cget 再 i2cset 手动组值了。对寄存器里只有几个标志位需要翻转的场景-m非常省事。没有它的话你每次写整个寄存器都可能把其他的保留位或状态位搞乱反复调几次就蒙了。所以我个人的习惯是凡是只改部分位的场景优先用掩码方式减少误伤。3. 实战给一颗传感器芯片做完整调试命令都过了一遍现在用一个实际例子串起来。假设我手里有一块板子I2C 总线上挂了一颗温度传感器数据手册说设备 7 位地址是 0x48寄存器 0x00 是温度寄存器0x01 是配置寄存器配置寄存器的 bit7 为 1 时进入关机模式为 0 时正常测量。当然实际型号我就不点名了原理通用。3.1 第一步确认总线编号和设备地址先跑i2cdetect -l看所有总线输出类似i2c-0 i2c i2c-mux I2C adapter i2c-1 i2c i2c I2C adapter看到两条总线但不知道传感器挂在哪条。我一般先分别在两条上扫一下i2cdetect -y 0 i2cdetect -y 1总线 0 扫描结果全是 --总线 1 在 0x48 位置出现数字说明传感器挂在 I2C-1 上。这一步就完成了设备定位。如果你的 I2C 总线数量很多还有一个办法是看/sys/bus/i2c/devices/目录里面会列出所有已注册的 I2C 设备格式类似1-0048前面的1就是总线号0048是设备地址。如果设备出现在这里但 i2cdetect 扫不到可能是设备被某个驱动占用了加-r试试。3.2 第二步读设备 ID 寄存器确认通信很多传感器都有厂商 ID 或设备 ID 寄存器比如 0x00 或者 0x0F这是验证通信是否正常的首选。我先读一下不确定的寄存器但更安全的做法是查数据手册确认器件 ID 寄存器的地址和默认值。假设手册说寄存器 0x0F 是设备 ID默认值是 0x88那么执行i2cget -y 1 0x48 0x0F返回0x88说明物理通信链路、设备地址、寄存器地址这三件事全部正确。最有意思的是很多时候通信不通的根因不在这三步而是你 I2C 总线上根本没有设备对应这个地址这时候 i2cget 会报 Remote I/O error。如果返回 0x00 或者 0xff先检查供电和上拉。I2C 总线的 SDA 和 SCL 通常需要上拉电阻到 VCC没有上拉或上拉阻值不对波形就会失真导致读值异常。如果读 ID 这一步就失败了别急着怀疑工具先用示波器或逻辑分析仪抓一下 SDA 和 SCL 波形。我遇到过不少次工具命令没问题、代码也没问题最后发现是杜邦线接触不良或者 I2C 地址因为板卡上跳线设置不对而变了。示波器一看波形地址对不对、有没有 ACK一目了然。3.3 第三步写配置寄存器启动采样接下来把传感器从关机模式切到正常模式。手册里说配置寄存器 0x01 的 bit7 是关机控制位0 表示正常测量。那么我需要保证 bit7 是 0其他位保持默认值。先读一下当前配置寄存器i2cget -y 1 0x48 0x01假设返回 0x80说明 bit7 恰好是 1确实处于关机状态。那我把它写成 0x00 即可i2cset -y 1 0x48 0x01 0x00如果再谨慎一点不想动其他位也可以用掩码方式只清 bit7i2cset -y 1 0x48 0x01 0x00 -m 0x80这个写法理解起来稍绕一点但实际保护性更好。写完一定要回读验证i2cget -y 1 0x48 0x01如果返回 0x00说明写成功。这里有个经验传感器芯片的寄存器写入大多不是即时生效有些需要等几毫秒到几十毫秒。所以写完配置后最好sleep 0.1再读状态别立刻回读否则可能读到旧值误以为写入失败。3.4 第四步读传感器数据并验证结果启动测量后温度值通常要等一次转换完成才能读到。假设手册说寄存器 0x00 高 8 位是温度整数部分0x02 是状态标志位bit0 为 1 表示转换完成。那我可以写个快速循环while true; do status$(i2cget -y 1 0x48 0x02) if [ $status 0x01 ]; then break fi sleep 0.1 done i2cget -y 1 0x48 0x00这种循环调试方式在嵌入式里很常见本质就是轮询状态寄存器。温度读到后和实际环境温度对比一下如果差得离谱检查一下换算公式是不是把符号位处理错了。很多温度传感器输出的是有符号数0xFC 之类的值代表负温度你要是当无符号数算结果能差到天上。整个流程走下来你会发现 i2c-tool 的意义不只是“能读能写”它更重要的是帮你把硬件问题、地址问题、寄存器问题逐层剥离出来。哪一步卡住问题就大概率在哪个环节。4. 高级操作i2ctransfer 与复杂场景4.1 i2ctransfer 基本语法前面几个命令适合简单寄存器读写遇到 16 位寄存器地址、一次事务里既要写又要读的场景i2ctransfer 才是真正的利器。这个命令的语法更接近底层一次 I2C 事务可以组合多条消息。典型格式i2ctransfer -y 1 w20x48 0x00 0x10 r1解读一下-y 1还是总线号w20x48表示向设备 0x48 发送一个长度为 2 字节的写消息后面跟着的两个数据就是这次要写的内容r1表示接着读 1 个字节。整体完成了一次“写两个字节再读一个字节”的完整事务非常适合带 16 位寄存器地址的芯片。很多器件读寄存器时需要先写入寄存器地址再重新发起读操作。用 i2cset 然后 i2cget 也能做到但那是两次独立事务中间会有 STOP 和 START 状态某些设备要求必须连续这时 i2ctransfer 的优势就出来了。它可以在同一条总线上连续执行写、重启、读时序更可靠。我用 i2ctransfer 最典型的一个场景是读气压计的压力值。芯片寄存器地址是 16 位我要先发高字节地址和低字节地址然后读回 2 个字节的数据一条命令就搞定i2ctransfer -y 1 w20x60 0x00 0x04 r2这种写法干净利落。如果你在脚本里大量使用 i2cget 加 i2cset 的组合遇到芯片对时序敏感或者操作总是失败换成 i2ctransfer 试试成功率高很多。这个命令还支持多个消息连写比如w10x48 0x00 w10x48 0x01 r1这种一次调用里做了两件事。4.2 处理 16 位寄存器地址的设备再展开讲讲 16 位寄存器地址这个场景。早期的 EEPROM 和很多传感器芯片寄存器地址就 8 位取值范围 0x00~0xFF。后来功能多了寄存器不够用就开始用 16 位地址也就是一次要发两个字节的寄存器地址典型的是 0x00 到 0xFFFF。i2cget 和 i2cset 默认只支持 8 位寄存器地址遇到 16 位地址只能靠 i2ctransfer 或者自己写 ioctl 程序。举个例子某音频编解码芯片的数据手册说读取寄存器 0x1234 的值需要先发送 0x12 再发送 0x34然后读回 1 个字节。用 i2ctransfer 那就是i2ctransfer -y 1 w20x1A 0x12 0x34 r1写寄存器同理假设要把寄存器 0x1234 写成 0xABi2ctransfer -y 1 w30x1A 0x12 0x34 0xAB注意写消息的长度变成了 3前两个字节是寄存器地址最后一个字节是要写的数据。这类操作在 i2cget/i2cset 里很难干净地实现甚至会被当成两个不同设备来操作所以遇到 16 位寄存器地址的芯片我推荐直接用 i2ctransfer 一条命令搞定少走弯路。还有一个寄存器地址递增读取的场景比如读取连续 16 个字节可以用i2ctransfer -y 1 w20x1A 0x12 0x34 r16这个读 16 字节的能力在批量采集数据时很好用比一条条读快得多总线开销也小。4.3 总线复用器与多路开关复杂的 I2C 系统里经常有总线复用器比如 PCA9548它本身也是一个 I2C 设备写一个字节进去就能选择把后面的通道切到哪一路。遇到这种场景直接对挂在复用器后面的设备操作是无效的因为物理链路上还没选通。必须先写复用器的控制寄存器再操作目标设备。用 i2c-tool 手动模拟的话就是先 i2cset 写复用器通道再 i2cdetect 扫描对应总线。如果你的复用器后面的设备地址和前面的设备冲突那就只能选通一个通道后再逐个访问。这个场景在带多路传感器的板卡上非常常见尤其是服务器主板上管理控制器后面挂着几十个温度传感器的时候。遇到“明明扫描不到设备但我确定它存在”的情况先想想总线上有没有复用器挡路。5. 常见问题与排查技巧5.1 找不到设备怎么办这是被问得最多的问题。i2cdetect 扫不到设备先按这个顺序排查检查项具体操作说明物理连接确认 SDA、SCL、GND、VCC 四根线分别接对接反是最常见的低级错误设备供电万用表量 VCC 对 GND 电压很多传感器支持 1.8V 和 3.3V接错电压可能完全不工作上拉电阻检查 SDA 和 SCL 是否通过电阻接上拉电源没有上拉或阻值过大信号根本拉不起来设备地址确认 datasheet 里的是 7 位还是 8 位地址8 位地址右移一位再填入命令探测模式换-r或-q再扫描部分设备对默认探测方式不响应总线复用器确认目标总线是否被复用器隔离先选通通道再扫描内核占用看扫描结果是否出现 UUUU 是驱动占用了不是故障我在实际项目里发现超过一半的“找不到设备”问题出在物理连接和供电上。工具命令本身没有玄学硬件问题靠示波器一眼就能定位。5.2 权限和内核模块问题如果执行命令时报Error: Could not open file /dev/i2c-1或者Permission denied不要先去怀疑命令写错了。先用ls -l /dev/i2c-*看设备节点权限。很多发行版默认/dev/i2c-N属于 root 用户普通用户没有读写权限。临时方案是命令前面加sudo一劳永逸的做法是把当前用户加入 i2c 组sudo usermod -aG i2c $USER改完需要重新登录才生效。另一个常见问题是/dev/i2c-N节点根本不存在这时候看内核模块modprobe i2c-dev如果提示模块不存在说明内核编译时把 i2c-dev 编成了内置或者没开需要重新配置内核或换内核。树莓派这类系统默认已经打开了 I2C 内核支持但需要运行raspi-config里打开 I2C 接口否则设备节点也不会出现。这个步骤很多新手会漏掉。5.3 寄存器读写失败的 N 种原因如果你能扫描到设备但 i2cget/i2cset 报错或者读数不对原因通常是下面这几种。第一地址对了但寄存器地址写错。有的芯片寄存器地址只有 8 位但地址空间从 0x80 开始有的芯片寄存器地址是 10 位甚至 16 位。你拿 8 位地址去访问 16 位地址的芯片读出来的自然是错的。解决办法就是看数据手册确认寄存器地址宽度和物理地址映射。第二设备处于低功耗或睡眠模式。很多传感器默认上电后进入低功耗状态不响应 I2C 命令或者只响应特定唤醒命令。需要先按手册写唤醒寄存器再执行正常读写。这个坑特别隐蔽因为设备能扫描到但读写全失败排查半天才发现是睡眠模式。第三总线速度不匹配。I2C 有标准模式 100K、快速模式 400K、高速模式 3.4M 等。如果主控端配置了 400K但传感器只支持 100K时序通信就会不稳定时好时坏。用户态的 i2c-tool 无法直接调整总线速率需要改内核设备树里 I2C 控制器的 clock-frequency 属性这个属性通常以 Hz 为单位。第四寄存器只读或只写。芯片内部的寄存器不全是可读可写的有的状态寄存器只读你写进去当然不会生效有的控制寄存器只写你读回来的是 0 或者恒定的值。遇到特别诡异的读数先翻数据手册确认寄存器访问属性。第五电压域不匹配。有些 I2C 设备的 VCC 是 1.8V如果主控和传感器不是同一个电压域I2C 引脚电平可能不一致导致通信偶尔成功偶尔失败。这种问题在混合电压的系统里很常见。5.4 一个通用调试套路踩了这么多坑之后我总结了一个自己的排查套路分享给大家。拿到一个 I2C 设备先不管功能正不正常第一步永远是i2cdetect -y 总线号扫描确认设备地址存在这一步通过代表物理层和地址层都没问题。第二步是读设备 ID 或厂商 ID 寄存器确认寄存器寻址模式正确。这个步骤能通过说明通信协议没问题。第三步才是写配置寄存器而且只改必要位改完立刻回读确认。最后一步才验证功能数据比如读温度、读加速度。这四步每一步都有明确的失败含义按顺序排查效率最高。如果你怀疑总线上有信号完整性问题或者不确定设备是否真的在正常工作那就用逻辑分析仪抓波形。现代的逻辑分析仪已经很便宜了几十块钱就能买到支持 100M 采样率的抓 I2C 波形绰绰有余。看波形时重点确认三个东西起始条件、地址字节的 ACK 位、数据字节是否匹配。百分之九十的信号问题在这三点上都能看出端倪。我调试过的很多 I2C 设备问题最后追到根因都不是 i2c-tool 本身的问题而是地址换算、上拉、睡眠模式、电源时序这些外围因素。这也侧面说明工具越简单问题反而越容易暴露在明面上。只要你把自己调整到了“按层排查”的思路i2c-tool 就会变成你手里最可靠的那把螺丝刀。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门