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

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

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

; T& D* `2 }9 I' Q8 n" ^8 z
I2C支持多主机模式:

" [8 a# g# [, S, ?# ^. v7 r

4 R, Q3 t+ J5 r3 g% M+ G. o
微信图片_20231019162807.png
  S% l& y6 w2 K1 K9 u

  A( m. @9 T1 j
即在这个主线上可以挂载n个I2C外设。
( V7 d/ c, B) A$ n8 v
对于I2C协议,其实也很简单,不要想的那么复杂,其实就是电平的变换。我们可以人为的分为6个部分
+ I5 A; w1 h/ |  u" V
整体时序图:

+ [! e/ q# Q5 N/ V$ Y% J3 t
7 l* @' G  N+ Y& G, \5 G% t- y
微信图片_20231019162810.png
; i+ d1 v# N$ `* ~5 v+ r& B% @4 Y: _6 n- D, B( E8 E
各状态:
  • 空闲状态
    ( u' r8 H9 `8 E/ |" ~+ k

+ ]( R/ ]  i7 C3 [& a8 Y! V) j6 z
- N( F6 ~' D  [" `) K  x. ?6 W
微信图片_20231019162813.png 2 a1 v1 s+ Z% I1 S  d+ O  w  A

3 H$ `& o1 b6 |  O+ F. C" ?1 U7 b& u& p1 `
I2C总线的SCK和SDA两个线同时处于高电平的时候,规定为总线的空闲状态,这个就是由总线上的上拉电阻把电平拉高的。
8 ~- G7 s1 x' d0 |5 D7 v5 ]
  • 起始信号
    & J2 @' ?9 R# j/ i' j$ b$ |  b! X- V

/ [# ]& f) E" b3 [ 微信图片_20231019162816.png
8 _  T( q# x* `1 h/ r8 E' w) c5 J7 S+ m3 S
当SCL为高电平期间,SDA由高电平变成低电平,即为起始信号。启动信号是一种电平跳变时序信号,不是一个电平信号。
, S8 M" }' ~1 k
  • 停止信号

    $ w1 c+ [  w6 f: [1 E% w, t$ g
当SCL为高电平期间,SDA由低电平变为高电平,即为停止信号。停止信号也是一种电平跳变时序信号,不是一个电平信号。

# I7 a6 u$ ?! ~1 ?  I( s- ]
  • 应答信号
    % g2 d% w/ n3 W& s9 _* g/ m9 E

% \, H. B% i, M7 w6 Y

% s, a: ^$ c! a2 U9 I- R. e, h 微信图片_20231019162819.png
7 Y) S  U, p. @  F+ \, Z$ g! p5 Q7 V: [. H& ?1 R' `& e
发送器每发送一个字节(8bit)数据,就在时钟脉冲(SCL)9期间释放数据线(SDA),再由接收器来反馈一个应答信号,应答信号为低电平的时候,规定为有效应答位(ACK:应答位),表明接收器已经成功的接收了该字节,应答信号为高电平时,规定为非应答位(NACK:非应答位),表示接收器没有成功的接收该字节。
! \$ I; ]$ }. z6 p2 R# I
对于反馈有效应答位(ACK):接收器在第9个时钟脉冲之前的低电平期间将SDA拉低,并且保证在该时钟的高电平期间,SDA为稳定的低电平。大家主要看图,看看是不是这样的。

+ D% E: h) o, C% j; C
要是接收器是主控器,那么它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据的发送,并且释放SDA线,以便主控接收器发送一个停止信号。

& O, J9 M4 u. `* c4 Z$ ?; u
  • 数据的有效性

    6 g! K% J" |  ]* |. c/ E

* b0 ~/ p+ \2 v* J" i
3 d4 R! e* r9 I; E/ w
微信图片_20231019162822.png
, o8 D+ h& }' _& x4 H% Z# y. n& @. e" e) \5 i$ N3 u9 }
' ^9 j; ^+ B  @% ]
时钟信号(SCL)为高电平期间,数据线上的数据必须保持稳定,只有在时钟信号(SCL)为低电平期间,数据线上的高电平或者低电平才能发生变化。

/ n9 c' f- ~: Y1 U2 r
数据必须在时钟信号(SCL)的上升沿到来之前就准备好,并且在数据信号的下降沿来到之前必须稳定。

% o% \7 ~; a% K2 f+ {
  • 数据的传输
    " m8 u; ~$ Y* F, i
在SDA上的每一个位的数据的传输都需要一个时钟脉冲,即在SCL串行时钟的配合下,SDA上逐位的串行发送每一位数据。数据位的传输是边沿触发。

( H6 _0 ^% E, @8 b, M) u
示例代码讲解
  • 初始化IIC

    . h1 Z6 d  a4 o- }: B! y
& V4 s; m, V3 y( @8 J0 J
9 m& _/ q+ t5 J  B
微信图片_20231019162824.png # G' W1 O( H& m) g
9 g3 \& ?3 o6 r  d% B: t3 ]. C
; p1 F5 K* V- p- D" O9 }: m- g
其实就是对两个线的初始化,我这里使用的是PA6和PA7,开始都设置为输出,中途会改变PA7的输入输出属性,在空闲状态,我们知道SCL和SDA是被拉高的,所以这个地方我们给高电平。

  D# d6 W5 p% I  m* l* w* Z" t8 w; \% [& |
  • 产生IIC起始信号

    ! G' \8 A1 r8 c* h$ N

4 K/ F; w) N* w# t# L. X0 m% S
& |: K) }8 o$ U
微信图片_20231019162827.png
8 D% R  [& d2 K2 s0 J: q+ S* Q/ E# |9 y! k" O7 h! a
微信图片_20231019162830.png ) b5 F, O/ i- ?- i

1 Q- Z) P$ c7 z' u9 s8 v7 X* F8 Z
将SDA线设置为输出,然后SDA和SCL都设置为高电平,延迟4us,然后将SDA拉低,延迟4us,最后将SCL拉低。这其实就是协议规定的动作了。
: [# |- \; p: F
  • 产生IIC停止信号
    4 C- s3 d% ^5 G- }

5 p, ]+ V1 _" R6 ?' c, ^
  w/ B9 M- c1 L7 f9 B3 Q
微信图片_20231019162834.png + A( M+ e+ N: S4 p2 I
2 C& a, E( z. {0 z
! W8 Z( q; b* D' ^  f4 R
同样的道理,和协议的时序保持一致就好了。
! c$ k! N1 U9 ~. l0 |
  • 等待应答信号到来

    $ L' S) r8 a' {) @
& r2 i1 J5 `0 y+ E# T: H% P9 s
) w, F/ r5 m6 e' Z2 i
微信图片_20231019162837.png - l$ r3 J2 a9 v: Q$ G" R

& ~6 L0 y: k8 F# ^0 B! ?2 A; @
首先我们需要把SDA设置为输入,因为接收方要给发射方返回一个应答信号的。就是在SCL第9个高电平的时候,释放信号线,先拉高,然后持续等待,是不是有应答信号返回,其实就是返回一个低电平,所以我们一直在检测READ_SDA这个电平,持续一段时间,要是没有返回的话,我们认为超时了,就直接停止协议了,

+ P* m; D: C0 Z; Y) ^
  • 产生应答信号

    3 T- x+ m) M/ [& i/ b

" K$ @3 r( U9 y9 X5 d- h- j

# d' j. j0 f9 |, e' P$ p 微信图片_20231019162842.png . K/ k- L* f) f

+ h9 r; l  n! m; f# A2 H& f
即在第9个时钟周期内,SDA都为低电平,为应答

9 R2 X. t3 B) W2 h6 p) y$ |: E
  • 不产生应答信号

    ( o& Y, _% N) }( R

/ S8 j0 l, W6 \' N# O; D' A

7 C4 k" L4 H" o% N1 f 微信图片_20231019162851.png
' u# a! _. ^6 ^2 ]
! S" J  d3 t" o" K2 \
8 Z/ b- Z0 \, A1 G/ O1 v% f
即在第9个时钟周期内,SDA都为高电平,为不应答

8 C/ U- D* }. s: _( {* Q8 D9 I
  • IIC发送一个字节

    % ]5 s: m3 ]0 d

9 t6 i9 s# ?* U9 p

- |' a% \9 f# E5 @  n 微信图片_20231019162856.png % W4 r; g: {+ z& _' w4 d

& b- k1 q; F' v5 B$ S' D, ~ 微信图片_20231019162858.png + a- s/ l% O9 B# N$ S9 H9 r" C

  L6 a' e  j' k1 e% w( G* z
发送数据,SDA设置为输出。SCL拉低,SDA准备。
做一个8次循环,拿出1byte的数据,将你发送的数据和0x80作与运算,拿出最高位,然后右移7位,将这个数据放到最低位,这个数据要是1的话,那么SDA输出一个高电平,要是与后的结果为低电平的话,那么SDA输出一个低电平。这都属于准备发送信号阶段。
然后SCL拉高,完成数据的发送,然后SCL拉低,此时SDA也就可以拉低了,接着开始次高位的传输,直到传输完成。

. U. M1 p% l8 O6 M0 r
  • 读取数据
    . c5 V% o' N" |" [* h0 e5 Z

    9 c0 \- e! W& Q
微信图片_20231019162903.png
: j3 _! \1 d* q; I' y- b+ w- g, D1 U* M1 k) X, n' y' R

7 f+ x8 S9 b1 C4 \) _( B
读取数据,SDA要设置为输入了。SCL开始为低电平,然后SCL为高电平,我们开始读SDA上的数据,然后左移数据,将读取的数据放在低位。然后检测一下有没有应答。
其实基本思路就是这样了。我把源码附上:

' D( L  n; h$ v
i2c.h
  1. #ifndef __MYIIC_H
    % a  z1 J) ?5 B- k
  2. #define __MYIIC_H
    , j0 ^. z1 e9 b' a! s: ^% p& @
  3. #include "sys.h"8 ?; x- |/ A) Q0 a! q$ ^
  4. #include "stm32f10x_gpio.h"
    8 R' D6 z1 C4 [! k0 }8 }
  5. //IO方向设置
    8 `1 W, B, O% ~* T4 l5 b+ R2 }4 ?
  6. #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}" ^  A- v; i- K, R  n# N
  7. #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}3 {1 T* B  r0 Q/ u
  8. //IO操作函数# r& l0 U! `* C$ a2 Y
  9. #define IIC_SCL PBout(6) //SCL
    / Q2 G+ f6 W2 |+ p
  10. #define IIC_SDA PBout(7) //SDA
    7 R: q0 ]/ B6 I! C) T& \
  11. #define READ_SDA PBin(7) //输入SDA
    # k8 I9 ]7 `5 w1 h8 _* N. U
  12. //IIC所有操作函数
    & r) s5 R) q( x3 `& R# O9 f
  13. void IIC_Init(void); //初始化IIC的IO口& J; J! s1 N: j; g0 Y" o
  14. void IIC_Start(void); //发送IIC开始信号
    & i$ K! S7 X  r9 z' C
  15. void IIC_Stop(void); //发送IIC停止信号
    ' l0 @5 q( _+ ]& n" `2 K
  16. void IIC_Send_Byte(u8 txd); //IIC发送一个字节' k; _5 c+ G0 w  e. K2 e
  17. u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
    ' T/ w9 N& X& {  B
  18. u8 IIC_Wait_Ack(void); //IIC等待ACK信号
    " Y0 ^0 [0 D: x2 N* _$ }
  19. void IIC_Ack(void); //IIC发送ACK信号
    2 G6 n# k7 p  N0 L* k) q$ |
  20. void IIC_NAck(void); //IIC不发送ACK信号
    2 p% A/ {5 a! V+ G8 Q3 w7 @9 @. L
  21. void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
    2 H9 D' v( K  f2 ]
  22. u8 IIC_Read_One_Byte(u8 daddr,u8 addr);, v: a8 H9 A& t5 i' M+ ]
  23. #endif
复制代码
- z' Z, k) D' E  I) B
i2c.c
  1. #include "myiic.h"
    4 P* }) u, v7 O
  2. #include "delay.h"& T' R! }# \3 v" c! q* k- c* ?
  3. //初始化IIC  A, a# _8 u2 o! C- U% ?9 h
  4. void IIC_Init(void)9 g! b7 ?6 t8 s
  5. {                                         * U% i2 _" {1 p3 W, y
  6.         GPIO_InitTypeDef GPIO_InitStructure;
    4 j4 m/ ^- q) S' m1 g7 [
  7.         RCC_APB2PeriphClockCmd(        RCC_APB2Periph_GPIOB, ENABLE );        //使能GPIOB时钟
    ! `$ {$ D% X+ c; D+ I8 k
  8.          6 |1 t- D5 m( w  ~( i/ q
  9.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;- {, S5 K4 ]7 V* o" _6 M& X
  10.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出7 @5 w1 l7 d$ f5 g  b
  11.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    ! A) e* O: T9 u- O0 h; ]3 D
  12.         GPIO_Init(GPIOB, &GPIO_InitStructure);2 Q4 [  b1 Z( ?
  13.         GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7);         //PB6,PB7 输出高! U8 |9 z: p5 b3 w3 ]: P
  14. }
    " }$ j# w  L! H: x* G
  15. //产生IIC起始信号6 l. [* O' O) d* n  n4 _; ^
  16. void IIC_Start(void)0 W5 |  P* ]! r* q0 x1 w& l
  17. {' O1 Z/ Y$ h2 I, l* |, ~; D4 u6 D
  18.         SDA_OUT(); //sda线输出
    + y4 Q* N5 [9 J
  19.         IIC_SDA=1;                  
    4 A0 L9 W) w3 J" @
  20.         IIC_SCL=1;0 ^, S9 B. D7 d* H, B
  21.         delay_us(4);5 F* J7 O7 F6 q% G4 {
  22.         IIC_SDA=0;//START:when CLK is high,DATA change form high to low ( p, z! R' x% @: g2 ^; s
  23.         delay_us(4);
    : q6 d" @' r; {$ H+ p9 H
  24.         IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
    ( e2 l3 y0 P, s8 _+ u  l# p) u1 R
  25. }         1 Y: Q' }5 \+ o, I% K
  26. //产生IIC停止信号0 v/ M  A% U9 x7 |9 m6 z
  27. void IIC_Stop(void)
    & R. d$ }* @0 d/ g
  28. {! P+ Q. h4 f% V4 U, L& @: z
  29.         SDA_OUT();//sda线输出
    ' {8 i3 r* U: y' H  B
  30.         IIC_SCL=0;; E+ P) t8 y3 D" Z
  31.         IIC_SDA=0;//STOP:when CLK is high DATA change form low to high& m" q* {3 k  k
  32.         delay_us(4);, v; G  l, y0 f1 w
  33.         IIC_SCL=1;
    4 ~" x1 j/ u: U4 [
  34.         IIC_SDA=1;//发送I2C总线结束信号
    1 D7 q6 Z* |6 U' R6 _5 A9 @% v
  35.         delay_us(4);                                                                
    5 \6 O! T1 @6 w% U( m* @3 M
  36. }: Z# O7 y1 @3 h7 ^4 s
  37. //等待应答信号到来# X8 I4 J) B3 @" y$ E3 r& J% N
  38. //返回值:1,接收应答失败
    / _8 k) [+ R4 b3 p$ n
  39. // 0,接收应答成功) p+ Z, ~- n5 F9 X+ o
  40. u8 IIC_Wait_Ack(void)
    ; `0 i/ Q3 h* U$ @9 @( c
  41. {
    . g/ Z7 _" K" P. a
  42.         u8 ucErrTime=0;
    8 K. Y* u, m$ |2 l# V) a* f5 H
  43.         SDA_IN(); //SDA设置为输入
    . x' e9 A2 w& t
  44.         IIC_SDA=1;delay_us(1);         
    9 l8 j( g; O$ b
  45.         IIC_SCL=1;delay_us(1);         
    ! Q; v# a2 K# s" _3 I5 E
  46.         while(READ_SDA)& @# \' ~; [8 {4 }9 \  f
  47.         {$ m. w. H, O( [8 c  f8 J: {) w! L  @
  48.                 ucErrTime++;
    # H6 B' y4 O5 z1 P' [+ z6 f
  49.                 if(ucErrTime>250)+ A$ V% ]; o) n4 a
  50.                 {* p# P$ ]$ {3 s) A: ^) P7 f; I
  51.                         IIC_Stop();- ~( F7 c7 ?8 y& e- H1 c6 c
  52.                         return 1;
    # t6 k; h& ~) O7 Z
  53.                 }
    - d$ d4 \9 e" C  `- _. i
  54.         }
    4 m3 B8 t; ^7 I
  55.         IIC_SCL=0;//时钟输出0          
    . q: @0 I- o/ D" v9 h5 r  Q) ?  ]
  56.         return 0;
    4 K9 \. i8 r8 ?6 H/ L: ~; v; K
  57. } 0 _) I% Q5 r6 Y
  58. //产生ACK应答
    1 u' r) G* d1 X4 m% X6 o+ m
  59. void IIC_Ack(void)( g' d+ l7 z6 N% u1 x7 V+ x
  60. {" Z9 e- g# P9 U! u4 J5 r; d
  61.         IIC_SCL=0;1 w7 t7 \3 `, x+ c
  62.         SDA_OUT();
    2 `$ `, {7 N: p" m5 U
  63.         IIC_SDA=0;2 b) v1 X# ^  Y  R/ k+ H
  64.         delay_us(2);
    : A7 e$ i  r% B, `9 q/ d2 ]4 }7 |! B
  65.         IIC_SCL=1;1 }) I; c' N; ~! q: o- {# d3 W+ \
  66.         delay_us(2);
    8 o! |  S' h" N$ C! T
  67.         IIC_SCL=0;
    * `, C) D+ o1 O" O
  68. }
    7 G+ u) Y7 V+ r! b( u0 ^0 z
  69. //不产生ACK应答                 ! o) y5 Z' G+ h; U4 F. h
  70. void IIC_NAck(void)( H  o" v2 U; S, _/ {
  71. {3 w" h, K0 C( y2 |. c$ J2 ?
  72.         IIC_SCL=0;! X: e) m7 t: K& r8 X9 h
  73.         SDA_OUT();
    ( X7 U9 |3 ~$ f9 S  I, r+ T
  74.         IIC_SDA=1;
    . G; F( M( I1 ~" h/ [
  75.         delay_us(2);
    " ?! ]; I7 P- O* U
  76.         IIC_SCL=1;
    5 I" ~# B) l/ ^6 \
  77.         delay_us(2);
    , K/ b) w/ G% j4 {! f9 Z) X
  78.         IIC_SCL=0;5 b2 F+ I% H+ T4 c- t
  79. }                                                                          $ R) K) b0 ?3 B7 E3 `
  80. //IIC发送一个字节9 [( V5 O, q) X+ [( v$ i
  81. //返回从机有无应答1 L; e& X$ ~$ R/ n0 a
  82. //1,有应答" Y0 t9 F% n! {3 d! E6 r
  83. //0,无应答                         
    * V1 O; {- L: R) a
  84. void IIC_Send_Byte(u8 txd)/ Q2 `- @) x% o  y( C: `1 Z3 f0 b
  85. {
    1 R- s0 k* ?2 X; `5 J2 Z
  86. u8 t; , ^. ^. V- F% D0 G
  87.         SDA_OUT();          
    * z9 l: z3 f5 |$ u/ s4 G9 W
  88. IIC_SCL=0;//拉低时钟开始数据传输3 {) X6 \& o6 m9 M
  89. for(t=0;t<8;t++)
    ! T& {* k: Y; J$ g) n
  90. { 1 Y" n6 S. d+ L2 {* W
  91. //IIC_SDA=(txd&0x80)>>7;/ s2 n+ [: t1 w2 P+ w9 r1 Q9 A: h2 x
  92.                 if((txd&0x80)>>7)
    / A0 f# B4 p/ j. l7 D9 d, L- K
  93.                         IIC_SDA=1;% ~$ D/ @' i3 y; F) d
  94.                 else
    : _7 B6 C, _: e9 h2 `
  95.                         IIC_SDA=0;; ]8 g: c/ [, o$ x. j: n& t) c# F
  96.                 txd<<=1;          
    4 v( n" R2 w/ t) P, ^8 z
  97.                 delay_us(2); ' s# d# j+ {9 l8 V$ E
  98.                 IIC_SCL=1;: ]' Z# i% _& i' q+ d  a
  99.                 delay_us(2); 0 d( k0 @/ u/ g
  100.                 IIC_SCL=0;        % {3 d$ P* L7 \
  101.                 delay_us(2);
    8 g1 A4 @, S4 {, }- T7 ?
  102. }         
    : }2 {8 i5 K: C7 i; o; r6 r
  103. }          ( Y6 w- {! q  K& B5 l% L0 `- ~
  104. //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
    / i+ k( H/ ?! I5 a+ q. Z4 L  M
  105. u8 IIC_Read_Byte(unsigned char ack)
    ! K. E+ Q4 Q, O& V9 w% j/ k% g
  106. {
    / k' K9 M8 X* [/ O4 |
  107.         unsigned char i,receive=0;) {8 x2 w+ T6 j, H6 E# ]* G
  108.         SDA_IN();//SDA设置为输入% u7 l* u& l3 c- U8 K
  109. for(i=0;i<8;i++ )
    $ a2 n+ D6 b# @0 i. H5 G: o
  110.         {
    ' f1 D- E: q2 T% l9 D" R
  111. IIC_SCL=0; 8 T$ i0 [# p/ x+ G) x
  112. delay_us(2);) s# r/ M+ ?" c" e' O  q/ x
  113.                 IIC_SCL=1;
    6 P! Z$ E/ \. P
  114. receive<<=1;. M/ K+ e4 D$ p9 a
  115. if(READ_SDA)receive++;
    % f3 J( {0 v+ F  Q6 O$ d6 Y
  116.                 delay_us(1); + R- D/ u" g! ?
  117. }                                         
    % u. U$ t0 k) \8 i1 |8 s, ^' H- e
  118. if (!ack)
    ; q7 i& \. T& Y% g+ `: K
  119. IIC_NAck();//发送nACK
    2 Y0 @$ }! n0 C8 @# q. a) o
  120. else. `/ t4 {8 N6 t" d
  121. IIC_Ack(); //发送ACK 7 o  Q# L3 [7 [8 [( E
  122. return receive;9 O; h. P! e3 h0 T$ O) M% `/ H. L, T; D
  123. }
复制代码

+ W( U& J' Q6 W: o" T3 p' l) R转载自:电源研发精英圈
7 l4 b8 N, Y: b0 n$ R& J如有侵权请联系删除$ y2 s; E* M# ?: e

- n% q' A9 r; P/ @7 U3 }/ n0 i# C0 O- M
收藏 评论0 发布时间:2023-10-19 16:29

举报

0个回答

所属标签

相似技术帖

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