|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
K M8 {. R3 m3 m$ [+ I现在很多单片机都已经对常见的通信方式进行了集成,但是51单片机没有这些功能。如果想要使用其他通信方式,只能使用通用I/O引脚进行模拟。
, s9 V# g; E* b$ Z今天我们就以I2C通信作为例子,演示一下如何模拟这些通信方式。0 q$ [$ R8 h7 u4 ^
+ g; T* C0 I$ ^" D3 b6 Q/ k0 d9 d一、I2C通信介绍
, X- s# |" [+ a3 Z. Q& |I2C总线是一种简单的双向串行总线结构。只需要SDA(数据线)、SCL(时钟线)两根线就能在器件之间传送信息。
; W3 Z/ @: a; _ D' mI2C通信中,器件有主有从,一般以控制产生时钟信号的器件作为主器件,其余作为从器件。每个从器件又有一个7位设备地址,用于寻址。这就意味着,一条I2C总线上可以并联多个I2C设备,主器件通过设备地址可以分别对这些器件进行操作。3 W+ T& w9 \; T0 |. E: \# d& z9 [
一次I2C通信可分为三个部分:I2C通信起始信号(SCL高电平下,SDA从高到低跳变);I2C有效通信;I2C通信结束信号(SCL高电平下,SDA由低到高跳变)。! A+ o- {! ]: Y6 |0 Y7 k9 _! h3 o
同时,在I2C有效通信阶段,数据接收方每次成功接收一字节数据,都会主动将SDA线拉低,表示传输成功。 M. I) E. f5 s& e9 z" k7 [, q0 e1 ~ F
/ b l$ G* z: ~% l5 c1 ]$ B5 M9 {由此,我们可以编写得到I2C设备的基本读写函数与等待应答函数:7 }- V1 |8 E9 `' q7 {! R0 J
//开始信号3 v' ?9 X+ E# G U7 O, i) @3 |! x
void I2C_start()
& n; d' F' n7 |' E+ M {2 a3 Z{
' ]2 C, u9 q, U9 @* p9 \8 @SDA = 1;//同时拉高数据线与时钟线0 x+ b5 O% ^+ q! f" U! S. w
SCL = 1;
) j; _' Q L+ G+ M9 Cdelay_1us(1);8 V# {; l7 G; n) |- i$ O8 |
SDA = 0;//SCL高电平,SDA下降,启动I2C通信! `* y0 J& Z' O# x) n) g& d' C
delay_1us(1);//等待1us9 D5 V5 }* ~, r5 S2 r9 h% e! z
SCL = 0; Q2 ?+ R, Y, _' r4 D
}( y: I. O$ d% X* r# e' U7 `
3 y# P/ H# }8 Y m
//结束信号
* T( }+ G7 {. \' |6 E/ @void I2C_Stop(), e" [* R9 [4 N5 r" k' p
{
, Z5 }$ v/ m5 o/ W) P2 t& sSDA = 0;$ L0 h2 l9 {2 @% J9 x8 _- R9 ]
SCL = 1;$ ^$ Z0 u7 M0 q" b! s
delay_1us(1);
+ l! [& {; \; G$ ?2 ISDA = 1;
. w$ I4 R; a* B' ?/ pdelay_1us(1);1 r0 d$ N& E B! {: t- N0 V
}3 `' s+ ~0 _$ U2 k
- A) B8 o; @/ K1 Y' {) y! [5 w9 w
//主设备应答函数
+ p& F) b$ T2 g& N+ c/ nvoid I2C_SenDACK(bit ack)
( u" P c. P i, \2 B! g1 x{( Q: L. @. Y. r9 ^( e. g
SCL = 0;//SCL 拉低,数据改变+ Y4 @% d6 n i2 _3 J
SDA = ack;//数据位改变
6 [9 Q) k. g9 I+ Rdelay_1us(1);//延时等 稳定信号: u( q5 Z" @+ T2 i: V' p0 {
SCL = 1;//SCL拉高,传送数据
7 E' W8 j7 c) D% v% w8 Mdelay_1us(1);//等待6 p: Y. l2 D `' J
SCL = 0;//SLC拉低
) g0 m8 [! m$ t& u( p}
# m# A( z1 U; G' c
+ Z# j6 p* L9 o. m, `7 @& }//从设备应答函数
( L, r+ C6 r1 fbit I2C_RecvACK()
7 m* l' l7 F( w1 d2 K6 L4 L{
4 k7 c; X( o2 u( Q/ f/ c) i+ [uchar wait=0xff;
5 X' i; k3 Q& ?* J1 YSCL = 1;//SCL时钟拉高 数据有效2 @. U6 h3 D. a
while(SDA&&wait--);//SDA高电平,并且在512us没有走完, R1 i8 E1 d( [: B; x3 n
if(wait<=0)
* W$ z, C7 K8 s( b, R{: N, X# \( I, b7 h
I2C_Stop();+ c% u+ t' Y5 g" N' z
return -1;) {/ `! C' ], R* d n% J5 \9 e3 R
}1 {# O- y7 o, x4 n' T
WSCL = 0;//否则SCL为低电平,结束该应答! r M: r; m% h$ G3 y+ }. D
delay_1us(1);
! c9 L3 _9 |& {return 0;
7 [6 [4 m" @7 H5 n+ I9 v- `}
( t# N0 r1 x) n, G( _//数据发送函数9 y, f5 }3 b7 B
void I2C_SendByte(uchar dat), b6 h) i+ J% @9 d
{" R% P- p, O; ^. a+ p2 |
uchar i;% _9 G2 A/ N% J. j% z
for(i=0;i<8;i++)
9 V: m$ }% u/ A# P{5 g/ C" C% B& w' e! ~: {
if(dat&0x80)//dat与0x80进行与运算
& i: M3 B( d- p* {% pSDA = 1;
6 V8 y1 U8 `/ F* {else
* v6 o, J: [* o: pSDA = 0;
& b5 X) W0 f* r4 cSCL = 1;
5 F$ x8 P8 A, z0 e. M- U9 b7 H* v% adelay_1us(1);2 r; k: E K& t! b% x: {
SCL = 0;0 I8 C: ]7 W! j/ ~9 W
delay_1us(1);
8 {# m4 Z. f% V3 Y9 Edat<<=1;
' ?, v* N4 ?0 S4 F5 K- M; \}
: J0 v- e: \; O8 d9 X}5 T/ k! F) j% u5 q9 P* J
//数据接收函数" U3 w) v9 N6 l: i' B, r4 v# Z
uchar I2C_RecvByte()//接受一个字节
) ~& F8 m* n6 c6 j' I2 g) X. O{
M' |, l7 u3 H2 ^! c& J% l) vuchar i;
3 z! G# ^1 M' L9 @uchar dat = 0;8 X, S3 }. U& m$ [( |, F( ]' n( n
WSDA = 1;, g ^' c& B( m9 R2 S8 t
for(i=0;i<8;i++)//一字节 八位
% L, H1 M' G$ M! O% ]5 X{+ X# l% N. x; U/ n$ u1 t$ Y
dat<<=1;//dat左移一位,从高到低接受数据
0 |" \+ g, |. F2 W4 h8 V7 |WSCL = 1;//SCL拉高,数据有效
- r6 G6 U7 O. {0 O) Bdelay_1us(1);//延时保持4 a# W k% z0 x/ T& Q# k& N7 W
dat|=WSDA;
" R [% t1 @: q4 \' HWSCL = 0;//SCL拉低结束数据传输
$ T( V5 I9 X0 {( \' Hdelay_1us(1);//延时保持
% p4 [8 `/ w& L9 P" l% T8 D}% O0 g; i% Q* W+ S$ a) n
return dat;//接受八次之后返回dat值3 ~! d. H+ B& @) ~# N# x
}
6 d8 v' w0 A9 z' v* q% D
- q% M" m$ O+ ]) A0 x9 {二、I2C通信流程
1 ?; e* T& z6 b( F# G. Z主设备向从设备完整的写操作4 J5 y' f9 C* N5 ~- v
1.主设备发出I2C开始信号:
; j w2 L, @# ` ?. `: A, y% q2.主设备向I2C总线发送要操作的设备地址以及要进行的操作(读/写)7位设备地址+1位读写标识位(0读1写)% ]$ M" M+ k% `& J; K
3.主设备向从设备发送要操作的从设备寄存器地址/ S6 c" w* j, y r1 k2 i9 M6 h1 L1 K
4.主设备向从设备发送要写入的寄存器的数据- N: {- y/ W0 H* A
5.发起I2C停止信号
: {( }: E2 ~; b9 |8 R//对I2C设备的完整写操作! }8 n3 _' ]7 A! d9 O5 U( }
void Single_WriteI2C(uchar REG_Address,uchar REG_data)0 W; T k) q: l
{5 Y: F: O/ n# H z
I2C_Start();6 F! M& [" P" |
I2C_SendByte(SlaveAddress<<1|0x00);
+ U. w- U+ \7 BI2C_RecvACK();* _3 S. t. \% @% F: ?
I2C_SendByte(REG_Address);
0 a" F2 u6 p3 Q* z3 eI2C_RecvACK();
& u, G% i$ L& B2 R* S8 XI2C_SendByte(REG_data);
$ I& t {$ ^. N' e1 t; M' LI2C_RecvACK();
2 A0 \1 E5 _! h; W7 C3 D% xI2C_Stop();
9 ^. Q1 P; ~' F5 `5 t- m/ {! }}. J/ F0 J8 [5 N t) \* R; t
处SlaveAddress为设备地址,使用宏定义。1 N6 }! s6 B, z% Z3 f+ O, H
主设备接收数据流程如下:' ^' j9 P0 f' r5 p+ R
1.主设备发出I2C开始信号:
1 k0 X6 ]6 a! X3 y% O) O( B5 H2.主设备向I2C总线发送要操作的设备地址以及要进行的操作(读/写)此时,最后一位要为 写操作
- j7 U: C# |" w" s3.主设备向从设备写入要读取的从设备寄存器的地址
% A0 o# I" L) r. Q3 p4.主设备向总线发送7位I2C设备地址+1位读操作 标识1 k) U6 m% n4 a% l. Z w
5.接收一个字节的数据% |5 I" |' }& O2 M; X t
6.发起I2C停止信号7 [8 K; ^5 v* t E3 E5 {7 \
: p! `6 F6 {3 j3 k4 ?
void Single_ReADI2C(uchar REG_Address,uchar *REG_data,uchar len): q2 v2 u2 e j4 |
{: n2 L* P* q2 h; i
int i;( v- L6 q+ ]" U! {& }1 S
I2C_Start();+ K# x# x$ l7 p. ^' ?& h" s' N. T
I2C_SendByte((SlaveAddress<<1)|0x00);* I' S/ N4 D. H+ f
I2C_RecvACK();5 y0 f% F) S) p% N5 y2 Z
I2C_SendByte(REG_Address);8 o) r3 v& f0 C) A" f
I2C_RecvACK();
, P$ \$ t5 C4 c( oI2C_Start();0 s( S9 P, P0 n8 I7 @3 l
Delay_Ms(50);, s8 ^: G k1 U, o' y
I2C_SendByte((SlaveAddress<<1)|0x01);( M( H( y, d1 n# t; u; T( f4 a
I2C_RecvACK();* p. I. s. z8 Y! ~- S
( e+ j6 x. T; `' E6 G
for(i=0;i<len;i++)* S q1 q* Y/ ?; G$ B
{ E# w' k4 @5 { j* n$ S7 x
REG_data=I2C_RecvByte();& {# J) B2 i# Z! |# r
if(i==len-1)% P5 ^8 t& n5 ^6 G
I2C_SendACK(1);
0 r$ P/ E: @6 N/ Kelse0 T) t- S6 n; @. k! z2 J
I2C_SendACK(0);
( a; I2 V+ g3 ~1 ~! J/ A}; C" d; K% P e6 G* p1 u) Y
I2C_Stop();' g, g; j* g/ _6 |% d. x' s
! ]: K! i+ V9 H* I$ S9 A5 Z}
* z3 O% R/ ]) }以上就是常见的I2C设备通信的通用函数。% a( @ L, ]! D P- a- m0 `
使用单片机的I/O接口,再配合时钟,多数的通信方式都可以被模拟出来,就比如51单片机只有一组串口,如果有需要,自然也可以使用两组通用I/O引脚进行串口通信的模拟。 |
|