|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
7 A: k& [9 s5 L* g( e+ s现在很多单片机都已经对常见的通信方式进行了集成,但是51单片机没有这些功能。如果想要使用其他通信方式,只能使用通用I/O引脚进行模拟。/ c _ d, G9 A% [
今天我们就以I2C通信作为例子,演示一下如何模拟这些通信方式。
- X7 ?' w" ~, f; b
! X* W. G ~: j3 ?* }一、I2C通信介绍
, n# x n1 c5 TI2C总线是一种简单的双向串行总线结构。只需要SDA(数据线)、SCL(时钟线)两根线就能在器件之间传送信息。
2 {* ?: I9 l3 {4 m# p' ], WI2C通信中,器件有主有从,一般以控制产生时钟信号的器件作为主器件,其余作为从器件。每个从器件又有一个7位设备地址,用于寻址。这就意味着,一条I2C总线上可以并联多个I2C设备,主器件通过设备地址可以分别对这些器件进行操作。
% }1 d9 b- x0 c一次I2C通信可分为三个部分:I2C通信起始信号(SCL高电平下,SDA从高到低跳变);I2C有效通信;I2C通信结束信号(SCL高电平下,SDA由低到高跳变)。
+ A9 e9 A6 j2 S7 u5 K, P) U同时,在I2C有效通信阶段,数据接收方每次成功接收一字节数据,都会主动将SDA线拉低,表示传输成功。" H8 x2 Y$ b, c4 m$ B7 h9 ]
- m* `0 g& d, y5 z" z: D
由此,我们可以编写得到I2C设备的基本读写函数与等待应答函数:
* s& c# l/ R i- s7 l//开始信号
5 L% n! L7 U/ h0 Z% ?. F! g+ vvoid I2C_start()
\" c! l2 M; q+ h3 r( g& P{
/ o" k" X6 }- V6 jSDA = 1;//同时拉高数据线与时钟线% f" |% a; d$ t8 j
SCL = 1;
' R0 g4 k2 V" D9 odelay_1us(1);
- p- N- z$ i6 Z* d+ g3 Q% [SDA = 0;//SCL高电平,SDA下降,启动I2C通信
$ z, O) c3 V) J _delay_1us(1);//等待1us% M% r4 M. M8 X, ^ H
SCL = 0;
& t% q/ ^' u1 I, Q- b1 J}
9 |8 a1 T4 @( p/ b2 {2 U% q. O( x9 ^9 i( `9 M H
//结束信号+ |3 A* Z, S2 _( ~
void I2C_Stop()
" }) ]$ Z: J2 q% F{+ E) f: G/ L/ O7 @
SDA = 0;
& [) F" u; G/ v6 {, W ~SCL = 1;1 k: q7 f, L6 l2 D
delay_1us(1);" t( i9 U( E/ h! O: K4 {
SDA = 1;. K7 ~% q9 f. x& }. A2 Q; ]
delay_1us(1);
( R! T( o, N( Q& b- `}
1 X; N: q) h: {2 M: Y' _' F% q7 O7 F/ T7 ]0 W4 U
//主设备应答函数
" W6 ]( `5 [7 ?; G2 G1 Zvoid I2C_SenDACK(bit ack). c/ p0 K! g) v5 E$ O+ B
{
# W# ^$ w) p) d4 y {: K8 I1 nSCL = 0;//SCL 拉低,数据改变9 N7 n8 O* B9 P: w/ d
SDA = ack;//数据位改变
/ k1 `5 @0 }' s1 O! Cdelay_1us(1);//延时等 稳定信号, D2 o3 a( U% y5 O+ W& }* x
SCL = 1;//SCL拉高,传送数据
. k7 r/ o3 E: i3 i; Odelay_1us(1);//等待
! G$ U0 j, P1 Y- y1 cSCL = 0;//SLC拉低
% O4 K" o# v q7 i}7 v, ^) k s+ Z# A* B
. g! |( _+ l* q' m, q" y//从设备应答函数
5 P9 Z$ L" B/ u# rbit I2C_RecvACK()
t% A! p4 H# T6 W5 m8 o{
! d H; _% B& A) Tuchar wait=0xff;
5 _$ i' ^# c" B1 W6 h% E/ s% n5 k$ pSCL = 1;//SCL时钟拉高 数据有效/ s; d0 |/ d0 B; \7 }4 `
while(SDA&&wait--);//SDA高电平,并且在512us没有走完
' S6 i8 A( S! Z* W" B! ?1 K" Zif(wait<=0)
, Z6 f# `+ J& {9 E{
/ B) ^0 b5 g. d. I- KI2C_Stop();
; z2 |: l0 h* q, U1 Wreturn -1;( f5 m' K- p) ]& R# X4 {, ]
}/ j1 Q5 i" U; a
WSCL = 0;//否则SCL为低电平,结束该应答
( o5 u, {7 K) d, K8 L" Kdelay_1us(1);
; a2 ^4 d* b: L: \2 E9 Zreturn 0;; B' z ?# `' v
}
+ ^/ J$ Q, X }//数据发送函数
. R9 h3 Y6 s# ~void I2C_SendByte(uchar dat)
) P( L5 ^& D% }{
0 o: J% _& W( J9 O8 a& ^! X' H! w. \6 Yuchar i;
. F4 x: H1 X0 C9 _' u( c pfor(i=0;i<8;i++)6 W2 ]1 u9 G. [7 G5 v! b) @
{! a6 n' O1 ^1 g' @& Z6 l
if(dat&0x80)//dat与0x80进行与运算
/ B: _/ N5 w" ?1 P3 E( sSDA = 1;
. R' T) @4 A( S2 B; j2 `) ielse; r6 y6 t2 E' Z' g# v: j' V
SDA = 0;
' K, Z( ?: R; k6 J8 gSCL = 1;9 \6 D8 L- i* i, y; J
delay_1us(1);
# p' d) ~$ ^( w9 fSCL = 0;, z& L% c# ^! M2 `
delay_1us(1);" ]" L" t6 C0 s E
dat<<=1;
5 ?: T! x& j8 H}
3 c8 j o g* O7 p}
. k8 U8 a* x: _8 |" \//数据接收函数" O9 F6 A' r8 J5 S% ?# E
uchar I2C_RecvByte()//接受一个字节: m) G" P' H/ ~" Y5 H$ Y
{; S- W0 W s6 F# w# ]
uchar i;- _4 [+ Z( W; Q6 D
uchar dat = 0;
7 _7 p0 o! l( z$ ?WSDA = 1;; `4 v" }/ B1 H8 r$ t
for(i=0;i<8;i++)//一字节 八位. Q; j7 `2 s# s; R
{
# ^! x1 X2 l" ]. j edat<<=1;//dat左移一位,从高到低接受数据( Z4 l3 g# u" ~: a6 H
WSCL = 1;//SCL拉高,数据有效5 p( Z# o' n2 `! b2 n! K
delay_1us(1);//延时保持
3 X5 n7 {6 m$ U8 A5 Hdat|=WSDA;
' A) U! o8 r4 S9 t* YWSCL = 0;//SCL拉低结束数据传输
8 j" g" N6 n# v2 A% jdelay_1us(1);//延时保持! ]4 F, j- L+ Q
}
- Y+ F' f3 i0 b- W- e& @return dat;//接受八次之后返回dat值: N) O* g+ `+ G; Q5 N6 M0 \
}
( g. V, i& ]3 V$ h4 e& }" e$ P; `! U2 _
二、I2C通信流程
# H6 F4 Y% e3 z5 V U. ^) w主设备向从设备完整的写操作1 g3 d+ I6 i9 t$ O( [& K- m- V
1.主设备发出I2C开始信号:
. A0 x+ J9 X# V2 O2.主设备向I2C总线发送要操作的设备地址以及要进行的操作(读/写)7位设备地址+1位读写标识位(0读1写)
* P" p1 S' F7 ^% ?, [( j3.主设备向从设备发送要操作的从设备寄存器地址
8 ]. p1 v* Z1 D4 Y3 U+ H9 O4.主设备向从设备发送要写入的寄存器的数据
) A: F9 Z9 `; e+ c. U- D5.发起I2C停止信号
$ I- t, I3 r% N3 L8 y* y/ U( v% ?//对I2C设备的完整写操作
" s) z) N" f- V" X' ?) {void Single_WriteI2C(uchar REG_Address,uchar REG_data)0 m* S- N7 P9 d! r8 p; L
{
0 v/ F/ J. i' F% N( rI2C_Start();' q! D: F6 C0 Y& M4 R. n
I2C_SendByte(SlaveAddress<<1|0x00);/ P% `' J* x$ H% s3 J0 K: ^! R# y t
I2C_RecvACK(); M' P* h! x% ]9 g" `/ z
I2C_SendByte(REG_Address);
; Z* {8 ]4 Y2 u# w( l+ g0 iI2C_RecvACK();' t, T' T: k* z% ~! H8 g6 s$ `1 \
I2C_SendByte(REG_data);
& v% x1 X& W s# K0 II2C_RecvACK();3 `: f: U3 B, z3 }. L
I2C_Stop();
" h5 [/ \& S$ K6 l9 a, D3 q/ ]}2 v7 s7 H* U! U2 V& V
处SlaveAddress为设备地址,使用宏定义。
! p7 N9 n& w5 {主设备接收数据流程如下:
$ d+ p% D' L& o& M: q1.主设备发出I2C开始信号:
" P5 O+ n7 V6 [ i6 g) C4 \2.主设备向I2C总线发送要操作的设备地址以及要进行的操作(读/写)此时,最后一位要为 写操作
: o9 {: ^+ @% ?3.主设备向从设备写入要读取的从设备寄存器的地址
! B( I6 U/ I/ T h) u4 ~2 k4.主设备向总线发送7位I2C设备地址+1位读操作 标识# G6 H$ w1 t& p! Z+ {# ^3 P
5.接收一个字节的数据2 C: E6 V8 N/ L* {6 u: C: O
6.发起I2C停止信号
. s2 O! z" M; ~; }3 i3 \6 Z3 q
* x- Y& G% R4 h! r" h) z2 Pvoid Single_ReADI2C(uchar REG_Address,uchar *REG_data,uchar len) s- j- I7 U/ b9 j6 T6 M* ?7 M ~6 E
{
. i8 d' i: V' j( iint i;4 W+ H/ g" B# Q" ]( ^
I2C_Start();
+ B0 [( A: W; x& V6 r( V$ z R8 X. yI2C_SendByte((SlaveAddress<<1)|0x00);0 B+ Q& s, J j$ M4 l
I2C_RecvACK();
! q$ e- M1 t9 Z) w rI2C_SendByte(REG_Address);0 ?6 h! c& [/ o
I2C_RecvACK();2 i* P( J1 Z2 }& F9 W
I2C_Start();
) q$ w! p) j; Z/ GDelay_Ms(50);
& I+ |! ^! @) L/ f+ C1 |/ N$ vI2C_SendByte((SlaveAddress<<1)|0x01);
2 ?' q, d- N* [! aI2C_RecvACK();' S5 X O8 w3 V4 p; M5 J, s: m' j: a" J
4 Z1 F. l$ {2 O8 I# z( Zfor(i=0;i<len;i++)
3 T, m1 F+ y W2 I: i{7 [/ D* R4 Z( S- W b: g
REG_data=I2C_RecvByte();
3 C. H0 g% _' m, o, b; jif(i==len-1)- ^, u* `3 L$ @1 u
I2C_SendACK(1);) V: {; Q" Y4 N4 Z8 |1 }
else2 [" R# u% p4 |4 Y& E4 S
I2C_SendACK(0); E* M/ {. [) T. Q. o% C8 Q. U0 G
}
4 S [3 Y6 b5 v3 a' O8 r( fI2C_Stop();5 k8 E. `3 e3 U; F
/ l- F6 N. m1 t4 k}
6 y1 A- C2 a# ^* k' p& P以上就是常见的I2C设备通信的通用函数。
2 ^3 [- `+ _7 C" R1 y& W使用单片机的I/O接口,再配合时钟,多数的通信方式都可以被模拟出来,就比如51单片机只有一组串口,如果有需要,自然也可以使用两组通用I/O引脚进行串口通信的模拟。 |
|