|
I2C两线式串行总线通讯协议,它是由飞利浦开发的,主要用于连接微控制器及其外围设备之间,它是由数据线SDA和信号线SCL构成的,可发送和接收数据即在MUC和I2C设备之间,I2C和I2C之间进行全双工信号传输,高速I2C总线一般可达到400kbps。一般我们也称为TWI接口。
& U5 f6 \3 ?1 |# i4 y1 Y
I2C支持多主机模式: , M+ y; B% U, l' O
4 l2 @2 w9 L2 q# k" W( ?
8 R, H, L! M/ ^& C6 y+ |- q
8 K! t) ]5 N- n
即在这个主线上可以挂载n个I2C外设。 8 V0 w. X. r. L! q5 S, C. F& \4 d
对于I2C协议,其实也很简单,不要想的那么复杂,其实就是电平的变换。我们可以人为的分为6个部分
1 b5 B* E: I( W5 r6 T# x
整体时序图: 5 J3 ]! W$ p6 b& Z5 g0 }& `
( O* R: s' }" m9 o5 N! J9 F$ K, e
# w7 O% r) m5 J- w/ S
) J. n0 Q$ r! j2 s. J. {* @, X各状态: 空闲状态 + c! f$ A" p. a! Q$ g# K" k5 D
$ T9 g5 ?# i" L6 u$ W; L
, @( \; `) E6 B# m8 B
' g2 o! i, O- [/ G$ R2 L3 ~# R* v
8 R: q* D# [. f$ t6 pI2C总线的SCK和SDA两个线同时处于高电平的时候,规定为总线的空闲状态,这个就是由总线上的上拉电阻把电平拉高的。 " @$ W, Z U$ E; }' ^
4 a; N, F' d; k: ]& Y! S: ~! J
& |1 T5 k- ~8 c; E( f, }- J8 z2 k) V
当SCL为高电平期间,SDA由高电平变成低电平,即为起始信号。启动信号是一种电平跳变时序信号,不是一个电平信号。 6 X/ H' B. j* K) s/ D2 F
停止信号 $ I+ G' v- l' E+ T$ x
当SCL为高电平期间,SDA由低电平变为高电平,即为停止信号。停止信号也是一种电平跳变时序信号,不是一个电平信号。 1 Z$ ^3 `- {/ C1 D. [
应答信号
0 G6 s4 g. d# E3 J& G8 P
' H1 L4 t& d; S1 G: Z8 R: ?' Y
4 B9 c# }6 a) t9 M
; D3 y1 A* ?* Q1 b) }. a
- m2 J1 Z# k& v' f2 p3 `, E
发送器每发送一个字节(8bit)数据,就在时钟脉冲(SCL)9期间释放数据线(SDA),再由接收器来反馈一个应答信号,应答信号为低电平的时候,规定为有效应答位(ACK:应答位),表明接收器已经成功的接收了该字节,应答信号为高电平时,规定为非应答位(NACK:非应答位),表示接收器没有成功的接收该字节。 9 R( W, ~; G& q8 }, W1 L1 A8 | g2 J
对于反馈有效应答位(ACK):接收器在第9个时钟脉冲之前的低电平期间将SDA拉低,并且保证在该时钟的高电平期间,SDA为稳定的低电平。大家主要看图,看看是不是这样的。
- A: Z; X8 M" e0 t% b1 a
要是接收器是主控器,那么它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据的发送,并且释放SDA线,以便主控接收器发送一个停止信号。
4 d7 b; [& j! o2 h- | k
数据的有效性
% `) Z3 g- f$ P! {) T
$ Z. V7 X7 w5 g9 ]$ [
: M. p+ k! t" N: Z4 j9 k9 a
! ]2 Z) I9 J7 H# _ \1 A5 ?
# U; c) r7 d @& M5 G( ^" v0 ~/ N4 q
时钟信号(SCL)为高电平期间,数据线上的数据必须保持稳定,只有在时钟信号(SCL)为低电平期间,数据线上的高电平或者低电平才能发生变化。 9 I% T+ P7 K6 S* N n1 r+ R6 L
数据必须在时钟信号(SCL)的上升沿到来之前就准备好,并且在数据信号的下降沿来到之前必须稳定。
# y& g7 v1 E* b* x& y- R3 X
数据的传输
4 h. w3 c$ v' y* O( B
在SDA上的每一个位的数据的传输都需要一个时钟脉冲,即在SCL串行时钟的配合下,SDA上逐位的串行发送每一位数据。数据位的传输是边沿触发。 ; q* H2 t* K6 |% x' E: n6 ]
示例代码讲解初始化IIC
n, ?; s* C b# T) u! ~: f 2 _% j' R5 m2 K" i* q: i# F/ }
2 L: ]: E- S/ N& v
9 J0 y: \" p1 k. ^
) i2 u$ i4 M+ t1 V
, N) q2 s2 G, m5 D2 g) l
其实就是对两个线的初始化,我这里使用的是PA6和PA7,开始都设置为输出,中途会改变PA7的输入输出属性,在空闲状态,我们知道SCL和SDA是被拉高的,所以这个地方我们给高电平。
2 l. F3 Q9 r8 {3 d
产生IIC起始信号
( T$ ?2 M3 ]# U+ M
2 q& @8 M. e" Y0 d; @+ k, y q1 a
: v% n2 |& _+ Y) M8 x) G
* k9 s' D( r4 B; f
8 G# n, Z5 q8 U3 S. w: Y
# c I4 ` J' Q( O. O6 p1 Q- y
5 a0 @- t* a) q3 {# C7 G6 Q将SDA线设置为输出,然后SDA和SCL都设置为高电平,延迟4us,然后将SDA拉低,延迟4us,最后将SCL拉低。这其实就是协议规定的动作了。
: R% A+ R0 _: e
产生IIC停止信号
: M" _4 H* ?. r2 y4 j
# y+ ^9 h R8 S j
2 W. g6 y1 [* J5 V6 P7 h/ D
( N# F6 O: z( y; W s6 J) f5 t/ s6 s; D3 R" f7 Y2 @: H( C
9 ~1 j. p; Z, `: g2 t
同样的道理,和协议的时序保持一致就好了。 + O1 k- L; Q& `; X y: F
等待应答信号到来
* h8 H2 D. ?, i }# F }. f/ a6 ^ ( T5 ^3 _2 Y; d4 W% s
H0 p. e1 B8 R: d7 _
+ M; _# _8 V: c4 {" w. A$ a. d# @, x1 I e
" n1 b0 {4 D5 L* L5 d4 D+ P
首先我们需要把SDA设置为输入,因为接收方要给发射方返回一个应答信号的。就是在SCL第9个高电平的时候,释放信号线,先拉高,然后持续等待,是不是有应答信号返回,其实就是返回一个低电平,所以我们一直在检测READ_SDA这个电平,持续一段时间,要是没有返回的话,我们认为超时了,就直接停止协议了, # E* g1 i9 e/ b/ V. ]) f
- Y) d) M- ], v, O
1 z: @0 ]3 e* U" R0 L! f+ y
% z4 F) ~. T# S0 k8 [+ Q; S3 t' U# X4 b$ X1 |
即在第9个时钟周期内,SDA都为低电平,为应答 ' A5 Q& l! s/ ^" }, J
不产生应答信号 ' A3 H! U; Y& }4 y; T
" V8 _, P* a7 c q
0 w( e+ ]' o6 W/ ?
2 A* d7 u0 }* ?( K2 ]* q
& l9 `! `3 |6 W3 @+ V* X4 h9 Q$ M* U' Y+ R- ^6 a/ l9 p+ |
即在第9个时钟周期内,SDA都为高电平,为不应答
0 U O0 M/ m7 p2 ~& h
IIC发送一个字节
6 N! J8 x6 y- \: z$ ~ 0 z. t: n% t5 h
7 q. M8 V& {# p
( V0 e$ R2 o6 J' |6 x
6 Q0 `/ F1 L( L' X. `8 {& `& g$ d( o4 Z
5 c) q1 n, U5 p! A1 ?7 X# g: Q
' X9 }) N% H3 S1 H# {发送数据,SDA设置为输出。SCL拉低,SDA准备。 做一个8次循环,拿出1byte的数据,将你发送的数据和0x80作与运算,拿出最高位,然后右移7位,将这个数据放到最低位,这个数据要是1的话,那么SDA输出一个高电平,要是与后的结果为低电平的话,那么SDA输出一个低电平。这都属于准备发送信号阶段。 然后SCL拉高,完成数据的发送,然后SCL拉低,此时SDA也就可以拉低了,接着开始次高位的传输,直到传输完成。 0 N# k: A* j P0 `+ r
读取数据
) g. X) ^( o/ Q: Y
, F/ p7 H i8 o
% |& y3 X) b; y( h
' E. {3 d& R7 \( B5 }3 w3 u& I
) s3 g) J( H( P+ C" |/ T& o读取数据,SDA要设置为输入了。SCL开始为低电平,然后SCL为高电平,我们开始读SDA上的数据,然后左移数据,将读取的数据放在低位。然后检测一下有没有应答。 其实基本思路就是这样了。我把源码附上:
9 j7 l" ^8 w/ n, J7 P6 y
i2c.h - #ifndef __MYIIC_H
/ }' [7 a: U- N! A5 H, v - #define __MYIIC_H
$ k( u9 h4 P# j& ? - #include "sys.h"% Y8 @: p' v, p4 }4 m0 d4 d
- #include "stm32f10x_gpio.h"1 z$ B& N' _2 s6 `) Y7 Z4 ~
- //IO方向设置
8 y" s- o0 u6 M j ^& w - #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}2 \2 s$ A# ^! @
- #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
% ], Y+ q( H8 } K, ^ x4 J - //IO操作函数* G* D7 h. I' `1 }+ S: D
- #define IIC_SCL PBout(6) //SCL8 w1 K Z( Y6 j& Z, w1 m
- #define IIC_SDA PBout(7) //SDA! }" I: ^5 z+ [* D; e
- #define READ_SDA PBin(7) //输入SDA7 p# n9 \: V4 d9 G6 A
- //IIC所有操作函数! S$ _3 ~- T% j" @( T. ], m% k0 v6 E
- void IIC_Init(void); //初始化IIC的IO口8 n* j" N+ x- O' ?% P( s9 ~
- void IIC_Start(void); //发送IIC开始信号. G& L7 O4 ~6 y' `6 s8 h9 F5 Q
- void IIC_Stop(void); //发送IIC停止信号
9 I4 F! Z& d! i+ P+ ?2 D% V" ^ - void IIC_Send_Byte(u8 txd); //IIC发送一个字节. [0 H, H9 m: A, X
- u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
& N! a+ G- \9 A4 B3 k/ r - u8 IIC_Wait_Ack(void); //IIC等待ACK信号( S1 r7 C7 ~+ Y, \/ S3 I! t% Q$ y
- void IIC_Ack(void); //IIC发送ACK信号
. W2 }( K Q( G" }1 T - void IIC_NAck(void); //IIC不发送ACK信号( W* G( t/ s: L6 l3 o, w6 Q
- void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);+ L% q! p2 d. i
- u8 IIC_Read_One_Byte(u8 daddr,u8 addr);8 V9 \6 O! P' W M
- #endif
复制代码
' f8 u! G# j" y. m* P: `4 \! Mi2c.c - #include "myiic.h"9 r! x1 C: ^/ i1 E! b
- #include "delay.h"; Y% j/ ?% P! R
- //初始化IIC. w$ u% ^3 j: Q4 x" V( T
- void IIC_Init(void)
) I# @$ d, U/ b3 Y2 p% N; M - { ! q, C5 k: L4 M
- GPIO_InitTypeDef GPIO_InitStructure;
* F! K: Y; f$ x8 K - RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //使能GPIOB时钟4 p7 O" ~& x2 U# |' K$ [
- , W* z) f; t6 K7 y6 j5 J3 }7 F
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;7 \9 m% C" m# Y; R
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出) _1 I$ V1 F% C2 z3 }# c
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- S+ p3 T1 }4 r/ Y" y1 p* x - GPIO_Init(GPIOB, &GPIO_InitStructure);/ [4 i. Y' X, G( ?# ~9 l+ t, d
- GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 输出高
' Q' x$ e) Y* s, b/ T( i! q s - }& J5 H! I: P x+ w, T$ \0 F
- //产生IIC起始信号
* n( a5 f9 }8 e- F+ Z; f8 ] - void IIC_Start(void)
# q2 ] U( w+ p4 Q( A$ Z- |. Q - {1 e: E q" p. O; B% U1 U
- SDA_OUT(); //sda线输出
" Q" M3 I, M1 e9 p" k% ^ - IIC_SDA=1;
$ D! _1 _3 {( V- W7 M6 ? - IIC_SCL=1;
7 l* P0 s! H8 b0 Q" S - delay_us(4);
! r: C5 b' v* s0 h: W - IIC_SDA=0;//START:when CLK is high,DATA change form high to low 0 R6 r9 z3 g/ {" i
- delay_us(4);# P- E, J7 f1 L
- IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
1 F* _4 W% x' B2 f$ P) | - }
1 s7 m2 [5 I# y5 w - //产生IIC停止信号
, m/ r/ X9 Q, K - void IIC_Stop(void)6 e+ z, Y! n) K. J6 E- {9 ?3 @
- {1 A: [8 l# T" r% o$ f& p
- SDA_OUT();//sda线输出( K3 f8 T$ S, w; m/ r- j
- IIC_SCL=0;+ q# n0 D1 ~% e6 \+ v, N! T* Q f! I
- IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
/ ]; b( |$ F; w# R, t' i9 l' ]4 ? - delay_us(4);
$ I Q. G& n1 H/ a9 U - IIC_SCL=1; ; F, v5 U q% v5 w% c5 [" m& W8 V
- IIC_SDA=1;//发送I2C总线结束信号
+ E& \, ]3 |) u [* m$ U - delay_us(4); + Y d7 p5 N0 @9 G
- }( y9 l# M: V/ ` `/ ]
- //等待应答信号到来
- O9 c% ^" L8 i: U% r, X - //返回值:1,接收应答失败' z/ f4 H/ D C, V2 v/ m
- // 0,接收应答成功% k% D; R+ p. }2 \5 a+ X# t9 G1 i
- u8 IIC_Wait_Ack(void)
+ l% `2 Y( a2 X, |2 }) | - {
/ R' y7 D( {* o" F( M* P - u8 ucErrTime=0;
|; |3 T, Y, v( o - SDA_IN(); //SDA设置为输入 * _6 {6 `: J4 e$ T3 ^# X, o! l4 t
- IIC_SDA=1;delay_us(1);
$ G& I" Z6 o: S9 ~ - IIC_SCL=1;delay_us(1); 3 h* L% t t+ m A8 S
- while(READ_SDA)5 G7 c3 L0 c& K& s a& v
- {4 N$ w* i) q) |2 t: F( l5 M! z' v
- ucErrTime++;
' P8 `" ^/ |5 t: d6 i# t" i - if(ucErrTime>250)! Y; v8 k6 Z' B3 r$ S) Z: R
- {
& F( n. Q3 o, o - IIC_Stop();; ?) [6 J2 C6 e$ L0 a0 s8 @. i
- return 1;" V1 Q6 Q. f* z& ?" \+ l/ d
- }2 y+ j( H" z7 I7 X! w. a+ _
- }- u w2 B3 G' |' A
- IIC_SCL=0;//时钟输出0 1 o/ f" k+ F/ s$ o( g
- return 0;
& J8 e) J1 O/ N0 a0 d - } 1 [/ K0 M- ~) y- x& { F0 }% e
- //产生ACK应答
4 c5 G* R# J- V: h) z - void IIC_Ack(void)
r+ k+ g% ~6 X5 m. f/ i a# ?/ b5 r - {
2 w' i. ?9 m' d1 {% h/ h7 E - IIC_SCL=0;
+ ]' i! d! m5 t, c0 f3 g% c - SDA_OUT();" d/ f* b9 A5 G0 N% Z# e$ D/ b+ R% u1 y
- IIC_SDA=0;7 ]; a" g2 o- {. @7 g8 ]: b# x
- delay_us(2);
; X* b2 {" d3 E2 ^3 N1 Z - IIC_SCL=1;( }8 X6 Z3 X4 M0 Y! L0 z! A
- delay_us(2);8 `# E6 \% R3 ?3 `
- IIC_SCL=0;
) d, [. J7 I, b" B) f7 f. ~ - }
3 y$ C% P# Z$ O, ]. E - //不产生ACK应答 2 N6 p0 {7 J1 z4 i& L
- void IIC_NAck(void)
- d& O2 U, T* Z/ F- i$ F: W - {
/ `% C3 ^/ }1 I3 G - IIC_SCL=0;
4 ?* H# S1 [& U( |3 n - SDA_OUT();
5 H% ?# E: e4 g n6 g - IIC_SDA=1;
+ F: U0 S; D t6 G& u) {) [4 O - delay_us(2);
7 k( w2 a& h e7 C( n3 J - IIC_SCL=1;
- ^7 q% L! S6 ]; K- ?! A - delay_us(2);
# c1 K- a6 g1 Q4 o% ` - IIC_SCL=0;
" S6 [1 D' a3 y. w1 Q6 ~ o - }
, B; X( B- h1 ~$ o% G - //IIC发送一个字节
8 t' r# z* \4 L J9 H# C - //返回从机有无应答
$ N+ ~6 A! X, |4 B - //1,有应答
9 M& P% k, w U3 x - //0,无应答
* ?/ n; E, J! A: z: g - void IIC_Send_Byte(u8 txd)- x6 L" G& a. i* ~, D2 w
- { 6 \! U, E3 ]% q j' g* r9 _: u
- u8 t;
! H' Y" ?& F2 l1 k/ {. d6 ^ - SDA_OUT();
b: D1 y; G, B7 j+ E - IIC_SCL=0;//拉低时钟开始数据传输$ h( K* p- O1 C7 X
- for(t=0;t<8;t++)
7 ]- b& e5 ^$ _. ?0 h( K- I6 a - {
) k6 N# M S' u - //IIC_SDA=(txd&0x80)>>7;. G. B; `: \* [; f) f' @
- if((txd&0x80)>>7)
( I9 s0 o8 e0 `/ C - IIC_SDA=1;
9 j% a9 u1 z! @( r/ z7 e - else
" n# U, |: f, q9 d - IIC_SDA=0;) S g0 ]# _' n- @* L( s/ t
- txd<<=1; ( P' m. b" G6 s* G* W) `
- delay_us(2); , A9 j {/ c2 m+ h; i
- IIC_SCL=1;
) h5 B6 \% T) h8 } - delay_us(2);
8 z7 k i2 f# V. g2 Z) A. W) q - IIC_SCL=0;
8 g# _/ F. ^; _1 Z - delay_us(2);
1 B+ w* C0 e& i( g2 k* T - } 5 b! ?. w5 V1 o, S( V
- }
4 Y9 i6 y! T: a7 q2 G/ W - //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
6 R; E6 I+ O$ r3 }4 `3 z8 t - u8 IIC_Read_Byte(unsigned char ack)4 A" o$ A$ D- m$ _6 F# h( X3 a
- {# q2 y3 q* o0 Q( A9 H8 l- k W
- unsigned char i,receive=0;
. H" V- ~" M5 M$ j - SDA_IN();//SDA设置为输入
- o( ]8 r/ ?* D3 O - for(i=0;i<8;i++ )
% F5 Y+ i3 A0 W9 ` - {
' z% R5 M5 }. c - IIC_SCL=0; ; n# J5 A. Q+ l
- delay_us(2);
6 S4 d5 Z A: E9 P2 h - IIC_SCL=1;9 }" b3 h+ z! m# Y0 S! v! |3 w
- receive<<=1;! i0 y" x: @" g
- if(READ_SDA)receive++;
% l& X$ a! f8 Q2 M - delay_us(1); % c3 d: h/ w& |- S# r) h
- } ( p0 S8 Q1 K. B0 U. ^
- if (!ack)
/ n1 P* e7 D# C; r) q+ T - IIC_NAck();//发送nACK6 `# ^* u& M0 a
- else
5 M& H& }) f3 G! H - IIC_Ack(); //发送ACK 2 E# C% R( X2 ~
- return receive;
3 F' \5 I) `/ o' R - }
复制代码 8 s4 k$ H& v$ L+ x/ f
转载自:电源研发精英圈& L: e' @6 Q* L! ]1 o6 U5 G$ P7 v
如有侵权请联系删除8 j* \1 p! H6 x. O$ X8 |
4 l) i: C# L. x2 @ ?
' r" Q3 |% u# U `/ [$ B |