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

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

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

- d2 |3 L7 `% a4 N0 p, W
+ C+ L$ ]' ~2 e' B+ A
微信图片_20231019162807.png
" i( I  S! R3 a' v9 k
1 j) J  `+ Y# [; `/ R/ Z
即在这个主线上可以挂载n个I2C外设。

* d# ]( A; I+ k1 Y1 ?
对于I2C协议,其实也很简单,不要想的那么复杂,其实就是电平的变换。我们可以人为的分为6个部分

; g9 _/ q, f( r: U# G8 m# Y. f
整体时序图:

/ b3 Z1 O* J7 I1 b2 C
9 U# p2 f% j# L- ?6 x  N
微信图片_20231019162810.png
: X5 u3 {0 b3 f
8 C. z$ k( Z- w8 Y! _
各状态:
  • 空闲状态
    9 S# q% D7 c6 O
) n0 J& i( E# j  [* D# E

( |( z4 j6 `7 G3 N& j/ v 微信图片_20231019162813.png
: }0 B  v6 z  M$ B0 x
% w: s6 C' S, M7 g  B; |. V% L8 b2 X
I2C总线的SCK和SDA两个线同时处于高电平的时候,规定为总线的空闲状态,这个就是由总线上的上拉电阻把电平拉高的。
% R0 p) L( C+ m' H1 V
  • 起始信号
    : D* U0 g& S% I) W0 Q* r

- f! V$ ]  n1 M/ q# m. V! d 微信图片_20231019162816.png ) P% B1 c4 {/ }( f  d

1 f0 F  I8 L- s6 }
当SCL为高电平期间,SDA由高电平变成低电平,即为起始信号。启动信号是一种电平跳变时序信号,不是一个电平信号。

0 V, `' T, k9 D! T
  • 停止信号
    3 r4 I& |6 j4 X% ^
当SCL为高电平期间,SDA由低电平变为高电平,即为停止信号。停止信号也是一种电平跳变时序信号,不是一个电平信号。

/ ^: V& r8 {4 X2 W* B
  • 应答信号

    - X! _+ S/ H% _! \( ^

4 `' H2 Z7 s! l/ i; n6 C
2 n8 `) _# _9 O- Q+ x1 ]3 s5 s
微信图片_20231019162819.png 5 }. d7 t0 G4 ^% ~6 v; @! L1 `
) u; U) `" C2 |$ _& V( _# u0 I
发送器每发送一个字节(8bit)数据,就在时钟脉冲(SCL)9期间释放数据线(SDA),再由接收器来反馈一个应答信号,应答信号为低电平的时候,规定为有效应答位(ACK:应答位),表明接收器已经成功的接收了该字节,应答信号为高电平时,规定为非应答位(NACK:非应答位),表示接收器没有成功的接收该字节。
& I, h. L7 h# ^5 |
对于反馈有效应答位(ACK):接收器在第9个时钟脉冲之前的低电平期间将SDA拉低,并且保证在该时钟的高电平期间,SDA为稳定的低电平。大家主要看图,看看是不是这样的。
" L: l; z2 R  w6 C$ S
要是接收器是主控器,那么它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据的发送,并且释放SDA线,以便主控接收器发送一个停止信号。
' p( V! x. Z6 M: Z; g0 [
  • 数据的有效性

    ( X8 i- a3 V  B7 ], Y1 ]

. m' |2 }6 K* m( r7 z; V
' s, V' I0 k, f' L+ g  t1 b' w
微信图片_20231019162822.png
5 e$ Q! ?2 f; Y
4 D( a  U- l# J# i; Z9 r( @

- r% C1 F4 V1 i, {3 f! x8 P
时钟信号(SCL)为高电平期间,数据线上的数据必须保持稳定,只有在时钟信号(SCL)为低电平期间,数据线上的高电平或者低电平才能发生变化。
+ X* P  o) F0 Q
数据必须在时钟信号(SCL)的上升沿到来之前就准备好,并且在数据信号的下降沿来到之前必须稳定。
! c. s$ `! Y; l5 T! [
  • 数据的传输
    & u7 `7 D- M2 L& _' e) Z
在SDA上的每一个位的数据的传输都需要一个时钟脉冲,即在SCL串行时钟的配合下,SDA上逐位的串行发送每一位数据。数据位的传输是边沿触发。
1 T- b, k, V+ }+ D% N
示例代码讲解
  • 初始化IIC

    4 N3 y2 V+ G$ P& p& ^- p

- m2 b( {( a( [/ W3 v3 g

9 x7 R+ ?( G/ e( \+ T, x 微信图片_20231019162824.png 5 @" Q( j& W4 y, I. F3 V4 ~: v
7 K: _( e3 m, a4 ?
% l7 [0 Z: l. H
其实就是对两个线的初始化,我这里使用的是PA6和PA7,开始都设置为输出,中途会改变PA7的输入输出属性,在空闲状态,我们知道SCL和SDA是被拉高的,所以这个地方我们给高电平。
1 k1 c' ]& o0 ~5 M" u+ }4 C. H6 T
  • 产生IIC起始信号
    4 O2 L3 J0 e" N. L4 T8 h8 `, y7 i

" r: P. Y1 l* F' v7 t

0 e, s: X+ z/ q" B 微信图片_20231019162827.png   K: \& `6 V  p! p

2 s* v' }/ B9 |+ U6 j
微信图片_20231019162830.png % ~/ c. e& v9 w3 c" k" c& ]; b7 d
7 j# c7 M+ s8 W2 G# e- ~0 d
将SDA线设置为输出,然后SDA和SCL都设置为高电平,延迟4us,然后将SDA拉低,延迟4us,最后将SCL拉低。这其实就是协议规定的动作了。
7 s0 W! a* r0 E5 c
  • 产生IIC停止信号

    ; b8 n( x8 g7 X  z. z( j3 {6 H
* g+ i( i5 z* w( C& y# A2 M9 x* A9 _

2 Z; p- e$ m9 h+ w$ M 微信图片_20231019162834.png
, w9 i+ N5 {- R$ x2 P" a0 u! Q: e3 W. e$ D

7 b) [: H5 X5 T
同样的道理,和协议的时序保持一致就好了。

0 _) d' k6 S* s8 w* d# p$ r7 D: }
  • 等待应答信号到来

    , U% D  o% y$ ^* I; _% c

# K% X6 E8 q$ ]" U) }0 w

5 N6 f3 P2 F3 Q8 z- T7 I 微信图片_20231019162837.png ' Q: t! m3 |+ }" I7 w' a
6 U/ J: J) t; A/ f
首先我们需要把SDA设置为输入,因为接收方要给发射方返回一个应答信号的。就是在SCL第9个高电平的时候,释放信号线,先拉高,然后持续等待,是不是有应答信号返回,其实就是返回一个低电平,所以我们一直在检测READ_SDA这个电平,持续一段时间,要是没有返回的话,我们认为超时了,就直接停止协议了,

' y) O& k1 l0 H6 S) y9 X
  • 产生应答信号
    5 _" A" C% t5 v7 k1 `

# b% U8 j! ]1 `) d$ H" b
; E% D2 ]& ]1 I& y
微信图片_20231019162842.png
" S, h9 x, R+ w7 b3 r/ f/ I% Q7 L4 r+ N0 v* r" @& k$ r
即在第9个时钟周期内,SDA都为低电平,为应答

! W6 ?2 W3 s5 h4 a- b( X
  • 不产生应答信号

    & C9 P- v# i. k+ c; }7 W
" C, A2 F- L* a! p0 Y
) s. o$ j% O$ ~# |+ u8 g. Y5 e+ w
微信图片_20231019162851.png , R9 \0 p# j; O, S

$ v) H2 d* d% n- i& B

; x% _- h8 Z; \; e! |3 D8 ?' U) E- M! ]
即在第9个时钟周期内,SDA都为高电平,为不应答
8 }& ]3 l) [4 Y/ H" R& @
  • IIC发送一个字节

    2 L  E, A5 N/ |7 j$ i/ T/ X

5 T$ M2 r1 y' J2 f; u5 y

' {. }, W! X: B, n- H& n 微信图片_20231019162856.png 7 i5 N5 C8 w& ~6 a/ B+ k7 B8 U

8 h) i* i  {/ S$ O2 H, b2 l 微信图片_20231019162858.png # B5 j$ i9 h# q$ m1 |1 g
6 \4 T- g/ ^( t7 K" H' L+ l
发送数据,SDA设置为输出。SCL拉低,SDA准备。
做一个8次循环,拿出1byte的数据,将你发送的数据和0x80作与运算,拿出最高位,然后右移7位,将这个数据放到最低位,这个数据要是1的话,那么SDA输出一个高电平,要是与后的结果为低电平的话,那么SDA输出一个低电平。这都属于准备发送信号阶段。
然后SCL拉高,完成数据的发送,然后SCL拉低,此时SDA也就可以拉低了,接着开始次高位的传输,直到传输完成。

* J/ L0 X) L, y/ y
  • 读取数据

    7 t( R# j% y+ q  a) e2 I
    ' o# i9 r$ j; U5 f) V) O% F
微信图片_20231019162903.png 9 X. A- W: [1 t7 y% S! U- a
$ y3 t; k3 l# }4 a# A$ n
" {4 E6 ~8 ~; R. ~. O3 ^$ Y
读取数据,SDA要设置为输入了。SCL开始为低电平,然后SCL为高电平,我们开始读SDA上的数据,然后左移数据,将读取的数据放在低位。然后检测一下有没有应答。
其实基本思路就是这样了。我把源码附上:

% U" s% @; ~: B$ o. ]7 ]
i2c.h
  1. #ifndef __MYIIC_H; [: P( K4 Z' y3 a1 `
  2. #define __MYIIC_H
    # e( M/ L; p, v% y7 M
  3. #include "sys.h"6 j* V+ a2 v9 K7 P' Q
  4. #include "stm32f10x_gpio.h"
    + c6 M. k5 u6 K9 v4 H" b
  5. //IO方向设置$ I1 U/ W/ f) ?5 \" p& u
  6. #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}3 w. D) B" O" o9 W$ ~! {# j1 k
  7. #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
    , e/ h6 ~: K) `7 \
  8. //IO操作函数
    , ~: @$ z; \0 w7 ?/ u7 d
  9. #define IIC_SCL PBout(6) //SCL5 D0 n! \2 J" }
  10. #define IIC_SDA PBout(7) //SDA
    ! E" g+ M8 O( A8 B" S
  11. #define READ_SDA PBin(7) //输入SDA" D/ B0 A6 U4 L9 X
  12. //IIC所有操作函数& D: }$ N9 i, D
  13. void IIC_Init(void); //初始化IIC的IO口+ ^' v( q. w  L
  14. void IIC_Start(void); //发送IIC开始信号
    ! v8 o$ }  @# }, T5 v. P
  15. void IIC_Stop(void); //发送IIC停止信号' j: h0 f: c/ J3 o% ?$ c
  16. void IIC_Send_Byte(u8 txd); //IIC发送一个字节/ b" i; e+ i5 q
  17. u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
    ; P' A! j' G. m0 b1 r* e
  18. u8 IIC_Wait_Ack(void); //IIC等待ACK信号( v, `4 G0 D! P# R( K" ^  z; c7 F
  19. void IIC_Ack(void); //IIC发送ACK信号
    ) ~0 O$ N# `1 T. ]( G
  20. void IIC_NAck(void); //IIC不发送ACK信号) {6 N5 f3 k! p' K1 _: V
  21. void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
    ' t4 w" c" D/ Q& c7 F& L
  22. u8 IIC_Read_One_Byte(u8 daddr,u8 addr);# ?* S4 ~1 t. o6 N) L" X
  23. #endif
复制代码

: [1 Z7 y1 O6 \7 D+ R; a
i2c.c
  1. #include "myiic.h"0 V0 N6 G7 Z5 ]! ^- @3 O; e
  2. #include "delay.h"
    0 x' ]) s' ]' P( |. |7 }; R; y4 r% L+ b
  3. //初始化IIC
    * J4 [8 `7 R5 N# T) x
  4. void IIC_Init(void)
    * e$ k; I( b2 d. w) q
  5. {                                         3 [0 k( r$ q8 N8 p% j8 j$ t
  6.         GPIO_InitTypeDef GPIO_InitStructure;2 a3 S) r: W" Q; V5 k
  7.         RCC_APB2PeriphClockCmd(        RCC_APB2Periph_GPIOB, ENABLE );        //使能GPIOB时钟) y; e7 T- b6 \' Y' |
  8.          0 c5 ]7 `8 W7 ?4 U& m0 W% a( j
  9.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
    4 x. j7 n# e& s! o7 f% Z& h) R, O
  10.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
    ! t! e! Q# Z  A
  11.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;6 d1 q# c) G1 ~  a9 T7 f
  12.         GPIO_Init(GPIOB, &GPIO_InitStructure);$ r8 N3 ]) g+ B2 t
  13.         GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7);         //PB6,PB7 输出高0 H' ], @% P  _9 ^, R
  14. }
    ' b/ ~/ U; s% ~" V& s  N
  15. //产生IIC起始信号. U  H4 M& M! Y; m! A. E3 u
  16. void IIC_Start(void)3 F% e: J& P+ H$ L! k2 m
  17. {+ D: D! ^6 A, Z& n; A! d. b: N1 D4 H
  18.         SDA_OUT(); //sda线输出
    2 g& {( C  H7 x! O
  19.         IIC_SDA=1;                  1 X) g" [. s2 Y2 ^8 i+ ^
  20.         IIC_SCL=1;+ Z5 E; `& k# x
  21.         delay_us(4);
    * y1 P" s4 L2 F4 V) \& J' j: J- S! V
  22.         IIC_SDA=0;//START:when CLK is high,DATA change form high to low $ |2 V& C' f4 V7 b
  23.         delay_us(4);
    5 U' H  ^, a6 a- ^- k
  24.         IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 8 \* ]6 u. a- w  ^' ^+ Y" A
  25. }         ( S: G+ i# e& g2 o
  26. //产生IIC停止信号& \" e7 I, R4 M( p" t
  27. void IIC_Stop(void)
    ; a5 \7 ]& y2 i1 q1 B
  28. {
    9 d4 g. e9 D7 b- n7 w% F# D
  29.         SDA_OUT();//sda线输出7 j. O: v$ [8 |$ u
  30.         IIC_SCL=0;
    ! N0 ~2 N2 \! M3 C
  31.         IIC_SDA=0;//STOP:when CLK is high DATA change form low to high/ Z0 }/ G. V/ f
  32.         delay_us(4);
    7 w5 W# }/ @* X
  33.         IIC_SCL=1;
    , M7 m' g( {( J  v& E- I
  34.         IIC_SDA=1;//发送I2C总线结束信号
    5 }- K1 m% d% H# Z* S  `
  35.         delay_us(4);                                                                 3 z" {% ~7 j- o- ]% ~
  36. }" A9 e/ z7 T- t$ g9 L
  37. //等待应答信号到来
    6 x* F0 C  n2 d- N/ y& h+ o) L1 d, p
  38. //返回值:1,接收应答失败# n4 L  S. R3 T: ]
  39. // 0,接收应答成功+ J% e1 z2 o/ c9 L
  40. u8 IIC_Wait_Ack(void)
    5 H# n7 D, Y- K
  41. {! {; C. T0 [: R0 c
  42.         u8 ucErrTime=0;
    # I2 F- I- E0 V+ m- K( o
  43.         SDA_IN(); //SDA设置为输入   V* R! A* _( ^; l% C- C  s" @9 ]1 R
  44.         IIC_SDA=1;delay_us(1);         ' U( \1 G2 e9 x/ {) }. R) a$ y9 T: r
  45.         IIC_SCL=1;delay_us(1);         
    5 G+ E. c( C) ?/ Z% J' H" ?) R
  46.         while(READ_SDA)9 q. T6 b- f8 _( o3 G3 e
  47.         {
    8 Q/ O1 \8 U: h6 X) J7 _
  48.                 ucErrTime++;
      c% U3 B4 O' U# B# t
  49.                 if(ucErrTime>250). T2 P/ t# w! i1 d) m3 J
  50.                 {0 }; E; R- V' s5 x$ f
  51.                         IIC_Stop();! {; t+ h9 J( Z
  52.                         return 1;
    * n/ ]' k' ^2 o" `- Z
  53.                 }
    ( h/ S4 g$ S5 I7 M" |' |( b* g# [5 t
  54.         }2 t+ i5 A7 T( `; V2 U! U
  55.         IIC_SCL=0;//时钟输出0          
    ! F" s! ?0 |" d0 _7 K  `
  56.         return 0; ' B7 {4 L9 o! l# ^2 a
  57. } 2 {- v* {' s7 `) l( |) u0 h
  58. //产生ACK应答
    ( Q! A- t+ i4 F
  59. void IIC_Ack(void)
    / |) G2 T$ A- F* x, t9 Z
  60. {
    8 o8 W! Y. `! A
  61.         IIC_SCL=0;
    2 v: X0 j6 D; _$ }* L
  62.         SDA_OUT();0 I2 ^3 r* g. H1 ]
  63.         IIC_SDA=0;0 X2 c* K7 t8 i$ b% N- J. ~. w& ^
  64.         delay_us(2);+ c/ ^. z  j1 O+ R& c7 W
  65.         IIC_SCL=1;
    5 g. @3 I0 r! x% Q/ O% Y* b
  66.         delay_us(2);
    % l9 r/ |# L9 F" j& ]
  67.         IIC_SCL=0;3 Y" U* X% H5 F) S
  68. }
    4 K' X! {9 Q4 z8 w4 h1 A5 R
  69. //不产生ACK应答                 
    " a# {% e+ Z, B% I2 V% A+ E, k
  70. void IIC_NAck(void)
    3 }5 i( e- y9 q6 l$ V
  71. {" i3 A1 U0 o% N9 D4 T
  72.         IIC_SCL=0;( j' t$ N. M! W) v# m" y  e' t. {, D
  73.         SDA_OUT();& [* Z/ ]5 g5 ]1 I2 }
  74.         IIC_SDA=1;
    & w7 O- L4 [! k. K* S! _$ ?0 C. ?
  75.         delay_us(2);
    ; g; C" N5 a) `2 n3 D
  76.         IIC_SCL=1;
    " C$ k9 G3 X: n$ \9 K
  77.         delay_us(2);/ W' z- [; _! R/ g: x) R
  78.         IIC_SCL=0;
    # g% J0 ]& O; q5 M/ T
  79. }                                                                          % [9 G1 Z2 m  f: W/ ]! u
  80. //IIC发送一个字节/ w( f8 D) n& g6 S# j+ }
  81. //返回从机有无应答6 Z+ D0 y$ o) b
  82. //1,有应答# a+ a( X. T7 x3 _; h) d8 y. J
  83. //0,无应答                         
    ( j% L3 f$ @/ ]
  84. void IIC_Send_Byte(u8 txd). l- e% W" @* g, ?" w
  85. { , E& H' Q% `: s
  86. u8 t;
    # Q# E+ Q! d; e8 c2 W% r2 H' }
  87.         SDA_OUT();          
    8 x% P  G6 t8 y
  88. IIC_SCL=0;//拉低时钟开始数据传输
    0 O8 `/ c% E+ ~& {1 x" T" q" K
  89. for(t=0;t<8;t++)! Z# s* |# Q- l0 L4 O
  90. { # R! I+ c" J" S2 r7 U/ [! h
  91. //IIC_SDA=(txd&0x80)>>7;* s# A4 m% i6 e) r9 [
  92.                 if((txd&0x80)>>7)+ S8 L! Q7 l0 M
  93.                         IIC_SDA=1;& w  b9 \/ G6 G. q: Z4 u
  94.                 else
    * j5 {" n' b% E0 e
  95.                         IIC_SDA=0;
    : d: v/ W! l; I& x( V
  96.                 txd<<=1;          
    # Z$ n# t" ^6 n, m
  97.                 delay_us(2); - E9 {5 @3 x9 b  q8 i
  98.                 IIC_SCL=1;
    & j# f+ G$ p- A5 I# S
  99.                 delay_us(2); 5 i& h$ s" ^5 A6 W+ k* R
  100.                 IIC_SCL=0;        % j2 e4 t, [7 U
  101.                 delay_us(2);, g! l' d9 Z3 f4 p3 h1 P. j
  102. }         
    * L6 m( o3 y& X
  103. }          3 d* d, E& z# ]5 W  I+ r
  104. //读1个字节,ack=1时,发送ACK,ack=0,发送nACK 9 i, U" P* C7 _2 x
  105. u8 IIC_Read_Byte(unsigned char ack)
    # x  S0 Q" i  T2 x( ?
  106. {  |9 ~% P; I" x) @8 ^
  107.         unsigned char i,receive=0;
    & k% r0 c  ~& q9 b  |: }
  108.         SDA_IN();//SDA设置为输入4 z/ v/ E) ~0 y7 y) Y) Q1 d2 {
  109. for(i=0;i<8;i++ )
    9 z/ s& J; O2 w: Y
  110.         {; {/ k6 N) L) Q) Z% h
  111. IIC_SCL=0; 2 _. g) y$ h* c4 U7 v; q
  112. delay_us(2);$ X) M! h* w/ J! y5 `
  113.                 IIC_SCL=1;% m% k4 V9 E; ^( s/ `0 `  z; _
  114. receive<<=1;
    + ~; B8 E! l7 Q$ t
  115. if(READ_SDA)receive++;
    * E, M% a2 W# e0 O: c3 [& _
  116.                 delay_us(1);
    - ?7 b7 M) _# a  A
  117. }                                         
    ' O; c) y- l& U7 a9 p
  118. if (!ack)" V" G6 k- G5 q; x
  119. IIC_NAck();//发送nACK
    7 G+ f9 f1 l, z+ ?. a
  120. else  D. }4 |1 g+ _& o1 X. F8 \: w
  121. IIC_Ack(); //发送ACK # q7 L* d# t2 ^) U
  122. return receive;
    . S6 [8 w9 @6 t/ j, M' k) [8 v! {
  123. }
复制代码

, g2 I8 @. H  V6 ?转载自:电源研发精英圈6 @# s+ J  R8 p- i
如有侵权请联系删除
  p3 O0 I+ W3 Z2 {
" }4 z1 y8 R1 K' w
; j* h% n/ r2 Z
收藏 评论0 发布时间:2023-10-19 16:29

举报

0个回答

所属标签

相似技术帖

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