EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
本帖最后由 EDA365_PCB 于 2024-1-22 17:45 编辑 7 G* g) ^2 e4 w" |: J6 u
9 G4 [4 u7 @5 _: r. S
I2C七宗罪之第六罪——枯燥的协议
: X7 G' f" E: Q7 _; QEDA365原创 作者:John % W! C9 y& U( G" [% k* U) N
+ W# g* j& K4 j1 y+ v: y) V- i! ^ |, L7 c: t5 j, s
' B! v2 p% M" Q
- T$ }6 D- w2 b- v3 l * c; Q* K6 n% v9 C- L. r) v3 N8 e7 r
越到后面的几宗罪,难度越来越大,请读者要认真仔细的思考,确保学为己用。5 \; _# {! O. V% l! k
- u" O" f: I" f% `) N6 U8 L* D+ Y* C, [ h
先来讲个有趣的故事,我们在大学里喜欢去图书馆自习的时候抢位置,特别是喜欢抢靠近漂亮女生旁边的位置,可以一边上自习一边欣赏美女,^_^ m6 U8 }- t2 @1 [( t
9 w( O& p# o7 A ^; c' ~0 b4 _2 l* c9 J' b9 C; Y; d/ y9 ?# ]7 G' t1 K1 E
. P- _* F& M8 {! h# T& w6 S
当你要临时离开一会儿的时候,方法也比较简单,就是放一本书在椅子上,很多时候当你回来的时候却发现你的书被人拿开了,椅子上坐着的另外一个帅哥和旁边的那位美女在攀谈,恨的牙痒痒啊。0 ]# Z- b l% P% n% n* X! ^! E- Q9 J1 R* u7 P
8 X; { D- r1 X& F- ]& w* _* J1 H: a% D
你能做的就是等那位帅哥和美女聊完走人,你再回到那个座位上,可是你却沮丧的发现,刚刚旁边那位美女也走了,换了另外一位男的,唉,人生最悲惨的事情不过如此。* w. I" l6 ?- c, V. K% y5 D H% v) l& N8 T& t. u5 W2 }* ~3 y
下面给出一张图,大家来看看是否存在问题。这张图是用I2C协议分析仪抓出来的,乍一看也没啥问题,该有的都有,特别是最后在Stop之前的NAK也是有的,但真的是OK的吗?9 r* s, r$ x+ Q0 F6 W+ i+ v& f# Q
我们再来看一张图。& a2 K- t2 N; x! O% @, [2 T
* V) n3 r. S ^ 对比这两张图,我们得出:这是一个读数据的操作。
/ M9 k# R/ \1 v) k4 n2 o0 Q' w* _, L5 C$ h, @/ X6 F9 T& U5 C6 H8 ~( x/ z
要找到第一张图的问题其实不难,我们只要仔细核对就可以了。
" |2 {7 v6 c) Y+ K/ d5 ^6 X4 G: i) [% [& g3 F6 j- Q
* s; v6 r- ]7 z M( c. C下面是核对出来的结果,我们发现在第二个Start之前,多了一个Stop。 这样一看,这个错误还是挺明显的。4 z! N/ u3 Z5 S+ u4 U$ _( J+ N5 @1 S4 [! t9 g6 `
; c; {- s9 s1 m: G$ ~' n' N3 k4 @2 s" ~8 Z7 r
说说总是很容易,软件工程师在代码里构建这个时序的时候,很自然会认为,前面第一笔的写操作(把Word address写入Slave的指针)已经结束,后面的一笔,读操作开始之前就应该Stop掉。
为了清洗的说明,我们列一个顺序如下:4 K& j' t: P$ v n B" y. D; M# P+ r
* y, {" L; q4 S( `+ {
# c! p. b1 q+ l. V2 ^( G1.Master把要读的数据(或者寄存器)的Address先写入Slave,这里要注意理解好,这里Word Address相当于是一笔数据;
: x- ~& x5 @! W; [' J2.此时要注意整个读操作刚刚进行了一半,千万不能加Stop;
0 g* d/ ?$ N) `3.Master在收到前面写操作的ACK后,发一个Start;; |0 i- l- \0 W4 \" X/ F- X- X: J, ]0 s
4.Master再发一次Device Address,然后开始接收读的数据;/ M7 M) T; t& \, w7 j' p$ R5 z) ?* F5 P1 R; d7 \( U* s' D
5.Master收到数据发一个NAK, 然后再发一个Stop结束整笔操作。8 E# k/ N2 A$ S' s* X9 ]/ C
7 K% V4 {& Y0 ^6 t0 y有人问,既然我们在第二个Start前多加了一个Stop,也没有见系统报错,一切正常啊,有时候访问还是成功的,这到底是为什么啊?, d v& W8 c& z; {* c& T% S9 F
$ U9 L; s+ ]% F, Z2 ^" |0 V; O1 b+ d+ f% ` ~7 Z1 K2 W
这里有个原因很重要:因为读操作最后是有Master发出的NAK + Stop来结束掉的,而NAK是SDA-HIGH,所以即便有时候操作不正常,只要不操作SDA(SDA默认的电平时HIGH),也能得到NAK误导对方。7 Z8 w. R7 l5 b7 B
1 I$ q! G1 G o: K; Z
我们继续来说一下,如果第二Start前面多了一个Stop会产生什么样的现象?% d& d" q! `1 s& l, ?5 K
" t. H3 g, Z: i& b9 G4 I; D; R! q; c9 x: x- m1 v, o
这是发生在ONU光猫上的Issue,现象是:从光模块SFP读回来的数据值总是不对,反复试验发现偶尔也能读对,但是写操作都是准确的。& P" X/ |" | e9 X; }+ o! c1 ]: o% N* G0 X! \8 c
9 _$ |3 W4 M% R5 e2 O9 j$ V
这里一定会有人问,你读操作不正常,怎么知道写的是对的啊? 好问题, 我们通过设置环回和打开/关闭光模块等写寄存器操作,反复确认我们写寄存器的操作是准确的。
% W; f; R+ l" E& p& _- g2 U9 a1 k3 Z; }" G0 ?9 D9 B3 i8 y
d8 e! E: z5 q; ]5 j! k我们来看光模块的I2C读写标准SFF-8431里面的图,可以看到一次读操作和前面叙述的一样,分为两个部分,中间用一个START隔开。% H; e0 o! S$ D4 I8 e/ k6 K
1.Master写device address =0XA2和写命令3 x. ]# j! Q+ T3 C5 y1 S
2.Master发出word address 0X6E
. @, U* g+ b% i# j3.Master插入第二个Start. b$ E9 H' K! C' J3 q5 F: [8 L& K1 L% G# y7 d) g$ {. J1 G7 T
4.Master再次发出device address =0XA2和读命令6 K7 f# k0 D$ U! I# {' n
! w: K0 o% y8 J" Z% L2 I0 N+ _' q5.Master接收光模块的数据0X82
: Y' \5 }& ]* J6.Master发NAK8 u8 E1 j7 Z' f2 B- x( u/ L$ T4 C1 a0 [3 Y5 c' s. H0 t% B' p
7.Master发Stop结束本次操作
' k2 d/ U+ U3 t8 H$ [, l注意:图中黑色部分是我们在Vendor的平台上用准确的方式读写抓到的波形。3 H" p8 a8 @2 R ~: D
9 S( L& E8 y* d" m7 z9 `
6 ]: _8 P7 r6 p4 X! J/ n1 O如果按照上面所说的,我们在中间加了一个Stop会产生什么样的现象呢?
+ i7 `( Y0 o0 k7 b" J: {+ a- D: c, `5 n. y. G) f4 `( r5 a
为啥读数据会不正确,但是I2C总线并没有出错信息吖? 前面我们已经林林总总的叙述了一些,下面给出最终的描述。/ D6 M/ M6 {, t, {8 s- ]! V# S
7 @/ {: l! m# B看下面这张图,是我们和Samtec的FAE在出问题的板子上一起抓到的波形图,很明显我们看到多了一个Stop,下面我们来进行分析:8 D0 \/ J4 H/ S"
1.Master写device address =0XA2和写命令& i4 c5 b* S5 I9 o
2.Master发出word address 0X6E$ B/ h9 Z" Q4 w- Q/ U! D. X( z: F% S% j: `% b! v1 r0 ?& {+ V
3.Master多插入了一个Stop+ ^9 p6 ^% \! [& i
k# Q8 c+ j, p9 F3 W1 v) i4.Master插入第二个Start3 b: v0 w, `6 ^4 E
注意:下面被插入了一笔完整的写数据的操作。. H; o8 l% M# K# \2 n7 g
9 k; j6 e' e8 s1 J3 Y) H1 h5 V& E5 P, I8 k$ [
5.Master又发出device address =0XA2和写命令
g! e* m( p4 H) c! n6.Master发出word address 0X7F- }" i i e4 b+ P0 @4 e
" l5 V% f v- e9 R3 b+ o8 X/ n- ]7.Master发出写的数据0X80
+ v3 u$ w5 b. G注意:开始接着上面未完成的读操作继续
$ v6 ]6 ~! g2 V$ a3 ?4 ?: ] U, q9 C: x$ f) A H3 D- @; b2 F
8.Master又多插入了一个Stop; b6 [: [, S& Y
9.Master又插入第二个Start* E' \$ G( @* |2 n' [: f
8 F$ g7 l5 o! H1 R# g3 n- _$ ` c: o10.Master再次发出device address =0XA2和读命令9 L; O% k! B- |+ g2 G: y, e% f5 S- [; i9 o! I
11.Master接收光模块的数据,我们看到读到的数据是全0,为什么呢?& L6 S5 l1 m/ O4 n) F- P k& v6 E% o. Q; y, m
12.Master发NAK7 F, c" ?2 K7 v& G' U. s3 H7 X1 M$ e3 a* R! s" l
13.Master发Stop结束本次操作
9 ], _% P/ t0 d1 _8 A" B/ e2 s: `9 ^) P( O c* v+ j- T- L4 t+ k q4 I$ ~, {
相信很多人已经晕了,这到底是咋回事啊?- B' Y: O6 }4 G; M
+ V, }: B7 _4 X9 t# w4 S1 A2 _3 f: i/ j( D- ]0 L* S# a2 D
$ ^2 U0 A V, v/ O# Z原来:一笔读操作,由于中间多了一个Stop,所以系统软件进程误以为前面读操作完成了,所以横空插进来一个写的操作,并且这里的写操作准确的完成了。- E9 w; X# u4 `: D4 V" S% W9 g
, l) p/ @* v! n, S0 X4 }9 n3 H2 B
在写操作完成了,我们看到Master试图继续完成刚刚被中断掉的读操作,其实这也可以啊,大不了分两次,只要最终数据能准确读出来也行,可我们此时得到的数据却不是刚刚的0X82了,而是0X00,这又是为什么呢?
我们来结合这张图描述一下发生错误的过程:
" @9 o3 n/ d# T4 X2 X) E& ~3 {
0 @( g# Y. n" r2 p- M2 K1.Master开始读操作的第一步把0X6E写入Device;
, E' v8 A3 x: K2 Y( t: {$ u2.此时被插入另外一个写操作;- j9 u2 ~# a, R/ x+ ~
. A* H6 y* q V, P( ~* s+ _9 I, m3.写操作顺利完成并且把Pointer写成了0X7F(注意已经不是原来的0X6E);# S& x' U* t- {: T1 }8 l" d0 F' U6 I2 h9 k' S; f
4.Master继续刚才被中断的写操作;. T& H) r$ K6 V. B- }' g- u- p* x$ e' a
) n2 b- u v/ J: \. O5.注意此时Pointer的值是0X7F,所以读到的值是0X7F这个地址的值。3 R6 j) l5 V: h8 L: K9 X
c1 ]9 Z/ V. f1 g, f- P. o# a5 n& z3 s. Q0 ^7 o% P a
这里就清楚了:一次完整的I2C读操作访问,如果中间加了不应该有的Stop,就会被其它进程强占,从而插入另外的写操作,导致访问memory或者寄存器的地址指针被覆盖,Master然后接着完成刚刚被中断的操作,也不能正确读写到要访问的值。 B$ d0 |' z1 t9 m
g4 E, H* n. A( n$ d2 ?8 f9 _6 O& t! U: I8 L
这里分享几件有趣的事:
' T9 S3 B' \# L2 y3 q+ N- W1.由于只是读有问题,写操作是好的,所以产品的功能是OK的,在市场卖了那么多,都没有人发现这个问题,也蛮搞笑的;
7 T. @. a; I- f( j. ~7 k. _7 ^) l2.I2C的读操作一直NAK操作是SDA=HIGH, 由于SDA默认就是High(前面讲过Open drain和上拉),所以即便设备没有做什么? 也会让等待NAK的设备误认为NAK已经产生了;4 a6 ^3 A: ~ B- v& g
3.系统软件有时候是会和硬件打架的,所以相互合作才能找到问题的根源,否则相互推责任只会让解决问题很困难;
, X- X+ \! s) P4.发现问题并且解决问题,写个文章很简单,但是调试的过程却是痛苦的,特别是I2C这种接口,一共2根线,很多人比较轻视,这是不可取的。
5 |3 x/ }& U) w& g5 {6 L注:本文为EDA365电子论坛原创文章,未经允许,不得转载。
4 b0 C+ n$ G! g- f) i" O6 g2 H6 H |