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

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

[复制链接]
攻城狮Melo 发布时间:2023-10-19 16:29
I2C两线式串行总线通讯协议,它是由飞利浦开发的,主要用于连接微控制器及其外围设备之间,它是由数据线SDA和信号线SCL构成的,可发送和接收数据即在MUC和I2C设备之间,I2C和I2C之间进行全双工信号传输,高速I2C总线一般可达到400kbps。一般我们也称为TWI接口。

& U5 f6 \3 ?1 |# i4 y1 Y
I2C支持多主机模式:
, M+ y; B% U, l' O

4 l2 @2 w9 L2 q# k" W( ?
微信图片_20231019162807.png 8 R, H, L! M/ ^& C6 y+ |- q
8 K! t) ]5 N- n
即在这个主线上可以挂载n个I2C外设。
8 V0 w. X. r. L! q5 S, C. F& \4 d
对于I2C协议,其实也很简单,不要想的那么复杂,其实就是电平的变换。我们可以人为的分为6个部分

1 b5 B* E: I( W5 r6 T# x
整体时序图:
5 J3 ]! W$ p6 b& Z5 g0 }& `
( O* R: s' }" m9 o5 N! J9 F$ K, e
微信图片_20231019162810.png # w7 O% r) m5 J- w/ S

) J. n0 Q$ r! j2 s. J. {* @, X
各状态:
  • 空闲状态
    + c! f$ A" p. a! Q$ g# K" k5 D
$ T9 g5 ?# i" L6 u$ W; L
, @( \; `) E6 B# m8 B
微信图片_20231019162813.png
' g2 o! i, O- [/ G$ R2 L3 ~# R* v
8 R: q* D# [. f$ t6 p
I2C总线的SCK和SDA两个线同时处于高电平的时候,规定为总线的空闲状态,这个就是由总线上的上拉电阻把电平拉高的。
" @$ W, Z  U$ E; }' ^
  • 起始信号

    & Z" `: o" I) g' H
4 a; N, F' d; k: ]& Y! S: ~! J
微信图片_20231019162816.png
& |1 T5 k- ~8 c; E( f, }- J8 z2 k) V
当SCL为高电平期间,SDA由高电平变成低电平,即为起始信号。启动信号是一种电平跳变时序信号,不是一个电平信号。
6 X/ H' B. j* K) s/ D2 F
  • 停止信号
    $ I+ G' v- l' E+ T$ x
当SCL为高电平期间,SDA由低电平变为高电平,即为停止信号。停止信号也是一种电平跳变时序信号,不是一个电平信号。
1 Z$ ^3 `- {/ C1 D. [
  • 应答信号

    0 G6 s4 g. d# E3 J& G8 P

' H1 L4 t& d; S1 G: Z8 R: ?' Y

4 B9 c# }6 a) t9 M 微信图片_20231019162819.png ; D3 y1 A* ?* Q1 b) }. a
- m2 J1 Z# k& v' f2 p3 `, E
发送器每发送一个字节(8bit)数据,就在时钟脉冲(SCL)9期间释放数据线(SDA),再由接收器来反馈一个应答信号,应答信号为低电平的时候,规定为有效应答位(ACK:应答位),表明接收器已经成功的接收了该字节,应答信号为高电平时,规定为非应答位(NACK:非应答位),表示接收器没有成功的接收该字节。
9 R( W, ~; G& q8 }, W1 L1 A8 |  g2 J
对于反馈有效应答位(ACK):接收器在第9个时钟脉冲之前的低电平期间将SDA拉低,并且保证在该时钟的高电平期间,SDA为稳定的低电平。大家主要看图,看看是不是这样的。

- A: Z; X8 M" e0 t% b1 a
要是接收器是主控器,那么它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据的发送,并且释放SDA线,以便主控接收器发送一个停止信号。

4 d7 b; [& j! o2 h- |  k
  • 数据的有效性

    % `) Z3 g- f$ P! {) T

$ Z. V7 X7 w5 g9 ]$ [

: M. p+ k! t" N: Z4 j9 k9 a 微信图片_20231019162822.png ! ]2 Z) I9 J7 H# _  \1 A5 ?

# U; c) r7 d  @& M
5 G( ^" v0 ~/ N4 q
时钟信号(SCL)为高电平期间,数据线上的数据必须保持稳定,只有在时钟信号(SCL)为低电平期间,数据线上的高电平或者低电平才能发生变化。
9 I% T+ P7 K6 S* N  n1 r+ R6 L
数据必须在时钟信号(SCL)的上升沿到来之前就准备好,并且在数据信号的下降沿来到之前必须稳定。

# y& g7 v1 E* b* x& y- R3 X
  • 数据的传输

    4 h. w3 c$ v' y* O( B
在SDA上的每一个位的数据的传输都需要一个时钟脉冲,即在SCL串行时钟的配合下,SDA上逐位的串行发送每一位数据。数据位的传输是边沿触发。
; q* H2 t* K6 |% x' E: n6 ]
示例代码讲解
  • 初始化IIC

      n, ?; s* C  b# T) u! ~: f
2 _% j' R5 m2 K" i* q: i# F/ }

2 L: ]: E- S/ N& v 微信图片_20231019162824.png 9 J0 y: \" p1 k. ^
) i2 u$ i4 M+ t1 V
, N) q2 s2 G, m5 D2 g) l
其实就是对两个线的初始化,我这里使用的是PA6和PA7,开始都设置为输出,中途会改变PA7的输入输出属性,在空闲状态,我们知道SCL和SDA是被拉高的,所以这个地方我们给高电平。

2 l. F3 Q9 r8 {3 d
  • 产生IIC起始信号

    ( T$ ?2 M3 ]# U+ M

2 q& @8 M. e" Y0 d; @+ k, y  q1 a

: v% n2 |& _+ Y) M8 x) G 微信图片_20231019162827.png
* k9 s' D( r4 B; f
8 G# n, Z5 q8 U3 S. w: Y
微信图片_20231019162830.png
# c  I4 `  J' Q( O. O6 p1 Q- y
5 a0 @- t* a) q3 {# C7 G6 Q
将SDA线设置为输出,然后SDA和SCL都设置为高电平,延迟4us,然后将SDA拉低,延迟4us,最后将SCL拉低。这其实就是协议规定的动作了。

: R% A+ R0 _: e
  • 产生IIC停止信号

    : M" _4 H* ?. r2 y4 j

# y+ ^9 h  R8 S  j

2 W. g6 y1 [* J5 V6 P7 h/ D 微信图片_20231019162834.png
( N# F6 O: z( y; W  s6 J) f5 t/ s6 s; D3 R" f7 Y2 @: H( C
9 ~1 j. p; Z, `: g2 t
同样的道理,和协议的时序保持一致就好了。
+ O1 k- L; Q& `; X  y: F
  • 等待应答信号到来

    * h8 H2 D. ?, i  }# F  }. f/ a6 ^
( T5 ^3 _2 Y; d4 W% s

  H0 p. e1 B8 R: d7 _ 微信图片_20231019162837.png + M; _# _8 V: c4 {" w. A$ a. d# @, x1 I  e
" n1 b0 {4 D5 L* L5 d4 D+ P
首先我们需要把SDA设置为输入,因为接收方要给发射方返回一个应答信号的。就是在SCL第9个高电平的时候,释放信号线,先拉高,然后持续等待,是不是有应答信号返回,其实就是返回一个低电平,所以我们一直在检测READ_SDA这个电平,持续一段时间,要是没有返回的话,我们认为超时了,就直接停止协议了,
# E* g1 i9 e/ b/ V. ]) f
  • 产生应答信号
      s- ?$ U6 @( ^
- Y) d) M- ], v, O

1 z: @0 ]3 e* U" R0 L! f+ y 微信图片_20231019162842.png
% z4 F) ~. T# S0 k8 [+ Q; S3 t' U# X4 b$ X1 |
即在第9个时钟周期内,SDA都为低电平,为应答
' A5 Q& l! s/ ^" }, J
  • 不产生应答信号
    ' A3 H! U; Y& }4 y; T
" V8 _, P* a7 c  q
0 w( e+ ]' o6 W/ ?
微信图片_20231019162851.png 2 A* d7 u0 }* ?( K2 ]* q

& l9 `! `3 |6 W3 @+ V* X4 h
9 Q$ M* U' Y+ R- ^6 a/ l9 p+ |
即在第9个时钟周期内,SDA都为高电平,为不应答

0 U  O0 M/ m7 p2 ~& h
  • IIC发送一个字节

    6 N! J8 x6 y- \: z$ ~
0 z. t: n% t5 h
7 q. M8 V& {# p
微信图片_20231019162856.png
( V0 e$ R2 o6 J' |6 x
6 Q0 `/ F1 L( L' X. `8 {& `& g$ d( o4 Z 微信图片_20231019162858.png
5 c) q1 n, U5 p! A1 ?7 X# g: Q
' X9 }) N% H3 S1 H# {
发送数据,SDA设置为输出。SCL拉低,SDA准备。
做一个8次循环,拿出1byte的数据,将你发送的数据和0x80作与运算,拿出最高位,然后右移7位,将这个数据放到最低位,这个数据要是1的话,那么SDA输出一个高电平,要是与后的结果为低电平的话,那么SDA输出一个低电平。这都属于准备发送信号阶段。
然后SCL拉高,完成数据的发送,然后SCL拉低,此时SDA也就可以拉低了,接着开始次高位的传输,直到传输完成。
0 N# k: A* j  P0 `+ r
  • 读取数据

    ) g. X) ^( o/ Q: Y

    , F/ p7 H  i8 o
微信图片_20231019162903.png
% |& y3 X) b; y( h
' E. {3 d& R7 \( B5 }3 w3 u& I

) s3 g) J( H( P+ C" |/ T& o
读取数据,SDA要设置为输入了。SCL开始为低电平,然后SCL为高电平,我们开始读SDA上的数据,然后左移数据,将读取的数据放在低位。然后检测一下有没有应答。
其实基本思路就是这样了。我把源码附上:

9 j7 l" ^8 w/ n, J7 P6 y
i2c.h
  1. #ifndef __MYIIC_H
    / }' [7 a: U- N! A5 H, v
  2. #define __MYIIC_H
    $ k( u9 h4 P# j& ?
  3. #include "sys.h"% Y8 @: p' v, p4 }4 m0 d4 d
  4. #include "stm32f10x_gpio.h"1 z$ B& N' _2 s6 `) Y7 Z4 ~
  5. //IO方向设置
    8 y" s- o0 u6 M  j  ^& w
  6. #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}2 \2 s$ A# ^! @
  7. #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
    % ], Y+ q( H8 }  K, ^  x4 J
  8. //IO操作函数* G* D7 h. I' `1 }+ S: D
  9. #define IIC_SCL PBout(6) //SCL8 w1 K  Z( Y6 j& Z, w1 m
  10. #define IIC_SDA PBout(7) //SDA! }" I: ^5 z+ [* D; e
  11. #define READ_SDA PBin(7) //输入SDA7 p# n9 \: V4 d9 G6 A
  12. //IIC所有操作函数! S$ _3 ~- T% j" @( T. ], m% k0 v6 E
  13. void IIC_Init(void); //初始化IIC的IO口8 n* j" N+ x- O' ?% P( s9 ~
  14. void IIC_Start(void); //发送IIC开始信号. G& L7 O4 ~6 y' `6 s8 h9 F5 Q
  15. void IIC_Stop(void); //发送IIC停止信号
    9 I4 F! Z& d! i+ P+ ?2 D% V" ^
  16. void IIC_Send_Byte(u8 txd); //IIC发送一个字节. [0 H, H9 m: A, X
  17. u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
    & N! a+ G- \9 A4 B3 k/ r
  18. u8 IIC_Wait_Ack(void); //IIC等待ACK信号( S1 r7 C7 ~+ Y, \/ S3 I! t% Q$ y
  19. void IIC_Ack(void); //IIC发送ACK信号
    . W2 }( K  Q( G" }1 T
  20. void IIC_NAck(void); //IIC不发送ACK信号( W* G( t/ s: L6 l3 o, w6 Q
  21. void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);+ L% q! p2 d. i
  22. u8 IIC_Read_One_Byte(u8 daddr,u8 addr);8 V9 \6 O! P' W  M
  23. #endif
复制代码

' f8 u! G# j" y. m* P: `4 \! M
i2c.c
  1. #include "myiic.h"9 r! x1 C: ^/ i1 E! b
  2. #include "delay.h"; Y% j/ ?% P! R
  3. //初始化IIC. w$ u% ^3 j: Q4 x" V( T
  4. void IIC_Init(void)
    ) I# @$ d, U/ b3 Y2 p% N; M
  5. {                                         ! q, C5 k: L4 M
  6.         GPIO_InitTypeDef GPIO_InitStructure;
    * F! K: Y; f$ x8 K
  7.         RCC_APB2PeriphClockCmd(        RCC_APB2Periph_GPIOB, ENABLE );        //使能GPIOB时钟4 p7 O" ~& x2 U# |' K$ [
  8.          , W* z) f; t6 K7 y6 j5 J3 }7 F
  9.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;7 \9 m% C" m# Y; R
  10.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出) _1 I$ V1 F% C2 z3 }# c
  11.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    - S+ p3 T1 }4 r/ Y" y1 p* x
  12.         GPIO_Init(GPIOB, &GPIO_InitStructure);/ [4 i. Y' X, G( ?# ~9 l+ t, d
  13.         GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7);         //PB6,PB7 输出高
    ' Q' x$ e) Y* s, b/ T( i! q  s
  14. }& J5 H! I: P  x+ w, T$ \0 F
  15. //产生IIC起始信号
    * n( a5 f9 }8 e- F+ Z; f8 ]
  16. void IIC_Start(void)
    # q2 ]  U( w+ p4 Q( A$ Z- |. Q
  17. {1 e: E  q" p. O; B% U1 U
  18.         SDA_OUT(); //sda线输出
    " Q" M3 I, M1 e9 p" k% ^
  19.         IIC_SDA=1;                  
    $ D! _1 _3 {( V- W7 M6 ?
  20.         IIC_SCL=1;
    7 l* P0 s! H8 b0 Q" S
  21.         delay_us(4);
    ! r: C5 b' v* s0 h: W
  22.         IIC_SDA=0;//START:when CLK is high,DATA change form high to low 0 R6 r9 z3 g/ {" i
  23.         delay_us(4);# P- E, J7 f1 L
  24.         IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
    1 F* _4 W% x' B2 f$ P) |
  25. }         
    1 s7 m2 [5 I# y5 w
  26. //产生IIC停止信号
    , m/ r/ X9 Q, K
  27. void IIC_Stop(void)6 e+ z, Y! n) K. J6 E- {9 ?3 @
  28. {1 A: [8 l# T" r% o$ f& p
  29.         SDA_OUT();//sda线输出( K3 f8 T$ S, w; m/ r- j
  30.         IIC_SCL=0;+ q# n0 D1 ~% e6 \+ v, N! T* Q  f! I
  31.         IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
    / ]; b( |$ F; w# R, t' i9 l' ]4 ?
  32.         delay_us(4);
    $ I  Q. G& n1 H/ a9 U
  33.         IIC_SCL=1; ; F, v5 U  q% v5 w% c5 [" m& W8 V
  34.         IIC_SDA=1;//发送I2C总线结束信号
    + E& \, ]3 |) u  [* m$ U
  35.         delay_us(4);                                                                 + Y  d7 p5 N0 @9 G
  36. }( y9 l# M: V/ `  `/ ]
  37. //等待应答信号到来
    - O9 c% ^" L8 i: U% r, X
  38. //返回值:1,接收应答失败' z/ f4 H/ D  C, V2 v/ m
  39. // 0,接收应答成功% k% D; R+ p. }2 \5 a+ X# t9 G1 i
  40. u8 IIC_Wait_Ack(void)
    + l% `2 Y( a2 X, |2 }) |
  41. {
    / R' y7 D( {* o" F( M* P
  42.         u8 ucErrTime=0;
      |; |3 T, Y, v( o
  43.         SDA_IN(); //SDA设置为输入 * _6 {6 `: J4 e$ T3 ^# X, o! l4 t
  44.         IIC_SDA=1;delay_us(1);         
    $ G& I" Z6 o: S9 ~
  45.         IIC_SCL=1;delay_us(1);         3 h* L% t  t+ m  A8 S
  46.         while(READ_SDA)5 G7 c3 L0 c& K& s  a& v
  47.         {4 N$ w* i) q) |2 t: F( l5 M! z' v
  48.                 ucErrTime++;
    ' P8 `" ^/ |5 t: d6 i# t" i
  49.                 if(ucErrTime>250)! Y; v8 k6 Z' B3 r$ S) Z: R
  50.                 {
    & F( n. Q3 o, o
  51.                         IIC_Stop();; ?) [6 J2 C6 e$ L0 a0 s8 @. i
  52.                         return 1;" V1 Q6 Q. f* z& ?" \+ l/ d
  53.                 }2 y+ j( H" z7 I7 X! w. a+ _
  54.         }- u  w2 B3 G' |' A
  55.         IIC_SCL=0;//时钟输出0          1 o/ f" k+ F/ s$ o( g
  56.         return 0;
    & J8 e) J1 O/ N0 a0 d
  57. } 1 [/ K0 M- ~) y- x& {  F0 }% e
  58. //产生ACK应答
    4 c5 G* R# J- V: h) z
  59. void IIC_Ack(void)
      r+ k+ g% ~6 X5 m. f/ i  a# ?/ b5 r
  60. {
    2 w' i. ?9 m' d1 {% h/ h7 E
  61.         IIC_SCL=0;
    + ]' i! d! m5 t, c0 f3 g% c
  62.         SDA_OUT();" d/ f* b9 A5 G0 N% Z# e$ D/ b+ R% u1 y
  63.         IIC_SDA=0;7 ]; a" g2 o- {. @7 g8 ]: b# x
  64.         delay_us(2);
    ; X* b2 {" d3 E2 ^3 N1 Z
  65.         IIC_SCL=1;( }8 X6 Z3 X4 M0 Y! L0 z! A
  66.         delay_us(2);8 `# E6 \% R3 ?3 `
  67.         IIC_SCL=0;
    ) d, [. J7 I, b" B) f7 f. ~
  68. }
    3 y$ C% P# Z$ O, ]. E
  69. //不产生ACK应答                 2 N6 p0 {7 J1 z4 i& L
  70. void IIC_NAck(void)
    - d& O2 U, T* Z/ F- i$ F: W
  71. {
    / `% C3 ^/ }1 I3 G
  72.         IIC_SCL=0;
    4 ?* H# S1 [& U( |3 n
  73.         SDA_OUT();
    5 H% ?# E: e4 g  n6 g
  74.         IIC_SDA=1;
    + F: U0 S; D  t6 G& u) {) [4 O
  75.         delay_us(2);
    7 k( w2 a& h  e7 C( n3 J
  76.         IIC_SCL=1;
    - ^7 q% L! S6 ]; K- ?! A
  77.         delay_us(2);
    # c1 K- a6 g1 Q4 o% `
  78.         IIC_SCL=0;
    " S6 [1 D' a3 y. w1 Q6 ~  o
  79. }                                                                          
    , B; X( B- h1 ~$ o% G
  80. //IIC发送一个字节
    8 t' r# z* \4 L  J9 H# C
  81. //返回从机有无应答
    $ N+ ~6 A! X, |4 B
  82. //1,有应答
    9 M& P% k, w  U3 x
  83. //0,无应答                         
    * ?/ n; E, J! A: z: g
  84. void IIC_Send_Byte(u8 txd)- x6 L" G& a. i* ~, D2 w
  85. { 6 \! U, E3 ]% q  j' g* r9 _: u
  86. u8 t;
    ! H' Y" ?& F2 l1 k/ {. d6 ^
  87.         SDA_OUT();          
      b: D1 y; G, B7 j+ E
  88. IIC_SCL=0;//拉低时钟开始数据传输$ h( K* p- O1 C7 X
  89. for(t=0;t<8;t++)
    7 ]- b& e5 ^$ _. ?0 h( K- I6 a
  90. {
    ) k6 N# M  S' u
  91. //IIC_SDA=(txd&0x80)>>7;. G. B; `: \* [; f) f' @
  92.                 if((txd&0x80)>>7)
    ( I9 s0 o8 e0 `/ C
  93.                         IIC_SDA=1;
    9 j% a9 u1 z! @( r/ z7 e
  94.                 else
    " n# U, |: f, q9 d
  95.                         IIC_SDA=0;) S  g0 ]# _' n- @* L( s/ t
  96.                 txd<<=1;          ( P' m. b" G6 s* G* W) `
  97.                 delay_us(2); , A9 j  {/ c2 m+ h; i
  98.                 IIC_SCL=1;
    ) h5 B6 \% T) h8 }
  99.                 delay_us(2);
    8 z7 k  i2 f# V. g2 Z) A. W) q
  100.                 IIC_SCL=0;       
    8 g# _/ F. ^; _1 Z
  101.                 delay_us(2);
    1 B+ w* C0 e& i( g2 k* T
  102. }         5 b! ?. w5 V1 o, S( V
  103. }          
    4 Y9 i6 y! T: a7 q2 G/ W
  104. //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
    6 R; E6 I+ O$ r3 }4 `3 z8 t
  105. u8 IIC_Read_Byte(unsigned char ack)4 A" o$ A$ D- m$ _6 F# h( X3 a
  106. {# q2 y3 q* o0 Q( A9 H8 l- k  W
  107.         unsigned char i,receive=0;
    . H" V- ~" M5 M$ j
  108.         SDA_IN();//SDA设置为输入
    - o( ]8 r/ ?* D3 O
  109. for(i=0;i<8;i++ )
    % F5 Y+ i3 A0 W9 `
  110.         {
    ' z% R5 M5 }. c
  111. IIC_SCL=0; ; n# J5 A. Q+ l
  112. delay_us(2);
    6 S4 d5 Z  A: E9 P2 h
  113.                 IIC_SCL=1;9 }" b3 h+ z! m# Y0 S! v! |3 w
  114. receive<<=1;! i0 y" x: @" g
  115. if(READ_SDA)receive++;
    % l& X$ a! f8 Q2 M
  116.                 delay_us(1); % c3 d: h/ w& |- S# r) h
  117. }                                         ( p0 S8 Q1 K. B0 U. ^
  118. if (!ack)
    / n1 P* e7 D# C; r) q+ T
  119. IIC_NAck();//发送nACK6 `# ^* u& M0 a
  120. else
    5 M& H& }) f3 G! H
  121. IIC_Ack(); //发送ACK 2 E# C% R( X2 ~
  122. return receive;
    3 F' \5 I) `/ o' R
  123. }
复制代码
8 s4 k$ H& v$ L+ x/ f
转载自:电源研发精英圈& L: e' @6 Q* L! ]1 o6 U5 G$ P7 v
如有侵权请联系删除8 j* \1 p! H6 x. O$ X8 |

4 l) i: C# L. x2 @  ?
' r" Q3 |% u# U  `/ [$ B
收藏 评论0 发布时间:2023-10-19 16:29

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版