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

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

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

' l4 K8 e: b5 {% G8 }2 q
I2C支持多主机模式:
- h5 T/ ?5 E0 \1 G* m& E

* {9 H, C9 r# h, S) A! t9 i1 c
微信图片_20231019162807.png , _) }2 b( r. m# ]% L% _" q

3 E- Q2 T4 h2 f
即在这个主线上可以挂载n个I2C外设。

! G' X" O/ X  a! |7 m
对于I2C协议,其实也很简单,不要想的那么复杂,其实就是电平的变换。我们可以人为的分为6个部分

) e* _. [" G# p. U5 x5 J
整体时序图:

0 i  l5 F7 ~# N& ]. F
; e0 X7 d) c( `2 R0 N; G
微信图片_20231019162810.png
4 C$ N+ Y8 i! T; x1 u7 G& k$ k7 k2 a5 u$ y- M0 c' G% G, L
各状态:
  • 空闲状态
    , l3 C- g8 Z0 h, k; ]# X3 T0 m% D

  t4 b# l+ Q& a& ?3 f' ~+ G

/ }* k% b" R) X$ ~: w 微信图片_20231019162813.png ! f% `8 B: X. ^" C; _4 L
" [! Q! J+ O% H0 n
I2C总线的SCK和SDA两个线同时处于高电平的时候,规定为总线的空闲状态,这个就是由总线上的上拉电阻把电平拉高的。
$ y. q% [# s% p1 Q6 U2 U1 B7 b5 f
  • 起始信号

    # ^, W) m0 n$ N% t9 I
$ U& J2 C0 W9 E% X
微信图片_20231019162816.png
6 ?* z7 q0 @3 f: X# S& E
7 f% `. o% Q9 T
当SCL为高电平期间,SDA由高电平变成低电平,即为起始信号。启动信号是一种电平跳变时序信号,不是一个电平信号。

* R0 V/ B4 u1 l1 p
  • 停止信号
    $ t* v: r" b0 n1 e, b2 @+ Y
当SCL为高电平期间,SDA由低电平变为高电平,即为停止信号。停止信号也是一种电平跳变时序信号,不是一个电平信号。
7 h" J% C, g! n% z9 |' O- \% o$ R
  • 应答信号
    & `8 w1 \3 B, z5 F, a
# l) Y& i8 @" W$ q! A5 B: E* b
; i5 X# h* S# }2 t6 F7 e6 H
微信图片_20231019162819.png
! o2 @, w7 Z1 g4 V; E* L7 ]
4 B- _5 X7 N5 Z+ H( `8 X
发送器每发送一个字节(8bit)数据,就在时钟脉冲(SCL)9期间释放数据线(SDA),再由接收器来反馈一个应答信号,应答信号为低电平的时候,规定为有效应答位(ACK:应答位),表明接收器已经成功的接收了该字节,应答信号为高电平时,规定为非应答位(NACK:非应答位),表示接收器没有成功的接收该字节。
4 |0 f' P' s8 m7 F
对于反馈有效应答位(ACK):接收器在第9个时钟脉冲之前的低电平期间将SDA拉低,并且保证在该时钟的高电平期间,SDA为稳定的低电平。大家主要看图,看看是不是这样的。
4 ?2 m! Q5 F# c( w
要是接收器是主控器,那么它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据的发送,并且释放SDA线,以便主控接收器发送一个停止信号。

3 W; S6 O( [4 h2 J
  • 数据的有效性
      L" G  q% |( K6 }+ U+ F

$ A4 E- p, k: C5 B0 R8 p

, u+ q5 q3 o* @# Y 微信图片_20231019162822.png   @' v, ~2 F: ^# ~# ^
" ?  K3 D5 |( c  L( y

* h$ Z$ b( w- M) M! O* D
时钟信号(SCL)为高电平期间,数据线上的数据必须保持稳定,只有在时钟信号(SCL)为低电平期间,数据线上的高电平或者低电平才能发生变化。

; \$ V/ S/ H# k/ ]* [% R
数据必须在时钟信号(SCL)的上升沿到来之前就准备好,并且在数据信号的下降沿来到之前必须稳定。
% Z9 A$ E% ]$ |( ]* D$ z( C
  • 数据的传输
      j* c" A' w# G6 H4 |: D
在SDA上的每一个位的数据的传输都需要一个时钟脉冲,即在SCL串行时钟的配合下,SDA上逐位的串行发送每一位数据。数据位的传输是边沿触发。
2 e- V. \4 i3 \- b4 J2 e8 l
示例代码讲解
  • 初始化IIC

    : `6 u( G/ K9 Z( e2 X. Z% S

' w& t& Y- o' t6 O
3 q/ Q4 S9 C6 X0 O: O5 Q$ v( q& ]# A
微信图片_20231019162824.png
, P: N" Z0 o- E* h2 y* [" U9 Z6 p) R1 _' G9 \
/ P6 a; G$ w- h3 s3 p
其实就是对两个线的初始化,我这里使用的是PA6和PA7,开始都设置为输出,中途会改变PA7的输入输出属性,在空闲状态,我们知道SCL和SDA是被拉高的,所以这个地方我们给高电平。
3 q; y# f8 C3 p
  • 产生IIC起始信号

    ' ^- R4 T' z5 G, e/ Y4 K
! Y$ h0 `4 V1 m4 B) d! h
* \: [3 x; G* J& R" }8 m9 ~7 C
微信图片_20231019162827.png
2 P7 Z; H  b( D' S0 K3 H4 ^
6 S% c; ^/ g2 @6 `5 z" K4 c
微信图片_20231019162830.png ) b. c5 i6 X$ G* g: E

' |$ N; y  y, O( y
将SDA线设置为输出,然后SDA和SCL都设置为高电平,延迟4us,然后将SDA拉低,延迟4us,最后将SCL拉低。这其实就是协议规定的动作了。
% S9 q1 G) D! y: U! p3 K
  • 产生IIC停止信号
      V4 k: \: \, L. S- X# J
, S( ~. K. K' |" _
+ D6 S+ r1 J) ?4 I# {) F& p
微信图片_20231019162834.png
' m( s5 @7 s( h1 w/ A0 N
7 `3 e. v6 z# N9 }6 v7 ~

8 t4 j! ]5 a9 N; i% {. E7 m
同样的道理,和协议的时序保持一致就好了。
" x! S0 w( v% s2 K' Z
  • 等待应答信号到来
    6 X! r# k0 A: \
9 b/ F7 ?) [; q

$ y/ T- [( U( ~# ~" W 微信图片_20231019162837.png
  \$ X: K# P8 X2 E: n' S+ D# \. ^  s; U% g% @! K! A
首先我们需要把SDA设置为输入,因为接收方要给发射方返回一个应答信号的。就是在SCL第9个高电平的时候,释放信号线,先拉高,然后持续等待,是不是有应答信号返回,其实就是返回一个低电平,所以我们一直在检测READ_SDA这个电平,持续一段时间,要是没有返回的话,我们认为超时了,就直接停止协议了,

6 ?. J; A1 N, b. I
  • 产生应答信号
    2 ]3 z0 \* `, g8 y; g* h
' u" F* T  a! H4 m+ a7 F6 V

/ p( G! H% q9 j$ G 微信图片_20231019162842.png 4 Y" _7 H* E1 c$ H2 a. o

, f' H5 p% A4 R6 |5 l, Z; D
即在第9个时钟周期内,SDA都为低电平,为应答

5 K( I) M6 J) Q; U1 R
  • 不产生应答信号

    0 u" q" q% @) l' e% ]
! J- j/ S5 P( \# K/ g" p0 H" i8 R
/ O# @6 F& _% R: J5 c8 [8 B
微信图片_20231019162851.png 3 f& }  c& f/ R  O% Z; u/ q3 ~

0 c( v7 Z% e( B0 ]/ o; J4 D# w. b6 Q
- J% h) v1 d& v* l3 D! Q
即在第9个时钟周期内,SDA都为高电平,为不应答
# P1 Q! K* r# E
  • IIC发送一个字节

    . z# b4 ]5 n' E2 D
" w) g% ?9 L% x" a: Z
7 K6 H( b2 x, i6 c$ _" o1 z" h3 l
微信图片_20231019162856.png ! ^3 ?3 K* o* C# v

7 i  \, }" M$ n6 \* n' q! [' X 微信图片_20231019162858.png
: X: i' Q2 E" n+ Z6 `' P
% X% ?8 ~$ a0 y1 f
发送数据,SDA设置为输出。SCL拉低,SDA准备。
做一个8次循环,拿出1byte的数据,将你发送的数据和0x80作与运算,拿出最高位,然后右移7位,将这个数据放到最低位,这个数据要是1的话,那么SDA输出一个高电平,要是与后的结果为低电平的话,那么SDA输出一个低电平。这都属于准备发送信号阶段。
然后SCL拉高,完成数据的发送,然后SCL拉低,此时SDA也就可以拉低了,接着开始次高位的传输,直到传输完成。

, k; F& V; P, y
  • 读取数据
    * L; |) I4 K% W" ^" l

    6 X" X) s4 g0 d6 E1 z% I7 V
微信图片_20231019162903.png $ _3 t1 ]% \5 ]1 e  y! A/ V! j
3 R: o: s5 G+ n- F; _$ K
9 G( \7 B7 w7 s/ J) @7 A, a
读取数据,SDA要设置为输入了。SCL开始为低电平,然后SCL为高电平,我们开始读SDA上的数据,然后左移数据,将读取的数据放在低位。然后检测一下有没有应答。
其实基本思路就是这样了。我把源码附上:
+ [, q2 g( R' |6 @6 o: ~/ N
i2c.h
  1. #ifndef __MYIIC_H
      N5 f) o/ B3 U& }1 A4 H
  2. #define __MYIIC_H: v9 H  ^& N9 W- e
  3. #include "sys.h": V" g  c$ v( ?6 R' Z0 a
  4. #include "stm32f10x_gpio.h"$ a9 {" ^- g0 k
  5. //IO方向设置9 B  H7 ?* v( W( L* s$ e: N
  6. #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
    - K8 K0 E) A, S8 L- `
  7. #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
    1 y! _5 n6 o9 B) \4 C, ?
  8. //IO操作函数  e( y0 t; A/ p1 b& v' U
  9. #define IIC_SCL PBout(6) //SCL5 W8 p  V; m  s* B# n1 r5 A: m
  10. #define IIC_SDA PBout(7) //SDA
      z/ S( ]4 Z  \
  11. #define READ_SDA PBin(7) //输入SDA' D  Q! X% d* x( C) r- r2 `* ?
  12. //IIC所有操作函数
    9 z8 h" o5 G4 G; T( w1 Y
  13. void IIC_Init(void); //初始化IIC的IO口, {5 S" p. Y* Y# @; n. v5 B
  14. void IIC_Start(void); //发送IIC开始信号
    ! F8 U9 k+ G# f
  15. void IIC_Stop(void); //发送IIC停止信号* `1 E# x8 d3 a2 f
  16. void IIC_Send_Byte(u8 txd); //IIC发送一个字节
    # C. I$ d9 ]8 t. v: ^, z
  17. u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
    9 x' ^; b) _1 |4 f. O
  18. u8 IIC_Wait_Ack(void); //IIC等待ACK信号
    : C0 K  U% `9 E9 D0 n
  19. void IIC_Ack(void); //IIC发送ACK信号, C/ j3 }7 c6 m
  20. void IIC_NAck(void); //IIC不发送ACK信号4 i+ E! H3 P7 m( b' \' X
  21. void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);, e* Y7 L& I* u( t5 \
  22. u8 IIC_Read_One_Byte(u8 daddr,u8 addr);8 G% F5 Y$ z0 T# ^* {/ m
  23. #endif
复制代码
: ]* F3 q# x; }. }9 U0 t
i2c.c
  1. #include "myiic.h"6 [# B$ Z8 X9 u' Q1 O
  2. #include "delay.h"* H9 B* _2 j; I) w! u( s! k1 ?) R
  3. //初始化IIC
    & k  E+ G- Z8 c% n* E
  4. void IIC_Init(void)
    0 J. F4 |$ \9 r; `6 C& O* a
  5. {                                         
    * M. \( K, l4 ^( q' a3 k
  6.         GPIO_InitTypeDef GPIO_InitStructure;
    & i* _* v9 x, @2 ?5 r
  7.         RCC_APB2PeriphClockCmd(        RCC_APB2Periph_GPIOB, ENABLE );        //使能GPIOB时钟* i+ L# r, F6 i8 E
  8.          
    1 {" d+ @& S- H) `+ Y$ L% ]
  9.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;( q; G1 D/ Z3 z+ N9 P" e
  10.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出! w) B4 }# Q/ A! @) D$ p- R& z
  11.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    . V$ P. R) l; f. W. ~) I
  12.         GPIO_Init(GPIOB, &GPIO_InitStructure);
    ! R& D4 M6 o0 \! I- O
  13.         GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7);         //PB6,PB7 输出高
    # s5 @5 u1 }; n6 K8 f) |& w
  14. }) l) ]8 s7 P  o) A: q) r4 t3 C# s
  15. //产生IIC起始信号# `/ {1 f( ]; i) O* N2 v4 |$ G4 Y# Z
  16. void IIC_Start(void)
    1 I+ `$ h) C# q- M
  17. {5 i8 y2 U" j, v7 W* p4 k7 K
  18.         SDA_OUT(); //sda线输出! y% ?* a8 K1 N# `" |, y; h
  19.         IIC_SDA=1;                  . ~! u( z; F; P+ q: b- h" m' ~
  20.         IIC_SCL=1;' m; X' v+ k- b3 @
  21.         delay_us(4);
    " j; Q3 i/ `9 g9 Q. [+ s7 f' F
  22.         IIC_SDA=0;//START:when CLK is high,DATA change form high to low 4 e. W* D( Y. ^+ X1 h& l' U
  23.         delay_us(4);
      e+ o' I' f, I! ]! _) I
  24.         IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
    * k" s- j( j$ |% g
  25. }         
      N& i4 G1 J! W! O
  26. //产生IIC停止信号
    ) H0 C  V; }% H
  27. void IIC_Stop(void)+ Z! ?: T3 E: e2 x* l- S
  28. {
    7 _7 V( F  [0 v0 A% I& o
  29.         SDA_OUT();//sda线输出
    * `- z6 B; E- Z+ q0 R
  30.         IIC_SCL=0;
    9 y& D) g- o1 u! q+ ~7 r, e$ E5 V
  31.         IIC_SDA=0;//STOP:when CLK is high DATA change form low to high7 V5 \" P' O6 d$ ^9 Y) ]
  32.         delay_us(4);& a- [% L) k- Z
  33.         IIC_SCL=1;
    4 e8 i( H# i+ K  ?6 d
  34.         IIC_SDA=1;//发送I2C总线结束信号6 z. k0 x; B. p" I+ K( Q9 q, r6 i2 W  Z
  35.         delay_us(4);                                                                
    , t- Z4 v) f' M
  36. }5 A/ g7 N: o) I0 N& T
  37. //等待应答信号到来
    2 e( n( M! C4 ?0 O
  38. //返回值:1,接收应答失败  b: Z3 K$ i4 D9 O/ S; J4 I
  39. // 0,接收应答成功: o! X7 y7 r, ^. S7 X4 ~3 S% v2 H
  40. u8 IIC_Wait_Ack(void)
    " }$ @" g5 _4 j
  41. {
    ' E4 \- r* {0 }! j- V6 o7 t
  42.         u8 ucErrTime=0;+ ~& ]  }! \9 W$ G: L% R
  43.         SDA_IN(); //SDA设置为输入
    / c) H2 {; C; g- u- |' p0 |
  44.         IIC_SDA=1;delay_us(1);         2 b  U% R, u" v- G! k
  45.         IIC_SCL=1;delay_us(1);         / L0 N6 [: J/ |( s! m
  46.         while(READ_SDA)
    ) ]) q* [8 @; |& O
  47.         {3 F$ V7 Y2 i6 ?
  48.                 ucErrTime++;
    5 [, O4 B+ N3 H. b
  49.                 if(ucErrTime>250)
    2 |) k) N6 U% A0 l0 P
  50.                 {* X0 ^, k7 e! B% {; O2 x
  51.                         IIC_Stop();
    * _' G& T* \2 K4 ]
  52.                         return 1;
    ) u9 M7 k: t/ o: K
  53.                 }
    6 @  j5 f! V, C- F3 g* b
  54.         }: R* ]% j1 ]  Z2 I
  55.         IIC_SCL=0;//时钟输出0          
    " r3 _9 n$ f& o0 e7 V
  56.         return 0; $ y, G, J: g" t, U. g
  57. }
      ^; r. C* i  _; x1 x( l$ d2 R
  58. //产生ACK应答
    0 D$ X1 Y/ @# E& E, G0 q4 O
  59. void IIC_Ack(void)
    / J: V2 L% f( x$ g; @& j
  60. {
    4 G% f' G6 h: d/ h( ]  x
  61.         IIC_SCL=0;
    , l* C+ N7 Z! h$ W9 X
  62.         SDA_OUT();
    ' z! M/ d9 |. E6 b! z
  63.         IIC_SDA=0;* f3 ^/ y8 }2 h7 B4 w+ j
  64.         delay_us(2);! y9 |- Z+ I- K  w
  65.         IIC_SCL=1;
    & h% ?3 m6 G/ Y
  66.         delay_us(2);( R3 h/ r/ j# d" q3 ]: r) t
  67.         IIC_SCL=0;& _% F8 L, [# m4 k+ m% x
  68. }0 _9 a& m, U# v: c4 b- d, L
  69. //不产生ACK应答                 8 u6 r/ m  d8 H% H
  70. void IIC_NAck(void)1 ?) o: e. d# ]/ K5 g
  71. {1 U, q* T4 ~) v0 ~4 c
  72.         IIC_SCL=0;
    ( O3 X0 d  b. S* \- y
  73.         SDA_OUT();* P. U  ]! b8 j! j, J/ {7 \
  74.         IIC_SDA=1;$ ]! Z- }, ~7 s3 h: x9 @: h
  75.         delay_us(2);" W( K9 \7 V9 N$ K3 t7 I" A
  76.         IIC_SCL=1;* r5 C8 Q) f- @- S
  77.         delay_us(2);
    * w, R+ a# ~; a! T2 {
  78.         IIC_SCL=0;" I  x$ f: i; M4 k- z2 k  F0 V
  79. }                                                                          
    ; @$ r& z( Z  F/ Z( \
  80. //IIC发送一个字节7 X2 z1 ^& O$ K% [6 o& ]
  81. //返回从机有无应答/ e3 i3 F. k" q9 ?
  82. //1,有应答
      [; `8 D3 j, H/ ~: f
  83. //0,无应答                         1 G( y. {6 G8 W: i
  84. void IIC_Send_Byte(u8 txd)
    " i2 k! ?: m7 u: C8 M6 g& q0 W
  85. {
    * S" v" l& \# o& W% h
  86. u8 t; ( ^2 A: x5 G8 a5 H" o1 i& A
  87.         SDA_OUT();          5 L6 ]4 K9 K3 y6 R
  88. IIC_SCL=0;//拉低时钟开始数据传输
    8 Z; x5 u9 e$ S/ E; C& ?
  89. for(t=0;t<8;t++)# C7 |5 i; F) d1 e/ t: g+ n6 L
  90. {
    $ r  u. ?6 O% R: j$ |
  91. //IIC_SDA=(txd&0x80)>>7;
    ( Q+ ~* ]. j5 f2 E+ V
  92.                 if((txd&0x80)>>7)5 P: ~# t% o% K- ?$ @
  93.                         IIC_SDA=1;+ x  o* u$ T/ O/ n4 A4 B- m7 p
  94.                 else
    . s- Y0 G% P) @& k
  95.                         IIC_SDA=0;  z* `. V* a8 l4 \* t
  96.                 txd<<=1;          4 I' a  {! c* Y* d4 X
  97.                 delay_us(2); ! e8 n9 g: }4 N
  98.                 IIC_SCL=1;+ k) i! ]! s8 }1 ]7 A0 P  U
  99.                 delay_us(2); - w/ E9 x" U* U1 d- Q' t4 w$ T7 m' P
  100.                 IIC_SCL=0;       
    * T9 }, y& ]# A, b) h( F1 E
  101.                 delay_us(2);
    & `; J0 K6 K; h1 I0 a
  102. }         
    ! q8 Z4 m7 K- @. t
  103. }          3 K8 }: N" g5 I: I! s- Z
  104. //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
    3 ?. Z5 Q) w9 u
  105. u8 IIC_Read_Byte(unsigned char ack)
    ' n$ i! i- r* Y* Z- q0 d
  106. {
    8 h4 N/ O7 ?5 S0 O; Y( j' B# _
  107.         unsigned char i,receive=0;6 W  w2 K6 f* @- B4 a& {: \7 N
  108.         SDA_IN();//SDA设置为输入
    6 {" t6 O5 U; O" e8 y. R
  109. for(i=0;i<8;i++ )8 a$ U, p2 u, G: o, V9 E
  110.         {
    # \/ r: q) n5 w1 C% ]% `. ?
  111. IIC_SCL=0; $ e' p- e! @6 P7 U2 G3 n4 h
  112. delay_us(2);
    ' H3 r0 F. z! ]3 n: b0 G" E
  113.                 IIC_SCL=1;3 ^/ g6 I2 m0 d0 q( I! g: H
  114. receive<<=1;
    : o# s2 c& Q( c: t" P6 p
  115. if(READ_SDA)receive++;
    4 o- z( I% K$ [: W$ ~# G
  116.                 delay_us(1);
    5 m# P0 F8 ^  q* }+ |3 A9 D; M/ C
  117. }                                         / S( o3 A) t, `+ c, j! }/ w+ V
  118. if (!ack)
    2 N" s, c9 _" {
  119. IIC_NAck();//发送nACK. Z1 B* R) q# S
  120. else
    . j3 x. e) g( A7 h. Y$ }: @
  121. IIC_Ack(); //发送ACK 5 R, b1 F9 `9 H4 `  s
  122. return receive;: [2 j. E) a# Z! m8 j
  123. }
复制代码
' O3 ^  D4 r) N6 l/ ?: ~5 c
转载自:电源研发精英圈9 l2 d: u* y4 _( D4 R8 @) o" ?! D
如有侵权请联系删除6 v' s$ T3 Y5 I% d
- k1 D% u+ c: H& u& x

9 b+ ]' Z0 @% L$ k; O
赞 收藏 评论0 发布时间:2023-10-19 16:29

举报

0个回答

所属标签

相似技术帖

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