II2C(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备(特别是外部存储器件)。 UART通信只需要一条线即可完成,I2C总线是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。 从应用上来讲 ,UART通信多用于板间通信,I2C通信多用于板内通信。
直接来看时序图
时序图
I2C总线有起始信号、数据传输和停止信号。
解析
从图中可以看出,
起始信号是SCL为高的时候,SDA产生一个下降沿
以五一为例,SDA对应P3.6,SCL对应P33.7
那么产生起始信号的程序如下。这里强调SCL为时钟信号,SDA为数据信号
#define I2CDelay() {_nop_();_nop_();_nop_();_nop_();}
sbit I2C_SCL = P3^7;
sbit I2C_SDA = P3^6;
void I2CStart()
{
I2C_SDA = 1;
I2C_SCL = 1;
I2CDelay();
I2C_SDA = 0;
I2CDelay();
I2C_SCL = 0;//这里要拉低SCL,是为进行数据读取做准备
}
来看数据传输
1位的传输过程,SCL为低的时候,SDA产生变化,SCL为高的时候,读取SDA数据,
具体写一个字节程序如下
bit I2CWrite(unsigned char dat)
{
bit ack = 0;
unsigned char mask;
for(mask=0x80; mask!=0; mask>>=1) //先发高位数据,如果一个字节8位还没发完,则继续右移一位
{
if((mask&dat) == 0)//与上mask为0,则代表该位SDA为0
I2C_SDA = 0;
else
I2C_SDA = 1; //否则为1
I2CDelay();
I2C_SCL = 1;//拉高写入SDA数据
I2CDelay();
I2C_SCL = 0;//拉低继续下一位写入
}
I2C_SDA = 1;//释放SDA总线
I2CDelay();
I2C_SCL = 1;
ack = I2C_SDA;//获取应答
I2CDelay();
I2C_SCL = 0;
return ack;
}
从时序图中可以看出,在数据传输位后面有一个第九位,ACK应答位,亦即每一个字节的传输,从机都会应答一个位,代表这一个字节是否传输成功,所以函数最后返回应答位,应答位0表示成功,1表示失败
再看停止信号
先SCL为高,SDA由低变高,即产生一个上升沿
void I2CStop()
{
I2C_SCL = 0;
I2C_SDA = 0;
I2CDelay();
I2C_SCL = 1;
I2CDelay();
I2C_SDA = 1;
I2CDelay();
}
实际应用
从板子上EEPROM中读取一个字节和写一个字节,首先通信需要知道的是对方的地址,就是找到对方并且告诉对方我要对你进行读或者写操作。
所以读写操作的第一个字节就是先写入要进行数据传输对象的地址以及要进行的操作。
IIC的寻址模式
第一个字节
发送方:7位地址+1位读写:0 写 1 读
接收方:回应一个ACK
24C02芯片的地址从数据手册中查到为1010,十六进制即0x50
同样也在数据手册里面查到了它的读写时序图
读的时候还需要知道一个事情,就是连续读多个字节和读一个字节是有区别的。每读一个字节,如果还想继续读下一个字节,就发送一个“应答位0”,如果不想继续读了,就发送一个“非应答位1”。
怎么理解,看程序
unsigned char I2CRead()
{
unsigned char mask;
unsigned char dat;
I2C_SDA = 1;
for(mask=0x80; mask!=0; mask>>=1)
{
I2CDelay();
I2C_SCL = 1;
if(I2C_SDA == 0)
dat &= ~mask;
else
dat |= mask;
I2CDelay();
I2C_SCL = 0;
}//到这里一个字节八位的数据已经读取完毕,读跟写其实非常相似
I2C_SDA = 1;//这个就是第9位,1告诉24C02我只读一个字节,0表示我还要再读取一个字节
I2CDelay();
I2C_SCL = 1;
I2CDelay();
I2C_SCL = 0;
return dat;
}
所以一个字节读取的完整流程是
unsigned char ReadByte(unsigned char addr)
{
unsigned char dat;
I2CStart();
I2CWrite(0x50<<1);//写入要读取24C02的地址,这里先最后1位为0,先选择写操作
I2CWrite(addr);//具体该24C02上的数据起始地址
I2CStart();
I2CWrite((0x50<<1) |0x01);//重新发送地址,正式选择读操作,最后1位为1
dat = I2CRead();
I2CStop();
return dat;
}
写一个字节比读取要简单一点,不需要重新发送地址和选择读写操作
void WriteByte(unsigned char addr, unsigned char dat)
{
I2CStart();
I2CWrite(0x50<<1);
I2CWrite(addr);
I2CWrite(dat);
I2CStop();
}
这里只是简单的介绍了1个字节的读写,在实际开发中数据远远不止一个字节,不过IIC协议是固定的,了解其大概原理,会比只会用库要好一点。