资讯动态

嵌入式Linux调试利器:i2c-tool常用命令与实战排错

发布时间:2026/9/13 21:07:09 来源:尧图企业网站定制
前阵子帮同事查一块新板子上的温湿度传感器I2C地址明明扫描到了数据却怎么读都是0xff折腾了一下午最后靠i2c-tool一步步定位到设备其实处于未唤醒的待机状态。这种场景在嵌入式Linux开发里太常见了新板子调试、外设驱动移植、硬件排错几乎每天都离不开i2c-tool这套命令。它是Linux下调试I2C总线最基础也最实用的工具集能帮你扫描设备、读写寄存器、验证时序、排查总线故障。这篇文章就把我这些年用i2c-tool的常用命令、参数细节、以及踩过的坑一次讲清楚适合刚接触嵌入式Linux的开发者也适合经常和硬件打交道的驱动工程师收藏备用。1. 安装与环境准备设备节点、权限和内核依赖1.1 不同发行版下的安装方式i2c-tool在主流Linux发行版里都有现成包不需要源码编译。Debian/Ubuntu系用aptCentOS/RHEL系用yum或dnf一句话就装好# Debian / Ubuntu sudo apt install i2c-tools # CentOS / RHEL 7/8 sudo yum install i2c-tools # CentOS/RHEL 9 / Fedora sudo dnf install i2c-tools如果是嵌入式平台比如用Buildroot或Yocto构建根文件系统通常菜单里直接勾选i2c-tools就会编进去。BusyBox也自带了一套精简版i2c工具但功能不完整比如没有i2ctransfer交叉编译环境里我一般还是建议直接用完整的i2c-tools源码configure、make、make install三步走依赖很少几分钟就能编完。1.2 内核I2C子系统与设备节点的关系i2c-tool跑起来的前提是内核已经注册了I2C控制器并且生成了对应的设备节点。这个节点就是/dev/i2c-NN是总线编号。要确认系统里有哪些I2C总线直接用ls /dev/i2c-*如果这个命令返回空或者报No such file or directory说明内核没有使能I2C的设备文件支持。这时候要检查内核配置里有没有开CONFIG_I2C_CHARDEV这个选项在Device Drivers - I2C support - I2C device interface下面打开之后重新编译内核/dev/i2c-N就会出现了。很多嵌入式平台比如树莓派、RK系列默认会通过设备树把I2C控制器使能但要注意设备树里某个I2C控制器如果status disabled即使驱动加载了也不会有对应的/dev/i2c-N节点。我遇到过好几次新板子移植dts里I2C节点忘改状态浪费了大量时间。1.3 权限问题别老用root配置udev更省心i2c-tool访问设备节点需要权限。开发调试时直接sudo没问题但如果产品最终要交付或者你自己嫌每次sudo麻烦可以创建一个i2c用户组把设备节点归属到组里再把当前用户加进去sudo groupadd i2c sudo chown root:i2c /dev/i2c-* sudo usermod -aG i2c $USER不过/dev/i2c-N是内核动态创建的重启后权限会重置。正经做法是写一个udev规则新建/etc/udev/rules.d/99-i2c.rules内容如下KERNELi2c-*, GROUPi2c, MODE0660保存后执行sudo udevadm control --reload-rules sudo udevadm trigger设备节点权限就稳定了。这一步在量产产品上很有用避免应用程序必须用root跑也让调试阶段省掉不少重复sudo。1.4 确认总线号和设备树关系多总线平台第一步要确认哪个总线对应哪路硬件。用i2cdetect -l列出所有总线$ i2cdetect -l i2c-0 i2c i2c-gpio I2C adapter i2c-1 i2c rk3x-i2c I2C adapter i2c-2 i2c rk3x-i2c I2C adapter这里的adapter名字一般会对应设备树里的节点。以Rockchip平台为例设备树里i2c1节点的compatible是rockchip,rk3x-i2c注册后就是i2c-1。总线号和设备树alias直接相关alias里i2c1 i2c1就决定了编号。这个对应关系搞反了后面所有读写都会扑空。2. i2cdetect扫描总线、发现设备这是排错第一课2.1 基本用法和输出解读i2cdetect干的事就是扫描总线上哪些I2C地址有设备响应。最基本的用法i2cdetect -y -r 1这里1是总线号-y表示跳过交互确认不加-y它会先问你一句Proceed anyway?-r表示使用SMBus read byte方式探测。执行后输出是一个从0x03到0x77的地址表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:这个表格里每一格对应一个7位I2C地址0x50出现在第5行第0列说明0x50这个地址上有一个设备应答了。如果某个格子显示UU表示该地址被内核驱动占用了i2cdetect发探测请求时能看到应答但出于安全考虑不显示具体设备这时候如果要操作它后面配合-f参数强制访问。2.2 探测原理和参数差异i2cdetect扫描的原理很简单对0x03-0x77范围内每个地址发一个探测请求看有没有ACK回来。但请求方式有讲究这也是为什么有-r和默认两种模式。默认模式用的是SMBus quick write向设备发送一个写命令。问题在于某些设备对quick write会触发内部状态变化甚至会往寄存器里写入垃圾数据。所以安全起见我几乎永远推荐加-r用read byte代替quick write对设备的副作用最小。还有-q参数也是SMBus quick write而且要配合-y使用。除非你明确知道总线上是什么设备并且需要测试quick write响应否则别用。曾经我就因为用-q扫描一个只读传感器直接把它搞进了异常状态还得断电重启才恢复。-a参数扫描整个7位地址空间0x03到0x77默认会跳过0x50-0x5f之间部分其实默认范围就是0x03-0x77默认扫描范围是0x03到0x77-a可以扫到0x00-0x7f包括一般不会用到的保留地址。很少需要用到但排查特殊设备时可以试试。2.3 扫描不到设备的排查顺序扫描不到设备是日常高频问题别一上来就怀疑i2cdetect有问题按照下面顺序排查总线号换个总线号试试先用i2cdetect -l确认平台上有几条总线然后逐一扫描。很多新手以为设备挂在i2c-1上实际dts里配的是i2c-0。设备地址手册上写的0xA0是8位地址包含读写位而i2cdetect表格里显示的是7位地址需要右移一位。比如手册写0xA07位地址就是0x50。这个换算错误太常见了。供电和上拉I2C设备的VDD没供上、SCL/SDA没有上拉电阻或上拉电阻没焊都会导致扫描不到。用示波器或者万用表量一下SCL/SDA是否都处于高电平如果有一根被拉低那就是硬件问题。电平转换器如果传感器是1.8V电平而主控是3.3V中间一定有个电平转换芯片比如PCA9306转换芯片的使能脚EN没拉起来SCL/SDA就是断开的。地址线和冲突很多I2C芯片的7位地址里有几位由硬件引脚决定比如A0/A1/A2。如果引脚没接对设备实际响应的是另一个地址。同一总线上两个设备地址相同也会造成冲突表现就是扫描结果不稳定或者读写互相干扰。3. i2cdump和i2cget把设备寄存器扒个底朝天3.1 i2cdump的访问模式b、w、s、i各有什么讲究i2cdump是用来连续读取设备寄存器内容的命令维测设备状态时比一个个读效率高得多。最基本的用法i2cdump -y -f 1 0x50默认模式是bbyte模式逐个字节读从0x00到0xff输出左边是地址中间是十六进制数据右边是ASCII字符显示。这有个好处任何I2C设备都能用b模式读通用性最强。但速度慢一遍dump要发256次读事务。实际使用中更常用的是其他模式模式命令适用范围bi2cdump -y 1 0x50通用逐字节读wi2cdump -y 1 0x50 -m w16位寄存器宽度的设备si2cdump -y 1 0x50 -m sSMBus设备支持block readii2cdump -y 1 0x50 -m i支持I2C block read的设备如EEPROM比如一个16位ADC芯片寄存器值是16位的w模式一次读两个字节效率和可读性都比b模式好。但w模式对只支持字节寻址的设备不适用读出来的数据是乱的。s和i模式依赖设备支持对应的block read协议不支持的话读取会返回错误。注意i2cdump会从0x00一直读到0xff如果设备只实现了0x00-0x0f的寄存器后面地址返回的数据一般是0xff不要误以为是真实数据。所以i2cdump输出里出现大段0xff时先看设备手册确认寄存器地址空间范围。3.2 指定范围读取和页读取i2cdump有一个很容易被忽略但很实用的参数-r start-end。比如一家传感器寄存器就0x00到0x2f没必要读256个字节你可以指定范围i2cdump -y -f 1 0x48 -r 0x00-0x2f这样只读这一小段速度快一倍输出也清爽。这个参数在调试EEPROM时更有价值因为AT24C系列EEPROM的页大小通常只有8或16字节跨越页边界连续读会出问题。用-i模式配合-r 0x00-0x07正好读一页数据不会触发跨越边界的地址回卷。3.3 i2cget单独读一个寄存器的最快方式如果只想看某一个寄存器i2cget比i2cdump更直接i2cget -y 1 0x48 0x00这个命令表示从总线1上0x48设备的0x00寄存器读一个字节输出形如0x2c。不带寄存器参数时i2cget会执行一次读地址操作read without register address有些设备把这个当作查询设备是否就绪的指令。加-w参数可以用16位字模式读取i2cget -y 1 0x48 0x00 -w这里有个细节i2cget的-w是读一个16位字字节序默认是大端高字节在前。如果你的设备是小端模式读出来的高低字节需要自己交换。i2cget输出不会帮你处理字节序这是很多人读数据对不上号的原因之一。4. i2cset与i2ctransfer写入操作才是真正的干活4.1 i2cset基本写法和多字节连续写读很容易写才是真正验证驱动和硬件逻辑的关键。i2cset的基本格式i2cset -y 1 0x48 0x01 0x2c意思是对总线1上的0x48设备往寄存器0x01写入数据0x2c。这个命令会发起一次I2C写事务先发送设备地址再发送寄存器地址最后发送数据。如果要写多个字节比如向EEPROM连续写入一串数据可以在数据位置依次排列i2cset -y 1 0x50 0x00 0x01 0x02 0x03 0x04但这里必须提醒i2cset的多字节写入本质是发起一次连续写事务设备的页大小限制照样生效。AT24C02页大小是8字节你从0x00开始写9个字节第9个字节就会回卷写到0x00把前面的数据覆盖掉。跨页写数据必须分成多次i2cset每次从页边界开始。4.2 masked write只修改指定位的原子操作这也是i2cset一个非常实用的功能-m参数实现读-改-写read-modify-writei2cset -y -m 0x0f 1 0x48 0x01 0x05这条命令的意思是先读0x48设备0x01寄存器的当前值把低4位0x0f掩码改成0x05其余位保持不变再写回去。这在配置设备时太常用了。比如某个控制寄存器的bit3-0是增益设置bit7是使能位你只想改增益不想动使能位用-m掩码就能安全地做局部修改避免先读后写时读到旧值再覆盖导致的临界问题。-m参数在驱动调试中的价值更高调试时经常要反复切换一个芯片的模式如果每次都写整个寄存器很容易把其他位的配置冲掉然后设备行为变得诡异。用掩码操作问题定位会清晰很多。4.3 i2ctransfer手动构造任意I2C时序i2ctransfer是i2c-tools家族里最晚加入却最强大的命令。它能让你完全控制一次I2C事务的每一个细节包括写多少字节、读多少字节、先写后读还是先读后写、要不要stop信号。基本格式i2ctransfer -y 1 w10x48 0x01 r2这条命令在总线1上对0x48设备先写1个字节0x01然后紧接着读2个字节。这一个组合就实现了读寄存器0x01的值2字节的完整事务中间没有stop信号I2C总线上表现为一次repeated start。很多传感器比如一些加速度计读取多字节数据时需要先发寄存器地址再连读i2ctransfer就是为这种场景设计的。如果要操作寄存器地址是16位的设备i2ctransfer -y 1 w30x50 0x00 0x10 0xff这里w3表示写3个字节后续跟0x00 0x10是16位寄存器地址0xff是要写入的数据。所有字节参数都是十六进制不需要加0x前缀加了也可以。4.4 为什么i2ctransfer比i2cset/i2cget更值得掌握我用i2ctransfer之后调试效率提升了一个档次原因有三点第一它可以精确模拟驱动代码里的读写逻辑。驱动里经常有regmap_read(dev, reg, val)这类调用你完全可以把驱动里的寄存器读写搬到命令行里验证先读当前值再写一个测试值再读回来确认整个过程和驱动实际行为一模一样。第二一条命令完成复合事务。i2cgeti2cset是两条独立命令中间有间隔如果要快速连续操作时间窗口对某些时序敏感的设备不够。i2ctransfer一条命令在同一个事务内完成组合读写不存在间隙。第三调试异常设备时可以构造特殊的时序组合。比如怀疑设备在某个写序列后进入异常状态可以用i2ctransfer精确发送那段序列复现问题。这在分析硬件bug时几乎是必须的。5. 综合实战用i2c-tool完整调试一颗AT24C02 EEPROM5.1 拿到陌生I2C芯片的调试顺序所有陌生I2C设备的调试都应该遵循一个固定顺序能帮你快速定位问题先扫描确认地址再读ID或厂商寄存器确认芯片身份然后读当前配置最后做最小的写操作验证。比如一颗AT24C02 EEPROMI2C地址由A2/A1/A0引脚决定全接地时是0x50第一步先扫描i2cdetect -y -r 1如果输出里0x50位置显示50说明芯片在总线上有响应硬件连接基本没问题。5.2 用i2cdump查看EEPROM内容和状态接下来用i2cdump看芯片当前存储了什么i2cdump -y 1 0x50 -i -r 0x00-0x07-i用I2C block read模式-r指定只读前8个字节。新出厂EEPROM一般全0xff如果看到其他数据说明里面写过东西。这一步先确认器件通电后能否正常读取数据排除供电和地址线问题。5.3 写入并验证用i2c set写一页然后做写测试。AT24C02页大小8字节从页边界0x00开始写满一页i2cset -y 1 0x50 0x00 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88注意EEPROM写操作需要时间典型5ms写入后不能立刻读否则读回来的还是旧值或者总线NACK。要等一小段时间再验证sleep 0.01 i2cdump -y 1 0x50 -i -r 0x00-0x07应该看到0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88整齐排列。如果读出来是0xff检查WP写保护引脚有没有接地。AT24C02的WP引脚接高电平的时候整个芯片只读不写i2cset执行时不会报错但数据写不进去。这是最典型的写无效问题。5.4 用i2ctransfer模拟驱动读写操作最后演示一下i2ctransfer怎么模拟驱动里的操作。驱动里经常这样写EEPROM先写寄存器地址再写数据。比如往地址0x10写一个字节0xABi2ctransfer -y 1 w20x50 0x10 0xabw2表示写2个字节0x10是EEPROM内部地址0xab是要写的数据。等写完再读回来i2ctransfer -y 1 w10x50 0x10 r1这条命令先写1字节内部地址0x10然后读1字节一个事务搞定等价于驱动的read operation。如果返回值是0xAB说明这次写入验证成功。5.5 实战中容易忽略的时序细节EEPROM调试中最容易忽略的是写周期。AT24C系列的写周期write cycle time通常是5ms这个时间内芯片内部在擦写外部对它发任何命令都会NACK。所以我刚才的例子里sleep了10ms再读。如果你发现写完立刻读返回errno 6ENXIO或者数据不对先怀疑是不是撞上了写周期加个延时再试。另外EEPROM I2C地址的引脚改变后要重新上电才能生效。有些板子用了GPIO来控制A2/A1/A0改了引脚状态后没断电扫描到的还是旧地址这个坑困扰过不少新手。检查硬件连接时最好用示波器确认SCL/SDA上确实有信号活动而不只凭i2cdetect的扫描结果。6. 高频故障排查我在i2c-tool使用中踩过的坑6.1 UU显示的地址用不了怎么办有时候i2cdetect会显示UU而不是地址意思是这个地址被内核里的某个驱动占用了。内核驱动和用户态工具争用I2C设备时系统是禁止用户态再去访问的。要用i2c-tool访问可以加-f参数强制操作i2cdetect -y -f 1 i2cdump -y -f 1 0x50 i2cget -y -f 1 0x50 0x00-f的意思是把由内核驱动管理的设备强制纳入用户态访问范围。但强烈建议只在调试时用你正在操作的芯片如果同时有内核驱动在跑两边同时对芯片发命令轻则数据错乱重则把寄存器状态搞坏。更稳妥的办法是先把对应设备从设备树里disable掉让内核不加载驱动再随便用i2c-tool玩。6.2 总线上有设备但i2cset写不进去这个坑我帮人排查过很多次症状是i2cdetect能扫到地址i2cget也能读出数据但是i2cset执行后没有任何效果。排查链路从这几个方向展开确认设备是不是只读设备。像AT24C02的WP脚拉高、某些传感器的高位地址是只读寄存器、PMIC的写寄存器需要特殊口令解锁这些情况i2cset都会成功但数据不变。确认写地址的字节序和寄存器地址宽度。设备手册里的寄存器地址是16位你按8位写写入的就是错误寄存器。这时候i2cset不会报错设备会把你的数据当成寄存器地址的一部分解析表现出写不进去。确认I2C总线上有没有MUX如TCA9548A。如果有MUX需要先通过i2cset选中对应通道否则后面的读写都是总线上另一个通道的响应。很多板子挂在同一个I2C控制器下的多路从设备就是靠MUX区分的。6.3 SDA/SCL被拉低总线锁死总线死锁是I2C调试里最令人头疼的问题。现象是i2cdetect报错甚至执行任何i2c命令都返回I/O error用万用表量SCL或SDA发现一直是低电平。造成这种情况的常见原因有两个一是从设备在不满意的状态下等主机的stop信号没等到二是主控GPIO配置错误导致引脚被拉死。软件层面的排查顺序是先确认是不是从设备挂死。个别芯片在I2C通信中途断线后会进入异常状态一直把SDA拉低不放。简单复位方法是给整个板子断电重新上电大多数情况下能恢复。如果不想整板断电可以把SCL手动翻转几个周期让从设备退出异常状态做法是用gpioset命令或写GPIO sysfs直接操作SCL引脚模拟时钟# 假设SCL是GPIO49先配置为输出 gpioset gpiochip0 491 gpioset gpiochip0 490 # 重复若干次后SDA应该释放这招救过我两次不过要注意手动翻时钟之前必须确保SDA也被释放为开漏高阻否则会拉大电流。最稳妥的恢复方式依然是断电重启如果重启后仍然死锁基本可以断定是硬件问题比如上拉电阻没焊、引脚短路、电平不匹配。6.4 地址换算错误7位和8位地址到底怎么算I2C地址是7位的但在设备手册和数据手册里经常看到的是8位表示法包含读写位。比如一个芯片手册写从机地址0xA0实际上7位地址是0x50。因为0xA0的低位是0右移一位就是0x50。i2cdetect显示的是7位地址0x50i2cget、i2cset、i2ctransfer参数里的地址也是7位。这个换算关系我见过无数人搞错尤其在拿着手册对着i2cdetect输出核对地址的时候。记住一个原则i2c-tools所有命令的地址参数都是7位地址不需要左移。如果你想用8位地址0xA0直接写0x50就行。这个错误导致扫描不到设备或者读写失败的概率比硬件故障还高。6.5 读寄存器返回0xff该怎么分析0xff这个值在I2C调试里出现频率极高但含义不一定相同。首先要判断是设备真的返回了0xff还是总线NACK之后工具填充的假数据。一个快速区分方法对同一个地址连续读几次如果每次都返回0xff且无报错一般是设备真的在响应如果时而0xff时而又能读出正常数据大概率是接触不良或者时钟太快导致数据线采样错误。另一种常见情况是寄存器本身是保留位或只写位。比如某些传感器芯片读取未实现的高位地址会返回0xff这是正常现象不要怀疑硬件。还有一种情况是设备处于低功耗模式比如睡眠模式时I2C接口只响应地址不响应寄存器访问读任何寄存器都返回0xff。遇到这种情况查手册里唤醒设备的操作序列往往需要往特定寄存器写一个唤醒命令才能恢复正常访问。6.6 总线速度不匹配和电平问题i2c-tools的读写速度由I2C控制器的驱动决定标准模式100kHz快速模式400kHz个别平台还支持1MHz。如果总线上挂着一个旧设备只支持100kHz而控制器配置成400kHz表现就是扫描偶尔成功、读写随机失败、数据出现错位。排查方法是检查控制器驱动配置或者用示波器实测SCL频率是否符合期望。电平不匹配问题更隐蔽。现在很多板子混合了3.3V和1.8V器件I2C总线如果经过电平转换两边的高电平阈值不同。有时候设备虽然供电正常但SCL/SDA的电压达不到器件VIH阈值就会出现扫描到但读写失败的诡异现象。这时候别纠结软件直接用示波器量SCL和SDA的高电平幅值低于设备手册VIH阈值就要检查上拉电阻的电平连接是否正确。7. 几个能直接抄的小技巧最后分享几个我在实际调试中摸索出来的用法都属于手册上不会重点讲但实战价值很高的技巧。第一个是批量读寄存器并格式化输出。调试传感器时经常要看一组寄存器的变化趋势写个for循环就能实现for r in 0x00 0x01 0x02 0x03; do echo -n reg $r: ; i2cget -y 1 0x48 $r; done这个脚本输出比i2cdump清爽尤其寄存器地址不连续时更实用。第二个是用i2ctransfer加-shell通配符做多地址遍历。排查设备挂哪个地址时可以一条条试for addr in 0x40 0x41 0x42 0x48 0x50; do echo $addr ; i2ctransfer -y 1 w1$addr 0x00 r1; done这个命令会逐个地址尝试读寄存器0x00能快速确认设备实际挂在哪个地址、返回数据是什么。比手动改数字高效太多。第三个是i2cdetect扫描配合-w参数经常漏设备的问题。某些芯片在扫描时因为内部上电时序还没完成首次扫描不到过几秒再扫一次就出现了。如果新上板扫描为空别急着怀疑硬件先sleep 5sleep 5 i2cdetect -y -r 1这个小技巧帮我避免过好几次误判。 第四个是把i2c-tools和逻辑分析仪/示波器配合使用。手动执行i2cget的时候用示波器抓SCL/SDA波形能直观看到设备响应情况。有一次我自己调一个时序有问题的芯片就是用i2cget配合示波器抓到了设备在第9个时钟后没有回复ACK才知道是芯片处于忙状态才去找它忙的原因。工具再强大最终还是要和硬件测量手段结合这样排查问题才不会被软件现象误导。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价