I2C总线协议深度解析:从数据格式、操作模式到寄存器级实战
1. I2C总线协议:从理论到寄存器级实战
搞嵌入式开发这么多年,I2C总线是我打交道最多的通信协议之一。从早期的EEPROM、RTC芯片,到现在的各种传感器、触摸屏控制器,I2C的身影无处不在。它用两根线(SDA数据线和SCL时钟线)就能搞定多设备通信,硬件设计简单,成本也低,确实是嵌入式系统的“万金油”。
但说实话,很多开发者对I2C的理解还停留在“调用库函数”的层面,一旦遇到通信失败、数据错乱或者多主冲突的问题,往往就抓瞎了。我见过不少工程师,调I2C全靠“玄学”——改改上拉电阻、调调延时,运气好就通了,运气不好就卡在那里。这其实是因为没有真正吃透I2C的底层机制,特别是那些关键寄存器的配置和状态位的含义。
今天我就结合TI(德州仪器)某款处理器的I2C模块手册,把I2C从数据格式、操作模式到寄存器配置的每一个细节都掰开揉碎了讲清楚。我会用最直白的语言,告诉你每个比特位到底在干什么,为什么要这么设置,以及实际调试中会遇到哪些坑。无论你是刚接触I2C的新手,还是想深入理解寄存器操作的老鸟,这篇文章都能给你带来实实在在的收获。
2. I2C数据格式深度解析:不止是0和1
很多人觉得I2C的数据格式很简单,不就是起始位、地址、读写位、数据位和停止位吗?但魔鬼藏在细节里。不同的数据格式直接决定了你的通信效率和设备兼容性。
2.1 基础数据帧结构:每个脉冲都有意义
先看最基础的8位数据格式传输。手册里的图18-7展示了一个典型的数据传输过程,但光看图不够,你得理解每个信号边沿的含义。
SCL(时钟线)和SDA(数据线)的配合:I2C的所有数据都是在SCL为高电平时采样,在SCL为低电平时变化的。这意味着,SDA线上的每一个数据位,都对应SCL线上的一个完整时钟脉冲。发送方在SCL低电平期间准备好要发送的数据位(拉高或拉低SDA),然后在SCL上升沿到来时,这个数据位就已经稳定在SDA线上了,接收方在SCL高电平期间去读取这个值。在SCL下降沿之后,发送方才能改变SDA的状态,为下一个数据位做准备。这个“高电平采样,低电平变化”的规则是I2C时序的基石,任何违背这个规则的硬件或软件操作都会导致通信失败。
MSB优先传输:I2C规定数据总是最高位(MSB)先发送。比如你要发送一个字节0xA5(二进制10100101),那么在SDA线上出现的第一个比特就是最高位的1,然后是0,依此类推,最后才是最低位的1。这一点在编写底层驱动或者调试逻辑分析仪波形时要特别注意,别把字节顺序看反了。
数据长度灵活可调:手册里提到,I2C模块支持1到8位的数据值,通过ICMDR寄存器中的BC(Bit Count)字段来配置。这意味着你不仅可以传输标准的8位字节,还可以传输7位、6位甚至1位的数据。这个特性在某些特定场景下很有用,比如传输一个只需要4位表示的BCD码,或者与某些非标准设备通信时。但请注意,通信双方必须事先约定好数据位的长度,否则接收方会按照自己设定的位数去解析,必然导致数据错乱。
2.2 7位寻址格式:最常用的标准模式
7位寻址是I2C最经典、使用最广泛的模式。它的帧结构非常清晰:起始条件(S)之后,第一个字节的前7位是从机地址,第8位是读写方向位(R/W),然后是一个应答位(ACK)。
地址与方向位的组合:这个7位地址范围是0x00到0x7F(理论上128个地址),但0x00通常被保留为“广播呼叫”地址,0x01到0x07以及0x78到0x7F也各有用途,所以实际可用的普通设备地址并不多。这也是为什么I2C总线上经常出现地址冲突的原因。读写位为0表示主设备要写数据到从设备,为1表示主设备要从从设备读数据。这里有个常见的理解误区:这个“读/写”是站在主设备的角度看的。主设备发“写”(0),意味着它要当发送器;主设备发“读”(1),意味着它要当接收器。
应答机制(ACK)的奥秘:每个字节(包括地址字节和后面的数据字节)传输完毕后,都会跟一个额外的时钟周期用于应答。接收设备(对于地址字节,就是被寻址的从机;对于数据字节,就是当前的数据接收方)在这个时钟周期内,需要将SDA线拉低,表示“我收到了”(ACK)。如果接收设备没有拉低SDA(即SDA保持高电平),这就是一个“非应答”(NACK)。NACK是一个非常重要的信号,它可能表示:从机地址错误、从机忙、数据传输结束(主设备接收时,发送最后一个字节后主动发送NACK)或者发生了错误。在7位寻址模式下,要选择它,需要将ICMDR寄存器中的扩展地址使能位(XA)写0。
2.3 10位寻址格式:扩展设备寻址能力
随着系统越来越复杂,7位地址不够用了怎么办?10位寻址格式就是为了解决这个问题。它的原理是把10位地址拆成两个字节来发送。
两段式地址发送:第一个字节由固定的5位前缀“11110”、地址的最高两位(A9/A8)和读写位(此时固定为0,即写)组成。从机在收到这个字节并回复ACK后,主设备再发送第二个字节,即地址的低8位(A7-A0)。从机再次回复ACK,至此寻址完成。之后,主设备可以立即开始发送数据,或者发送一个“重复起始条件”(Repeated START)来改变数据传输方向(比如先写地址,再读数据)。10位地址将寻址空间从128个扩展到了1024个,但代价是寻址过程多了一个字节,效率略有降低。要启用10位寻址,需要将ICMDR寄存器中的XA位写1。
实际应用中的坑:不是所有标称支持I2C的设备都支持10位寻址。在选型时一定要仔细查看器件手册。另外,在软件驱动中,你需要把10位地址拆分成两个部分,并按照上述格式组装成两个字节发送,这个过程容易出错。
2.4 自由数据格式:无地址的原始数据流
这是一种比较特殊的模式,在标准I2C协议中不常见,但某些TI的处理器支持。在这种格式下,起始条件之后直接就是数据字,没有地址字节,也没有方向位。
适用场景与限制:自由数据格式适用于点对点通信,或者通信双方已经通过其他方式(比如固定的硬件连接)确定了彼此身份的场合。由于没有地址,所以总线上只能有两个设备(一个发,一个收),或者所有设备都必须时刻监听并准备接收数据。它的数据字长度也是1-8位可调(通过BC字段)。要使用这种格式,必须将ICMDR寄存器中的自由数据格式位(FDF)置1。最重要的一点:在整个传输过程中,数据方向必须保持不变。你不能像在寻址模式下那样,通过重复起始条件来切换读写方向。
2.5 重复起始条件(Repeated START):高效切换读写方向
这是I2C协议中一个非常精妙的设计,用于在不释放总线控制权的情况下,改变通信方向或切换通信对象。
工作原理:在一次通信序列中,主设备在发送完一些数据后,不发送停止条件(P),而是直接发送一个新的起始条件(S),然后跟上新的地址/数据帧。例如,主设备可以先写一个命令到传感器(设置寄存器地址),然后立即发送重复起始条件,再读该传感器的数据。整个过程总线始终被该主设备占用,其他主设备没有机会介入,从而保证了这一系列操作的原子性。
配置方法:手册图18-11展示了在7位寻址格式下使用重复起始条件。在软件上,你只需要在适当的时候(例如,发送完最后一个数据字节后,在等待ACK周期结束前)再次设置ICMDR寄存器中的起始条件生成位(STT),模块就会自动产生一个重复起始条件。这个功能在访问那些需要先写寄存器地址再读数据的器件(如EEPROM、传感器)时是标配操作,务必熟练掌握。
3. I2C四种操作模式与状态机流转
理解了数据格式,我们再来看看I2C模块本身可以扮演的四种角色:主发送器、主接收器、从接收器和从发送器。这四种模式不是孤立的,它们会根据总线上的命令动态切换,形成一个状态机。
3.1 主设备模式:发起与控制通信
作为主设备,I2C模块负责产生时钟信号(SCL)并控制通信的起始与终止。
主发送器模式(Master-Transmitter):这是主设备上电后的默认模式。在此模式下,模块控制SCL时钟,并将数据通过SDA线发送出去。它首先发送起始条件(S),然后发送从机地址和写方向位(R/W=0),接着发送一个或多个数据字节。每个字节后,它都会检测SDA线上的ACK信号。如果收到NACK,通常意味着从机未就绪或地址错误。当数据传输完成,主设备产生停止条件(P)释放总线。在发送数据后,如果发送移位寄存器(ICXSR)空了而数据发送寄存器(ICDXR)还没有被填入新数据,就会发生下溢(Underflow),状态寄存器ICSTR中的XSMT位会变为0。这时,如果软件没有及时补救,最后一个字节的数据可能会被重复发送,导致通信错误。
主接收器模式(Master-Receiver):主设备要读取从机数据时,必须切换到该模式。切换不是随意的,必须遵循一个固定流程:主设备先以主发送器模式发起通信,发送从机地址和读方向位(R/W=1)。如果从机应答了ACK,那么主设备模块内部会自动切换到主接收器模式。在此模式下,主设备仍然产生SCL时钟,但改为从SDA线上读取数据。每接收完一个字节,主设备需要向从机发送一个ACK(除非是最后一个字节,则发送NACK)。接收到的数据会被移入接收移位寄存器(ICRSR),然后复制到数据接收寄存器(ICDRR)供CPU读取。如果ICDRR的数据还没被读走,新的数据又来了,就会发生上溢(Overrun),ICSTR中的RSFULL位会置1,新数据会覆盖旧数据,造成数据丢失。
3.2 从设备模式:响应与提供服务
作为从设备,I2C模块不产生时钟,而是监听总线,等待被主设备寻址。
从接收器模式(Slave-Receiver):所有从设备上电后都处于这个模式,监听总线上的地址。当它检测到起始条件,并发现接下来的7位或10位地址与自己在ICOAR寄存器中设置的地址匹配时(或者收到全零的广播地址),它就会在第9个时钟周期将SDA拉低,发出ACK应答。之后,它继续接收主设备发来的数据字节,每收一个字节回一个ACK。如果从机的接收缓冲区满了(ICDRR未读,ICRSR又满了),它也可以通过拉低SCL线来通知主设备“等等我”(时钟拉伸),直到CPU处理完数据。
从发送器模式(Slave-Transmitter):从设备不会主动发送数据。它必须先从从接收器模式开始,被主设备寻址,并且主设备发送的R/W位是1(读请求)。这时,从设备才会切换到从发送器模式。切换后,从设备按照主设备提供的SCL时钟节拍,将数据放到SDA线上。主设备每接收一个字节,会回送一个ACK。当主设备希望停止接收时,它会在最后一个字节后发送NACK,然后跟停止条件。
模式切换的核心:理解模式切换的关键在于R/W位和地址匹配。对于主设备,发送R/W=1的地址帧是切换到接收模式的唯一途径。对于从设备,只有在地址匹配且R/W=1时,才会从接收模式切换到发送模式。这个状态机的逻辑非常严谨,在调试时,如果发现模式不对,首先要检查的就是地址和R/W位是否正确发送和解析了。
4. 关键寄存器配置与实战指南
理论讲得再多,最终都要落到寄存器的配置上。下面我们结合一个主接收器模式的初始化流程,把几个核心寄存器的作用和配置要点彻底讲明白。
4.1 核心寄存器功能详解
在动手配置之前,必须清楚每个寄存器是干什么的。手册里列出了二十多个寄存器,我们挑最核心的几个来说。
I2C模式寄存器(ICMDR):这是I2C模块的“大脑”。它决定了模块的几乎所有行为模式。
- IRS(I2C复位):写0复位整个I2C模块,引脚变为高阻态。特别注意:在数据传输过程中复位模块会导致总线挂死!配置前先复位,配置完成后置1使能。
- MST(主模式):1为主模式,0为从模式。
- TRX(发送/接收):1为发送器,0为接收器。在主模式下,这个位决定了初始的数据方向。
- XA(扩展地址):0为7位寻址,1为10位寻址。
- RM(重复模式):影响ARDY状态位的产生时机,在DMA传输中常用。
- DLB(数字回环):用于模块自测试,将发送端和接收端内部短接。
- FDF(自由数据格式):置1则使用自由数据格式。
- BC(位计数):设置数据字的长度,从1到8位。
- STP(停止条件):软件置1以产生停止条件。
- STT(起始条件):软件置1以产生(重复)起始条件。
- FREE:仿真时遇到断点,I2C是否继续运行。0为停止,1为继续。
I2C中断状态寄存器(ICSTR):这是I2C模块的“仪表盘”。所有重要状态都在这里。
- BB(总线忙):指示总线是否被占用。发起传输前必须检查BB是否为0。
- RSFULL(接收移位寄存器满):为1表示发生接收上溢,数据丢失。
- XSMT(发送移位寄存器空):为0表示发生发送下溢,可能重复发送数据。
- ICRRDY(接收就绪):为1表示ICDRR中有新数据可读。这是轮询接收数据的关键标志位。
- ICXRDY(发送就绪):为1表示ICDXR已空,可以写入下一个要发送的数据。这是轮询发送数据的关键标志位。
- ARDY(寄存器访问就绪):在非重复模式下,当内部数据计数器减到0时置1,表示当前命令(如地址、数据)已处理完,可以配置下一组了。
- NACK(无应答):为1表示收到了从机的NACK,通常意味着通信错误。
- AL(仲裁丢失):为1表示在多主竞争总线时失败。
I2C时钟寄存器(ICCLKL, ICCLKH):决定SCL时钟的频率。I2C总线速度=模块输入时钟频率 / (ICCLKL + ICCLKH + 6)。其中ICCLKL和ICCLKH分别决定了SCL低电平和高电平的持续时间。要得到标准的占空比(通常为50%),应设置ICCLKL = ICCLKH。
I2C数据计数寄存器(ICCNT):这个寄存器在DMA传输时特别有用,可以设置要传输的数据字节数量,当计数器减到0时,可以自动产生停止条件或中断。
4.2 主接收器模式初始化与数据接收流程
手册18.2.11.1节给出了一个非常标准的配置流程,我们结合实战经验来解读每一���的为什么和注意点。
步骤1-2:时钟使能与模块复位首先使能I2C模块的时钟源,然后将ICMDR中的IRS位清零。这一步是必须的,它让模块进入一个确定的初始状态,所有寄存器恢复默认值,SDA和SCL引脚变为高阻态,避免干扰总线。
步骤3:配置ICMDR寄存器这是核心配置步骤。以主接收器为例:
MST=1:设为主模式。TRX=0:设为接收器。注意:虽然我们最终要接收数据,但发起通信时,主设备首先是作为发送器去发送地址的。这个TRX=0的配置,是在地址发送完成后,模块自动切换到的模式。对于纯发送流程,这里应设为1。XA=0:使用7位寻址。RM=0:禁用重复模式。在简单的查询式传输中,通常用不到重复模式。DLB=0:禁用回环,正常通信。FDF=0:禁用自由数据格式,使用标准寻址格式。BC=0:设置数据长度为8位(BC=000b)。
步骤4-6:配置地址与时钟
- ICSAR:写入你要通信的从机7位地址。地址需要左移一位吗?不需要!这个寄存器就是存放7位地址本身,模块会在发送时自动加上R/W位。
- ICPSC:预分频寄存器,用于将系统主频分到一个中间频率(手册建议6.7-13.3 MHz)。这个频率会再经过ICCLKL/H分频产生最终的SCL时钟。计算时需根据你的系统时钟和期望的I2C速率来反推。
- ICCLKL & ICCLKH:这是设置I2C总线速率的关键。假设模块输入时钟(经过ICPSC分频后)为
PSC_CLK,目标SCL频率为I2C_CLK,那么有:I2C_CLK = PSC_CLK / (ICCLKL + ICCLKH + 6)通常我们设置ICCLKL = ICCLKH以获得50%占空比。所以公式简化为:ICCLKL = ICCLKH = (PSC_CLK / (2 * I2C_CLK)) - 3计算出的值取整后写入寄存器。例如,PSC_CLK=12MHz,目标I2C_CLK=400kHz(快速模式),则ICCLKL = (12,000,000 / (2*400,000)) - 3 = 15 - 3 = 12。
步骤7-9:清状态、使能、等总线空闲
- 读取ICSTR并写回,是为了清除所有可能悬挂的中断标志位。这是一个好习惯。
- 将IRS位置1,使能I2C模块。此时模块开始工作,但还未发起传输。
- 等待BB位变为0。如果总线上有其他设备正在通信,BB位会是1,你必须等待它变为0才能发起新的传输,否则会触发仲裁丢失(AL)。
步骤10-12:发起传输与循环接收
- 将STT位置1,模块会自动产生起始条件,并发送ICSAR中的地址(加上R/W=1,因为我们要读)。
- 等待ICRRDY位变为1。这个位为1,表示一个字节的数据已经从移位寄存器复制到了ICDRR中,可以读取了。
- 读取ICDRR寄存器,数据就被CPU取走了。然后继续等待ICRRDY,读取下一个字节,如此循环。
步骤13-14:结束传输的关键技巧
- 在接收倒数第二个字节后,你需要设置NACKMOD位。这个操作是告诉I2C模块:“在接收下一个(也就是最后一个)字节后,不要回ACK,要回NACK”。这是I2C协议规定的,主设备在接收最后一个字节后必须发送NACK,以告知从设备发送结束。
- 接收完最后一个字节后,将STP位置1,产生停止条件,释放总线。
避坑指南:为什么我的I2C读数据总是多读或少读一个字节?这个问题十有八九出在NACK的时机上。如果你在接收所有字节后才设置NACKMOD,那么模块会对最后一个字节也回复ACK,从设备会认为主设备还想继续读,可能会继续发送数据,导致主设备读到垃圾数据或者总线超时。正确的做法是,在预期接收的最后一个字节之前,就设置NACKMOD位。例如,你要读8个字节,那么在读完第7个字节后、等待第8个字节的ICRRDY之前,就设置NACKMOD=1。
4.3 中断与DMA事件配置
对于需要高效处理数据的应用,轮询(不断查询ICRRDY/ICXRDY)太浪费CPU资源了。这时就要用中断或DMA。
中断配置:通过ICIMR寄存器可以屏蔽或使能各种中断。比如,使能接收中断(ICRRDY),那么每当一个字节数据准备好,CPU就会跳转到中断服务程序(ISR)去读取ICDRR。在ISR里,你需要读取ICIVR寄存器来清除中断标志。ICIVR是一个只读寄存器,读它就会自动清除当前最高优先级的中断标志位。这种“读即清”的机制很常见,但要注意,如果你在ISR中只处理了部分中断事件,读ICIVR可能会清除你不希望清除的标志,最好在ISR入口处将ICIVR的值保存下来再做判断。
DMA配置:这是大数据量传输的终极解决方案。I2C模块可以产生两个DMA事件:
- 接收事件(ICREVT):当数据从ICRSR移动到ICDRR时触发。DMA控制器可以配置为响应此事件,自动将ICDRR中的数据搬运到指定的内存区域。
- 发送事件(ICXEVT):当数据从ICDXR移动到ICXSR时触发。DMA控制器可以响应此事件,自动将内存中下一个要发送的数据搬运到ICDXR。 使用DMA可以彻底解放CPU,你只需要配置好源地址、目的地址和数据数量(ICCNT),传输就自动完成了。在配置DMA时,要特别注意数据宽度对齐和传输结束的判断(通常结合ICCNT减到0和ARDY中断)。
5. 高级主题与疑难杂症排查
掌握了基本操作,我们再来啃几块硬骨头:NACK生成、仲裁和那些让人头疼的错误状态。
5.1 NACK位生成的多种场景与配置
NACK不总是错误,它也是一种重要的通信控制手段。手册表18-2详细列出了产生NACK的各种条件。
作为接收方时主动发送NACK:这是最常用的场景,即主设备接收最后一个字节后发送NACK。实现方法就是在接收倒数第二个字节后,设置ICMDR寄存器的NACKMOD位。模块会在下一个字节传输的ACK周期自动发出NACK。
从接收器模式下发送NACK:当从设备无法接收更多数据时(比如缓冲区满,RSFULL=1),它可以在ACK周期不回拉低SDA(即发出NACK),告知主设备“我吃不消了,别发了”。主设备看到NACK后,应当终止传输。
错误处理:当I2C模块作为发送方(主或从),却没有收到接收方的ACK(即检测到NACK)时,ICSTR寄存器中的NACK标志位会置1。这通常意味着通信链路出了问题:地址错误、从设备掉线、从设备忙或者物理连接故障。你的驱动代码必须能处理这种情况,常见的做法是重试几次,如果仍然失败,则复位I2C模块(IRS清零再置一)并上报错误。
5.2 多主仲裁机制解析
I2C支持多主设备,当两个主设备同时发起传输时,就需要仲裁。仲裁的规则很简单:“线与”逻辑,低电平优先。
仲裁过程:所有主设备同时发送起始条件,然后开始发送地址和数据。它们会同时监听SDA线。如果某个主设备发送了一个高电平(释放SDA,内部上拉电阻将SDA拉高),但它检测到SDA线实际是低电平,这说明有另一个主设备正在发送低电平。那么发送高电平的主设备就立即知道自己“输”了,它会关闭自己的输出驱动器,切换到从接收器模式,并监听赢家的通信。同时,它的AL(仲裁丢失)标志位会被置1。
仲裁的细节:仲裁可能持续多个字节,直到分出胜负。仲裁只发生在数据比特位之间,不允许在重复起始条件(S)或停止条件(P)与数据比特之间进行仲裁。这意味着,如果两个主设备想用重复起始条件或停止条件来改变总线状态,它们必须在完全相同的时刻发出,否则就会造成总线混乱。在实际编程中,除非你确实在构建多主系统,否则仲裁丢失通常意味着你的程序逻辑有BUG,比如在总线忙(BB=1)时强行发起传输。
5.3 常见问题排查实录
搞I2C调试,逻辑分析仪是必备神器。它能让你直观地看到SDA和SCL线上的每一个波形。下面是我总结的几个典型问题及排查思路:
问题一:通信完全无响应,SCL或SDA一直是高电平或低电平。
- 检查硬件:首先用万用表测量SDA和SCL对地电压。正常空闲时应为高电平(接近VCC)。如果一直被拉低,可能是某个设备损坏,将引脚钳位在了低电平。检查上拉电阻是否焊接,阻值是否合适(常用4.7kΩ,高速模式下可减小)。
- 检查软件初始化:确认是否在配置前将模块复位(IRS=0),配置完成后使能(IRS=1)。确认时钟配置(ICPSC, ICCLKL/H)是否正确,频率是否在从设备支持的范围内。
- 检查引脚复用:确认MCU的I2C引脚功能是否已正确映射到GPIO,并且配置为开漏输出模式。
问题二:能发送起始条件和地址,但收不到ACK(用逻辑分析仪看到SDA在第9个时钟周期仍是高电平)。
- 检查从机地址:这是最常见的原因。确认你写入ICSAR的地址是7位地址,并且与从设备手册上的地址一致。注意,很多传感器可以通过引脚设置地址偏移,别搞混了。
- 检查从设备电源和复位:确保从设备已正常上电,并完成了上电复位。有些设备需要特定的初始化序列。
- 检查总线负载:总线上设备太多、走线太长、上拉电阻过大,可能导致上升沿太慢,从设备在采样时数据还未稳定到高电平,误以为是低电平。可以尝试减小上拉电阻(如从4.7kΩ换为2.2kΩ)或降低通信速率。
问题三:能收到ACK,但数据错误,或者只能收发第一个字节。
- 检查时序:用逻辑分析仪测量SCL频率、高低电平时间、数据建立和保持时间是否满足从设备要求。特别是高速模式(400kHz或1MHz)下,时序裕量很小。
- 检查软件流程:在发送或接收每个字节后,是否正确等待了状态位(ICXRDY/ICRRDY)?是否在接收最后一个字节前正确设置了NACKMOD?发送完成后是否及时产生了停止条件(STP)?
- 检查中断与DMA:如果使用了中断或DMA,是否及时清除了中断标志?DMA传输长度(ICCNT)设置是否正确?是否发生了上溢(RSFULL)或下溢(XSMT)?
问题四:随机出现通信失败,特别是系统中有其他中断或高负载任务时。
- 检查中断优先级:I2C中断服务程序(ISR)的执行时间是否过长?是否被更高优先级的中断频繁打断?I2C是时序敏感的协议,ISR必须尽快响应并处理数据。
- 检查总线占用:在发起传输前,是否检查了BB(总线忙)位?如果多个任务可能操作I2C,需要增加软件互斥锁(mutex)来保证同一时间只有一个任务访问总线。
- 检查电源噪声:在电机、继电器等大功率设备附近,电源噪声可能干扰I2C通信。可以尝试增加电源滤波电容,或为I2C线路增加屏蔽。
最后,再分享一个调试小技巧:当你无法确定是软件问题还是硬件问题时,可以尝试用GPIO模拟I2C时序写一个最简化的驱动。如果模拟驱动能通,那问题很可能出在硬件或官方库的配置上;如果模拟驱动也不通,那就集中精力排查硬件连接和电源。
