|
I2C两线式串行总线通讯协议,它是由飞利浦开发的,主要用于连接微控制器及其外围设备之间,它是由数据线SDA和信号线SCL构成的,可发送和接收数据即在MUC和I2C设备之间,I2C和I2C之间进行全双工信号传输,高速I2C总线一般可达到400kbps。一般我们也称为TWI接口。 * k9 Y! C/ h0 V* x
I2C支持多主机模式: ! B( ?9 a9 l% e& [ M( E
4 f' o3 I- _! E8 g3 f# K
. V, F& z$ j# [9 l8 \
3 D+ v( y3 x+ L, A4 Q0 Y: A0 m4 f
即在这个主线上可以挂载n个I2C外设。 " ?( g) S) U# @. n( c
对于I2C协议,其实也很简单,不要想的那么复杂,其实就是电平的变换。我们可以人为的分为6个部分
' K1 `3 g& g, \( S" Z/ w
整体时序图: 3 U* g% O8 r9 R; l2 D1 e
' u$ b- c* m" K
+ D! y1 y" h Q; t/ ?7 ]. r7 G- |( ?- v9 m8 {( r; x
各状态: 空闲状态
3 G' b6 f: V) r5 b& \
* r# C2 }* N1 V. n8 T2 L
* j- G5 v1 B4 K- N) {6 r' I0 R" b1 i
; }( ?2 s' H9 o; n! m; O; K
' e/ t o: Q, X( q# I& h. P5 lI2C总线的SCK和SDA两个线同时处于高电平的时候,规定为总线的空闲状态,这个就是由总线上的上拉电阻把电平拉高的。 $ p5 x/ }9 ^, v4 a) W
- d" d5 D5 A! R; r; R" T: K! i9 g: |
0 ]" _$ C1 `, N& a! k1 t1 O8 P* d! E" S2 U6 Z3 j/ ]7 `6 a3 q
当SCL为高电平期间,SDA由高电平变成低电平,即为起始信号。启动信号是一种电平跳变时序信号,不是一个电平信号。 ' Y5 c- ]8 \* V+ ]1 o
停止信号 1 f) A# |" q1 ?0 r/ w# O
当SCL为高电平期间,SDA由低电平变为高电平,即为停止信号。停止信号也是一种电平跳变时序信号,不是一个电平信号。
% j* x" a' h7 \& V
应答信号 3 O' R% t& |& X5 n6 w+ p5 ~/ J4 O
. y$ E) v. ~) m! R1 i( M6 [4 x3 ^& M1 ]
9 J9 O9 L: a5 d8 o+ [
3 a$ G# {4 h9 B& G发送器每发送一个字节(8bit)数据,就在时钟脉冲(SCL)9期间释放数据线(SDA),再由接收器来反馈一个应答信号,应答信号为低电平的时候,规定为有效应答位(ACK:应答位),表明接收器已经成功的接收了该字节,应答信号为高电平时,规定为非应答位(NACK:非应答位),表示接收器没有成功的接收该字节。
/ F0 t' v+ n6 W+ D5 G2 k4 X
对于反馈有效应答位(ACK):接收器在第9个时钟脉冲之前的低电平期间将SDA拉低,并且保证在该时钟的高电平期间,SDA为稳定的低电平。大家主要看图,看看是不是这样的。 : [# b# t% b9 U' V: S
要是接收器是主控器,那么它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据的发送,并且释放SDA线,以便主控接收器发送一个停止信号。 3 C) j5 X+ p0 w$ _1 @4 v
数据的有效性
* W; X# z$ M! b8 ^9 w$ G 3 U8 R1 N/ F7 m/ \
0 m9 C/ @$ ^' `. u
8 E0 o. `. W& _$ I/ y/ x# c
) h+ Q. U# y, R6 H$ t; M6 w5 }: I$ m, d! J- D$ R( u+ a
时钟信号(SCL)为高电平期间,数据线上的数据必须保持稳定,只有在时钟信号(SCL)为低电平期间,数据线上的高电平或者低电平才能发生变化。 ; u! d g" }8 I
数据必须在时钟信号(SCL)的上升沿到来之前就准备好,并且在数据信号的下降沿来到之前必须稳定。
g! I. d: p+ [
数据的传输
0 H- x5 U1 c- h- @& O8 C" o( Y
在SDA上的每一个位的数据的传输都需要一个时钟脉冲,即在SCL串行时钟的配合下,SDA上逐位的串行发送每一位数据。数据位的传输是边沿触发。
. f6 b/ L4 |! n; D 示例代码讲解
4 b X# B5 _0 S7 L0 b4 B
: q( T# ~: @2 K1 v# h# F* F
1 o; b c9 s5 \! o1 {" T/ | f9 ~5 O R
+ ]" n0 L- z& N: R0 `
8 d; P, Y( U4 @
其实就是对两个线的初始化,我这里使用的是PA6和PA7,开始都设置为输出,中途会改变PA7的输入输出属性,在空闲状态,我们知道SCL和SDA是被拉高的,所以这个地方我们给高电平。 ( y, u2 k' R& s1 O
产生IIC起始信号 ' F! p$ N6 x1 p* q! a& k/ J
4 H5 S1 |7 l% r5 U5 _) W% J: T B
1 o) J& `4 H: ^3 u
, X7 ]- T% J0 T0 v v3 e" W9 R! J
# W, @! ] B- u1 ]9 Q
0 a4 P" [# i' V' J5 Q0 j4 n" k& \8 m' p' n
将SDA线设置为输出,然后SDA和SCL都设置为高电平,延迟4us,然后将SDA拉低,延迟4us,最后将SCL拉低。这其实就是协议规定的动作了。
0 \; k3 E7 T7 q) ]4 C. P
产生IIC停止信号 ; g! E% e& b9 [$ r
* \! B& ]* \1 R# d
, t5 q- f' p1 x% [" w3 g" P" J
( w- O. `0 C# {* Y4 ~! A( \; i$ r. Q1 K7 O% Y: f
' ~% u3 @3 N) F' y' j! a同样的道理,和协议的时序保持一致就好了。
; K# X4 Z( D3 T5 c. [% c. `! [$ h! i. v
等待应答信号到来
0 u2 O1 L9 L; t$ W; a1 r
% _/ W# [! n! y) K( K6 @2 ?/ G. m+ l5 t0 d
/ q+ q+ k8 ^* ]$ a, m! y7 `5 F: J' Y( X0 n
首先我们需要把SDA设置为输入,因为接收方要给发射方返回一个应答信号的。就是在SCL第9个高电平的时候,释放信号线,先拉高,然后持续等待,是不是有应答信号返回,其实就是返回一个低电平,所以我们一直在检测READ_SDA这个电平,持续一段时间,要是没有返回的话,我们认为超时了,就直接停止协议了, 2 z" d' k# i1 s7 q; d' a9 m
产生应答信号 6 v2 j! r/ p# G3 ^3 ?9 [. @
7 E1 {8 l; f1 p
7 h7 A6 M6 M1 z
% O) Q, J3 p$ u8 X- }
; O* s$ ], F1 [即在第9个时钟周期内,SDA都为低电平,为应答 0 F0 U4 h" q9 ?1 _
不产生应答信号 8 H: w+ ?. H) o% Q5 _
% d `5 @3 k; i' p A7 }, T! g0 f
s3 C4 ?6 v4 \1 M( }$ y* ?
- p' G' X; q0 b" f5 F6 l
. w' n# y, ?0 B) r# x
; Q+ } v/ ~' o, l9 K& q即在第9个时钟周期内,SDA都为高电平,为不应答 ) p+ e7 Y1 t- Y+ G' v: H3 z8 U
IIC发送一个字节 7 s5 X/ @ c# ]* S. s2 d9 R2 v
: M2 ~& p! y# E) q3 K
G3 ~' I! o1 O6 i
% h- q. ~: A3 G
; @9 g8 T/ ~" u& ~# d2 E/ \/ g- E0 _
! Q0 F3 I c8 l: N. `5 Y# _/ b* Y* f8 ] }
发送数据,SDA设置为输出。SCL拉低,SDA准备。 做一个8次循环,拿出1byte的数据,将你发送的数据和0x80作与运算,拿出最高位,然后右移7位,将这个数据放到最低位,这个数据要是1的话,那么SDA输出一个高电平,要是与后的结果为低电平的话,那么SDA输出一个低电平。这都属于准备发送信号阶段。 然后SCL拉高,完成数据的发送,然后SCL拉低,此时SDA也就可以拉低了,接着开始次高位的传输,直到传输完成。 9 r, M1 c8 t! ?" s
读取数据 / ]0 G5 z, `1 T" E( ?# F4 H
/ j s: V' d* x0 W; K. t
( w% A& I+ E# u: U( A" Q# I. `% j* R& ~0 B# L
1 g0 h' N8 F, ]* E; G6 d
读取数据,SDA要设置为输入了。SCL开始为低电平,然后SCL为高电平,我们开始读SDA上的数据,然后左移数据,将读取的数据放在低位。然后检测一下有没有应答。 其实基本思路就是这样了。我把源码附上: + e! e# E4 X1 p+ t' M2 X; L5 ]. c
i2c.h - #ifndef __MYIIC_H% u, u) `8 Y8 e. O8 |
- #define __MYIIC_H. g- y$ z1 J+ k" c
- #include "sys.h") e: t5 |4 E! V2 i
- #include "stm32f10x_gpio.h"; O5 I; w. w: G; u+ U
- //IO方向设置* ~' j# H8 }* \1 m
- #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}/ k5 D, V3 e& {( a6 T
- #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
I/ s2 S& `5 z4 h3 ]) b9 e | - //IO操作函数, I6 `! S% J* m' S- ]4 ^
- #define IIC_SCL PBout(6) //SCL
; p! z' y6 W- G. |/ C: z6 w - #define IIC_SDA PBout(7) //SDA
! [& m9 ?: j# ]/ z( m - #define READ_SDA PBin(7) //输入SDA
. v# S4 l& q* T. ^5 v- ]4 o - //IIC所有操作函数$ O( q0 q/ A# C. u
- void IIC_Init(void); //初始化IIC的IO口
' \+ {( v7 w) ]( q7 U1 k* N5 ] - void IIC_Start(void); //发送IIC开始信号
# ~! Y! ^. ^) t% e- d - void IIC_Stop(void); //发送IIC停止信号
( e" q/ W% \# o/ Y4 H& `( A+ A4 @8 N - void IIC_Send_Byte(u8 txd); //IIC发送一个字节
1 ]8 X& _7 t% l( N8 o M0 B2 G - u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
8 L, C; d: D& A: y6 g - u8 IIC_Wait_Ack(void); //IIC等待ACK信号
- `3 Z0 t) t. t) H - void IIC_Ack(void); //IIC发送ACK信号9 m& v; Q) d7 T" \9 r
- void IIC_NAck(void); //IIC不发送ACK信号8 V$ M7 g: Y; ]6 ~3 Q+ H+ y, p" K
- void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
" V. P; T# E6 s4 t- z - u8 IIC_Read_One_Byte(u8 daddr,u8 addr);
" |) [) ^0 P/ L$ S5 n1 n3 ~ - #endif
复制代码 $ ]0 E& j% }% ?0 N9 r* a
i2c.c - #include "myiic.h"
0 |8 R2 M4 L1 V - #include "delay.h") Y- w4 M# l" }/ G, j6 g" D
- //初始化IIC6 w, v( f j% T) S( I. b& U
- void IIC_Init(void)6 m9 t6 ~% }4 l9 ?% h6 o* y
- { 4 y. k8 M T: m2 C3 T6 l
- GPIO_InitTypeDef GPIO_InitStructure; u& V0 a2 X) S0 P+ N# H
- RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //使能GPIOB时钟5 [' q. ?& Q4 \3 |& s% o
-
: F; k, [7 }0 j! \' p. h1 x3 Y - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;3 j9 M- k# H' `- Y7 r3 b8 h
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出9 X( [ x7 p6 E9 E2 j' u
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
9 N; P2 W, f2 c$ Y8 o; M - GPIO_Init(GPIOB, &GPIO_InitStructure);
- L' [* j' S( h+ I% Q- s - GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 输出高
" v* V2 L# u1 g) | - }
; x$ p* ~! H. {: ^ - //产生IIC起始信号, u+ V) n1 n; d. u& q" ^# k
- void IIC_Start(void)
" \( I0 a5 B) { - {
( `, W8 ?0 G6 E! Y - SDA_OUT(); //sda线输出 e, A2 ~% D6 o' y2 e/ U G
- IIC_SDA=1; * `2 t5 B8 \0 ]' \
- IIC_SCL=1;
$ [0 ^1 Y- w+ h; R& Y - delay_us(4);
' @' _* j- `! Q" ]' O - IIC_SDA=0;//START:when CLK is high,DATA change form high to low 8 q& h7 V. r5 B: d9 J
- delay_us(4);& I1 X) d' u: K6 v, X5 U) u
- IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
* Y& l( Q1 D$ f1 _ - } + R$ q J1 ^( d, P# k( |8 o
- //产生IIC停止信号
, ^! {% R4 Q5 Q- K - void IIC_Stop(void). n- L3 C; h) X" U3 \
- {
8 n' W; J2 n q3 Y - SDA_OUT();//sda线输出. b+ D" _$ y7 ~! m& ~, q
- IIC_SCL=0;+ |/ K5 o4 |$ B, R2 Y
- IIC_SDA=0;//STOP:when CLK is high DATA change form low to high2 n# Y8 C y+ z+ |, w5 _0 G! z
- delay_us(4);
- [" Z- X+ m( Q5 C* V6 S - IIC_SCL=1; 5 b9 Z8 | c9 O7 {9 X8 T' z. S* S
- IIC_SDA=1;//发送I2C总线结束信号; d6 i; T2 y2 g8 L
- delay_us(4);
( x* }2 x" U' c6 x; r& Z - }
; j3 J$ g1 I+ i/ r) W F - //等待应答信号到来% R: Y& h, \ C& Z- B
- //返回值:1,接收应答失败
* E. p6 Q. i/ C9 b; b - // 0,接收应答成功& U" L. b* Z/ D m- D* _3 J1 n
- u8 IIC_Wait_Ack(void)
' T0 Q# x- r& k5 q9 ~) J - {1 l0 }8 [. F. [! A/ Y) l1 m$ B
- u8 ucErrTime=0;
2 N) B* v q9 S3 w6 }+ q. r* a2 K - SDA_IN(); //SDA设置为输入 4 V! V) S! E7 l' ?; B! g4 _
- IIC_SDA=1;delay_us(1); 1 S" f- D' y0 X# Y& v5 s
- IIC_SCL=1;delay_us(1); ) v- w% X! P0 r/ V: J( r* G: m) q
- while(READ_SDA)
0 H6 I- }- U1 n4 `! R n - {+ ^' t. |; T) _" _" r7 T$ S4 |: Z
- ucErrTime++;% Z" v, B' g# k6 E) D+ @
- if(ucErrTime>250)
* l( N4 Z. W# p3 b: j - {2 p# g( s% Y7 r% ]5 W/ y8 N
- IIC_Stop();
; J- @9 P# O' S7 _8 N5 F6 E - return 1;
/ c7 d! b! d, N9 ? - }
7 b: Z) }0 b) V4 i+ Q7 Y6 U4 X - }1 W1 H* }( ~! P0 f
- IIC_SCL=0;//时钟输出0 4 F. l4 u$ R& n3 Y3 `4 K/ a
- return 0;
3 O" [. `/ N0 [* f' i/ C1 R - } . H$ C9 C P6 S3 n) w
- //产生ACK应答
8 A/ U& O K2 C+ D5 I - void IIC_Ack(void)# N6 ?3 M4 c; @2 o
- {
. V1 d3 M* h7 I! H! X' Y - IIC_SCL=0;
: A4 Z8 n9 E7 {& |6 d# G( z - SDA_OUT();3 h. T# F$ G5 }- T; t
- IIC_SDA=0;6 i- p9 C* n3 o* b+ I
- delay_us(2);
5 Z4 B& Y2 J* t- P - IIC_SCL=1;) ]/ K4 x: B0 i+ W! s: D& Q
- delay_us(2);. k# n0 e: g* r0 }0 h
- IIC_SCL=0;
?& E- B1 F4 ^; g* e3 A, L3 M2 Y6 Z - }
; u; e* c8 Z3 L) w. B: S - //不产生ACK应答
) v. |8 q1 W2 l - void IIC_NAck(void)
& U3 F$ N d* Q& v - {2 z' h. V2 T3 {) y" ?- m
- IIC_SCL=0;; ?7 n" @9 p L
- SDA_OUT();, N3 G& b( f4 I( N- e3 p
- IIC_SDA=1;6 J2 O8 ^ R, N, q& N
- delay_us(2);# M' R0 U% s. _) d5 ]4 |
- IIC_SCL=1;
1 X" E5 c" n% } - delay_us(2);
! m9 c, }' M6 ~- j6 I - IIC_SCL=0;
% G: T q5 L4 g - } - E% h) U8 n! P$ Z6 u) S
- //IIC发送一个字节0 A# l8 c- a8 s* Y/ t- v
- //返回从机有无应答
5 y9 ?9 q* ?: k - //1,有应答8 p. K# t$ n$ G% P% h, I' P
- //0,无应答
: _$ K$ w+ D/ g: W - void IIC_Send_Byte(u8 txd)
- ]8 p: D Q! R) g - { ( W: S5 m/ M8 U0 a% ^4 T
- u8 t;
, [& U2 o, O$ D' ^, V - SDA_OUT();
& ]" b' E) i' N8 ^1 W, g6 y9 y0 |5 t - IIC_SCL=0;//拉低时钟开始数据传输
% W) _6 \. S" k5 E, a$ D - for(t=0;t<8;t++)" y9 }# f# g! f6 P
- {
7 \& I5 h+ k( P- l! t# U3 |' N - //IIC_SDA=(txd&0x80)>>7;$ t$ O5 N0 {3 N; Q: d& W
- if((txd&0x80)>>7)9 y- _+ @+ a- j, L) f9 b4 H
- IIC_SDA=1;; s4 Y- x1 |. w9 a g* x8 X
- else
8 l# j/ r( L# v3 e# s% W* a - IIC_SDA=0;
; b3 j( O# Y& w6 F; n - txd<<=1; u* L2 b1 U, @/ @0 h
- delay_us(2); - h% `% |9 W& M" ]. t& D) u( Y( U2 C
- IIC_SCL=1;) l5 M( Z( k2 Z( j7 d
- delay_us(2); / V# a+ y7 V4 D2 E* B L8 W( g, Y
- IIC_SCL=0;
; Z/ F. B& `: n) n3 P3 B - delay_us(2);0 y% ~! W9 U4 l- ?2 ?% J, q
- }
% [" _4 s0 \0 S - }
5 J$ @* Z- C6 J2 f* h) h( O - //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
0 U6 e, a) p# Q2 b: ^: v: q - u8 IIC_Read_Byte(unsigned char ack)
0 B) W% u0 I1 v; _4 E - {
# B8 }. d: A0 N7 F - unsigned char i,receive=0;/ o( V- ]5 u' O( _) d$ Z- S
- SDA_IN();//SDA设置为输入
" @' _: h7 l0 q! q - for(i=0;i<8;i++ )4 q0 p4 f. d6 t+ O+ v
- {; X! {# }5 A6 o5 Y: z
- IIC_SCL=0; 5 c, p3 B/ c+ j. ~5 d# c
- delay_us(2);
, _4 s) X& J+ u+ h5 m( J, p g9 O - IIC_SCL=1;/ Q# O, r& j2 L7 D6 ?0 }$ W/ k! J
- receive<<=1;
# n' |* t2 v4 O! v/ J. X - if(READ_SDA)receive++; 2 h' l: L/ r. {% W2 c
- delay_us(1);
. Z* _0 [/ G3 b3 b - } 0 ]- V5 T8 v; ?; M9 a8 p5 l' R% s
- if (!ack)! \ _( g+ P) c7 b8 x
- IIC_NAck();//发送nACK4 [% J7 b$ h! {) o: R
- else7 K* K# x- z4 M, d0 ^- ]8 g
- IIC_Ack(); //发送ACK
5 d$ L0 [0 \( J7 n- ^. i - return receive;
, X8 [3 f* a% b* C' x; H - }
复制代码
4 O, i0 s- X& |) T& v; E% K转载自:电源研发精英圈4 M5 K# N6 R: z$ _
如有侵权请联系删除2 b: q+ ^2 p5 d# V8 X7 a; E
. i! c3 ~# F$ t& K
' i: d* W/ T. E$ F+ b0 Y, S4 |3 A |