抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

目前常用的微机与外设之间进行数据传输的串行总线主要有UART、1-wire、I2C和SPI总线

  • UART:是以异步方式进行通信(一条数据输入线,一条数据输出线)。

  • 1-wire:即单线总线,又叫单总线(只有一条线)。

  • I2C:同步串行2线方式进行通信(一条时钟线,一条数据线)。

  • SPI:同步串行3线方式进行通信(一条时钟线,一条数据输入线,一条数据输出线)。

原理

IIC串行总线,只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL

如下图所示,IIC总线上可以挂多个器件,而每个器件都有唯一的地址,这样可以标识通信目标。数据的通信的方式采用主从方式,主机负责主动联系从机,而从机则被动回应数据

image-20210822161125324

在多主机系统中,可能同时有几个主机企图启动总线传送数据。为了避免混乱,I2C总线要通过总线仲裁,以决定由哪一台主机控制总线。

在80C51单片机应用系统的串行总线扩展中,我们经常遇到的是以80C51单片机为主机,其它接口器件为从机的单主机情况

总线“线与关系”:I2C总线通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。连到总线上的任一器件输出的低电平,都将使总线的信号变低,

SDA = SDA1 & SDA2 & SDA3 & ..., SCL=SCL1 & SCL2 & SCL3 & ...

image-20210822161725608

起始信号和终止信号

SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;

SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。

image-20210822162305371

数据位有效性

SCL为高电平期间,数据线上的数据必须保持稳定

只有SCL信号为低电平期间,SDA状态才允许变化。

image-20210822162522027

IIC总线的传送与应答

每一个字节必须保证是8位长度。数据传送时,先传送最高位(MSB),每一个被传送的字节最后面都必须跟随一位应答位(即一帧共有9位)

主机通过从机发出的应答位来判断从机是否成功接收数据:

  • 从机正忙于其他事情,发出应答1,表示没有收到
  • 当从机空闲可以接收该字节数据时,从机会发出应答0

当主机接收数据时,它收到最后一个数据字节后,必须向从机发出一个结束传送的信号。这个信号是由对从机的“非应答”来实现的。然后,从机释放SDA线,以允许主机产生终止信号。

数据帧格式

在起始信号后必须传送一个从机的地址(7位),第8位是数据的传送方向位(R/T),用"0"表示主机发送数据(T),"1"表示主机接收数据(R)。

每次数据传送总是由主机产生的终止信号结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。

软件模拟IIC总线传送数据

主机可以采用不带IIC总线接口的单片机,如80C51、STC89C52等单片机,利用软件实现IIC总线的数据传送,即软件与硬件结合的信号模拟。

为保证数据的可靠性,I2C总线的数据传送有严格的时序要求。I2C总线的起始信号、终止信号、发送“0”及发送“1”的模拟时序 :

image-20210822163923932

模拟的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//起始信号
void I2cStart(){
SCL = 1;
SDA = 1;
delay5us(); //SDA在拉低低之前,应该先保持高电平时间>4us,这里用5us
SDA = 0;
delay5us(); //SDA保持低电平时间>4us,这里用5us
}

//终止信号
void I2cStop(){
SCL = 0;
SDA = 0;
SCL = 1;
delay5us();
SDA = 1;
delay5us();
}

第3、4个图表示先将SCL拉高5us,然后读取并返回SDA的值,读取后将SCL拉低,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//主机读取从机的应答
bit ReadACK(){
SCL = 1;
delay5us();
if(SDA){
SCL = 0;
return(1);
}
else{
SCL = 0;
return(0);
}
}


发送数据i,在SCL为低时,将SDA拉高或拉低(根据发送的数据确定,如果发送1,则拉高,反之拉低);然后将SCL拉高,保持5us,再将SCL拉低,这样就发送了一位(bit)数据,最后释放总线

1
2
3
4
5
6
7
8
9
10
11
12
//主机发送应答
void SendACK(bit i){
SCL = 0;

if(i) SDA = 1;
else SDA = 0;

SCL = 1;
delay5us();
SCL = 0; //拉低时钟总线
SDA = 1; //释放数据总线
}

串行EEPROM的扩展

Ateml公司的AT24C系列:

  • AT24C01:128字节(128×8位)

  • AT24C02:256字节(256×8位)

  • AT24C04:512字节(512×8位)AT24C08:1K字节(1K×8位)

  • AT24C16:2K字节(2K×8位)

写入过程

**获取地址码:**AT24C系列E2PROM芯片地址的固定部分为1010,而当A2、A1、A0引脚接高、低电平后就得到确定的3位编码。形成的7位编码即为该器件的地址码。例如在开发板中A2, A1, A0都接地,则这7位码为 1010000

image-20210824140820582

**写操作:**单片机进行写操作时,首先发送该器件的7位地址码和写方向位“0”(共8位,即一个字节),发送完后释放SDA线并在SCL线上产生第9个时钟信号。被选中的存储器器件在确认是自己的地址后,在SDA线上产生一个应答信号作为相应,单片机收到应答后就可以传送数据了。

传送数据:

  • 这个过程单片机首先发送一个字节被写入器件的存储区首地址,收到存储器器件的应答后,单片机就逐个发送各数据字节,但每发送一个字节后都要等待应答。

  • AT24C系列器件片内地址在接收到每一个数据字节地址后自动加1(表示相对之前写入的位置往后移一位)

  • 当要写入的数据传送完后,单片机应发出终止信号以结束写入操作。写入n个字节的数据格式如下:

    image-20210822171711924

    说明:S为起始信号,然后是7位地址码+ 0(0表示写操作),然后写入第一个数据存放位置的首地址,后面的写入的数据自动往后移动一位,写入1Byte数据后,器件发送应答A确认已写入,最后如果写入完成后发送终止信号P。

  • 注意:在芯片的“一次装载字节数”(不同芯片字节数不同)限度内,只需输入首地址。装载字节数超过芯片的“一次装载字节数”时,数据地址将“上卷”,前面的数据将被覆盖。

读出过程

单片机先发送该器件的7位地址码和写方向位“0”(“伪写”),发送完后释放SDA线并在SCL线上产生第9个时钟信号。被选中的存储器器件在确认是自己的地址后,在SDA线上产生一个应答信号作为回应。

然后,再发一个字节的要读出器件的存储区的首地址,收到应答后,单片机要重复一次起始信号并发出器件地址和读方向位(“1”),收到器件应答后就可以读出数据字节,每读出一个字节,单片机都要回复应答信号。

当最后一个字节数据读完后,单片机应返回以“非应答”(高电平),并发出终止信号以结束读出操作。

image-20210822171925336

完整代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
/*********************************************************************************
* 【实验平台】: 清翔 QX-MCS51 单片机开发板
* 【外部晶振】: 11.0592mhz
* 【主控芯片】: STC89C52
* 【编译环境】: Keil μVisio4
* 【程序功能】: IIC通信,AT24C02读写数据,数码管显示数据。
**********************************************************************************/
#include <reg52.h>
#include <intrins.h>

#define uint unsigned int
#define uchar unsigned char
#define At24c02ADDR 0XA0 //AT24C02硬件地址
#define I2cRead 1 //I2C读方向位
#define I2cWrite 0 //I2C写方向位

sbit DU = P2^6;//数码管段选
sbit WE = P2^7;//数码管段选
sbit SCL = P2^1;//I2C时钟总线
sbit SDA = P2^0;//I2C数据总线
uchar num;//数码管显示的值
bit AckFlag;//应答标志位

//共阴数码管段选表0-9
uchar code SMGduan[]= {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F,};
//数码管位选码
uchar code SMGwei[] = {0xfe, 0xfd, 0xfb};

/*====================================
函数 : delay(uint z)
参数 :z 延时毫秒设定,取值范围0-65535
返回值 :无
描述 :12T/Fosc11.0592M毫秒级延时
====================================*/
void delay(uint z){
uint x,y;
for(x = z; x > 0; x--)
for(y = 114; y > 0 ; y--);
}

/*====================================
函数 :display(uchar i)
参数 :i 显示数值,取值范围0-255
返回值 :无
描述 :三位共阴数码管动态显示
====================================*/
void display(uchar i){
static uchar wei;
P0 = 0XFF;//清除断码
WE = 1;//打开位选锁存器
P0 = SMGwei[wei];
WE = 0;//锁存位选数据
switch(wei){
case 0: DU = 1; P0 = SMGduan[i / 100]; DU = 0; break;
case 1: DU = 1; P0 = SMGduan[i % 100 / 10]; DU = 0; break;
case 2: DU = 1; P0 = SMGduan[i % 10]; DU = 0; break;
}
wei++;
if(wei == 3)
wei = 0;
}
//定时器0初始化
void timer0Init(){
EA = 1; //打开总中断
ET0 = 1;//打开定时器0中断
TR0 = 1; //启动定时器0
TMOD |= 0X01; //定时器工作模式1,16位定时模式
TH0 = 0xED;
TL0 = 0xFF; //定时5ms
}
/****************************************************
IIC通信代码
****************************************************/

/*====================================
函数 :delay5us()
参数 :无
返回值 :无
描述 :5us延时函数
====================================*/
void delay5us(){
_nop_();
}

/*====================================
函数 :I2cStart()
参数 :无
返回值 :无
描述 :I2C总线起始信号
====================================*/
void I2cStart(){
//时钟总线为高电平期间数据总线又高变低产生起始型号
SCL = 1;
SDA = 1;
delay5us();//状态保持5us
SDA = 0;
delay5us();//状态保持5us
}

/*====================================
函数 :I2cStop()
参数 :无
返回值 :无
描述 :I2C总线停止信号
====================================*/
void I2cStop(){
//时钟总线为高电平期间,数据总线从高变低产生终止信号
SCL = 0;
SDA = 0;
SCL = 1;
delay5us();//状态保持5us
SDA = 1;
delay5us();//状态保持5us
}

/*====================================
函数 :ReadACK()
参数 :无
返回值 :1非应答,0应答
描述 :I2C总线读从机应答信号
====================================*/
bit ReadACK(){
SCL = 0; //拉低时钟总线,允许从机控制SDA
SCL = 1; //拉高,读SDA
delay5us();
if(SDA){ //NOACK
SCL = 0;
return(1); //返回1
}
else{ //ACK
SCL = 0;
return(0); //返回0
}
}

/*====================================
函数 :SendACK(bit i)
参数 :1主机发送非应答,0发送应答
返回值 :无
描述 :主机发送应答信号
====================================*/
void SendACK(bit i){
SCL = 0;//拉低时钟总线,允许主机控制SDA
if(i) //发非应答
SDA = 1;
else //发应答
SDA = 0;
SCL = 1; //拉高总线,让从机读SDA
delay5us();//保持5us
SCL = 0; //拉低时钟总线,允许SDA释放
SDA = 1;//释放数据总线
}

/*====================================
函数 :I2cSendByte(uchar DAT)
参数 :DAT需要发送的数据
返回值 :无
描述 :I2C发送一个字节数据
====================================*/
void I2cSendByte(uchar DAT){
uchar i;
for(i=0; i<8; i++){ //分别写8次,每次写1位
SCL = 0;//拉低时钟总线,允许SDA变化
if(DAT & 0x80)//先写数据最高位
SDA = 1; //写1
else
SDA = 0; //写0
SCL = 1; //拉高时钟,让从机读SDA
DAT <<= 1; //为发送下一位左移1位
}
SCL = 0; //拉低时钟总线,允许SDA释放
SDA = 1;//释放数据总线
}

/*====================================
函数 :At24c02Write(uchar ADDR, DAT)
参数 :ADDR 单元地址0-255,DAT 需要输入的数据0-255
返回值 :无
描述 :At24c02指定单元写入一个字节数据
====================================*/
void At24c02Write(uchar ADDR, DAT){
I2cStart();//I2C起始信号
I2cSendByte(At24c02ADDR + I2cWrite);//发送器件地址加读写方向位
if(ReadACK()) //读从机应答
AckFlag = 1; //NOACK
else
AckFlag = 0; //ACK
I2cSendByte(ADDR);//发送储存单元地址字节
if(ReadACK())//读从机应答
AckFlag = 1; //NOACK
else
AckFlag = 0; //ACK
I2cSendByte(DAT);//发送一字节数据
if(ReadACK())//读从机应答
AckFlag = 1; //NOACK
else
AckFlag = 0; //ACK
I2cStop(); //I2C停止信号
}

/*====================================
函数 :I2cReadByte()
参数 :无
返回值 :返回读出的一字节数据
描述 :I2C总线读一字节数据
====================================*/
uchar I2cReadByte(){
uchar i, DAT;
for(i=0; i<8; i++){ //分别读8次,每次读一位

DAT <<= 1; //数据左移1位,准备接收一位
SCL = 0; //拉低时钟总线,允许从机控制SDA变化
SCL = 1; //拉高时钟总线,读取SDA上的数据
if(SDA)
DAT |= 0X01; //为1则写1,否则不行执行写1,通过左移补0
}
return(DAT); //返回读出的数据
}

/*====================================
函数 :At24c02Read(uchar ADDR)
参数 :ADDR 单元地址 0-255
返回值 :返回指定单元的数据
描述 :读AT24C02指定单元内数据
====================================*/
uchar At24c02Read(uchar ADDR){
uchar DAT;
I2cStart();//I2C起始信号
I2cSendByte(At24c02ADDR + I2cWrite);//发送器件地址加读写方向位
if(ReadACK())//读从机应答
AckFlag = 1; //NOACK
else
AckFlag = 0; //ACK
I2cSendByte(ADDR);//I2C发送一个字节
ReadACK();//读从机应答
I2cStart();//再次产生I2C起始信号
I2cSendByte(At24c02ADDR + I2cRead);//发送器件地址加读写方向位 读
if(ReadACK())//读从机应答
AckFlag = 1; //NOACK
else
AckFlag = 0; //ACK
DAT = I2cReadByte();//读一字节
SendACK(1);//主机发送非应答
I2cStop(); //I2C停止信号
return(DAT);//返回读出数据

}

void main()//main函数自身会循环{
timer0Init();//定时器0初始化
EA = 0;//屏蔽中断
At24c02Write(3, 188);//给第3单元写入数据“188”
delay(1);//延时等待AT24C02处理
num = At24c02Read(3);//读出第3单元内数据送给显示变量
if(AckFlag)//当从机非应答
P1 = 0;//亮P1所有灯
else
P1 = 0XFF;//灭P1所有灯
EA = 1;//开中断
while(1);
}

//定时器0中断函数
void timer0() interrupt 1 {
TH0 = 0xED;
TL0 = 0xFF; //定时5ms
display(num); //数码管显示函数
}

评论