I2C问题的七宗罪

I2C第一罪


有些工程师聊起I2C的时候,会有不屑一顾的感觉, “切,不就是两根线吗,一个时钟,一个数据”!每每碰到这些人,就会有一种感觉,兄dei,你真是坑没有踩够啊!

下面是我按照本人工作时间先后顺序,列出碰到过的I2C的问题。有一点我要强调一下,我所列出的问题,不是从书上看来或者从哪里杜撰来的,每一个问题都是来自于不同的公司以及不同的项目,由本人亲历并且解决好的问题,希望对年轻的工程师有所帮助!

2005年, 在L公司(想想当年的L公司,那可是风光的很。)的光网络系统上有一块叫LKAXXX的线卡上出现了一个怪事,说到这里,肯定有人一看到MPC860, 就忍不住笑了, 一看就是上了年纪的大叔,哎,岁月不饶人呐 ^_^,我刚刚从学校里毕业的时候就是MPC860差不多诞生的时候。

先来描述一下问题:


1. 经常当有人按下Reset button后发现系统起不来了,UART console打印了一半就死在那里;

2. 再次按下复位后,症状一样, 不管怎么按复位按钮系统都死,UART打印一点点信息挂在那里;

3. 多次按复位按钮无效后,断电重启后OK;

4. 大部分情况,同一块板子按复位按钮后是OK的,但是少数情况是Fail的。


这时候很多就会说,这不是很简单,让软件用调试器加断点跟踪啊,说对了,我们firmware工程师还是很牛逼的,很快就告诉我们问题出在I2C上, 我们用示波器测量在死机情况下的I2C信号,发现SDA数据信号一直是低电平,怎么复位都没有用,一直是低,只有断电重启后,SDA才变高。那么原因初步定位了,正式因为I2C-SDA被强行拉低,才导致系统起不来,而断电后SDA被释放了,系统也就正常了。

我们知道I2C是open drain的,肯定是被什么芯片给拉了啊,这个很容易想到,不应该是CPU, 因为CPU已经被复位了啊, 那么怀疑的对象就到了和CPU相连的Device上, 乍一看下面的原理图, 我Kao,这是连连看么?还能有比这个更加简单的事情吗?

那么究竟是什么原因导致SDA被EEPROM拉低了呢?

我们再来看一张图:

我们看到这里对EEPROM的处理比较特殊, 在绝大部分的原理图里面,我从来都看不到,就是把EEPROM的电源加一个开关:

1. 当复位为低电平时,EEPROM的VDD连到低,断电;

2. 当复位为高电平时,EEPROM的VDD重新连到外部的VDD,恢复供电。


这时候聪明的小伙伴们已经悟出来, Kao!刚刚上面的LKAXXX的板子,如果我们每次按下复位按钮复位CPU的时候,通过按钮产生的复位信号High-Low-High由这里的开关电路把EEPROM的电源断开一会儿,是不是EEPROM就不会去把SDA拉低了啊,Bingo, 对了。

可是又有人说了,这不是增加成本嘛,还有啊,也没有看见有人这么干过啊,哈哈,对了,只有日本人才会这么用一根筋的设计方法,我们中国人永远找到更好的解决办法,我们继续往下走。

请看下面这张图,有没有很熟悉?

这是一个I2C的读操作,顺序如下:

1. Master发出start;

2. Master发出地址和读命令;

3. Slave给出ACK然后发出数据data7-0,这一共8拍由slave来驱动I2C SDA,(这里要记住SCL一直是由master来驱动的,当然后面也有特殊情况,这个我们留在后面的七宗罪里面详述),8拍的时间(假设100K的速率,周期为10us)是80us。


停停停停停, 搞笑的事情来了,如果在这80us的时间里面,有人按下了复位按钮, 会怎样 ?

举个“栗子”

假设四个人打麻将,我的上家把牌打出来后,就该轮到我出牌了,可是这时候来了个电话,我去接电话了, 等我回到牌桌后,我忘记了刚刚轮到我出牌,我以为上一把结束了,直接把麻将推倒洗牌了,这时候等着我的下家可就不干了,人家等着我出牌好胡呢。然后我这重来的举动惹恼了人家,道歉也好,赔不是也罢,人家不接受,不玩了!得嘞,这局麻将是玩不下去了。

类比上面I2C的情况,想象一下:

1. 80us的时候 EEPROM Slave正在配合CPU输出读的数据,人家玩得正high呢;

2. 这时候CPU收到一个复位指令,而且是强行复位,类似于接电话;

3. 等到CPU复位完了,完全忘记了刚刚EEPROM正在drive数据,甚至有可能EERPROM已经传完8bit数据,正在CPU的NAK(看上图),这事搁谁身上能受得了;

4. 这种情况CPU看起来也是没有办法,谁叫我们按下了复位按钮了呢。还有一种情况就是CPU自己不厚道,比如去执行优先级中断程序后,回来也忘记了别人(EEPROM)正在等待自己继续刚才的工作。

这里肯定会有人问,不是复位了吗?请仔细看上面的原理图, EEPROM是没有复位管脚的,也就是说, 我们按下复位按钮时, CPU复位了,但是EEPROM没有复位,它的状态机还在等待输出数据给CPU或者等待CPU的NAK指令以便结束当前的这笔操作,这和上面打麻将的例子是一回事。

那么请问刚刚的SDA被拉低是怎么回事呢?很简单, 上图的ACK就是低电平,或者Slave drive的data bit7-0其中有高有低啊。

那么除了上面给EEPROM的电源加开关的方式,我们还有上面办法来解决这个问题呢?

我们继续看图:

先来想想刚刚打麻将的事情,如果我打完麻将回来后,直接牌友每个人100块钱,别人肯定是乐意继续陪你玩的,很简单的办法解决了问题。如上图, 我们让软件工程师在代码里面做了下修改:

1. 当检测到SDA被拉低后;

2. 软件就持续发送9个时钟;

3. 在发9个clock的过程中SDA会变高变低;

4. 当Device发完所有数据后,SDA被释放;

5. 此时状态机到达NAK的phase时,SDA释放变高,产生了一个NAK;

6. 注意此时9个时钟不一定用完,EEPROM就把到达NAK phase把SDA释放了,但是CPU是不知道的,他会一直发完9个时钟;

7. 最后CPU再发送一个stop把整个读操作结束掉;


The 9 clock pulses make the hanging device’s state machine move to the next state after each clock pulse while the SDA released (not pulled down) which will cause a NACK when the state machine will move to the ACK phase. The NACK will force the device to go to idle mode(意思和我上面的步骤一样)。

1. Master tries to assert a logic 1 on the SDA line;

2. Master still see a logic 0 and then generate a clock pulse on SCL;

3. When device come to NAK phase, then master will generate SDA high which;is a NAK, but master does not know, until master send all the 9 clocks;

4. Master Generate a stop condition。


上面讲的是I2C读操作被中断导致死机的情况,下面聊聊I2C写操作被中断的情况,解决办法“简单粗暴”一些, 大家想想为什么。

这里一样还是只发9个时钟,在9个时钟的过程中,device就可能发出一个ack,CPU看到ACK后,再发一个stop结束本次操作:

1. Master tries to assert a logic 1 on the SDA line;

2. Master still see a logic 0 and then generate 9 clock pulse on SCL;

3. Generate a stop condition。


这里要注意,写操作被中断时,发出的9个clock一定是等到最后一个时钟发完, device才被释放。而读操作则不一定,有可能发了第一个时钟时device就被释放了只是CPU不知道,到第9个时钟时stop。

I2C和电源的关系


提到电源大家的心理通常是紧张滴,不自觉地发问:是不是问题很严重?是的, 非常严重, 而且通过软件是没有办法解决的。我们先来看下面这张简图:

1.系统有风扇,12V供电;

2.Fan和CPU之间有转速检测TACH和转速控制PWM信号;

3.CPU左边有一个颗I2C EXPANDER,用来做GPIO扩展的;

4.DC-DC产生VCC-3.3V分别供给CPU和I2C Expander。

乍一看,也没啥问题啊!俗话说得好,平静的水面下暗流涌动啊,我先来给大家一点点提示,看看谁能先猜到问题所在,我们来看看CPU PWM/TACH内部结构。

这个也没有啥问题啊, 我们都知道IBIS buffer的样子如下,这个在网络上随便google或者度娘,到处都是啊。

这些看起来都没有什么问题,可是偏偏我们把这里的CPU(MCU)和风扇放在一起就出问题了,看看下面这张示意图,你也许会开始有feel:

我们从电源的上电顺序开始:

1.插入12V电源,Fan先开始运转,注意此时DC-DC还没有开始工作,也就是说VCC-3.3V还没有产生;

2.由于风扇的12V先上电,PWN/TACH信号线已经有电了,于是通过图中MCU的保护二极管(上管)对3.3V充电;

3.注意此时DC-DC仍然还没有工作,但是3.3V已经开始升压,但是来源不是DC-DC Buck,而且来自Fan的I/O管脚漏过来的;

4.此时DC-DC电源开始工作,由于同步整流的电源BUCK都需要自举,也就说我们通常所说的Bootstrap的电路,下管的MOSFET要先打开(关于电源部分我们后面再详细叙述),产生的后果是把原先I/O漏过来的电压拉到地;

5.Bootstrap电路开始工作后,DC-DC开始正式工作,从0开始产生VCC-3.3V。

于是我们就看了如上图的波形, 图中绿色的就是3.3V, 12V电源插入瞬间开始上升,然后在某一个时间,突然下降到0, 再重新上升到3.3V, 电源完成上电。

说到这, 有人开始问了,你不是讨论I2C吗?怎么和我们讨论起3.3V电源来了啊?别急别急, 事情都是慢慢明朗起来的。

如果此时你回去看最上面的第一张图,那么你就发现图中有一个I2C Expander,对了,问题就出自这里,通过我们从上面的讨论,我们已经知道, 3.3V的上电不是一帆风顺的,而是会有一个Spike,也就是一个倒钩。如下面所示:

我再来给一张清晰一点图,VCC每次上电都是先被12V-Fan I/O充电,然后跌落到地,在爬升会3.3V, 原因上面我已经给出详细的解释,那么又有人问了,这个会有什么问题吗?我先明确的回答,会出大问题。

我先来贴一段英文,我非常强烈建议大家平时工作中要养成读英文论文的习惯,我倒不是崇洋媚外,老外那些大牛的文章,真的让你觉得很精妙, 有时候自己翻译成中文总觉得怪怪的,生怕曲解了大牛的原意,所以还是老老实实去读原版文章,不要翻译成中文。

If by any chance, the TACH pin is pulled or driven high while the Expander VCC is not applied (which should not be allowed during normal operation), the Expander could be powered up from the TACH pin through the connected internal diode between the pin and VCC. Such false power-up events do not ensure the required power supply to Expander the POR would not be ensured, and a lockup may occur.

Point和上面的英文意思差不多,就是I2C Expander(我们这里是CPU)被别的电路 通过I/O管脚商店了。我们先来把这颗粒I2C expander的内部电路给画一下, 我们看到这颗芯片没有外部复位pin脚,所以一般内部会有一个简单设计的POR电路:

我们看到,当3.3V VCC上电有倒钩时,由于Fan通过TACH/PWM对I2C expander提前上电, 然后突然又倒回来, 在这个过程中有两件事是不确定的:

The POR brings the Expander to a known working state (the default condition) by initializing the internal storage elements (flip-flops) and recognizing its connections (such as identifying the status of pins A1 and A0 as high or low, which is how the Expander slave address can be decided). Without the POR feature, the Expander may start up in a random state and thus may cause a lockup.

1.I2C expander是不是已经上电成功了?我们不知道,因为3.3V上电到了半山腰又倒回来,此时芯片内部的POR是不是对芯片完成内部复位我们不清楚,不仅如此I2C expander有没有准确锁定外部的strap pin脚的状态,我们也不清楚;

2.由于不能确定是否完成内部存储单元(触发器)的初始化,I2X expander可能工作一个很random的状态,而不是我们需要的Idle状态。

关于这个解释,后来TI的工程师给出详细的解释, 英文我不就翻译成中文了,请读者仔细品味和理解:

The device has a 6 bit state machine (64 states) of which 49 valid state and 15 are invalid states. The above fix will work assuming the device comes up in a valid state. Typically on all our designs, an invalid state would lead to the idle state on the immediately next clock cycle. However, the PCF8575 is our very first I2C device and there are some invalid states that could latch the SDA line low indefinitely. The 9 clk cycle fix will not work if the device were to power up into an invalid state. The only way to bring the device out of this state is to provide a proper reset.

那我们说了那么多,究竟结果是怎么样呢?结果就是I2C expander产生了Lock up, I2C接口访问不了,有时候还会拉住SCL或者SDA.

总结下来就是一句话:芯片需要POR电路来进行复位,如果因为别的原因提前上电,导致POR工作不正常,那么就会有问题。

关于上面的现象还有另外一种解释,就是当VCC的倒钩时,此时I2C Expander的内部电路已经被搞乱了, 但是有内部POR的RC电路的平滑作用, 导致内部Reset被滤平滑了,内部Reset没有碰到门限电压,请仔细看上图红色的凹陷。

这样导致芯片也不能正常复位,当然也就不能正常工作了。请看大牛Howard Johnson的描述:

Howard Johnson : Power interruptions drive power-on-reset circuits crazy.. If a processor is involved, the dropout is long enough to make scrambled eggs of the processor’s internal state machines but not long enough to discharge the RC circuit. If the RC circuit doesn't discharge, ~RESET doesn't activate, and the processor spins out of control, powered on, but lost in space.

I2C第三罪

我们都知道I2C是open drain的,不是普通的push-pull,我们也知道open drain的接口信号需要加上拉电阻,那么为什么要用open drain呢?上拉电阻的阻值是否需要计算考量呢?我们先来说说为什么。


第一个问题, 为什么要用open drain?

请看下图,I2C总线的拓扑往往是由于多个设备直接线与连接在一起的,最常用的就是一个master(一般是一个CPU)和多个slave设备(比如EEPROM, Thermal Sensor等等)。这样子的连接比较简单,容易理解,可是你知道吗?这也会有坑。

我们先来谈谈为什么不能用普通的push-pull,而是一定要用open drain,一张图告诉你二者的区别,所谓的push-pull就是由一个PMOS和一个NMOS组成的。

1.当输出高电平时:PMOS打开NMOS关闭;

2.当输出低电平时:PMOS关闭NMOS打开。

如上面的图左,此PMOS和NMOS交替打开和关闭,分别输出方波的高低电平,但是图右的open drain buffer则只有下面的NMOS:

1.当输出高电平时NMOS关闭,输出处于floating的状态;

2.当输出低电平时NMOS打开即可。

如此我们看出,open drain的输出高一定要借助外力,这就是为什么我们检查I2C的设计时,一定要确保外部有上拉电阻才行。那么为什么呢?如果我们用push-pull会产生什么样的情况?

上面第一张图已经解释了,I2C是有很多设备线与连接而成,如果采用push-pull的output buffer的话,难免会出现下面的情况,一个设备输出高,另外设备输出低,也就是左边设备的PMOS打开而右边设备的NMOS打开,这样就在VCC和GND之间形成短路,此时大的电流会把设备烧毁,后果是灾难性的。这种现象还有专门的英文名字叫bus contention。

为什么取了个这么奇怪的名字呢?

这还得从老的那些个公用总线说起,对于I2C来说,解决bus contention的方法很简单,那就是使用open drain,因为一共就两根信号线嘛。但是,老的产品很多并行的共用总线,比如一个32bit甚至64bit的总线,那就不能用open drain了,因为那么多的上拉电阻加在板子上,那PCB工程师可吃不消啊。

所以人们就发明了三态门,这又是怎么回事呢 ?我们知道64bit的数据总线是双向的,如下图所示:

当系统在复位或者紊乱时,总线的所有设备都自动把自己的data buffer设为三态,那么什么叫三态门呢?顾名思义就是:

1.输入、2.输出、3.三态

接着上图,绿色为输出buffer,红色为输入buffer,当两个buffer都被disable时,就是所谓的第三态输出。

故此,我们总结出一个规律:但凡多个设备直接相连并且双向输出的总线, 要么设计成open drain,要么设计成三态门。回到上面的bus contention,这个名称其实不是来自于I2C这种低速简单的总线,而是来自于高位宽的并行总线。看下图如果左边任意两个buffer同时为输出的话,那么就产生了bus contention,后果不堪设想。


第二个问题,上拉电阻的阻值是否需要计算考量

现在我们明白了I2C为什么一定要用open drain加上拉电阻的方式,那再来说说上拉电阻的选择,曾经我在C公司的一块ASR路由器的板子上,有一颗I2C芯片在高温时,就访问不了,但是在常温和低温下就一切正常,其实现在我们根据结果来说这个问题,就没有啥意思了,因为说者有心,听着无意。

我们刚刚开始压根没有怀疑上拉电阻的事情,各种调试手段都上了,比如更换不同的芯片,用协议分析仪找出高温失败时的读写波形,另外在高温温箱的情况下,捕捉并且定位这种问题也是非常困难的,最痛苦的是这种fail的问题很少能复现。

这里我们先来思考一个问题,I2C是latch,不是flip-flop,再通俗一点说就是电平触发,不是边沿触发,所以通常情况下,我们是不关心上升沿和下降沿的,可是偏偏问题就出在这里,我们的芯片会对SCL和SDA的边沿提出要求,这又是为什么呢?

先把这和个问题留着,看下面这张图,当SDA输出高电平时,是蓝色的箭头,有VCC通过上拉电阻和电容充电,而当SDA输出低电平时,是红色的箭头

我们很容易就能看出,输出高比较慢,因为有电阻和电容的阻挡,需要通过RC进行指数充电,而输出低电平就比较快,因为从电容放电到地全程无遮挡啊,所以下面我们之研究上升沿。

这里有人会问,这里的电容是从哪里来的,问得好。我们知道复杂的大板子上面芯片之间的距离比较远,走线的杂散电容比较大,另外板子的I2C接的芯片数量比较多,每个芯片的输入pin的电容加起来,就形成了这里的一个总的电容,为了描述方便,我们就用一个电容代替。

现在我们来回答为什么I2C会Rise time提出要求呢?太慢了会有什么问题呢?我们先看下面的表格,I2C其实也有fast mode的形式,据说I2C的频率也会有到5Mhz的,图表中我只看到400Kbit/s,我们在表格的最右面已经看到对上升时间的要求随着频率(或者叫速率)的提高,也越来越苛刻。

我们来用一张图来表达,当上升时间太慢会产生什么样的问题,图中SCL信号如果Tr够快的话,就是红色的信号,而Tr不达标太慢的话,那么就会是蓝色的信号。



不难看出,蓝色线越过绿色的threshold的时间太短,导致接收端不能正确识别出高电平,当然接收端也就不能正确latch SDA的数据了。

好的,现在我们明白了I2C信号的上升沿是有要求,尽管只是Latch,但是对边沿仍然有要求,而且只对上升沿,下降沿因为很快,所以无需担心。下面我们来讨论什么情况下会让I2C的Tr太慢导致问题呢?请参考上面有一张RC充电的公式以及下面这张图, 我们看到t=RC, 也就是两个因素:

1.电阻值、2.电容值




首先来说电容值,这个是我们改变不了的,如前面所说大板子面积大,总线上的设备多,走线非常复杂,导致各种芯片的输入电容相加,再加上又长又复杂的走线带来的杂散电容,我们是无法改变的,除非重新设计板子,加双向buffer,但是打工的工程师都知道,老板不允许啊。

我们能改变的其实就是电阻了,我们先来看下面示意图,通过适当调整电阻值,我们可以获得比较合理的上升时间,满足接收芯片的要求并有一定的margin就可以了。

但是电阻值的改变是需要精心计算的,不能太小也不能太大,而是要合理,我个人建议分为两步走:

1.先确定最大值:取值太大的话,会导致两个后果,一是芯片接收端驱动能力不足,而是上升时间不够,下面的两个公式分别对应这两个现象,我们去其中更加小的值。



2.再确定最小值:电阻取值太小的话,会导致Vol太大,芯片低电平下不来,我们举个极端的例子,就是上拉电阻为0时,不能产生低电平,下面的两个公式都是一个意思,一个精确考虑了电流,一个精确考虑电压,思路都是一样的。


最后揭晓一下前面我在C公司遇到的问题,经过很多实验,我们证明了I2C设备确实对信号的上升时间有要求,尽管只是电平触发的Latch。很多事情当你遇到了,历经苦难地解决了,才能记得清楚牢靠,也最能理解。希望工程师们在解决一个问题后,不要忙着开心欢喜,而是要多做总结,这样才能真正变成自己的经验。

I2C第四罪

今天来说一道Facebook的面试题,曾经我的一个朋友梦想去美国硅谷工作,Facebook的工程师在面试时问了一道关于I2C的问题,比较简单也比较直接,可惜我的那个兄弟由于太紧张,没有答好,大家在看这篇文章时,不妨自测一下自己能不能准确答出来。

我们先来看一张图,熟悉I2C的人一看,这不是I2C的level shifter电路嘛,在Philip的I2C标准里面有,没有啥复杂的。

我们先来解读下这张图,I2C的level shifter是通过NMOS管来实现的,有人可能会问,为什么不用PMOS呢?一般情况下,只有在外部电源输入端,我们设计INRUSH电流缓启动或者防反插电路的时候才会用到PMOS。理由很简单, PMOS是低电平打开,电源插入瞬间系统其实还没有用于打开MOSFET的高电平,所以只能选择PMOS, 而NMOS体积小,RDSON低,在系统内部会更多选择NMOS。

我们来举个PMOS的用例:

言归正传,我们来继续讨论I2C问题。首先我们要牢牢记住,I2C是Open-drain,所以level shifter电路的两边都是上拉电阻Rp的,下面讲下这个电路是怎么工作的。

下图是两张I2C的基本操作时序图,分别是Master对Slave所做的读和写:

我们可以看到, SDA一定是双向的,既然是双向的,那么就有四种情况,我们来一一解释level shifter是怎么来cover它们的。(注意:这里面会含有一个Facebook的问题,不要忘记自测哦。)


01

第一步,Master往Slave发数据1



1) 左边MasterSDA_1为输出,驱H-3.3V,NMOS的VGS=0, 此时NMOS关闭;

2) 右边的Slave的SDA_2是输入,对外呈现高阻;

3) NMOS关断和SLAVE为输入,导致SDA_2悬空;

4) 最终SDA_2依靠RP2上拉到5V,完成3.3V到5V的转换。


02

第二步,Master往Slave发数据0



1) 左边MasterSDA_1为输出,驱Low=0,NMOS的VGS>0, 此时NMOS打开;

2) 右边的Slave的SDA_2是输入,对外呈现高阻;

3) NMOS打开和SLAVE为输入,导致SDA_2=SDA_1=0;

4) 最终SDA_2被SDA_1拉到0,完成低电平的转换。


03

第三步,Slave往Master发数据1



1) 右边Slave SDA_2为输出,驱H-5V;

2) 左边Master SDA_1为输入,对外高阻, 被RP1上拉到3.3V;

3) NMOS 因为VGS=0一直关闭;

4) NMOS关闭,Master SDA_1维持3.3V高电平,完成电平转换。


04

第三步,Slave往Master发数据0



1) 右边Slave SDA_2为输出,驱LOW;

2) 左边Master SDA_1为输入,对外高阻, 被RP1上拉到3.3V;

3) NMOS 因为VGS=0一直关闭;

4) NMOS关闭,Master SDA_1为3.3V高电平。

问题来了,我们看到右边的SDA_2位Low,但是左边的是SDA_1位High,那不是失败了嘛,不可能啊,这个电路是好的啊。

我们来看看下一步会发生什么,其实这个问题没有那么难,仔细看看这张图,还是比较容易发现线索的。

我们来揭晓答案:大家看看上图的NMOS下面多了一个二极管,我们把这个二极管叫做Body Diode,它会在这个时候发挥作用。

5) 由于左边SDA_1为高,右边SDA_2为LOW, Body Diode导通;

6) SDA_1被拉低,导致NMOS VGS>0 后打开;

7) NOMO打开后,SDA_1和SDA_2相当于短在一起;

8) 最终左边SDA_1被右边的SDA_2拉低变为LOW。

我们看到由于体二极管的作用,打开NMOS管,使得右边Slave输出的L=0顺利到达左边的Master SDA_1输入端。

当你看着这张图,按图索骥,也许不难发现答案,但是在面试的时候,一是紧张,二是没有清晰的电路提示,有时候容易发挥不出来,你们觉得呢?

I2C第五罪


每一笔I2C的访问都是随着设备互相之间的ACK后结束的,这就好像你和别人商量个事情,要等到别人答应了,这次对话才算结束;又或者你给别人寄一个包裹,一般要等到收件人收到东西后,给你回了电话,你才会认为东西送到了。

这里的“答应”“回电话”就等于I2C里面的ACK,这一点在前面都已经反复叙述了,这里不再展开说明(可以戳文段开头的链接回顾哦)

我们先来看看下面这张图:

做几点说明,让大家明白讨论内容


1.系统串口打印信息如下,表示I2C访问失败:

I2C slave device not found num1,addr 0xe2====i2c_send_command_to+scc_rommon send_status Failed.

I2C slave device not found num1,addr 0xe2====i2c_send_command_to+scc_rommon send_status Failed.

2.上图中我在ACK的位置坐了标注,细心的读者只要和下面这张正常的图一比较,就可以看出差别,在ACK的位置,SCL为High/SDA为Low,但是上图确刚刚好相反;

3.我们现在知道在第一个ACK的位置, Slave设备本来应该给出SDA=Low,为了更加清楚的分析问题,我们再来一张说明更多的图。

由前面的说明,我知道是Slave设备出了问题,那么到底出了什么问题呢?

我们先把最前面那张图的案例分析一下,可能有人已经注意到了图中有一个@-40C,对了,没错!就是我们的交换机放在温箱里面做高低温时,在零下-40C的时候发现的问题。

有人问-40度下,这波形怎么量到的啊?

这明显问到了关键点上,硬件工程师的辛苦就体现出来。首先要把I2C信号线和串口线分别从板子上焊接出来,再分别连到外面的示波器和电脑上,然后就苦逼地守着温箱,运气好的话很快就能复现问题,运气不好要折腾很久才能trigger到这个issue,真的是一把辛酸泪啊。

解决的问题过程更加复杂,而且并没有什么技术含量可言——说服Supply Chain的人认为是vendor的器件问题,让他们在芯片生产线做Screen的时候,从原来的泡-40/5分钟改为-45/20分钟,把没有margin的芯片筛选掉。这个和Supply chain以及vendor沟通的过程是痛苦的,大公司嘛,不说了,你们懂的……

现在我们知道了器件受环境影响会产生I2C不ACK的问题,下面这张图是另外一种情况,先故设迷局,一起看看下图中有什么问题?

我承认,这个真的不好发现,以前团队中的某工程师在调试一个Sony的IMX291 Sensor时,CPU通过I2C死活访问不了,最后把波形一点一点在示波器上触发下来看,还是看不出来,这就难怪,没有经验确实很难找。

我们先来看单次写操作:

1.Start

2.Master发device address

3.Master发 R/W为Write

4.Slave 回ACK

5.Master发寄存器地址,也叫Word Address

6.Slave 再回ACK

7.Master发要写的数据给Slave

8.Slave收到数据后回ACK

9.Master看到ACK后发Stop结束本次操作

我们再来看单次多操作:

1.Start

2.Master发device address

3.Master发 R/W为Write

4.Slave 回ACK

5.Master发寄存器地址,也叫Word Address

6.Slave收到后再回ACK

7.此时Slave内部的寄存器指针已经知道对应的寄存器地址

8.Master发start

9.Master再次发device address

10.Master 发R/W为Read

11.Slave发出ACK

12.Slave发数据给Slave

13.Master收到数据后发NAK给Slave

14.Master发stop结束本次读操作

所以上图的读操作一共有两个错误,为了防止读者烧脑,我直接标注出来吧:

不知道大家在前面阅读的时候有没有看出来呢?特别是第一个Start很容易漏掉,不是老司机一下子真心发现不了。

添加了这里漏掉的START和ACK后,我们的Sony Sensor终于可以访问了,所以时刻要注意ACK是否少了。

为了让大家清晰理解,这里给出一张完整的逻辑图,大家在理解时要注意和写操作做对比,然后一定要独立思考弄清楚为什么会有这样的差别?一直到自己彻底想明白了,以后独立解决问题的速度也就会变快。

最后我再给大家看一张图,请大家自行研究错在哪里?答案我们将在“第六宗罪”里面揭晓。

I2C第六罪


越到后面的几宗罪,难度越来越大,请读者要认真仔细的思考,确保学为己用。

先来讲个有趣的故事,我们在大学里喜欢去图书馆自习的时候抢位置,特别是喜欢抢靠近漂亮女生旁边的位置,可以一边上自习一边欣赏美女,^_^

当你要临时离开一会儿的时候,方法也比较简单,就是放一本书在椅子上,很多时候当你回来的时候却发现你的书被人拿开了,椅子上坐着的另外一个帅哥和旁边的那位美女在攀谈,恨的牙痒痒啊。

你能做的就是等那位帅哥和美女聊完走人,你再回到那个座位上,可是你却沮丧的发现,刚刚旁边那位美女也走了,换了另外一位男的,唉,人生最悲惨的事情不过如此。

下面给出一张图,大家来看看是否存在问题。这张图是用I2C协议分析仪抓出来的,乍一看也没啥问题,该有的都有,特别是最后在Stop之前的NAK也是有的,但真的是OK的吗?

我们再来看一张图。

对比这两张图,我们得出:这是一个读数据的操作。

要找到第一张图的问题其实不难,我们只要仔细核对就可以了。

下面是核对出来的结果,我们发现在第二个Start之前,多了一个Stop。这样一看,这个错误还是挺明显的。

说说总是很容易,软件工程师在代码里构建这个时序的时候,很自然会认为,前面第一笔的写操作(把Word address写入Slave的指针)已经结束,后面的一笔,读操作开始之前就应该Stop掉。


为了清洗的说明,我们列一个顺序如下:

1.Master把要读的数据(或者寄存器)的Address先写入Slave,这里要注意理解好,这里Word Address相当于是一笔数据;

2.此时要注意整个读操作刚刚进行了一半,千万不能加Stop;

3.Master在收到前面写操作的ACK后,发一个Start;

4.Master再发一次Device Address,然后开始接收读的数据;

5.Master收到数据发一个NAK, 然后再发一个Stop结束整笔操作。

有人问,既然我们在第二个Start前多加了一个Stop,也没有见系统报错,一切正常啊,有时候访问还是成功的,这到底是为什么啊?

这里有个原因很重要:因为读操作最后是有Master发出的NAK + Stop来结束掉的,而NAK是SDA-HIGH,所以即便有时候操作不正常,只要不操作SDA(SDA默认的电平时HIGH),也能得到NAK误导对方。

我们继续来说一下,如果第二Start前面多了一个Stop会产生什么样的现象?

这是发生在ONU光猫上的Issue,现象是:从光模块SFP读回来的数据值总是不对,反复试验发现偶尔也能读对,但是写操作都是准确的。

这里一定会有人问,你读操作不正常,怎么知道写的是对的啊?好问题, 我们通过设置环回和打开/关闭光模块等写寄存器操作,反复确认我们写寄存器的操作是准确的。

我们来看光模块的I2C读写标准SFF-8431里面的图,可以看到一次读操作和前面叙述的一样,分为两个部分,中间用一个START隔开。

1.Master写device address =0XA2和写命令

2.Master发出word address 0X6E

3.Master插入第二个Start

4.Master再次发出device address =0XA2和读命令

5.Master接收光模块的数据0X82

6.Master发NAK

7.Master发Stop结束本次操作

注意:图中黑色部分是我们在Vendor的平台上用准确的方式读写抓到的波形。

如果按照上面所说的,我们在中间加了一个Stop会产生什么样的现象呢?

为啥读数据会不正确,但是I2C总线并没有出错信息吖?前面我们已经林林总总的叙述了一些,下面给出最终的描述。

看下面这张图,是我们和Samtec的FAE在出问题的板子上一起抓到的波形图,很明显我们看到多了一个Stop,下面我们来进行分析:

1.Master写device address =0XA2和写命令

2.Master发出word address 0X6E

3.Master多插入了一个Stop

4.Master插入第二个Start

注意:下面被插入了一笔完整的写数据的操作。

5.Master又发出device address =0XA2和写命令

6.Master发出word address 0X7F

7.Master发出写的数据0X80

注意:开始接着上面未完成的读操作继续

8.Master又多插入了一个Stop

9.Master又插入第二个Start

10.Master再次发出device address =0XA2和读命令

11.Master接收光模块的数据,我们看到读到的数据是全0,为什么呢?

12.Master发NAK

13.Master发Stop结束本次操作

相信很多人已经晕了,这到底是咋回事啊?

原来:一笔读操作,由于中间多了一个Stop,所以系统软件进程误以为前面读操作完成了,所以横空插进来一个写的操作,并且这里的写操作准确的完成了。

在写操作完成了,我们看到Master试图继续完成刚刚被中断掉的读操作,其实这也可以啊,大不了分两次,只要最终数据能准确读出来也行,可我们此时得到的数据却不是刚刚的0X82了,而是0X00,这又是为什么呢?


我们来结合这张图描述一下发生错误的过程:

1.Master开始读操作的第一步把0X6E写入Device;

2.此时被插入另外一个写操作;

3.写操作顺利完成并且把Pointer写成了0X7F(注意已经不是原来的0X6E);

4.Master继续刚才被中断的写操作;

5.注意此时Pointer的值是0X7F,所以读到的值是0X7F这个地址的值。

这里就清楚了:一次完整的I2C读操作访问,如果中间加了不应该有的Stop,就会被其它进程强占,从而插入另外的写操作,导致访问memory或者寄存器的地址指针被覆盖,Master然后接着完成刚刚被中断的操作,也不能正确读写到要访问的值。


这里分享几件有趣的事:


1.由于只是读有问题,写操作是好的,所以产品的功能是OK的,在市场卖了那么多,都没有人发现这个问题,也蛮搞笑的;

2.I2C的读操作一直NAK操作是SDA=HIGH, 由于SDA默认就是High(前面讲过Open drain和上拉),所以即便设备没有做什么?也会让等待NAK的设备误认为NAK已经产生了;

3.系统软件有时候是会和硬件打架的,所以相互合作才能找到问题的根源,否则相互推责任只会让解决问题很困难;

4.发现问题并且解决问题,写个文章很简单,但是调试的过程却是痛苦的,特别是I2C这种接口,一共2根线,很多人比较轻视,这是不可取的。

I2C第七罪


第七罪姗姗来迟,既然是大结局,当然就应该让大家更加深刻的来理解I2C。我们先来复习一下大家共有的对I2C的认知:

1. I2C的SCL(Clock)总是由Master来驱动;

2. I2C的SDA (Data)则不同,Master和Slave分别都驱动。

这两点想必读者都没有什么疑问,因为前面的六宗罪都已经说得比较多了,我们今天要说的是另外一种特殊情况,就是Slave也会去Drive。

先来看一个例子,我的一个项目发生过这样一件事情,CPU在访问板子上的另外一颗SENSOR时,一直Fail,我们非常仔细检查了时序,都是准确的。

注意:这里我们发现是有一个知识盲点,导致一直找不到Root Cause。

细心的工程师会在测量过程中发现, SCL上有下图这样的尖尖的毛刺,毛刺有矮的也有高的,于是我们认为SCL上这种尖毛刺会导致Slave的状态机误触发,跑飞了。

整整一个星期我们都是这么认为的,尝试各种方法试图去消除这个毛刺。

因为信号从主板通过连接器到子板,我们认为这里的SCL信号很容易受到外部的干扰,比如从空间耦合过来的噪声。

所以在信号上加电容、加匹配、降低上拉电阻等等,试图滤除毛刺。

得到的效果是,即便我们滤除的毛刺有改善,可问题依然存在,只是稍有好转。

虽然说,现在拿着结果来讲故事听起来很轻松,其实那个过程真的很难受,我们接着往下看。

俗话说三人行必有我师,有个聪明的工程师突然想到I2C有个Clock stretch的机制,来看这张图,我们看到Slave把SCL拉到低,这是什么情况呢?


先来叙述一下Clock Stretch:

1. 当Master是高速I2C设备,Slave是Low Speed设备时,Master输出的SCL的频率超出了Slave的承受范围,此时Slave跟不上Master的速度怎么办? Slave就要想办法告诉Master。

大家知道为什么这里会看到右边矮矮的Glitch吗?

因为Master此时是想驱动SCL高电平,而Slave却拖住SCL不让变为高电平,这个其实是一种想象,实际测试是看不到的,这里是为了方便大家理解。

2. 我们再来看一张图,加深一下理解,Slave会强行把SCL拉低,拖住Master,这和之前我们对I2C的认知是完全相反的,此时Slave 是输出SCL信号,而Master则是检测SCL状态的输入信号。

举个例子:

正常情况下Master/Slave都是通过在SDA上的ACK信号来确认一笔操作的成功,如图:

但是如果Slave来不及怎么办?

(看下图)Slave直接把SCL信号拖住告知Master:兄弟我还没有准备好,你先等等我啊。

此时Master要做的事情就是乖乖等着,并且一直检测SCL的状态(输入信号),当Slave松开SCL信号,由于上拉电阻的存在,SCL自然变高,Master检测到SCL变高后,才开始检测ACK信号,然后继续下一步操作。

说了那么多I2C stretch,想必大家应该理解了。

回到最上面我们遇到的CPU和SENSOR之间的I2C问题,我们测量得到毛刺确实是罪魁祸首。

因为此时CPU和SENSOR进入了Clock Stretch, Sensor拉低了SCL,而CPU Master不断地检测SCL的状态,期待高电平的到来,此时毛刺就误导了CPU,CPU看到尖的毛刺就认为Slave已经松开了SCL,就立刻开始下一步的动作,此时Slave很冤枉了,自己明明拉低了SCL让Master等着,可是这位兄弟怎么这么不听话呢?

解决的方法很简单,CPU的I2C控制器在进入Clock Stretch时,检测SCL并且判断高电平时有一个De-Glitch的功能,我们之前没有打开,打开后就可以滤除Glitch这样的窄脉冲了。

简单一点讲,就是当检测到一个SCL的高,用一个计数器继续连续计数,只有发现连续的40个高电平才认为是SCL真的拉高了,否则就认为是毛刺,不予理睬。

聪明的人很多,我们再来说说最近碰到的另外一件关于Clock Stretching的事情。

紫色的线有一段半高, 其实原因也简单,就是因为此时Slave拉低SCL,但是Master不支持Clock Stretch,此时就发生了冲突。

Master不支持Clock Stretch,我们就需要通过软件的方式去模拟,此时有两件事需要实现:

1. Master要把SCL切换为输入,然后不停检测SCL的状态;

2. Master在检测SCL状态一定要做De-bounce或者De-Glitch的滤波。

看完了上面的叙述,想必大家都可以理解原因。可是这样会让软件工程师们很麻烦,那么应该怎样绕开Clock Stretch呢?聪明的工程师总有自己的办法。

我们和Slave芯片的Vendor确认,每次Slave在做ACK后,芯片需要最多5ms的Clock Stretch延时。

我们拿到这个数字后,软件工程师只要注意在Master每次得到ACK后,先等待5ms后再对Slave做下一笔操作。

在这个5ms期间Master完全不用关心 SCL信号上是高电平和低电平,因为5ms以后Slave肯定松开了SCL,也就是说SCL肯定是高电平了。

这种方法就避免Master的SCL信号切换为输入,还要不停地检测SCL的状态,最重要的是不需要做软件的De-bounce或者De-Glitch算法。

自此I2C的七宗罪就结束了,希望这七宗罪可以cover所有硬件设计过程中的I2C问题,到目前我还没有发现有其它超出这七个范围的问题。

展开阅读全文

页面更新:2024-04-12

标签:毛刺   板子   电阻   电容   电路   芯片   信号   操作   数据   设备

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top