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