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

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

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

) A1 L$ q+ T# N7 R, e7 N" F
I2C支持多主机模式:

  U% p; K" r7 H" E7 n' o
% v) n) I6 @3 D1 `5 S! {* p6 e8 p. B
微信图片_20231019162807.png   q) f1 _# t  S- j1 U+ ?
6 ~$ K$ v9 U: j" G
即在这个主线上可以挂载n个I2C外设。

0 {! k; z  N" D: Z+ u
对于I2C协议,其实也很简单,不要想的那么复杂,其实就是电平的变换。我们可以人为的分为6个部分
) F) R& g7 H  d3 D; N+ x# H
整体时序图:

/ F4 D- Y; P' B, I: f
3 V, y2 B3 y$ B6 J8 w+ V& U. s
微信图片_20231019162810.png 2 u6 y6 d* d- B2 t! A: ^
/ w  x! r8 P3 v+ {) L. Q
各状态:
  • 空闲状态

    . _" v, v& t6 N. R
! }  u  ?  O' W& I

+ P% j+ |6 }8 D 微信图片_20231019162813.png
2 `2 q6 {1 o/ f3 @9 [# X0 {
, O' t2 E% V1 Y
I2C总线的SCK和SDA两个线同时处于高电平的时候,规定为总线的空闲状态,这个就是由总线上的上拉电阻把电平拉高的。
1 f! t5 e: u2 ?$ p/ q- U
  • 起始信号
    4 O# E* i4 Q# m% R6 l# K

9 g; W- q  M1 U1 n0 O6 z+ t 微信图片_20231019162816.png
! Y; u& B$ V) ~  m# ?8 x
; ~) ~4 F6 _* w. o8 E2 k% \( }
当SCL为高电平期间,SDA由高电平变成低电平,即为起始信号。启动信号是一种电平跳变时序信号,不是一个电平信号。

. h0 G$ l; `3 N2 ^$ e; R
  • 停止信号
    0 e7 r/ P1 Y+ v0 y
当SCL为高电平期间,SDA由低电平变为高电平,即为停止信号。停止信号也是一种电平跳变时序信号,不是一个电平信号。

' V3 w  s& v& ~5 W6 H' T, l
  • 应答信号

    7 l  |* f% \9 k+ k. \' N# p# W
' j3 g4 r3 O! h# ]! B( E9 B
$ B# K4 h4 f+ A9 ^$ m. j6 ^
微信图片_20231019162819.png
0 B7 \& h4 N+ g4 ~) Z: p) U  s% z, K+ p7 c5 j4 ~5 \5 g9 r
发送器每发送一个字节(8bit)数据,就在时钟脉冲(SCL)9期间释放数据线(SDA),再由接收器来反馈一个应答信号,应答信号为低电平的时候,规定为有效应答位(ACK:应答位),表明接收器已经成功的接收了该字节,应答信号为高电平时,规定为非应答位(NACK:非应答位),表示接收器没有成功的接收该字节。
" r* n- `; I6 ?/ m& Y
对于反馈有效应答位(ACK):接收器在第9个时钟脉冲之前的低电平期间将SDA拉低,并且保证在该时钟的高电平期间,SDA为稳定的低电平。大家主要看图,看看是不是这样的。
  ~( N. `( }1 X0 c$ h+ `8 M
要是接收器是主控器,那么它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据的发送,并且释放SDA线,以便主控接收器发送一个停止信号。

2 t0 c; B7 G; R0 h( |/ {
  • 数据的有效性

    ! x6 x2 y7 O7 p3 N
+ Q- s. a* ?2 M( ^; P) Z

/ q( c' H) q* L' I% ^/ b2 E! k 微信图片_20231019162822.png
3 o1 i7 f& H0 C% `, |! i" i$ L+ X2 G# G* u& E* a
, h( u- v9 `  v* K. [
时钟信号(SCL)为高电平期间,数据线上的数据必须保持稳定,只有在时钟信号(SCL)为低电平期间,数据线上的高电平或者低电平才能发生变化。
6 v* A1 J0 R3 R* }, [( \! G  H
数据必须在时钟信号(SCL)的上升沿到来之前就准备好,并且在数据信号的下降沿来到之前必须稳定。
* `& K/ }: o8 h' I$ y
  • 数据的传输
    8 i; w) O0 O3 W7 d2 t. q: @& X
在SDA上的每一个位的数据的传输都需要一个时钟脉冲,即在SCL串行时钟的配合下,SDA上逐位的串行发送每一位数据。数据位的传输是边沿触发。

0 u9 c) U. i9 Z# `" Q2 _: W' z
示例代码讲解
  • 初始化IIC
    2 W  Q* ~$ |$ V
: E4 R  a/ s: O( z0 L. F" G

/ h. S1 y6 Y. i- H! f. E- e 微信图片_20231019162824.png
% R9 u  {% j, K$ I, E( v- o0 @1 x. r, t$ H6 K

- H; w3 @# s& ?" R$ a
其实就是对两个线的初始化,我这里使用的是PA6和PA7,开始都设置为输出,中途会改变PA7的输入输出属性,在空闲状态,我们知道SCL和SDA是被拉高的,所以这个地方我们给高电平。
$ |: u; u; l, @6 m
  • 产生IIC起始信号
    & _8 I! e5 P; s. C' p
* I& _) M8 {  b$ D* L

* ^2 T' [$ N" @6 u9 L' q. o/ Z 微信图片_20231019162827.png
! @. X; }" g" n1 Z' K- t' N+ u; r% Z# {% ?- w/ e" q# O0 u5 w
微信图片_20231019162830.png / j$ K5 Z. \% k! H$ l( o

+ K; X2 O, ?8 q; t% @
将SDA线设置为输出,然后SDA和SCL都设置为高电平,延迟4us,然后将SDA拉低,延迟4us,最后将SCL拉低。这其实就是协议规定的动作了。

2 M: C+ v0 j6 w
  • 产生IIC停止信号
    ( a3 Z5 f0 d( ^

! O" K5 P7 q5 H1 N& P

8 d$ t7 y: I& w( s8 I" | 微信图片_20231019162834.png 0 q% _- O: Z0 P2 e0 y( J

4 A- E) D5 o  V: p) W3 R# F
8 w5 y6 f& d2 C. |2 w, U
同样的道理,和协议的时序保持一致就好了。

! |; M% \( T1 k! }
  • 等待应答信号到来
    ) E' j; S" A. d6 ^( b
5 p0 I) i. O9 y# b. o- L

" b: F1 U0 ?  `8 G8 I7 c2 v- d8 h 微信图片_20231019162837.png ) t6 W. {) \/ H: \$ |* Z
, b6 k, _- H! _1 g
首先我们需要把SDA设置为输入,因为接收方要给发射方返回一个应答信号的。就是在SCL第9个高电平的时候,释放信号线,先拉高,然后持续等待,是不是有应答信号返回,其实就是返回一个低电平,所以我们一直在检测READ_SDA这个电平,持续一段时间,要是没有返回的话,我们认为超时了,就直接停止协议了,
( m  g/ Q7 P4 U2 N7 p% P
  • 产生应答信号

    4 W$ X1 f( y6 k% i! i: N

7 i7 d- \- q& I5 N

; A" }# {/ k& P2 a$ f7 N 微信图片_20231019162842.png 9 C, S* n0 p1 g- J
9 }/ v* G1 m1 J4 J; x
即在第9个时钟周期内,SDA都为低电平,为应答
6 [7 P& l8 G" O1 f0 K* w
  • 不产生应答信号
    ; @6 g( B8 F0 e. Z

& h2 W8 [  R/ w

0 o+ s+ Y: \; p4 D 微信图片_20231019162851.png
' t: w0 s0 P1 M/ @
7 J3 R2 @, k1 |4 w: U! a; D

  I5 Z" f4 S2 N4 y$ k+ y+ Q
即在第9个时钟周期内,SDA都为高电平,为不应答
# Q0 Y2 x& r1 J+ z  \
  • IIC发送一个字节

    / W' s. Y. h) z. D# N- }
3 ]$ p+ q; F6 a  R3 W+ x9 r
# R& W, P( h7 Y
微信图片_20231019162856.png
2 |8 r( B1 M5 J0 c
& X1 z0 ~  I& V5 o7 y 微信图片_20231019162858.png 3 l5 l5 q) w/ Z# @- M! M2 S. D

1 |; v8 ~/ I& v! \+ n5 R, C
发送数据,SDA设置为输出。SCL拉低,SDA准备。
做一个8次循环,拿出1byte的数据,将你发送的数据和0x80作与运算,拿出最高位,然后右移7位,将这个数据放到最低位,这个数据要是1的话,那么SDA输出一个高电平,要是与后的结果为低电平的话,那么SDA输出一个低电平。这都属于准备发送信号阶段。
然后SCL拉高,完成数据的发送,然后SCL拉低,此时SDA也就可以拉低了,接着开始次高位的传输,直到传输完成。

6 r, f: L, T- b
  • 读取数据

    $ \3 ^8 P/ r, _6 d8 P. G
    - q6 C3 N; Z* ~, ^4 J( b
微信图片_20231019162903.png
2 l- K, Z9 [9 H& z
0 D4 e, U7 x0 M; y
: v& G( ]% G; V( s6 K
读取数据,SDA要设置为输入了。SCL开始为低电平,然后SCL为高电平,我们开始读SDA上的数据,然后左移数据,将读取的数据放在低位。然后检测一下有没有应答。
其实基本思路就是这样了。我把源码附上:
  a/ q5 ~# E$ _4 b* `7 p* e
i2c.h
  1. #ifndef __MYIIC_H
    : z& u, @/ H( z0 P" p
  2. #define __MYIIC_H
    ( N3 j  e# z5 d9 a$ M9 Z
  3. #include "sys.h"9 s) s3 v9 i! v- f# m
  4. #include "stm32f10x_gpio.h"
      X0 S6 ]* j1 {/ c
  5. //IO方向设置
    . @  N( I# A8 h0 @
  6. #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
    ; u5 \3 N7 s& l& J1 W6 B: \# J
  7. #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}2 `! j5 T. n# K" ~/ S* B  y
  8. //IO操作函数* Y$ C6 C' n+ I9 o. U
  9. #define IIC_SCL PBout(6) //SCL5 _1 `! L- [! F
  10. #define IIC_SDA PBout(7) //SDA
    8 K! v5 a& }  P2 C' \) W9 z' ]
  11. #define READ_SDA PBin(7) //输入SDA
    & ]3 \. r7 |- X4 o  z
  12. //IIC所有操作函数' J+ Z; }5 q2 K3 j9 G. ~, R
  13. void IIC_Init(void); //初始化IIC的IO口
    " a3 S# b& `% h
  14. void IIC_Start(void); //发送IIC开始信号
      G5 d$ j/ X! W) Q6 t0 A7 g
  15. void IIC_Stop(void); //发送IIC停止信号
    ' |# u8 N; Y$ J" k3 C; b! S
  16. void IIC_Send_Byte(u8 txd); //IIC发送一个字节
    # a. f+ f  J- {* X: s9 |
  17. u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
    . N, X$ T( R) {# \
  18. u8 IIC_Wait_Ack(void); //IIC等待ACK信号
    # R, p! a$ c0 L" m. u; n% I
  19. void IIC_Ack(void); //IIC发送ACK信号7 P) c" W! J2 \: s5 U' o/ `
  20. void IIC_NAck(void); //IIC不发送ACK信号
    , @+ j0 a4 ?$ r' n3 m* F
  21. void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);& k& s' \) e4 w1 C& [
  22. u8 IIC_Read_One_Byte(u8 daddr,u8 addr);& l+ }( ~. S; E. ~8 i# Y8 J: M
  23. #endif
复制代码

' a$ I* L3 z, V6 s6 D* ^, G3 I* }
i2c.c
  1. #include "myiic.h"
    3 C! y- B  O% X
  2. #include "delay.h"
    ! V, ]" U( {9 `9 w6 p6 ~' J- B
  3. //初始化IIC" H% S9 ]8 L# R& s
  4. void IIC_Init(void)( G: J1 g& E- a, J8 V. R
  5. {                                         
    4 Z7 \2 I3 h3 o; B" i2 p
  6.         GPIO_InitTypeDef GPIO_InitStructure;
    3 d+ ^3 |+ e# d0 B, `
  7.         RCC_APB2PeriphClockCmd(        RCC_APB2Periph_GPIOB, ENABLE );        //使能GPIOB时钟# y) h# a. m" b
  8.          
    ' I" k9 I& U! o; j  Q  O- b- y" _9 n
  9.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;  A/ f8 s1 k! `5 H+ M
  10.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出* R% w7 [  ~' J4 a6 `2 s2 d" l/ \
  11.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;; F6 Y+ X  V3 [+ f0 @, b/ a* {( t
  12.         GPIO_Init(GPIOB, &GPIO_InitStructure);
    7 e* H& D6 d' i9 _
  13.         GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7);         //PB6,PB7 输出高
    1 z- q# d. r8 ]
  14. }1 W- f- ~" s7 I. n4 J
  15. //产生IIC起始信号: r/ \- c; z0 ?! R$ D# E
  16. void IIC_Start(void)
    , u& B; f) Q) o  M9 M7 G
  17. {
    5 r3 i2 [9 ?9 D- q3 w( n
  18.         SDA_OUT(); //sda线输出
    3 o1 k1 L/ u. n
  19.         IIC_SDA=1;                  
    5 e5 B* C" f6 B% K+ O" J# ~8 o
  20.         IIC_SCL=1;* U. Z7 n6 F/ O7 B; ^6 ^
  21.         delay_us(4);4 ^3 |; j" J+ ]/ @# V1 ]0 _
  22.         IIC_SDA=0;//START:when CLK is high,DATA change form high to low
    7 N7 ]' J' O0 r3 N) y8 O
  23.         delay_us(4);& e0 w# K& A: H& x
  24.         IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 5 ^/ c$ t+ x  K, X
  25. }         2 X$ p& Q/ m- J* L, g0 ?3 D- f& k  W
  26. //产生IIC停止信号
    % f9 N( [, k& _9 ]$ @1 `
  27. void IIC_Stop(void)
    7 K( q* ^" ?, F, f
  28. {
    ; x' b$ |' u1 c6 B
  29.         SDA_OUT();//sda线输出1 Y4 N' r+ W" z4 d! l
  30.         IIC_SCL=0;6 N3 F: H$ i" G
  31.         IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
    & y; @. o7 u+ p0 M$ Y% M. U3 c5 J- t% T
  32.         delay_us(4);
    , ?. D7 v! e. F" e' K! P
  33.         IIC_SCL=1; 4 p! W' H: b' |7 c5 \% _
  34.         IIC_SDA=1;//发送I2C总线结束信号
    1 C8 ?1 _: }7 i8 `' Z% `, D
  35.         delay_us(4);                                                                
    ; B8 C4 x/ w' x1 |
  36. }) v% r% w+ Q, z8 o1 G$ y
  37. //等待应答信号到来
    # ?) N2 W$ |% l* H
  38. //返回值:1,接收应答失败
    ! V2 @: ]( x4 `- n
  39. // 0,接收应答成功& t* c/ ~) E' e- z  S/ p# {( ~4 h$ J
  40. u8 IIC_Wait_Ack(void)3 c  k7 ]" F9 {0 W9 K3 d7 [
  41. {( K9 w8 [/ A. w
  42.         u8 ucErrTime=0;
    + @2 x* ]( s  X* m, k% v
  43.         SDA_IN(); //SDA设置为输入
    ' w- N1 h4 R( P- x8 O- k$ b" T  y
  44.         IIC_SDA=1;delay_us(1);         . g. k& u7 i. E% R. `  {
  45.         IIC_SCL=1;delay_us(1);         
    & l7 t- s$ V# C" L1 H2 |! i' @7 k5 T
  46.         while(READ_SDA). {! A) G7 g  i
  47.         {
    . X# ~6 O# g, U
  48.                 ucErrTime++;" D- D) S3 b- x4 P* z2 ~9 K; @" |! N
  49.                 if(ucErrTime>250)- a: w0 u3 {( l5 X0 a" C- s1 {; H
  50.                 {  y: m( P! `/ Q. m6 r- u
  51.                         IIC_Stop();3 v7 |! ^' S% _: c/ s
  52.                         return 1;
    " C) E; x2 `" o/ f0 S6 B8 _1 H+ Q/ v
  53.                 }
    ) D3 C7 @, n! ^1 s4 r# ^% H3 h) h
  54.         }
    1 c1 A& I" j2 D; w6 v: I% g
  55.         IIC_SCL=0;//时钟输出0          4 a: D& n' ]$ t$ V* k* f; c4 K* G5 g
  56.         return 0; 1 N/ \6 [* }. F/ [, b0 P
  57. } * I3 V4 {( ]) m
  58. //产生ACK应答
    5 q. ^7 ~/ W, _  ?0 e5 r" E
  59. void IIC_Ack(void)
    * H& Y* d1 _5 m. ~2 u. w0 r+ ?
  60. {
    - {" M& [. ]( h! p6 S# d/ k
  61.         IIC_SCL=0;/ ?  O! H7 g( E5 {- V0 d& M
  62.         SDA_OUT();
    . M; h2 h/ l9 A# ^
  63.         IIC_SDA=0;* G/ ?" h0 S  T
  64.         delay_us(2);
    4 S% s/ W$ A8 [) \* a
  65.         IIC_SCL=1;/ Q: x& y3 `, o
  66.         delay_us(2);
    # p5 S, ?: r( t& z
  67.         IIC_SCL=0;9 B# J( t& g& v0 O8 V+ r# |
  68. }5 b& I1 |7 F" _$ b
  69. //不产生ACK应答                 : f% U6 d. f- Z' T  n( Y
  70. void IIC_NAck(void)
    8 r; f7 h% c( ]5 i+ }
  71. {
    ( p' g' [. h) Z" F3 Z& f
  72.         IIC_SCL=0;8 H( Y( u; @2 F# Y9 L5 z
  73.         SDA_OUT();
    : f/ N' p, `& f" u, Y& a9 y, u
  74.         IIC_SDA=1;* o+ M! S4 A. q" Q& b1 c
  75.         delay_us(2);
    " x0 N' b+ y# m: A
  76.         IIC_SCL=1;
    ; I( g+ _2 o3 q
  77.         delay_us(2);+ w" p3 A7 c+ Z$ A
  78.         IIC_SCL=0;
    5 J- B/ v( w5 ^7 b4 x6 Y/ }/ w
  79. }                                                                          
    * B7 g, i, k1 f
  80. //IIC发送一个字节
    * V0 T1 T0 B* g! J7 |
  81. //返回从机有无应答7 Z9 |1 k* _, e6 G
  82. //1,有应答7 ?. f# O6 v; @9 e. E5 c5 h
  83. //0,无应答                         , @/ u3 [9 {1 `5 h
  84. void IIC_Send_Byte(u8 txd)
    & \/ z. z& \+ P5 o9 k7 U: C+ E2 ^  e
  85. {
    5 e( Y( {8 }, M0 P5 U$ s
  86. u8 t; $ i6 r! h3 V. i* D& E
  87.         SDA_OUT();          
    8 ?/ @$ Z+ P( E6 W* F( {
  88. IIC_SCL=0;//拉低时钟开始数据传输
    . E& _9 ~6 t2 `; Y8 r  l
  89. for(t=0;t<8;t++)
    % x! N8 |2 v/ H$ Y
  90. {
    , M. r4 U+ d. B0 ?. R0 g2 B
  91. //IIC_SDA=(txd&0x80)>>7;
    : x' d0 i! @4 I
  92.                 if((txd&0x80)>>7)
    6 r! b$ w. D" R& I, k# X, L
  93.                         IIC_SDA=1;6 R3 |  ?! a- `1 l
  94.                 else
    . l- g7 d5 C8 L2 x
  95.                         IIC_SDA=0;
    ( l) K! ]- i2 K) A
  96.                 txd<<=1;          / K# A/ ?: `) m5 r! M# z4 l4 I, ]
  97.                 delay_us(2); ! Q* e# \) {- x$ s7 `
  98.                 IIC_SCL=1;/ S9 x5 b% b/ N  I" N) R) z
  99.                 delay_us(2);
    % h& j" B# `8 T! c" I3 K
  100.                 IIC_SCL=0;       
    9 ~" q9 {3 v/ l; v" e
  101.                 delay_us(2);
    $ R& z, S, O- c
  102. }         
    4 b' P( `1 n/ S/ F- Q/ G
  103. }          ! O  t7 R% d. h/ s" g  j# g
  104. //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
    $ j5 y) X. k4 K2 X6 k: N
  105. u8 IIC_Read_Byte(unsigned char ack)
    $ @$ a5 i5 e* t7 ^8 ^6 H- w
  106. {
    4 b$ l: O3 t7 X6 z/ ^! |4 f
  107.         unsigned char i,receive=0;
    / u( `2 O0 ~$ W9 V
  108.         SDA_IN();//SDA设置为输入
    : t! x0 [! ^9 W
  109. for(i=0;i<8;i++ )# N  ^7 L$ q/ k3 N" g5 s6 U) g
  110.         {
    ; p# Y7 l1 P! d$ ?$ g
  111. IIC_SCL=0;
    ( C; Y  H& ^4 t: G. ~) ?
  112. delay_us(2);
    8 O8 ]2 t3 A/ p" @/ A$ k$ `
  113.                 IIC_SCL=1;5 U! i& J: E+ \/ w
  114. receive<<=1;
    , ?! Q$ p' y: @: R
  115. if(READ_SDA)receive++;
      p' e1 k7 s; W
  116.                 delay_us(1); 8 S  Q+ e6 t9 F+ d! _
  117. }                                         
    + A  E; M) ]) j. m
  118. if (!ack)
    5 l  r' d% \7 J; `8 M5 l
  119. IIC_NAck();//发送nACK
    ( K: d7 \. n3 y  h2 j
  120. else2 J( z4 W4 j+ v  H7 r( [
  121. IIC_Ack(); //发送ACK & a! P4 f# c( |/ r
  122. return receive;* w! x% M* h4 Q( s$ C
  123. }
复制代码

4 H- c& U# B6 T/ v转载自:电源研发精英圈; M0 c  G( s6 T& V
如有侵权请联系删除
% b: Q1 T# [5 Y, t3 s0 [! P3 m; s
) }1 x: B2 l& @0 A: D0 U
赞 收藏 评论0 发布时间:2023-10-19 16:29

举报

0个回答

所属标签

相似技术帖

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