ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

OpenHarmony I2C驱动开发实战:从协议原理到故障排查

OpenHarmony I2C驱动开发实战:从协议原理到故障排查 我在这块板子上调了整整两天OLED 屏幕就是不出字i2cdetect扫描半天也只看到一堆UU最后发现不是地址错了而是驱动把从机地址当成 8 位地址发了出去。这种I2C 总线的坑做 OpenHarmony 开发的基本都会踩一轮。I2C 看起来只有两根线但正是因为它“简单”一旦出错从物理层到驱动层每一环都可能藏问题。这篇文章我打算按照实际项目推进的顺序来写先讲清楚 I2C 一次通信到底是怎么完成的再拆 OpenHarmony 里从应用层到内核的软件调用链然后给一个完整的实战驱动案例最后把我在设备上遇到过的高频故障和排查思路全部摊开。做 OpenHarmony 系统移植、外设驱动开发或者只是想搞懂 I2C 协议细节的中高级开发者这篇都能直接拿来当排障手册用。1. 从一根SDA一根SCL说起I2C一次通信到底发生了什么很多人写 I2C 驱动是照着厂商驱动抄抄完能用就再也不回头看了。但排障的时候不懂底层时序非常吃亏。比如波形上明明有数据从机就是不应答——你不清楚 ACK 机制根本不知道问题出在地址格式还是从机没上电。1.1 物理层漏极开路、上拉电阻和“广播式”寻址I2C 物理上只有两根线SDA数据线和 SCL时钟线。两线都是开漏结构也就是说设备只能把线拉低不能主动拉高。高电平完全靠外部上拉电阻提供。所以一个合格的 I2C 电路板上SDA 和 SCL 必须接上拉电阻典型值是 4.7kΩ对应 5V/3.3V 电平、短距离、低速场景。如果总线上挂的设备多、走线长总线电容变大就得用 2.2kΩ 甚至 1kΩ。判断方法很简单用示波器看上升沿如果 SDA 从低到高的边沿过于平缓明显是缓慢爬坡)那就是上拉电阻太大换更小的电阻能直接改善时序。我见过最典型的故障是 I2C 在 400kHz 下时不时丢字节降到 100kHz 就正常最后测出来就是上拉电阻取值偏大、边沿时间过长。物理层的特点决定了寻址方式所有从机都挂在同一条总线上主机发数据时每帧开头带一个 7 位从机地址从机在地址匹配后才会应答。这也是 I2C 能“两线挂一堆设备”的根本原因——靠地址区分而不是靠片选引脚。1.2 时序门道不止 start/stopACK、时钟延展和重复起始条件一次完整的 I2C 传输包含这些关键阶段起始条件STARTSCL 为高电平时SDA 从高拉低。地址帧8 个时钟周期先发 7 位地址第 8 位是读写标志0写1读。应答位ACK第 9 个时钟周期。SDA 被从机拉低表示“我收到了”。数据帧每字节后跟一个 ACK/NAK。停止条件STOPSCL 为高时SDA 从低拉高。这里有两个细节特别容易坑人。第一地址格式。大部分数据手册写的地址是 7 位比如 BH1750 是0x23写入字节是0x460x23 1 | 0读取字节是0x47。很多驱动框架内部会帮你左移但用ioctl直接收发时i2c_msg.addr填的是 7 位地址还是 8 位地址不同驱动版本、不同桥接工具处理都不一样。我此前就是在这上面栽的跟头。第二个是时钟延展clock stretching。某些从机尤其是一些传感器和 EEPROM在内部处理数据时会把 SCL 拉低让主机等着。这是合法的主机控制器一般会自动处理。但在逻辑分析仪上看到 SCL 低电平时间明显拉长时不要以为是故障先看它是否在 ACK 位之后持续拉低——如果是说明从机在“忙”你只需要等它释放即可。真正的问题是如果主机控制器的 I2C 外设在硬件上不支持时钟延展或者等待超时被设得很短就会出现 EIO 错误。此时把速率从 400kHz 降到 100kHz 往往能直接解决。1.3 从 EEPROM 读一字节完整帧拆解我把一次“向 AT24C02 的地址 0x10 写入 0x5A再读回来”的操作拆开看能很直观地理解上面这些概念。整套动作其实由三次总线事务完成步骤事务内容波形上的表现1. 写寄存器地址START - 0xA0 W - ACK - 0x10 - ACK - STOP两次起始但中间没停止2. 写数据START - 0xA0 W - ACK - 0x5A - ACK - STOP常规写3. 读数据START - 0xA0 W - ACK - 0x10 - ACK -重复起始- 0xA1 R - ACK - (读字节) - NAK - STOP第三次事务里出现重复起始第 3 步里的“重复起始条件restart”是 I2C 协议里比较难理解的部分。它在没有发出 STOP 的情况下再次把 SDA 拉低来发起新事务作用是在同一总线占用期间把方向从写切换成读避免中途释放总线。EEPROM、大多数传感器的读操作都是这种模式。如果用逻辑分析仪抓波形时看到 START 之后没有 STOP 又出现一个 START别觉得奇怪那是标准的 repeated START。2. OpenHarmony里的I2C软件栈应用到寄存器的调用路径I2C 硬件协议讲完接下来是 OpenHarmony 这套系统里的软件链路。很多新手拿到开发板纠结“用 HDI 还是直接操作 /dev/i2c-x”其实两条路最终汇合到同一个内核 I2C 子系统只是你所在的层级不同。2.1 三层结构应用 / HDI 服务 / 内核驱动OpenHarmony 的 I2C 访问路径大致是三层应用层通过 HDI APII2cOpen、I2cTransfer或标准文件接口open(/dev/i2c-x)发起访问。系统服务层HDI I2C 服务drivers/peripheral/i2c把上层请求转换为内核可识别的数据。内核层最终调用 Linux 内核的 I2C 设备驱动和控制器驱动也就是i2c-core、i2c-dev以及具体 SoC 的 adapter。HDI I2C 的核心结构是I2cMsg它在很多代码里长这样struct I2cMsg { uint32_t addr; /* 从机地址7位格式读写标志用flags表示 */ uint32_t len; /* buf长度 */ uint8_t *buf; /* 数据缓冲 */ uint16_t flags; /* 0表示写1表示读或按位组合标记 */ };调用方式很直接#include i2c_if.h int32_t i2cFd I2cOpen(0); /* 打开I2C控制器0 */ struct I2cMsg msgs[1]; uint8_t writeBuf 0x01; msgs[0].addr 0x23; /* BH1750的7位地址 */ msgs[0].flags 0; /* 写 */ msgs[0].len 1; msgs[0].buf writeBuf; I2cTransfer(i2cFd, msgs, 1); /* 执行一次事务 */ I2cClose(i2cFd);实际项目里不要把地址和寄存器映射写死在应用层。我通常会在驱动层封装一个I2cReadReg(devAddr, regAddr, buf, len)和I2cWriteReg(devAddr, regAddr, value)HDI 层只负责传输这样上层业务代码可读性会好很多。2.2 HDF 驱动框架设备如何被“配置”出来OpenHarmony 的 HDFHardware Driver Foundation采用配置文件.hcs声明硬件资源代码实现驱动逻辑的模式。要做 I2C 设备驱动通常不是去写一个字符设备而是写一个 HDF 驱动在配置里指定deviceMatchAttr然后把平台层的 I2C 控制器资源和你的驱动绑定起来。在模块加载时HDF 会找到配置文件中对应的device_info节点通过I2cOpen(controllerId)拿到控制器句柄之后所有 I2C 操作都是围绕控制器 ID 做的。相比直接调用内核 ioctlHDF 的优势在于统一了热插拔、电源管理和权限管控设备节点由框架统一创建应用层甚至不需要知道/dev/i2c-x的存在。我自己的习惯是系统服务类组件都走 HDF 驱动比如传感器、触控、显示面板这些需要常驻的纯粹的调试验证工具比如读写 EEPROM 的小工具直接用它对应的ioctl更快因为不需要写完整驱动框架代码。2.3 内核侧i2c-dev 和 ioctl 收发链路如果你的开发板内核保留了CONFIG_I2C_CHARDEV那/dev/i2c-0、/dev/i2c-1这些节点就是给上层用的。用户态通过文件操作加ioctl访问核心结构来自linux/i2c-dev.h#include fcntl.h #include linux/i2c-dev.h #include sys/ioctl.h int fd open(/dev/i2c-2, O_RDWR); if (fd 0) { // 错误处理 } /* 单次消息写一个字节 */ struct i2c_msg msg { .addr 0x23, .flags 0, .len 1, .buf (__u8[]){ 0x01 }, }; struct i2c_rdwr_ioctl_data data { .msgs msg, .nmsgs 1, }; if (ioctl(fd, I2C_RDWR, data) 0) { perror(ioctl); } close(fd);注意这里的msg.addr是 7 位地址内核驱动会在发送时自动左移一位并拼上读写位。但也有些模拟 I2C 的工具或者老驱动会直接接受 8 位地址。调试时如果发现逻辑分析仪上抓到的地址是0x92而不是0x49说明你填错了格式。3. 实战在OpenHarmony上驱动一块BH1750光照传感器排完前面的原理我们进入实操。我拿最常见的 BH1750 光照传感器做例子因为它的寄存器少、读时序典型非常适合把 I2C 驱动流程走一遍。板子方面教程以 OpenHarmony 标准系统的用户态程序为运行环境底层 I2C 控制器用/dev/i2c-节点或 HDI 均可逻辑是一致的。3.1 硬件连接GPIO、上拉电阻和供电细节BH1750 模块一般是 5V/3.3V 供电板上自带 I2C 接口但很多模块板上已经焊好了上拉电阻。真正要留意的是电压域如果SoC的 I2C 是 1.8V 电平而传感器模块是 5V 电平那要么用电平转换芯片要么确认模块支持双向电平转换否则轻则读乱码重则烧坏引脚。接线时 SDA 和 SCL 尽量用同一组控制器下的两个引脚别跨组因为不同组可能是不同 adapter代码里打开的控制器号要对上。我提供的连接表BH1750引脚开发板引脚说明VCC3.3V具体电压以模块手册为准GNDGND共地必须接SDAI2C-2 SDA示例用控制器2SCLI2C-2 SCL时钟线如果开发板没有上拉外部对 SDA/SCL 各接一个 4.7kΩ 到 VCC。这一项漏掉总线完全不会工作。3.2 驱动代码I2cTransfer读写封装先在驱动层把读写原语封装起来。用 HDI 版本写一遍后面应用层调用会非常省事#include i2c_if.h #include stdio.h #include stdlib.h #include string.h #define BH1750_ADDR 0x23 /* 7位地址 */ #define I2C_BUS 2 /* 控制器号 */ static int32_t g_i2cFd -1; int bh1750_init(void) { g_i2cFd I2cOpen(I2C_BUS); return (g_i2cFd 0) ? -1 : 0; } static int bh1750_write_cmd(uint8_t cmd) { struct I2cMsg msg; msg.addr BH1750_ADDR; msg.flags 0; /* 写 */ msg.len 1; msg.buf (uint8_t *)cmd; return I2cTransfer(g_i2cFd, msg, 1); } /* 连续读两个字节对应BH1750的测量结果 */ static int bh1750_read_lux_raw(uint8_t *data, uint8_t len) { struct I2cMsg msg; msg.addr BH1750_ADDR; msg.flags 1; /* 读 */ msg.len len; msg.buf data; return I2cTransfer(g_i2cFd, msg, 1); } int bh1750_get_lux(float *lux) { uint8_t raw[2]; if (bh1750_write_cmd(0x01) ! 0) { /* 上电 */ return -1; } if (bh1750_write_cmd(0x10) ! 0) { /* 连续H分辨率模式 */ return -1; } usleep(180 * 1000); /* 两次转换之间至少等待180ms */ if (bh1750_read_lux_raw(raw, 2) ! 0) { return -1; } *lux ((raw[0] 8) | raw[1]) / 1.2f; /* 高分辨率模式1lx对应数据1.2 */ return 0; }这段代码有几个值得说清楚的地方。第一BH1750 芯片地址有两种0x23和0x5C由 ADDR 引脚高低决定。如果你的模块不通先确认地址引脚接法别默认是 0x23。第二写命令时我们只发一个字节没有寄存器地址——BH1750 把所有动作都定义为命令这点和大多数带寄存器地址的传感器不一样。第三读数据时只发从机地址读位从机会直接吐出两个字节的测量结果不需要先写寄存器地址因为测量数据是自动连续输出的。3.3 应用层调用与数据解析把上面的封装放到一个动态库或 HDF 驱动里业务层调用就是这么简单float lux 0.0f; if (bh1750_init() ! 0) { printf(i2c open failed\n); return -1; } if (bh1750_get_lux(lux) ! 0) { printf(read failed\n); return -1; } printf(lux %.1f\n, lux);第一次跑通后建议马上用逻辑分析仪抓一次完整波形。你应该能在波形上看到START - 0x231|0 - ACK - 0x01 - ACK - STOP然后是0x231|0 - 0x10最后是读方向的0x231|1 - ACK - 数据 - 数据 - NAK - STOP。如果波形和这个不一致说明驱动或接线有问题立刻按第四部分排查。这里还想补充一点返回值的判空。I2cTransfer的返回值不是简单的成功/失败标志而是实际传输的消息数量。如果一次传了 2 个 msg返回值应该也是 2。有些驱动判断if (I2cTransfer(...) ! 0)这其实是偷懒严格应该判断是否等于nmsgs。否则第二个读消息失败时第一个写消息成功返回值是 1你以为是成功数据却是错的。3.4 换 SSD1306 OLED 或 EEPROM代码要改哪里很多项目里读过光照传感器后又想去点亮 SSD1306 OLED 或给 AT24Cxx EEPROM 写数据。它们本质都是 I2C 设备你只需要把握三点设备地址SSD1306 一般是 0x3CAT24C02 一般是 0x50。寄存器或命令组织方式SSD1306 是“控制字节数据”模式发送数据时要先发一个控制字节0x00 表示后续为命令0x40 表示后续为显示数据EEPROM 是先写一个 8 位寄存器地址再写数据。读写方向的切换EEPROM 读需要先写寄存器地址再用 repeated START 切换读方向SSD1306 在初始化时基本全是写操作。所以驱动封装别写死“先写地址再读”的流程而是提供两个原语任意组合。我的经验是单独维护一个i2c_common.c里面就放i2c_write(addr, buf, len)、i2c_read(addr, buf, len)和i2c_write_then_read(addr, wbuf, wlen, rbuf, rlen)三个函数绝大部分传感器的数据手册都能用它表达。4. 排障手册I2C挂不上的完整排查链路设备不通的时候最忌讳的就是反复改代码重新编译而不去看现象。我强烈建议先把排障流程固定下来按顺序过一遍。下面的几个类别覆盖了我实际遇到过的绝大多数故障。4.1 第一类问题检测不到设备——地址、电平、上拉的排查顺序故障现象是i2cdetect或 HDI 扫描函数扫不到任何从机地址。按下面顺序排查用万用表量电压SDA 和 SCL 在不通信时应该是上拉后的高电平。如果量出来是 0V说明上拉电阻没接、烧断或者SoC引脚配置成了其他复用功能。确认设备地址把数据手册翻出来确认地址引脚复位默认值和实际接线一致。比如 GT911 触控芯片I2C 地址由 INT 引脚时序决定有的板子上默认 0x5D有的带 0x14没有统一答案必须实测。核对驱动里填的地址位数先看逻辑分析仪抓到的地址字节。抓到的首字节如果是0xA1而手册说地址是0x50那多半你传入了 8 位地址驱动又帮你左移一次。正确的应该是0x50 1。测量从机供电和上电时序很多从机需要先上电稳定后再被访问。SSD1306 OLED 需要 RST 引脚按手册时序拉低再拉高GT911 甚至要先让 INT 引脚完成特定的初始化时序。如果上电时序不对从机根本不参与总线仲裁当然扫不到。用示波器抓 START 条件确认主机确实发出了 START。有些 I2C 控制器在 open 时配置错误导致引脚没有正确映射SDA 完全不动。这一步能分清“主机没发”和“从机没回”。i2cdetect本身也有坑它默认做的是读探测对某些只支持写、或者写时序敏感的器件可能无效。可以加参数-y -r强制用读模式或者手动用i2ctransfer发一个写字节再看看从机是否 ACK。4.2 第二类问题能检测到但读取乱码——速率、时序、寄存器映射能扫到地址说明从机 ACK 了但数据不对这种情况通常不是“通不通”的问题而是“对不对”的问题速率过高导致边沿混乱总线电容大、上拉电阻大400kHz 下 SDA 上升沿太慢导致主机采到错误的电平。降到 100kHz 试试现象消失就是速率问题。这是最典型的“看着通读着乱”。寄存器地址映射理解错误比如某些传感器的寄存器只有 8 位你偏要按 16 位寄存器地址写后续所有数据都会偏移。字节序问题读取传感器原始数据时大端/小端搞反解析出来就完全不对。属于代码问题而不是 I2C 通路问题。排查工具这里有一个特别推荐OpenHarmony 和 Linux 都支持i2ctransfer它可以直接拼任意时序。比如读 BH1750 一个字节i2ctransfer -y 2 w10x23 0x10 r2这条命令的意思是在总线 2 上向地址 0x23 写入 1 个字节 0x10然后读 2 字节。命令敲通后再用自己的驱动复现同样时序就能定位是协议问题还是驱动代码问题。还有个容易忽略的上电后立即读一次可能失败第二次就好了。部分传感器上电后需要几十毫秒才能 I2C 就绪驱动里加一个上电延时比什么都管用。BH1750 在上电命令之后手册明确写了 max 转换时间你延时不够就开读必然读到 0xFF 或 0x00。这种问题不算总线故障是器件时序要求需要在驱动里做严格的状态机。4.3 第三类问题总线锁死——SDA低电平卡死、休眠唤醒和GT911案例这是最让人崩溃的一类前天还正常的设备今天一上电 SDA 就一直为低无论怎么发起始条件都不行。原因一般有三个第一个原因从机把 SDA 拉死不释放。常见于从机在事务中途接收到非法序列进入异常状态。处理手段很粗暴但有效如果 SoC 的 I2C 控制器支持强制复位把控制器模块 reset 一下如果支持 GPIO 模拟把 SDA/SCL 引脚手动拉高或拉低几个周期模拟 9 个时钟脉冲“伪时钟”可以骗过卡住的从机让它释放 SDA。这个方法在 EEPROM、传感器里屡试不爽。第二个原因休眠唤醒后控制器状态没恢复。开发板进入休眠后I2C 控制器外设可能被断电或时钟被关闭唤醒后如果不重新初始化寄存器就是“看起来接口还在实际不工作”。解决方法是驱动在休眠回调里保存必要状态唤醒后重新调用 I2C 控制器初始化或者直接对设备节点重新 open 一次。OpenHarmony 的 PM 框架里I2C 平台驱动一般会处理这部分但你自己写的 HDF 外设驱动要记得在Rebind或电源恢复回调里把设备重新初始化。第三个原因GT911 这类特殊器件上下电时序不规范。网上很多人抱怨“gt911 i2c通信失败”一大半都是 INT 和 RST 引脚时序没做好。GT911 要求上电后先拉高 RST再在 INT 引脚上完成地址选择动作。如果你只是把它当普通 I2C 探测很容易在开机瞬间就发起了访问芯片还没完成内部准备总线直接被拉死。正确做法是在驱动初始化时严格按规格书控制 RST 和 INT 电平时序然后再去探测 I2C 地址。如果总线已经被锁死最快速的一招是断开从机电源或者把从机从总线上摘下来。等 SDA 恢复高电平再重新上电从机。物理断开往往比任何软件复位都靠谱。4.4 工具逻辑分析仪和全地址扫描怎么用才不浪费时间排障里最值得投资的工具是逻辑分析仪哪怕是几十块钱的 8 通道采样设备也够用。抓 I2C 时注意三个设置项采样率不低于 4 倍总线速率推荐至少 1MHz 采样。触发条件设为 SDA 下降沿这样一按开始就能抓到 START 条件。协议解析选 I2C地址格式选 7 位大多数工具默认按 RAW 显示你要手动切到解码视图。抓到波形后优先看三个位置START 之后的第一个字节、第 9 个时钟沿的 ACK、STOP 之前最后一个字节。这三个位置能覆盖 90% 的问题。关于“全地址扫描”还有个小技巧。扫描时如果看到两个连续地址都显示设备存在比如0x50和0x51那很可能不是两个设备而是同一个设备的地址线被浮空或校准正常或者是驱动在地址上多做了一次移位。这种地址漂移现象我调试时见过很多次先怀疑代码再怀疑硬件。5. 再往深走一点自由数据模式、总线扩展和选型取舍基础通了之后很多人会问I2C 能不能一口气把一个寄存器的写和另一个寄存器的读放在同一次调用里总线设备太多地址打架怎么办以及 I2C、SPI、UART 到底该怎么选这些问题在真实项目里会直接影响代码架构。5.1 “自由数据模式”用I2cMsg数组拼任意读写序列OpenHarmony HDI I2C 的I2cTransfer支持一次传多个I2cMsg内核会按数组顺序在总线上依次生成对应的时序段。这就是被大家称为“自由数据模式”的底层能力——你可以把若干个独立的读写动作拼成一条复杂事务而不需要多次打开关闭设备。一个常见的组合是“先写寄存器地址再读数据”这就是 EEPROM 和大多数传感器的标准读流程struct I2cMsg msgs[2]; uint8_t reg 0x00; /* 第1条写寄存器地址 */ msgs[0].addr BH1750_ADDR; msgs[0].flags 0; msgs[0].len 1; msgs[0].buf reg; /* 第2条读数据内核会自动发送repeated START */ msgs[1].addr BH1750_ADDR; msgs[1].flags 1; msgs[1].len 2; msgs[1].buf raw; if (I2cTransfer(fd, msgs, 2) ! 2) { /* 错误处理 */ }如果不需要 repeated START而是两条独立事务中间带 STOP那就拆成两次I2cTransfer调用或者把 flags 设置为带 STOP 的变体。I2cMsg的flags字段在 OpenHarmony 里主要区分读写方向但如果你在底层 ioctl 里使用 Linux 的i2c_msg还能见到I2C_M_NOSTART、I2C_M_STOP这些细粒度标记。组合这些 flag你可以构造出非常自由的时序包括在中间插入任意长度的停止或重复起始条件。我在调试不熟悉的器件时习惯先用这种 free-form 方式把 datasheet 里的每个时序图都验证一遍再封装成正式驱动函数。这样能把“器件要求”和“驱动框架”彻底分离排查起来边界清晰。5.2 设备太多怎么办I2C多路复用器和扩展器当多颗同型号传感器地址相同或者总线电容大到速率上不去时有一个成熟方案I2C 复用器比如 TCA9548A。它本身是一个 I2C 从机内部有多组子通道主机先向它写一个字节bit mask 选择通道之后所有 I2C 访问只在选中的子总线上生效。典型用法是uint8_t channel_mask 0x01; /* 打开通道0 */ // 向 TCA9548A 写命令其地址默认0x70 // 然后读写挂在通道0上的传感器这解决了两个问题一是地址冲突二是一条总线上设备太多导致的总线电容超标。每次切换通道时重新写掩码操作完成后可以关闭通道写 0x00减少不必要的漏电流。如果你的板子有多路传感器且地址都相同不要试图改传感器地址——大多数芯片地址引脚只有一两个能拼的组合很有限直接上复用器最省事。5.3 什么时候别用I2C和SPI、UART的选型对比做系统级项目选总线不能只看“能不能用”。我把三种总线的关键差异列成一个表项目初期就能快速决策总线线数速度典型值寻址/片选适合场景不适合I2C2100kHz~1MHz7位地址广播板内低速传感器、EEPROM、触控、OLED高吞吐、大数据量、跨板长线SPI4CS1MHz~几十MHz片选引脚Flash、SD卡、显示屏、ADC采样多设备时引脚爆炸无标准应答UART2115200bps~数Mbps点对点串口日志、蓝牙模块、GPS、长距离外设多设备需额外总线管理I2C 的核心优势是布线少且自带应答机制主机发出去能立刻知道从机有没有收到。这是 SPI 不具备的——SPI 没有 ACK数据发出去后不知道对端是否成功接收只能靠读回校验。所以可靠性要求高的传感器场景我倾向 I2C大流量屏显和数据写入倾向 SPI需要跨板通信的选 UART 或直接上工业总线。遇到“I2C 不稳定”时也别只想着调总线参数换一条总线往往是更工程化的解法。我做过一个项目I2C 线走了十几厘米跨过排线连接器400kHz 下无论如何都丢数据降到 100kHz 又满足不了刷新率最终把传感器换成了 SPI 接口问题一次性解决。这种取舍比在 I2C 上硬扛要划算得多。写在最后我在 I2C 上踩过的坑几乎都可以归结为对“每个字节的时序”缺乏敬畏。地址格式差一位、上拉电阻差几 kΩ、唤醒时序差几十毫秒都可能让你在逻辑分析仪前耗上整整一下午。如果你现在正被某个 I2C 设备折磨我的建议是先别改代码拿万用表量两线电压拿逻辑分析仪抓一次完整通信把“主机到底发了什么、从机有没有回 ACK”这个事实搞清楚再动手改驱动问题基本能在一小时内定位。I2C 不是一个华丽的协议但它足够简单可靠只要每一环都按规格来它就会非常稳定。
返回列表