你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于STM32 的IIC通讯原理及其实验经验分享

[复制链接]
攻城狮Melo 发布时间:2023-10-19 16:29
I2C两线式串行总线通讯协议,它是由飞利浦开发的,主要用于连接微控制器及其外围设备之间,它是由数据线SDA和信号线SCL构成的,可发送和接收数据即在MUC和I2C设备之间,I2C和I2C之间进行全双工信号传输,高速I2C总线一般可达到400kbps。一般我们也称为TWI接口。
* k9 Y! C/ h0 V* x
I2C支持多主机模式:
! B( ?9 a9 l% e& [  M( E

4 f' o3 I- _! E8 g3 f# K
微信图片_20231019162807.png . V, F& z$ j# [9 l8 \

3 D+ v( y3 x+ L, A4 Q0 Y: A0 m4 f
即在这个主线上可以挂载n个I2C外设。
" ?( g) S) U# @. n( c
对于I2C协议,其实也很简单,不要想的那么复杂,其实就是电平的变换。我们可以人为的分为6个部分

' K1 `3 g& g, \( S" Z/ w
整体时序图:
3 U* g% O8 r9 R; l2 D1 e
' u$ b- c* m" K
微信图片_20231019162810.png
+ D! y1 y" h  Q; t/ ?7 ]. r7 G- |( ?- v9 m8 {( r; x
各状态:
  • 空闲状态

    3 G' b6 f: V) r5 b& \

* r# C2 }* N1 V. n8 T2 L

* j- G5 v1 B4 K- N) {6 r' I0 R" b1 i 微信图片_20231019162813.png ; }( ?2 s' H9 o; n! m; O; K

' e/ t  o: Q, X( q# I& h. P5 l
I2C总线的SCK和SDA两个线同时处于高电平的时候,规定为总线的空闲状态,这个就是由总线上的上拉电阻把电平拉高的。
$ p5 x/ }9 ^, v4 a) W
  • 起始信号

    ) V* D. T' J$ y$ d

- d" d5 D5 A! R; r; R" T: K! i9 g: | 微信图片_20231019162816.png
0 ]" _$ C1 `, N& a! k1 t1 O8 P* d! E" S2 U6 Z3 j/ ]7 `6 a3 q
当SCL为高电平期间,SDA由高电平变成低电平,即为起始信号。启动信号是一种电平跳变时序信号,不是一个电平信号。
' Y5 c- ]8 \* V+ ]1 o
  • 停止信号
    1 f) A# |" q1 ?0 r/ w# O
当SCL为高电平期间,SDA由低电平变为高电平,即为停止信号。停止信号也是一种电平跳变时序信号,不是一个电平信号。

% j* x" a' h7 \& V
  • 应答信号
    3 O' R% t& |& X5 n6 w+ p5 ~/ J4 O

. y$ E) v. ~) m! R1 i
( M6 [4 x3 ^& M1 ]
微信图片_20231019162819.png 9 J9 O9 L: a5 d8 o+ [

3 a$ G# {4 h9 B& G
发送器每发送一个字节(8bit)数据,就在时钟脉冲(SCL)9期间释放数据线(SDA),再由接收器来反馈一个应答信号,应答信号为低电平的时候,规定为有效应答位(ACK:应答位),表明接收器已经成功的接收了该字节,应答信号为高电平时,规定为非应答位(NACK:非应答位),表示接收器没有成功的接收该字节。

/ F0 t' v+ n6 W+ D5 G2 k4 X
对于反馈有效应答位(ACK):接收器在第9个时钟脉冲之前的低电平期间将SDA拉低,并且保证在该时钟的高电平期间,SDA为稳定的低电平。大家主要看图,看看是不是这样的。
: [# b# t% b9 U' V: S
要是接收器是主控器,那么它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据的发送,并且释放SDA线,以便主控接收器发送一个停止信号。
3 C) j5 X+ p0 w$ _1 @4 v
  • 数据的有效性

    * W; X# z$ M! b8 ^9 w$ G
3 U8 R1 N/ F7 m/ \
0 m9 C/ @$ ^' `. u
微信图片_20231019162822.png 8 E0 o. `. W& _$ I/ y/ x# c

) h+ Q. U# y, R6 H$ t; M6 w
5 }: I$ m, d! J- D$ R( u+ a
时钟信号(SCL)为高电平期间,数据线上的数据必须保持稳定,只有在时钟信号(SCL)为低电平期间,数据线上的高电平或者低电平才能发生变化。
; u! d  g" }8 I
数据必须在时钟信号(SCL)的上升沿到来之前就准备好,并且在数据信号的下降沿来到之前必须稳定。

  g! I. d: p+ [
  • 数据的传输

    0 H- x5 U1 c- h- @& O8 C" o( Y
在SDA上的每一个位的数据的传输都需要一个时钟脉冲,即在SCL串行时钟的配合下,SDA上逐位的串行发送每一位数据。数据位的传输是边沿触发。

. f6 b/ L4 |! n; D
示例代码讲解
  • 初始化IIC
    ' ?* C. Z' B6 v3 B

4 b  X# B5 _0 S7 L0 b4 B

: q( T# ~: @2 K1 v# h# F* F 微信图片_20231019162824.png 1 o; b  c9 s5 \! o1 {" T/ |  f9 ~5 O  R
+ ]" n0 L- z& N: R0 `
8 d; P, Y( U4 @
其实就是对两个线的初始化,我这里使用的是PA6和PA7,开始都设置为输出,中途会改变PA7的输入输出属性,在空闲状态,我们知道SCL和SDA是被拉高的,所以这个地方我们给高电平。
( y, u2 k' R& s1 O
  • 产生IIC起始信号
    ' F! p$ N6 x1 p* q! a& k/ J

4 H5 S1 |7 l% r5 U5 _) W% J: T  B

1 o) J& `4 H: ^3 u 微信图片_20231019162827.png
, X7 ]- T% J0 T0 v  v3 e" W9 R! J
# W, @! ]  B- u1 ]9 Q
微信图片_20231019162830.png
0 a4 P" [# i' V' J5 Q0 j4 n" k& \8 m' p' n
将SDA线设置为输出,然后SDA和SCL都设置为高电平,延迟4us,然后将SDA拉低,延迟4us,最后将SCL拉低。这其实就是协议规定的动作了。

0 \; k3 E7 T7 q) ]4 C. P
  • 产生IIC停止信号
    ; g! E% e& b9 [$ r
* \! B& ]* \1 R# d

, t5 q- f' p1 x% [" w3 g" P" J 微信图片_20231019162834.png
( w- O. `0 C# {* Y4 ~! A( \; i$ r. Q1 K7 O% Y: f

' ~% u3 @3 N) F' y' j! a
同样的道理,和协议的时序保持一致就好了。

; K# X4 Z( D3 T5 c. [% c. `! [$ h! i. v
  • 等待应答信号到来

    0 u2 O1 L9 L; t$ W; a1 r

% _/ W# [! n! y) K( K6 @2 ?
/ G. m+ l5 t0 d
微信图片_20231019162837.png
/ q+ q+ k8 ^* ]$ a, m! y7 `5 F: J' Y( X0 n
首先我们需要把SDA设置为输入,因为接收方要给发射方返回一个应答信号的。就是在SCL第9个高电平的时候,释放信号线,先拉高,然后持续等待,是不是有应答信号返回,其实就是返回一个低电平,所以我们一直在检测READ_SDA这个电平,持续一段时间,要是没有返回的话,我们认为超时了,就直接停止协议了,
2 z" d' k# i1 s7 q; d' a9 m
  • 产生应答信号
    6 v2 j! r/ p# G3 ^3 ?9 [. @
7 E1 {8 l; f1 p
7 h7 A6 M6 M1 z
微信图片_20231019162842.png
% O) Q, J3 p$ u8 X- }
; O* s$ ], F1 [
即在第9个时钟周期内,SDA都为低电平,为应答
0 F0 U4 h" q9 ?1 _
  • 不产生应答信号
    8 H: w+ ?. H) o% Q5 _
% d  `5 @3 k; i' p  A7 }, T! g0 f
  s3 C4 ?6 v4 \1 M( }$ y* ?
微信图片_20231019162851.png
- p' G' X; q0 b" f5 F6 l
. w' n# y, ?0 B) r# x

; Q+ }  v/ ~' o, l9 K& q
即在第9个时钟周期内,SDA都为高电平,为不应答
) p+ e7 Y1 t- Y+ G' v: H3 z8 U
  • IIC发送一个字节
    7 s5 X/ @  c# ]* S. s2 d9 R2 v
: M2 ~& p! y# E) q3 K

  G3 ~' I! o1 O6 i 微信图片_20231019162856.png
% h- q. ~: A3 G
; @9 g8 T/ ~" u& ~# d2 E/ \/ g- E0 _ 微信图片_20231019162858.png
! Q0 F3 I  c8 l: N. `5 Y# _/ b* Y* f8 ]  }
发送数据,SDA设置为输出。SCL拉低,SDA准备。
做一个8次循环,拿出1byte的数据,将你发送的数据和0x80作与运算,拿出最高位,然后右移7位,将这个数据放到最低位,这个数据要是1的话,那么SDA输出一个高电平,要是与后的结果为低电平的话,那么SDA输出一个低电平。这都属于准备发送信号阶段。
然后SCL拉高,完成数据的发送,然后SCL拉低,此时SDA也就可以拉低了,接着开始次高位的传输,直到传输完成。
9 r, M1 c8 t! ?" s
  • 读取数据
    / ]0 G5 z, `1 T" E( ?# F4 H

    / j  s: V' d* x0 W; K. t
微信图片_20231019162903.png
( w% A& I+ E# u: U( A" Q# I. `% j* R& ~0 B# L
1 g0 h' N8 F, ]* E; G6 d
读取数据,SDA要设置为输入了。SCL开始为低电平,然后SCL为高电平,我们开始读SDA上的数据,然后左移数据,将读取的数据放在低位。然后检测一下有没有应答。
其实基本思路就是这样了。我把源码附上:
+ e! e# E4 X1 p+ t' M2 X; L5 ]. c
i2c.h
  1. #ifndef __MYIIC_H% u, u) `8 Y8 e. O8 |
  2. #define __MYIIC_H. g- y$ z1 J+ k" c
  3. #include "sys.h") e: t5 |4 E! V2 i
  4. #include "stm32f10x_gpio.h"; O5 I; w. w: G; u+ U
  5. //IO方向设置* ~' j# H8 }* \1 m
  6. #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}/ k5 D, V3 e& {( a6 T
  7. #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
      I/ s2 S& `5 z4 h3 ]) b9 e  |
  8. //IO操作函数, I6 `! S% J* m' S- ]4 ^
  9. #define IIC_SCL PBout(6) //SCL
    ; p! z' y6 W- G. |/ C: z6 w
  10. #define IIC_SDA PBout(7) //SDA
    ! [& m9 ?: j# ]/ z( m
  11. #define READ_SDA PBin(7) //输入SDA
    . v# S4 l& q* T. ^5 v- ]4 o
  12. //IIC所有操作函数$ O( q0 q/ A# C. u
  13. void IIC_Init(void); //初始化IIC的IO口
    ' \+ {( v7 w) ]( q7 U1 k* N5 ]
  14. void IIC_Start(void); //发送IIC开始信号
    # ~! Y! ^. ^) t% e- d
  15. void IIC_Stop(void); //发送IIC停止信号
    ( e" q/ W% \# o/ Y4 H& `( A+ A4 @8 N
  16. void IIC_Send_Byte(u8 txd); //IIC发送一个字节
    1 ]8 X& _7 t% l( N8 o  M0 B2 G
  17. u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
    8 L, C; d: D& A: y6 g
  18. u8 IIC_Wait_Ack(void); //IIC等待ACK信号
    - `3 Z0 t) t. t) H
  19. void IIC_Ack(void); //IIC发送ACK信号9 m& v; Q) d7 T" \9 r
  20. void IIC_NAck(void); //IIC不发送ACK信号8 V$ M7 g: Y; ]6 ~3 Q+ H+ y, p" K
  21. void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
    " V. P; T# E6 s4 t- z
  22. u8 IIC_Read_One_Byte(u8 daddr,u8 addr);
    " |) [) ^0 P/ L$ S5 n1 n3 ~
  23. #endif
复制代码
$ ]0 E& j% }% ?0 N9 r* a
i2c.c
  1. #include "myiic.h"
    0 |8 R2 M4 L1 V
  2. #include "delay.h") Y- w4 M# l" }/ G, j6 g" D
  3. //初始化IIC6 w, v( f  j% T) S( I. b& U
  4. void IIC_Init(void)6 m9 t6 ~% }4 l9 ?% h6 o* y
  5. {                                         4 y. k8 M  T: m2 C3 T6 l
  6.         GPIO_InitTypeDef GPIO_InitStructure;  u& V0 a2 X) S0 P+ N# H
  7.         RCC_APB2PeriphClockCmd(        RCC_APB2Periph_GPIOB, ENABLE );        //使能GPIOB时钟5 [' q. ?& Q4 \3 |& s% o
  8.          
    : F; k, [7 }0 j! \' p. h1 x3 Y
  9.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;3 j9 M- k# H' `- Y7 r3 b8 h
  10.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出9 X( [  x7 p6 E9 E2 j' u
  11.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    9 N; P2 W, f2 c$ Y8 o; M
  12.         GPIO_Init(GPIOB, &GPIO_InitStructure);
    - L' [* j' S( h+ I% Q- s
  13.         GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7);         //PB6,PB7 输出高
    " v* V2 L# u1 g) |
  14. }
    ; x$ p* ~! H. {: ^
  15. //产生IIC起始信号, u+ V) n1 n; d. u& q" ^# k
  16. void IIC_Start(void)
    " \( I0 a5 B) {
  17. {
    ( `, W8 ?0 G6 E! Y
  18.         SDA_OUT(); //sda线输出  e, A2 ~% D6 o' y2 e/ U  G
  19.         IIC_SDA=1;                  * `2 t5 B8 \0 ]' \
  20.         IIC_SCL=1;
    $ [0 ^1 Y- w+ h; R& Y
  21.         delay_us(4);
    ' @' _* j- `! Q" ]' O
  22.         IIC_SDA=0;//START:when CLK is high,DATA change form high to low 8 q& h7 V. r5 B: d9 J
  23.         delay_us(4);& I1 X) d' u: K6 v, X5 U) u
  24.         IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
    * Y& l( Q1 D$ f1 _
  25. }         + R$ q  J1 ^( d, P# k( |8 o
  26. //产生IIC停止信号
    , ^! {% R4 Q5 Q- K
  27. void IIC_Stop(void). n- L3 C; h) X" U3 \
  28. {
    8 n' W; J2 n  q3 Y
  29.         SDA_OUT();//sda线输出. b+ D" _$ y7 ~! m& ~, q
  30.         IIC_SCL=0;+ |/ K5 o4 |$ B, R2 Y
  31.         IIC_SDA=0;//STOP:when CLK is high DATA change form low to high2 n# Y8 C  y+ z+ |, w5 _0 G! z
  32.         delay_us(4);
    - [" Z- X+ m( Q5 C* V6 S
  33.         IIC_SCL=1; 5 b9 Z8 |  c9 O7 {9 X8 T' z. S* S
  34.         IIC_SDA=1;//发送I2C总线结束信号; d6 i; T2 y2 g8 L
  35.         delay_us(4);                                                                
    ( x* }2 x" U' c6 x; r& Z
  36. }
    ; j3 J$ g1 I+ i/ r) W  F
  37. //等待应答信号到来% R: Y& h, \  C& Z- B
  38. //返回值:1,接收应答失败
    * E. p6 Q. i/ C9 b; b
  39. // 0,接收应答成功& U" L. b* Z/ D  m- D* _3 J1 n
  40. u8 IIC_Wait_Ack(void)
    ' T0 Q# x- r& k5 q9 ~) J
  41. {1 l0 }8 [. F. [! A/ Y) l1 m$ B
  42.         u8 ucErrTime=0;
    2 N) B* v  q9 S3 w6 }+ q. r* a2 K
  43.         SDA_IN(); //SDA设置为输入 4 V! V) S! E7 l' ?; B! g4 _
  44.         IIC_SDA=1;delay_us(1);         1 S" f- D' y0 X# Y& v5 s
  45.         IIC_SCL=1;delay_us(1);         ) v- w% X! P0 r/ V: J( r* G: m) q
  46.         while(READ_SDA)
    0 H6 I- }- U1 n4 `! R  n
  47.         {+ ^' t. |; T) _" _" r7 T$ S4 |: Z
  48.                 ucErrTime++;% Z" v, B' g# k6 E) D+ @
  49.                 if(ucErrTime>250)
    * l( N4 Z. W# p3 b: j
  50.                 {2 p# g( s% Y7 r% ]5 W/ y8 N
  51.                         IIC_Stop();
    ; J- @9 P# O' S7 _8 N5 F6 E
  52.                         return 1;
    / c7 d! b! d, N9 ?
  53.                 }
    7 b: Z) }0 b) V4 i+ Q7 Y6 U4 X
  54.         }1 W1 H* }( ~! P0 f
  55.         IIC_SCL=0;//时钟输出0          4 F. l4 u$ R& n3 Y3 `4 K/ a
  56.         return 0;
    3 O" [. `/ N0 [* f' i/ C1 R
  57. } . H$ C9 C  P6 S3 n) w
  58. //产生ACK应答
    8 A/ U& O  K2 C+ D5 I
  59. void IIC_Ack(void)# N6 ?3 M4 c; @2 o
  60. {
    . V1 d3 M* h7 I! H! X' Y
  61.         IIC_SCL=0;
    : A4 Z8 n9 E7 {& |6 d# G( z
  62.         SDA_OUT();3 h. T# F$ G5 }- T; t
  63.         IIC_SDA=0;6 i- p9 C* n3 o* b+ I
  64.         delay_us(2);
    5 Z4 B& Y2 J* t- P
  65.         IIC_SCL=1;) ]/ K4 x: B0 i+ W! s: D& Q
  66.         delay_us(2);. k# n0 e: g* r0 }0 h
  67.         IIC_SCL=0;
      ?& E- B1 F4 ^; g* e3 A, L3 M2 Y6 Z
  68. }
    ; u; e* c8 Z3 L) w. B: S
  69. //不产生ACK应答                 
    ) v. |8 q1 W2 l
  70. void IIC_NAck(void)
    & U3 F$ N  d* Q& v
  71. {2 z' h. V2 T3 {) y" ?- m
  72.         IIC_SCL=0;; ?7 n" @9 p  L
  73.         SDA_OUT();, N3 G& b( f4 I( N- e3 p
  74.         IIC_SDA=1;6 J2 O8 ^  R, N, q& N
  75.         delay_us(2);# M' R0 U% s. _) d5 ]4 |
  76.         IIC_SCL=1;
    1 X" E5 c" n% }
  77.         delay_us(2);
    ! m9 c, }' M6 ~- j6 I
  78.         IIC_SCL=0;
    % G: T  q5 L4 g
  79. }                                                                          - E% h) U8 n! P$ Z6 u) S
  80. //IIC发送一个字节0 A# l8 c- a8 s* Y/ t- v
  81. //返回从机有无应答
    5 y9 ?9 q* ?: k
  82. //1,有应答8 p. K# t$ n$ G% P% h, I' P
  83. //0,无应答                         
    : _$ K$ w+ D/ g: W
  84. void IIC_Send_Byte(u8 txd)
    - ]8 p: D  Q! R) g
  85. { ( W: S5 m/ M8 U0 a% ^4 T
  86. u8 t;
    , [& U2 o, O$ D' ^, V
  87.         SDA_OUT();          
    & ]" b' E) i' N8 ^1 W, g6 y9 y0 |5 t
  88. IIC_SCL=0;//拉低时钟开始数据传输
    % W) _6 \. S" k5 E, a$ D
  89. for(t=0;t<8;t++)" y9 }# f# g! f6 P
  90. {
    7 \& I5 h+ k( P- l! t# U3 |' N
  91. //IIC_SDA=(txd&0x80)>>7;$ t$ O5 N0 {3 N; Q: d& W
  92.                 if((txd&0x80)>>7)9 y- _+ @+ a- j, L) f9 b4 H
  93.                         IIC_SDA=1;; s4 Y- x1 |. w9 a  g* x8 X
  94.                 else
    8 l# j/ r( L# v3 e# s% W* a
  95.                         IIC_SDA=0;
    ; b3 j( O# Y& w6 F; n
  96.                 txd<<=1;            u* L2 b1 U, @/ @0 h
  97.                 delay_us(2); - h% `% |9 W& M" ]. t& D) u( Y( U2 C
  98.                 IIC_SCL=1;) l5 M( Z( k2 Z( j7 d
  99.                 delay_us(2); / V# a+ y7 V4 D2 E* B  L8 W( g, Y
  100.                 IIC_SCL=0;       
    ; Z/ F. B& `: n) n3 P3 B
  101.                 delay_us(2);0 y% ~! W9 U4 l- ?2 ?% J, q
  102. }         
    % [" _4 s0 \0 S
  103. }          
    5 J$ @* Z- C6 J2 f* h) h( O
  104. //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
    0 U6 e, a) p# Q2 b: ^: v: q
  105. u8 IIC_Read_Byte(unsigned char ack)
    0 B) W% u0 I1 v; _4 E
  106. {
    # B8 }. d: A0 N7 F
  107.         unsigned char i,receive=0;/ o( V- ]5 u' O( _) d$ Z- S
  108.         SDA_IN();//SDA设置为输入
    " @' _: h7 l0 q! q
  109. for(i=0;i<8;i++ )4 q0 p4 f. d6 t+ O+ v
  110.         {; X! {# }5 A6 o5 Y: z
  111. IIC_SCL=0; 5 c, p3 B/ c+ j. ~5 d# c
  112. delay_us(2);
    , _4 s) X& J+ u+ h5 m( J, p  g9 O
  113.                 IIC_SCL=1;/ Q# O, r& j2 L7 D6 ?0 }$ W/ k! J
  114. receive<<=1;
    # n' |* t2 v4 O! v/ J. X
  115. if(READ_SDA)receive++; 2 h' l: L/ r. {% W2 c
  116.                 delay_us(1);
    . Z* _0 [/ G3 b3 b
  117. }                                         0 ]- V5 T8 v; ?; M9 a8 p5 l' R% s
  118. if (!ack)! \  _( g+ P) c7 b8 x
  119. IIC_NAck();//发送nACK4 [% J7 b$ h! {) o: R
  120. else7 K* K# x- z4 M, d0 ^- ]8 g
  121. IIC_Ack(); //发送ACK
    5 d$ L0 [0 \( J7 n- ^. i
  122. return receive;
    , X8 [3 f* a% b* C' x; H
  123. }
复制代码

4 O, i0 s- X& |) T& v; E% K转载自:电源研发精英圈4 M5 K# N6 R: z$ _
如有侵权请联系删除2 b: q+ ^2 p5 d# V8 X7 a; E
. i! c3 ~# F$ t& K

' i: d* W/ T. E$ F+ b0 Y, S4 |3 A
赞 收藏 评论0 发布时间:2023-10-19 16:29

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版