原文章地址:http://blog.chinaunix.net/uid-16100003-id-3059814.html
- 原文内容
SDA和SCL已经通过上拉电阻被上拉,master可以控制(拉低或者释放)这两条线,而slaver只能控制SDA线。当master发送数据时,master会适时地将SDA和SCL拉低或释放(拉高)。确切的时序应该是这样的:
当mater要发送一个start时,mater会将SDA拉低,这就可以了,因为此时的SCL一定是High。好了,一个start就这样发出去了。而slaver也会发现这个start信号的发生,slaver便会准备好接收接下来的数据了。紧接着,master要发送一个Byte的数据了,一位一位的发出这8个bits。这时master会先将SCL拉低,然后在SCL为低的状态下将一个bit准备好放到SDA上(比如要发送一个 0,master就会通过拉低SDA来放好这个0),然后master会把SCL拉高(释放),此时slaver会立刻检测到SCL的变化,由此聪明的slaver便知道master已经将要发送的那个bit准备好了,slaver便会在这个SCL的高电平期间尽快(maser不会等你很久的哦)去读取一下SDA,嗯读到了一个0,slaver就把这个0放到自己的移位寄存器中待后续处理。master会在一个设定好的时间后把SCL再次拉低,然后在SCL为低电平期间把下一个bit放到SDA上,然后再把SCL拉高,然后slaver在SCL的高电平期间再去读SDA。。。。。如此反复8次,一个Byte的传输便告结束。当这8个bit发完后,SCL是处于低电平的(被master拉低的),SDA是出于高电平的(master已经释放了SDA)。
***当一个字节发送完毕后,master会释放SDA(拉高)并拉低SCL,此时slaver如果打算发出一个ACK的话,它必须在这个SCL被master拉低的短暂时间内去主动将SDA拉低并保持住 (此前我们说过,SDA此时已经被master释放,所以slaver才有机会去拉低这个SDA)***。master会在一个确定的时间后再次将SCL拉高,并在拉高的期间去读取SDA线的状态,如果读到低电平,则认为收到了来自slaver的响应(ACK),否则认为slaver没有响应(NACK)刚才发送的那一个Byte。这个过程就是我们说的i2c通讯中的第9个时钟周期。当master读完这个ACK / NACK 后,会再次将SCL拉低,用以通知slaver:第9个时钟周期已经结束,你现在可以释放SDA了。而此时master也可以向SDA上准备下一个Byte的第一个bit。继而重复上述过程。。。。。或者,master也许想在接下来发送一个stop过去,***那么master会在这个SCL为低的时间内将SDA拉低,而后再将SCL拉高,在SCL为高的期间再将SDA释放 (拉高) 。这样,一个STOP位就产生了。你会发现此后的***SDA和SCL都是高,这就是是所谓的总线空闲了!
一句话:SCL是单向的,由master控制。而SDA是双向的,master可以控制,slaver也可以控制。
- 总结
使用STC89C52单片机与AT24C02进行模拟IIC协议通信
共SCL和SDA两条线,通过这两条线上电平高低的组合实现
- 开始信号;
- 字节发送信号;包括八位数据发送信号,从机应答接受信号;
- 字节接受信号;只需对SCL进行控制,然后接受SDA上的信号,这里需要在SCL=1的时候读取SDA信号(因为此时SDA信号不变),应答信号直接由主机发出。
- 结束信号;
- 函数程序
#include "reg52.h"
#include "I2C.h"
void Delay10us(void) //误差 0us
{
unsigned char a,b;
for(b=1;b>0;b--)
for(a=2;a>0;a--);
}
void I2cStart()
{
SCL=0;
Delay10us();
SDA=1;
Delay10us();
SCL=1;
Delay10us();
SDA=0;
Delay10us();
}
void I2cStop()
{
SCL=0;
Delay10us();
SDA=0;
Delay10us();
SCL=1;
Delay10us();
SDA=1;
Delay10us();
}
unsigned char I2cSendByte(unsigned char dat)
{
unsigned char a,b=0;
for(a=0;a<8;a++) //发送8位数据位
{
SCL=0;
Delay10us();
SDA=dat>>7;
Delay10us();
dat=dat<<1;
SCL=1;
Delay10us();
}
SCL=0; //为释放SDA准备
Delay10us();
SDA=1; //释放SDA,把SDA控制权交给从机
Delay10us();
while(SDA)
{
b++;
if(b>200)
{
SCL=1; //SCL置高时,SDA=1;表示无应答;
Delay10us();
return 0;
}
}
SCL=1; //SCL置高时,SDA=0;表示有应答
Delay10us();
SCL=0; //为SDA变化做准备
Delay10us();
SDA=1; //SDA控制权交给主机
Delay10us();
return 1;
}
unsigned char I2cReadByte()
{
unsigned char a,dat=0;
SCL=0;
Delay10us();
for(a=0;a<8;a++)
{
dat=dat<<1; //接收端左移一位
SCL=1; //SCL置高
Delay10us();
dat=dat|SDA; //dat从MSB到LSB开始接受数据
SCL=0;
Delay10us();
}
SDA=1; //交换控制权
Delay10us();
SDA=0;
Delay10us();
SCL=1;
Delay10us();
SCL=0;
Delay10us();
return dat;
}
void At24c02WriteByte(unsigned char addr,unsigned char dat)
{
I2cStart();
I2cSendByte(0xa0);
I2cSendByte(addr);
I2cSendByte(dat);
I2cStop();
}
unsigned char At24c02ReadByte(unsigned char addr)
{
unsigned char rec;
I2cStart();
I2cSendByte(0xa0);
I2cSendByte(addr);
I2cStart();
I2cSendByte(0xa1);
rec=I2cReadByte();
return rec;
}
- 注意事项
应答前需:在SCL=0的情况下,将SDA置1,代表着交换SDA控制权;
对于谨慎对待SCL=1的执行,SCL=0—>1的过程伴随着对SDA上数据的读取。
- 发送数据函数流程:
开始信号——>发送总线地址(4位固定+3位可调+0)——>应答——>发送存储器地址(1-255)——>应答——>发送数据——>应答——>结束信号
读取数据函数流程:
开始信号——>发送总线地址(4位固定+3位可调+0)——>应答——>发送存储器地址(1-255)——>应答——>发送总线地址(4位固定+3位可调+1)——>应答——>发送数据——>应答——>结束信号