|
I2C两线式串行总线通讯协议,它是由飞利浦开发的,主要用于连接微控制器及其外围设备之间,它是由数据线SDA和信号线SCL构成的,可发送和接收数据即在MUC和I2C设备之间,I2C和I2C之间进行全双工信号传输,高速I2C总线一般可达到400kbps。一般我们也称为TWI接口。 ) P( R" ]3 z# a0 p) R4 G0 H1 m
I2C支持多主机模式:
- d2 |3 L7 `% a4 N0 p, W+ C+ L$ ]' ~2 e' B+ A
" i( I S! R3 a' v9 k1 j) J `+ Y# [; `/ R/ Z
即在这个主线上可以挂载n个I2C外设。
* d# ]( A; I+ k1 Y1 ?
对于I2C协议,其实也很简单,不要想的那么复杂,其实就是电平的变换。我们可以人为的分为6个部分
; g9 _/ q, f( r: U# G8 m# Y. f
整体时序图:
/ b3 Z1 O* J7 I1 b2 C9 U# p2 f% j# L- ?6 x N
: X5 u3 {0 b3 f
8 C. z$ k( Z- w8 Y! _各状态: ) n0 J& i( E# j [* D# E
( |( z4 j6 `7 G3 N& j/ v
: }0 B v6 z M$ B0 x
% w: s6 C' S, M7 g B; |. V% L8 b2 XI2C总线的SCK和SDA两个线同时处于高电平的时候,规定为总线的空闲状态,这个就是由总线上的上拉电阻把电平拉高的。 % R0 p) L( C+ m' H1 V
起始信号 : D* U0 g& S% I) W0 Q* r
- f! V$ ] n1 M/ q# m. V! d
) P% B1 c4 {/ }( f d
1 f0 F I8 L- s6 }当SCL为高电平期间,SDA由高电平变成低电平,即为起始信号。启动信号是一种电平跳变时序信号,不是一个电平信号。
0 V, `' T, k9 D! T
当SCL为高电平期间,SDA由低电平变为高电平,即为停止信号。停止信号也是一种电平跳变时序信号,不是一个电平信号。
/ ^: V& r8 {4 X2 W* B
应答信号
- X! _+ S/ H% _! \( ^
4 `' H2 Z7 s! l/ i; n6 C2 n8 `) _# _9 O- Q+ x1 ]3 s5 s
5 }. d7 t0 G4 ^% ~6 v; @! L1 `
) u; U) `" C2 |$ _& V( _# u0 I
发送器每发送一个字节(8bit)数据,就在时钟脉冲(SCL)9期间释放数据线(SDA),再由接收器来反馈一个应答信号,应答信号为低电平的时候,规定为有效应答位(ACK:应答位),表明接收器已经成功的接收了该字节,应答信号为高电平时,规定为非应答位(NACK:非应答位),表示接收器没有成功的接收该字节。 & I, h. L7 h# ^5 |
对于反馈有效应答位(ACK):接收器在第9个时钟脉冲之前的低电平期间将SDA拉低,并且保证在该时钟的高电平期间,SDA为稳定的低电平。大家主要看图,看看是不是这样的。 " L: l; z2 R w6 C$ S
要是接收器是主控器,那么它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据的发送,并且释放SDA线,以便主控接收器发送一个停止信号。 ' p( V! x. Z6 M: Z; g0 [
数据的有效性
( X8 i- a3 V B7 ], Y1 ]
. m' |2 }6 K* m( r7 z; V' s, V' I0 k, f' L+ g t1 b' w
5 e$ Q! ?2 f; Y
4 D( a U- l# J# i; Z9 r( @
- r% C1 F4 V1 i, {3 f! x8 P时钟信号(SCL)为高电平期间,数据线上的数据必须保持稳定,只有在时钟信号(SCL)为低电平期间,数据线上的高电平或者低电平才能发生变化。 + X* P o) F0 Q
数据必须在时钟信号(SCL)的上升沿到来之前就准备好,并且在数据信号的下降沿来到之前必须稳定。 ! c. s$ `! Y; l5 T! [
数据的传输 & u7 `7 D- M2 L& _' e) Z
在SDA上的每一个位的数据的传输都需要一个时钟脉冲,即在SCL串行时钟的配合下,SDA上逐位的串行发送每一位数据。数据位的传输是边沿触发。 1 T- b, k, V+ }+ D% N
示例代码讲解初始化IIC
4 N3 y2 V+ G$ P& p& ^- p
- m2 b( {( a( [/ W3 v3 g
9 x7 R+ ?( G/ e( \+ T, x
5 @" Q( j& W4 y, I. F3 V4 ~: v
7 K: _( e3 m, a4 ?
% l7 [0 Z: l. H
其实就是对两个线的初始化,我这里使用的是PA6和PA7,开始都设置为输出,中途会改变PA7的输入输出属性,在空闲状态,我们知道SCL和SDA是被拉高的,所以这个地方我们给高电平。 1 k1 c' ]& o0 ~5 M" u+ }4 C. H6 T
产生IIC起始信号 4 O2 L3 J0 e" N. L4 T8 h8 `, y7 i
" r: P. Y1 l* F' v7 t
0 e, s: X+ z/ q" B
K: \& `6 V p! p
2 s* v' }/ B9 |+ U6 j
% ~/ c. e& v9 w3 c" k" c& ]; b7 d
7 j# c7 M+ s8 W2 G# e- ~0 d
将SDA线设置为输出,然后SDA和SCL都设置为高电平,延迟4us,然后将SDA拉低,延迟4us,最后将SCL拉低。这其实就是协议规定的动作了。 7 s0 W! a* r0 E5 c
产生IIC停止信号
; b8 n( x8 g7 X z. z( j3 {6 H * g+ i( i5 z* w( C& y# A2 M9 x* A9 _
2 Z; p- e$ m9 h+ w$ M
, w9 i+ N5 {- R$ x2 P" a0 u! Q: e3 W. e$ D
7 b) [: H5 X5 T同样的道理,和协议的时序保持一致就好了。
0 _) d' k6 S* s8 w* d# p$ r7 D: }
等待应答信号到来
, U% D o% y$ ^* I; _% c
# K% X6 E8 q$ ]" U) }0 w
5 N6 f3 P2 F3 Q8 z- T7 I
' Q: t! m3 |+ }" I7 w' a
6 U/ J: J) t; A/ f
首先我们需要把SDA设置为输入,因为接收方要给发射方返回一个应答信号的。就是在SCL第9个高电平的时候,释放信号线,先拉高,然后持续等待,是不是有应答信号返回,其实就是返回一个低电平,所以我们一直在检测READ_SDA这个电平,持续一段时间,要是没有返回的话,我们认为超时了,就直接停止协议了,
' y) O& k1 l0 H6 S) y9 X
产生应答信号 5 _" A" C% t5 v7 k1 `
# b% U8 j! ]1 `) d$ H" b; E% D2 ]& ]1 I& y
" S, h9 x, R+ w7 b3 r/ f/ I% Q7 L4 r+ N0 v* r" @& k$ r
即在第9个时钟周期内,SDA都为低电平,为应答
! W6 ?2 W3 s5 h4 a- b( X
不产生应答信号
& C9 P- v# i. k+ c; }7 W " C, A2 F- L* a! p0 Y
) s. o$ j% O$ ~# |+ u8 g. Y5 e+ w
, R9 \0 p# j; O, S
$ v) H2 d* d% n- i& B
; x% _- h8 Z; \; e! |3 D8 ?' U) E- M! ]即在第9个时钟周期内,SDA都为高电平,为不应答 8 }& ]3 l) [4 Y/ H" R& @
IIC发送一个字节
2 L E, A5 N/ |7 j$ i/ T/ X
5 T$ M2 r1 y' J2 f; u5 y
' {. }, W! X: B, n- H& n
7 i5 N5 C8 w& ~6 a/ B+ k7 B8 U
8 h) i* i {/ S$ O2 H, b2 l
# B5 j$ i9 h# q$ m1 |1 g
6 \4 T- g/ ^( t7 K" H' L+ l
发送数据,SDA设置为输出。SCL拉低,SDA准备。 做一个8次循环,拿出1byte的数据,将你发送的数据和0x80作与运算,拿出最高位,然后右移7位,将这个数据放到最低位,这个数据要是1的话,那么SDA输出一个高电平,要是与后的结果为低电平的话,那么SDA输出一个低电平。这都属于准备发送信号阶段。 然后SCL拉高,完成数据的发送,然后SCL拉低,此时SDA也就可以拉低了,接着开始次高位的传输,直到传输完成。
* J/ L0 X) L, y/ y
读取数据
7 t( R# j% y+ q a) e2 I' o# i9 r$ j; U5 f) V) O% F
9 X. A- W: [1 t7 y% S! U- a
$ y3 t; k3 l# }4 a# A$ n
" {4 E6 ~8 ~; R. ~. O3 ^$ Y
读取数据,SDA要设置为输入了。SCL开始为低电平,然后SCL为高电平,我们开始读SDA上的数据,然后左移数据,将读取的数据放在低位。然后检测一下有没有应答。 其实基本思路就是这样了。我把源码附上:
% U" s% @; ~: B$ o. ]7 ]
i2c.h - #ifndef __MYIIC_H; [: P( K4 Z' y3 a1 `
- #define __MYIIC_H
# e( M/ L; p, v% y7 M - #include "sys.h"6 j* V+ a2 v9 K7 P' Q
- #include "stm32f10x_gpio.h"
+ c6 M. k5 u6 K9 v4 H" b - //IO方向设置$ I1 U/ W/ f) ?5 \" p& u
- #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}3 w. D) B" O" o9 W$ ~! {# j1 k
- #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
, e/ h6 ~: K) `7 \ - //IO操作函数
, ~: @$ z; \0 w7 ?/ u7 d - #define IIC_SCL PBout(6) //SCL5 D0 n! \2 J" }
- #define IIC_SDA PBout(7) //SDA
! E" g+ M8 O( A8 B" S - #define READ_SDA PBin(7) //输入SDA" D/ B0 A6 U4 L9 X
- //IIC所有操作函数& D: }$ N9 i, D
- void IIC_Init(void); //初始化IIC的IO口+ ^' v( q. w L
- void IIC_Start(void); //发送IIC开始信号
! v8 o$ } @# }, T5 v. P - void IIC_Stop(void); //发送IIC停止信号' j: h0 f: c/ J3 o% ?$ c
- void IIC_Send_Byte(u8 txd); //IIC发送一个字节/ b" i; e+ i5 q
- u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
; P' A! j' G. m0 b1 r* e - u8 IIC_Wait_Ack(void); //IIC等待ACK信号( v, `4 G0 D! P# R( K" ^ z; c7 F
- void IIC_Ack(void); //IIC发送ACK信号
) ~0 O$ N# `1 T. ]( G - void IIC_NAck(void); //IIC不发送ACK信号) {6 N5 f3 k! p' K1 _: V
- void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
' t4 w" c" D/ Q& c7 F& L - u8 IIC_Read_One_Byte(u8 daddr,u8 addr);# ?* S4 ~1 t. o6 N) L" X
- #endif
复制代码
: [1 Z7 y1 O6 \7 D+ R; ai2c.c - #include "myiic.h"0 V0 N6 G7 Z5 ]! ^- @3 O; e
- #include "delay.h"
0 x' ]) s' ]' P( |. |7 }; R; y4 r% L+ b - //初始化IIC
* J4 [8 `7 R5 N# T) x - void IIC_Init(void)
* e$ k; I( b2 d. w) q - { 3 [0 k( r$ q8 N8 p% j8 j$ t
- GPIO_InitTypeDef GPIO_InitStructure;2 a3 S) r: W" Q; V5 k
- RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE ); //使能GPIOB时钟) y; e7 T- b6 \' Y' |
- 0 c5 ]7 `8 W7 ?4 U& m0 W% a( j
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
4 x. j7 n# e& s! o7 f% Z& h) R, O - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
! t! e! Q# Z A - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;6 d1 q# c) G1 ~ a9 T7 f
- GPIO_Init(GPIOB, &GPIO_InitStructure);$ r8 N3 ]) g+ B2 t
- GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 输出高0 H' ], @% P _9 ^, R
- }
' b/ ~/ U; s% ~" V& s N - //产生IIC起始信号. U H4 M& M! Y; m! A. E3 u
- void IIC_Start(void)3 F% e: J& P+ H$ L! k2 m
- {+ D: D! ^6 A, Z& n; A! d. b: N1 D4 H
- SDA_OUT(); //sda线输出
2 g& {( C H7 x! O - IIC_SDA=1; 1 X) g" [. s2 Y2 ^8 i+ ^
- IIC_SCL=1;+ Z5 E; `& k# x
- delay_us(4);
* y1 P" s4 L2 F4 V) \& J' j: J- S! V - IIC_SDA=0;//START:when CLK is high,DATA change form high to low $ |2 V& C' f4 V7 b
- delay_us(4);
5 U' H ^, a6 a- ^- k - IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 8 \* ]6 u. a- w ^' ^+ Y" A
- } ( S: G+ i# e& g2 o
- //产生IIC停止信号& \" e7 I, R4 M( p" t
- void IIC_Stop(void)
; a5 \7 ]& y2 i1 q1 B - {
9 d4 g. e9 D7 b- n7 w% F# D - SDA_OUT();//sda线输出7 j. O: v$ [8 |$ u
- IIC_SCL=0;
! N0 ~2 N2 \! M3 C - IIC_SDA=0;//STOP:when CLK is high DATA change form low to high/ Z0 }/ G. V/ f
- delay_us(4);
7 w5 W# }/ @* X - IIC_SCL=1;
, M7 m' g( {( J v& E- I - IIC_SDA=1;//发送I2C总线结束信号
5 }- K1 m% d% H# Z* S ` - delay_us(4); 3 z" {% ~7 j- o- ]% ~
- }" A9 e/ z7 T- t$ g9 L
- //等待应答信号到来
6 x* F0 C n2 d- N/ y& h+ o) L1 d, p - //返回值:1,接收应答失败# n4 L S. R3 T: ]
- // 0,接收应答成功+ J% e1 z2 o/ c9 L
- u8 IIC_Wait_Ack(void)
5 H# n7 D, Y- K - {! {; C. T0 [: R0 c
- u8 ucErrTime=0;
# I2 F- I- E0 V+ m- K( o - SDA_IN(); //SDA设置为输入 V* R! A* _( ^; l% C- C s" @9 ]1 R
- IIC_SDA=1;delay_us(1); ' U( \1 G2 e9 x/ {) }. R) a$ y9 T: r
- IIC_SCL=1;delay_us(1);
5 G+ E. c( C) ?/ Z% J' H" ?) R - while(READ_SDA)9 q. T6 b- f8 _( o3 G3 e
- {
8 Q/ O1 \8 U: h6 X) J7 _ - ucErrTime++;
c% U3 B4 O' U# B# t - if(ucErrTime>250). T2 P/ t# w! i1 d) m3 J
- {0 }; E; R- V' s5 x$ f
- IIC_Stop();! {; t+ h9 J( Z
- return 1;
* n/ ]' k' ^2 o" `- Z - }
( h/ S4 g$ S5 I7 M" |' |( b* g# [5 t - }2 t+ i5 A7 T( `; V2 U! U
- IIC_SCL=0;//时钟输出0
! F" s! ?0 |" d0 _7 K ` - return 0; ' B7 {4 L9 o! l# ^2 a
- } 2 {- v* {' s7 `) l( |) u0 h
- //产生ACK应答
( Q! A- t+ i4 F - void IIC_Ack(void)
/ |) G2 T$ A- F* x, t9 Z - {
8 o8 W! Y. `! A - IIC_SCL=0;
2 v: X0 j6 D; _$ }* L - SDA_OUT();0 I2 ^3 r* g. H1 ]
- IIC_SDA=0;0 X2 c* K7 t8 i$ b% N- J. ~. w& ^
- delay_us(2);+ c/ ^. z j1 O+ R& c7 W
- IIC_SCL=1;
5 g. @3 I0 r! x% Q/ O% Y* b - delay_us(2);
% l9 r/ |# L9 F" j& ] - IIC_SCL=0;3 Y" U* X% H5 F) S
- }
4 K' X! {9 Q4 z8 w4 h1 A5 R - //不产生ACK应答
" a# {% e+ Z, B% I2 V% A+ E, k - void IIC_NAck(void)
3 }5 i( e- y9 q6 l$ V - {" i3 A1 U0 o% N9 D4 T
- IIC_SCL=0;( j' t$ N. M! W) v# m" y e' t. {, D
- SDA_OUT();& [* Z/ ]5 g5 ]1 I2 }
- IIC_SDA=1;
& w7 O- L4 [! k. K* S! _$ ?0 C. ? - delay_us(2);
; g; C" N5 a) `2 n3 D - IIC_SCL=1;
" C$ k9 G3 X: n$ \9 K - delay_us(2);/ W' z- [; _! R/ g: x) R
- IIC_SCL=0;
# g% J0 ]& O; q5 M/ T - } % [9 G1 Z2 m f: W/ ]! u
- //IIC发送一个字节/ w( f8 D) n& g6 S# j+ }
- //返回从机有无应答6 Z+ D0 y$ o) b
- //1,有应答# a+ a( X. T7 x3 _; h) d8 y. J
- //0,无应答
( j% L3 f$ @/ ] - void IIC_Send_Byte(u8 txd). l- e% W" @* g, ?" w
- { , E& H' Q% `: s
- u8 t;
# Q# E+ Q! d; e8 c2 W% r2 H' } - SDA_OUT();
8 x% P G6 t8 y - IIC_SCL=0;//拉低时钟开始数据传输
0 O8 `/ c% E+ ~& {1 x" T" q" K - for(t=0;t<8;t++)! Z# s* |# Q- l0 L4 O
- { # R! I+ c" J" S2 r7 U/ [! h
- //IIC_SDA=(txd&0x80)>>7;* s# A4 m% i6 e) r9 [
- if((txd&0x80)>>7)+ S8 L! Q7 l0 M
- IIC_SDA=1;& w b9 \/ G6 G. q: Z4 u
- else
* j5 {" n' b% E0 e - IIC_SDA=0;
: d: v/ W! l; I& x( V - txd<<=1;
# Z$ n# t" ^6 n, m - delay_us(2); - E9 {5 @3 x9 b q8 i
- IIC_SCL=1;
& j# f+ G$ p- A5 I# S - delay_us(2); 5 i& h$ s" ^5 A6 W+ k* R
- IIC_SCL=0; % j2 e4 t, [7 U
- delay_us(2);, g! l' d9 Z3 f4 p3 h1 P. j
- }
* L6 m( o3 y& X - } 3 d* d, E& z# ]5 W I+ r
- //读1个字节,ack=1时,发送ACK,ack=0,发送nACK 9 i, U" P* C7 _2 x
- u8 IIC_Read_Byte(unsigned char ack)
# x S0 Q" i T2 x( ? - { |9 ~% P; I" x) @8 ^
- unsigned char i,receive=0;
& k% r0 c ~& q9 b |: } - SDA_IN();//SDA设置为输入4 z/ v/ E) ~0 y7 y) Y) Q1 d2 {
- for(i=0;i<8;i++ )
9 z/ s& J; O2 w: Y - {; {/ k6 N) L) Q) Z% h
- IIC_SCL=0; 2 _. g) y$ h* c4 U7 v; q
- delay_us(2);$ X) M! h* w/ J! y5 `
- IIC_SCL=1;% m% k4 V9 E; ^( s/ `0 ` z; _
- receive<<=1;
+ ~; B8 E! l7 Q$ t - if(READ_SDA)receive++;
* E, M% a2 W# e0 O: c3 [& _ - delay_us(1);
- ?7 b7 M) _# a A - }
' O; c) y- l& U7 a9 p - if (!ack)" V" G6 k- G5 q; x
- IIC_NAck();//发送nACK
7 G+ f9 f1 l, z+ ?. a - else D. }4 |1 g+ _& o1 X. F8 \: w
- IIC_Ack(); //发送ACK # q7 L* d# t2 ^) U
- return receive;
. S6 [8 w9 @6 t/ j, M' k) [8 v! { - }
复制代码
, g2 I8 @. H V6 ?转载自:电源研发精英圈6 @# s+ J R8 p- i
如有侵权请联系删除
p3 O0 I+ W3 Z2 {
" }4 z1 y8 R1 K' w
; j* h% n/ r2 Z |