|
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
, _) }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
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
! 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
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
! 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
@' 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 O3 q/ Q4 S9 C6 X0 O: O5 Q$ v( q& ]# A
, 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
2 P7 Z; H b( D' S0 K3 H4 ^
6 S% c; ^/ g2 @6 `5 z" K4 c
) 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
' 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
9 b/ F7 ?) [; q
$ y/ T- [( U( ~# ~" W
\$ 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
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
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
! ^3 ?3 K* o* C# v
7 i \, }" M$ n6 \* n' q! [' X
: 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
$ _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 - #ifndef __MYIIC_H
N5 f) o/ B3 U& }1 A4 H - #define __MYIIC_H: v9 H ^& N9 W- e
- #include "sys.h": V" g c$ v( ?6 R' Z0 a
- #include "stm32f10x_gpio.h"$ a9 {" ^- g0 k
- //IO方向设置9 B H7 ?* v( W( L* s$ e: N
- #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
- K8 K0 E) A, S8 L- ` - #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
1 y! _5 n6 o9 B) \4 C, ? - //IO操作函数 e( y0 t; A/ p1 b& v' U
- #define IIC_SCL PBout(6) //SCL5 W8 p V; m s* B# n1 r5 A: m
- #define IIC_SDA PBout(7) //SDA
z/ S( ]4 Z \ - #define READ_SDA PBin(7) //输入SDA' D Q! X% d* x( C) r- r2 `* ?
- //IIC所有操作函数
9 z8 h" o5 G4 G; T( w1 Y - void IIC_Init(void); //初始化IIC的IO口, {5 S" p. Y* Y# @; n. v5 B
- void IIC_Start(void); //发送IIC开始信号
! F8 U9 k+ G# f - void IIC_Stop(void); //发送IIC停止信号* `1 E# x8 d3 a2 f
- void IIC_Send_Byte(u8 txd); //IIC发送一个字节
# C. I$ d9 ]8 t. v: ^, z - u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
9 x' ^; b) _1 |4 f. O - u8 IIC_Wait_Ack(void); //IIC等待ACK信号
: C0 K U% `9 E9 D0 n - void IIC_Ack(void); //IIC发送ACK信号, C/ j3 }7 c6 m
- void IIC_NAck(void); //IIC不发送ACK信号4 i+ E! H3 P7 m( b' \' X
- void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);, e* Y7 L& I* u( t5 \
- u8 IIC_Read_One_Byte(u8 daddr,u8 addr);8 G% F5 Y$ z0 T# ^* {/ m
- #endif
复制代码 : ]* F3 q# x; }. }9 U0 t
i2c.c - #include "myiic.h"6 [# B$ Z8 X9 u' Q1 O
- #include "delay.h"* H9 B* _2 j; I) w! u( s! k1 ?) R
- //初始化IIC
& k E+ G- Z8 c% n* E - void IIC_Init(void)
0 J. F4 |$ \9 r; `6 C& O* a - {
* M. \( K, l4 ^( q' a3 k - GPIO_InitTypeDef GPIO_InitStructure;
& i* _* v9 x, @2 ?5 r - RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //使能GPIOB时钟* i+ L# r, F6 i8 E
-
1 {" d+ @& S- H) `+ Y$ L% ] - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;( q; G1 D/ Z3 z+ N9 P" e
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出! w) B4 }# Q/ A! @) D$ p- R& z
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
. V$ P. R) l; f. W. ~) I - GPIO_Init(GPIOB, &GPIO_InitStructure);
! R& D4 M6 o0 \! I- O - GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 输出高
# s5 @5 u1 }; n6 K8 f) |& w - }) l) ]8 s7 P o) A: q) r4 t3 C# s
- //产生IIC起始信号# `/ {1 f( ]; i) O* N2 v4 |$ G4 Y# Z
- void IIC_Start(void)
1 I+ `$ h) C# q- M - {5 i8 y2 U" j, v7 W* p4 k7 K
- SDA_OUT(); //sda线输出! y% ?* a8 K1 N# `" |, y; h
- IIC_SDA=1; . ~! u( z; F; P+ q: b- h" m' ~
- IIC_SCL=1;' m; X' v+ k- b3 @
- delay_us(4);
" j; Q3 i/ `9 g9 Q. [+ s7 f' F - IIC_SDA=0;//START:when CLK is high,DATA change form high to low 4 e. W* D( Y. ^+ X1 h& l' U
- delay_us(4);
e+ o' I' f, I! ]! _) I - IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
* k" s- j( j$ |% g - }
N& i4 G1 J! W! O - //产生IIC停止信号
) H0 C V; }% H - void IIC_Stop(void)+ Z! ?: T3 E: e2 x* l- S
- {
7 _7 V( F [0 v0 A% I& o - SDA_OUT();//sda线输出
* `- z6 B; E- Z+ q0 R - IIC_SCL=0;
9 y& D) g- o1 u! q+ ~7 r, e$ E5 V - IIC_SDA=0;//STOP:when CLK is high DATA change form low to high7 V5 \" P' O6 d$ ^9 Y) ]
- delay_us(4);& a- [% L) k- Z
- IIC_SCL=1;
4 e8 i( H# i+ K ?6 d - IIC_SDA=1;//发送I2C总线结束信号6 z. k0 x; B. p" I+ K( Q9 q, r6 i2 W Z
- delay_us(4);
, t- Z4 v) f' M - }5 A/ g7 N: o) I0 N& T
- //等待应答信号到来
2 e( n( M! C4 ?0 O - //返回值:1,接收应答失败 b: Z3 K$ i4 D9 O/ S; J4 I
- // 0,接收应答成功: o! X7 y7 r, ^. S7 X4 ~3 S% v2 H
- u8 IIC_Wait_Ack(void)
" }$ @" g5 _4 j - {
' E4 \- r* {0 }! j- V6 o7 t - u8 ucErrTime=0;+ ~& ] }! \9 W$ G: L% R
- SDA_IN(); //SDA设置为输入
/ c) H2 {; C; g- u- |' p0 | - IIC_SDA=1;delay_us(1); 2 b U% R, u" v- G! k
- IIC_SCL=1;delay_us(1); / L0 N6 [: J/ |( s! m
- while(READ_SDA)
) ]) q* [8 @; |& O - {3 F$ V7 Y2 i6 ?
- ucErrTime++;
5 [, O4 B+ N3 H. b - if(ucErrTime>250)
2 |) k) N6 U% A0 l0 P - {* X0 ^, k7 e! B% {; O2 x
- IIC_Stop();
* _' G& T* \2 K4 ] - return 1;
) u9 M7 k: t/ o: K - }
6 @ j5 f! V, C- F3 g* b - }: R* ]% j1 ] Z2 I
- IIC_SCL=0;//时钟输出0
" r3 _9 n$ f& o0 e7 V - return 0; $ y, G, J: g" t, U. g
- }
^; r. C* i _; x1 x( l$ d2 R - //产生ACK应答
0 D$ X1 Y/ @# E& E, G0 q4 O - void IIC_Ack(void)
/ J: V2 L% f( x$ g; @& j - {
4 G% f' G6 h: d/ h( ] x - IIC_SCL=0;
, l* C+ N7 Z! h$ W9 X - SDA_OUT();
' z! M/ d9 |. E6 b! z - IIC_SDA=0;* f3 ^/ y8 }2 h7 B4 w+ j
- delay_us(2);! y9 |- Z+ I- K w
- IIC_SCL=1;
& h% ?3 m6 G/ Y - delay_us(2);( R3 h/ r/ j# d" q3 ]: r) t
- IIC_SCL=0;& _% F8 L, [# m4 k+ m% x
- }0 _9 a& m, U# v: c4 b- d, L
- //不产生ACK应答 8 u6 r/ m d8 H% H
- void IIC_NAck(void)1 ?) o: e. d# ]/ K5 g
- {1 U, q* T4 ~) v0 ~4 c
- IIC_SCL=0;
( O3 X0 d b. S* \- y - SDA_OUT();* P. U ]! b8 j! j, J/ {7 \
- IIC_SDA=1;$ ]! Z- }, ~7 s3 h: x9 @: h
- delay_us(2);" W( K9 \7 V9 N$ K3 t7 I" A
- IIC_SCL=1;* r5 C8 Q) f- @- S
- delay_us(2);
* w, R+ a# ~; a! T2 { - IIC_SCL=0;" I x$ f: i; M4 k- z2 k F0 V
- }
; @$ r& z( Z F/ Z( \ - //IIC发送一个字节7 X2 z1 ^& O$ K% [6 o& ]
- //返回从机有无应答/ e3 i3 F. k" q9 ?
- //1,有应答
[; `8 D3 j, H/ ~: f - //0,无应答 1 G( y. {6 G8 W: i
- void IIC_Send_Byte(u8 txd)
" i2 k! ?: m7 u: C8 M6 g& q0 W - {
* S" v" l& \# o& W% h - u8 t; ( ^2 A: x5 G8 a5 H" o1 i& A
- SDA_OUT(); 5 L6 ]4 K9 K3 y6 R
- IIC_SCL=0;//拉低时钟开始数据传输
8 Z; x5 u9 e$ S/ E; C& ? - for(t=0;t<8;t++)# C7 |5 i; F) d1 e/ t: g+ n6 L
- {
$ r u. ?6 O% R: j$ | - //IIC_SDA=(txd&0x80)>>7;
( Q+ ~* ]. j5 f2 E+ V - if((txd&0x80)>>7)5 P: ~# t% o% K- ?$ @
- IIC_SDA=1;+ x o* u$ T/ O/ n4 A4 B- m7 p
- else
. s- Y0 G% P) @& k - IIC_SDA=0; z* `. V* a8 l4 \* t
- txd<<=1; 4 I' a {! c* Y* d4 X
- delay_us(2); ! e8 n9 g: }4 N
- IIC_SCL=1;+ k) i! ]! s8 }1 ]7 A0 P U
- delay_us(2); - w/ E9 x" U* U1 d- Q' t4 w$ T7 m' P
- IIC_SCL=0;
* T9 }, y& ]# A, b) h( F1 E - delay_us(2);
& `; J0 K6 K; h1 I0 a - }
! q8 Z4 m7 K- @. t - } 3 K8 }: N" g5 I: I! s- Z
- //读1个字节,ack=1时,发送ACK,ack=0,发送nACK
3 ?. Z5 Q) w9 u - u8 IIC_Read_Byte(unsigned char ack)
' n$ i! i- r* Y* Z- q0 d - {
8 h4 N/ O7 ?5 S0 O; Y( j' B# _ - unsigned char i,receive=0;6 W w2 K6 f* @- B4 a& {: \7 N
- SDA_IN();//SDA设置为输入
6 {" t6 O5 U; O" e8 y. R - for(i=0;i<8;i++ )8 a$ U, p2 u, G: o, V9 E
- {
# \/ r: q) n5 w1 C% ]% `. ? - IIC_SCL=0; $ e' p- e! @6 P7 U2 G3 n4 h
- delay_us(2);
' H3 r0 F. z! ]3 n: b0 G" E - IIC_SCL=1;3 ^/ g6 I2 m0 d0 q( I! g: H
- receive<<=1;
: o# s2 c& Q( c: t" P6 p - if(READ_SDA)receive++;
4 o- z( I% K$ [: W$ ~# G - delay_us(1);
5 m# P0 F8 ^ q* }+ |3 A9 D; M/ C - } / S( o3 A) t, `+ c, j! }/ w+ V
- if (!ack)
2 N" s, c9 _" { - IIC_NAck();//发送nACK. Z1 B* R) q# S
- else
. j3 x. e) g( A7 h. Y$ }: @ - IIC_Ack(); //发送ACK 5 R, b1 F9 `9 H4 ` s
- return receive;: [2 j. E) a# Z! m8 j
- }
复制代码 ' 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 |