|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
4 T. X/ F4 l0 x2 o5 P现在很多单片机都已经对常见的通信方式进行了集成,但是51单片机没有这些功能。如果想要使用其他通信方式,只能使用通用I/O引脚进行模拟。! p1 Q2 G, }& x) S% r
今天我们就以I2C通信作为例子,演示一下如何模拟这些通信方式。
U9 Q/ u: H3 M% U
; P9 g3 f4 U! R: a' s1 r一、I2C通信介绍
0 N i9 O+ s* N$ R" pI2C总线是一种简单的双向串行总线结构。只需要SDA(数据线)、SCL(时钟线)两根线就能在器件之间传送信息。
: Y0 D, \$ m8 B g1 n$ xI2C通信中,器件有主有从,一般以控制产生时钟信号的器件作为主器件,其余作为从器件。每个从器件又有一个7位设备地址,用于寻址。这就意味着,一条I2C总线上可以并联多个I2C设备,主器件通过设备地址可以分别对这些器件进行操作。
, B5 p) }4 F! }一次I2C通信可分为三个部分:I2C通信起始信号(SCL高电平下,SDA从高到低跳变);I2C有效通信;I2C通信结束信号(SCL高电平下,SDA由低到高跳变)。
' o7 i/ A2 g0 R* z7 h% K+ X同时,在I2C有效通信阶段,数据接收方每次成功接收一字节数据,都会主动将SDA线拉低,表示传输成功。
& W o% X9 ~4 X% i& u& t" `& l
/ u7 D8 h9 M$ N6 E6 m0 a% y
由此,我们可以编写得到I2C设备的基本读写函数与等待应答函数:& z' {( B+ s" M1 f, R5 e0 @6 l* J+ `
//开始信号9 R N) {* k- h B5 H% V* z
void I2C_start()& B; z" E! F9 h3 j
{4 }4 p" F- g( g* @! U
SDA = 1;//同时拉高数据线与时钟线
w. w" E9 I9 f5 f& d( TSCL = 1;9 _1 d. {. \- u! k
delay_1us(1);
' b( `8 P- w8 t7 c; c/ i( dSDA = 0;//SCL高电平,SDA下降,启动I2C通信1 P6 G7 X3 y4 A l; ?
delay_1us(1);//等待1us" g4 q: T9 }& X2 Q7 e: y+ z
SCL = 0;
^+ M# L' Z/ x# E3 ~6 q, M N}* t) u; y' p- F0 ?3 T' I
+ n4 {2 Z3 W D+ C3 A2 D3 s//结束信号
V' E3 J: o& Kvoid I2C_Stop()
9 s" `. U( v7 R* h3 Z{
6 s, A# ?* M% k5 M4 a+ h- cSDA = 0;
9 B2 ]. m8 U3 E+ H+ x# X ~SCL = 1;) s# ]$ R( M' K9 r
delay_1us(1);
: Q6 P! r* n- j8 Y+ LSDA = 1;
& c. P j j& ?% W8 v: Idelay_1us(1);
/ [6 |; E; Q! o" k, `& M; A E9 r}, O. d$ c( s2 B1 E3 T
+ j2 A+ |( w$ a5 ?$ s R, ~
//主设备应答函数- V* G% `5 M: _/ R/ N% n* r( g
void I2C_SenDACK(bit ack)0 [2 w' B9 L' I
{" }+ P4 x; m* ]1 y8 ` A
SCL = 0;//SCL 拉低,数据改变
8 A/ ?9 K6 s1 p6 R, N9 N# {SDA = ack;//数据位改变' K. h( L( G6 _& }$ n
delay_1us(1);//延时等 稳定信号
6 ?# G7 y4 d) D0 N4 i& { d: `SCL = 1;//SCL拉高,传送数据
1 `- {4 f$ ]% m- Z& @6 X( `delay_1us(1);//等待
/ o$ P: K3 u4 _& sSCL = 0;//SLC拉低( r7 S0 L2 ?7 v
}$ h: o7 s2 u. W! c% S8 D
4 @# l, y; o+ m& y+ ^
//从设备应答函数* F4 Z- g& c& |
bit I2C_RecvACK()
6 v4 |8 H0 q) t* d9 q0 M2 n{
- {2 I }$ j# ^! Quchar wait=0xff;; p. R: t' F" O1 d% K" V
SCL = 1;//SCL时钟拉高 数据有效9 ]% t+ w' A0 F6 A
while(SDA&&wait--);//SDA高电平,并且在512us没有走完9 T' k, @: _; x5 P/ M2 A2 _
if(wait<=0)% S; e3 s: r8 G9 [" O [ O- y
{" m$ Y5 i# v. y; D$ u2 Q* {+ r
I2C_Stop();
- H- A1 @4 S! }return -1;, n/ v m; a3 s4 `
}' b4 p5 ?. F$ \! s$ N% R- e
WSCL = 0;//否则SCL为低电平,结束该应答1 N" V0 f& {7 ]$ Z
delay_1us(1);
9 j8 T4 \7 j) u( c6 r; }8 yreturn 0;
3 U1 @# X' J& f9 S% }5 c+ K+ v}
; i. o: w3 s' p+ p R) ]8 \+ f/ W//数据发送函数+ O; m& N$ Q0 u
void I2C_SendByte(uchar dat); F0 Q8 u+ h7 G- I
{* H- q. w/ O$ ?+ z7 N
uchar i;2 p$ ?, J& z" F* }9 c
for(i=0;i<8;i++)
2 O% x- p& w: S+ E: `{* E; _% R+ j. m0 O. D( d( o1 w
if(dat&0x80)//dat与0x80进行与运算
0 d. n( K5 \1 h& l7 `% X7 WSDA = 1;
' c8 b( {/ h# j3 |! |1 F) n, Aelse4 m9 i1 v7 b. I# z
SDA = 0;) f9 _: l! c" G$ y5 h
SCL = 1;! ?1 S7 ^! [3 ]9 ~+ |5 c7 q
delay_1us(1);
/ h/ I, K% C8 G* tSCL = 0;- ?. m5 S5 j2 ~% T$ U! P
delay_1us(1);4 f; {. D4 P! p" M' J% Q
dat<<=1;
! |7 }, s5 ^/ J" O* J}0 \6 p# C1 t" B% k% {
}
2 C) ~) Z; C% b+ \4 c//数据接收函数* Z. o! F7 ?8 X: ]
uchar I2C_RecvByte()//接受一个字节
% D) R K( j& ]" S( Y{/ m% D) ]9 K/ u7 @$ T7 w% u8 E5 ^
uchar i;* j/ b5 v1 ~! C' f7 W) ^% F% `
uchar dat = 0;
+ f& {6 a) l& G" T: i6 rWSDA = 1;
: `$ ]% W9 R6 \& q0 y( wfor(i=0;i<8;i++)//一字节 八位
( R5 ^8 V# a" K# C& z{
- i3 Q8 X) W. g5 Y' I4 Fdat<<=1;//dat左移一位,从高到低接受数据) Y& a, u! C8 y/ q7 V
WSCL = 1;//SCL拉高,数据有效- x2 c# J: F9 Q* q( W# X( ?
delay_1us(1);//延时保持; W! k2 M# V0 N0 n: T8 z
dat|=WSDA;
; \# m! L( y, ^5 c: qWSCL = 0;//SCL拉低结束数据传输
, D v/ ^0 E0 | V2 m6 \delay_1us(1);//延时保持$ |& I* B/ ?3 }4 K# _# h, g
}
6 U# c& v8 K- y2 F8 i$ }$ h2 A4 ]return dat;//接受八次之后返回dat值/ R7 f* m A; _" o8 W( U" Z/ J
}
/ n4 z7 P0 \# I$ v# E
: s+ G! t5 V( Z& _. H( Q( W3 }/ |二、I2C通信流程, \) r) `; G3 k* m: ]3 j) A
主设备向从设备完整的写操作) X3 p6 g3 z' c9 R6 w
1.主设备发出I2C开始信号:8 q2 |0 T% @+ g a/ [& v
2.主设备向I2C总线发送要操作的设备地址以及要进行的操作(读/写)7位设备地址+1位读写标识位(0读1写)- |& E' L$ u5 t$ A8 ]
3.主设备向从设备发送要操作的从设备寄存器地址2 I2 ], M( ?; u" I4 D
4.主设备向从设备发送要写入的寄存器的数据
8 ]% W2 x1 e1 d4 _, C' L6 ?5.发起I2C停止信号
/ G- Y, F$ `/ C% Q* j//对I2C设备的完整写操作
* X8 \5 H& l0 D5 h( I* w6 Svoid Single_WriteI2C(uchar REG_Address,uchar REG_data)
* R8 G; |/ `* ]" I& F% d{
* o$ ]* U& y9 U/ L1 q3 B. q# j1 YI2C_Start();4 X/ h$ Q f6 } x2 M4 V
I2C_SendByte(SlaveAddress<<1|0x00);
) f/ U( ?" g& ` x1 e) H6 tI2C_RecvACK();
X) h: ]1 m. \& t" D9 k! G& [I2C_SendByte(REG_Address);
9 [7 C" W+ F7 i- N: V$ t4 @& YI2C_RecvACK();+ p2 v4 |. A2 }2 T
I2C_SendByte(REG_data);
" d- f$ n {5 s* N, x( }I2C_RecvACK();' A4 {7 B9 N+ z/ u
I2C_Stop();
/ i0 f4 |. c" o$ Z" X9 f! |; `}' |0 m2 V- a, |* c2 B# i
处SlaveAddress为设备地址,使用宏定义。
& m3 ^" I: W4 ?7 j5 z- i主设备接收数据流程如下:
" i9 |" Q" d: B4 ^2 [1.主设备发出I2C开始信号:
# S8 y o) g* k5 w& c% {) u2.主设备向I2C总线发送要操作的设备地址以及要进行的操作(读/写)此时,最后一位要为 写操作8 ^/ O" e5 _% l) H3 n) e
3.主设备向从设备写入要读取的从设备寄存器的地址
) Z, I+ w; p# H# |" E: X4.主设备向总线发送7位I2C设备地址+1位读操作 标识8 R+ B* O0 K( Y, j4 {5 J
5.接收一个字节的数据
% a1 g3 j. Y% l. Z5 s, M( m6.发起I2C停止信号4 Z/ W- |4 j4 f1 T) s
0 C b4 L) X) G# {) G+ ?
void Single_ReADI2C(uchar REG_Address,uchar *REG_data,uchar len)
) p1 q# J( R9 X1 G9 H( z{
; Y8 S3 V. n+ ~7 X" s9 i$ I) Yint i;9 \ ^# G9 z8 u/ _4 T
I2C_Start();
- r/ P& B8 d% \" h! ~5 d* TI2C_SendByte((SlaveAddress<<1)|0x00);, Y% e3 f$ x {& s# [) e- y# ^' u
I2C_RecvACK();- ?9 y. W. g9 |2 ?. }7 d5 `
I2C_SendByte(REG_Address);1 c& T7 A) D. Y ^0 C$ x# |- `3 E
I2C_RecvACK();
3 c9 ?# ~1 F2 C v( d% ~I2C_Start();( ~! V; B# O- n/ {! ` J
Delay_Ms(50);
$ \9 m* T7 s1 t3 D6 K" p$ WI2C_SendByte((SlaveAddress<<1)|0x01);
7 f, u' D& \% f LI2C_RecvACK();
: @( p3 R& e& e) C4 K0 O% W2 y
for(i=0;i<len;i++)
/ M. d* h) Z ]2 R9 d( N; T9 b. ~0 T{
s( X: }' q- F1 `REG_data=I2C_RecvByte();
* V1 N) m# ~6 F4 w$ t( xif(i==len-1)
" W& H3 y+ P- f: V6 b( H" d2 I' R! V- B; [/ BI2C_SendACK(1);- T; U, d* M( y2 O6 H# A
else
0 z4 z) O* q9 c9 m8 a. S7 vI2C_SendACK(0);" N/ y0 P8 Z. l7 N3 b& a& R( `
}
* `' b3 W) M, O" lI2C_Stop();
6 ]1 U! V5 u7 Y- {9 b& E! S6 Q& B! M2 \
}! W2 e1 d! B, I( |; h% C
以上就是常见的I2C设备通信的通用函数。0 L6 @& q9 Q u; q. z& j+ V1 N
使用单片机的I/O接口,再配合时钟,多数的通信方式都可以被模拟出来,就比如51单片机只有一组串口,如果有需要,自然也可以使用两组通用I/O引脚进行串口通信的模拟。 |
|