目录
目录
前言
本篇介绍的是以51单片机开发板上的AT24C02存储芯片为例的I2C通信,文中大量篇幅会倾向于介绍I2C的通信协议,逻辑时序和开发方法。
先来简短地介绍下存储器,存储器主要分为两类:易失性存储器(RAM)和非易失性存储器(ROM),也就是存储器中的数据掉电丢失和掉电不丢失的区别。RAM的读写速度要远大于ROM,容量较小且成本昂贵,典型的如单片机的内存,手机内存电脑内存等。ROM是只读存储器,但也可以进行写操作,是因为早期的ROM是不可以重复写入数据的,所以关于它的命名就是个历史遗留问题,不用太过于执着,就好比火车还叫火车,即使它早就是不烧煤了。AT24C02是一种EEPROM(Electrically Erasable Programmable Read - Only Memory)即电控可擦除可编程只读存储器,容量大小为2k bit,类似的还有如W25Qxx系列,这些存储芯片多数都是用I2C通信的方式读写的。
一、I2C协议的相关介绍
I2C(Inter-Integrated Circuit)其实是IIC BUS的简称,即内部集成电路总线,是PHILIPS(飞利浦)公司发明的一种通信协议。
硬件线路:
I2C的通信只需要两根传输线,结构上很简单。
①SCL(serial clock):时钟线,是I2C主设备向从设备提供时钟信号的通道。
②SDA(serial data): 数据线,通过SDA线传输数据。
I2C的传输线外接了上拉电阻,当总线处于空闲状态时,SCL和SDA默认都为高点平,IIC传递信息的方式以拉低电平为主要手段,为什么要这样设计呢?我是这样想的,因为实现从高电平到低电平的跳变很容易,只需要接地就可以了,外面接了上拉电阻,默认状态一直是高电平,所以一接地就会产生下降沿,设备通过检测这些下降沿实现相互通信。这样设计方便控制,相反则需要设备向外输出电流,这样会增加芯片的负担。
通信特征:
①串行:I2C属于穿行通信,所有数据以bit为单位在SDA上串行传输。
②同步:I2C拥有时钟线SCL,通信双方工作在同一个时钟下,所以为同步通信。
③半双工:由于只有一根数据线,不能同时进行数据发送和接收,故I2C为半双工。
④低速率:I2C一般都用于同一平台的两个IC之间的通信,而且传输的数据量较小,所以本身的传输速率相比于其他协议很低(一般也就几百KHz)。
⑤非差分:因为I2C通信速率不高,且双方距离很近,所以使用电平信号通信。
⑥一主多从:此外I2C总线上还可以挂载多设备。
I2C的优缺点都很明显,优点是硬件资源利用率高,传输稳定,设计精巧,广泛易用,缺点就是传输速率较低。
主要用途:
SOC和周边外设之间的通信(典型的如EEPROM、电容触摸IC),AT24C02等存储设备,MPU6050等器件。
二、I2C的逻辑时序
51单片机并没有集成I2C的硬件外设,所以要想实现I2C通信,必须要用软件来模拟I2C的时序,它的几个个基本的时序单元是真样的:
起始信号:SCL高电平期间,SDA从高电平切换到低电平。
终止信号:SCL高电平期间,SDA从低电平切换到高电平。
发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节。
接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)。
发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。
接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)。
发送一帧数据:第一个字节为地址,意为向某某地址发送某某数据
接收一帧数据:第一个字节为地址,意为从某某地址接收到某某数据
这就是一帧完整数据了,以起始信号S为帧头,终止信号P为帧尾,其间包括地址字节,数据字节,发送应答和接收应答。
三、I2C读写AT24C02
AT24C02存储器读写非常简单,并不需要操纵寄存器,它的内部有0-255个地址位,都可以存储8位数据,8*256=2048bit,刚好是 2K bit,也就是AT24C02的容量大小。可以用I2C的通信层函数对其某地址位上进行读操作和写操作。
四、代码
I2C通信层代码:
#include <REGX52.H>
sbit I2C_SCL=P2^1; //AT24C02的I2C引脚定义
sbit I2C_SDA=P2^0;
void I2C_Start(void) //起始信号
{
I2C_SDA=1; //起始信号必须先拉高SCL和SDA,以确保后面的时序正确
I2C_SCL=1;
I2C_SDA=0;
I2C_SCL=0;
}
void I2C_Stop(void) //终止信号
{
I2C_SDA=0;
I2C_SCL=1;
I2C_SDA=1;
}
void I2C_SendByte(unsigned char Byte) //发送一个字节
{
unsigned char i;
for(i=0;i<8;i++) //循环8次,采样每一位的数据
{
I2C_SDA=Byte&(0x80>>i);
I2C_SCL=1;
I2C_SCL=0;
}
}
unsigned char I2C_ReceiveByte(void) //接收一个字节
{
unsigned char i,Byte=0x00;
I2C_SDA=1;
for(i=0;i<8;i++)
{
I2C_SCL=1;
if(I2C_SDA){Byte|=(0x80>>i);}
I2C_SCL=0;
}
return Byte;
}
void I2C_SendAck(unsigned char AckBit) //发送应答
{
I2C_SDA=AckBit;
I2C_SCL=1;
I2C_SCL=0;
}
unsigned char I2C_ReceiveAck(void) //接收应答
{
unsigned char AckBit;
I2C_SDA=1;
I2C_SCL=1;
AckBit=I2C_SDA;
I2C_SCL=0;
return AckBit;
}
I2C读写 AT24C02代码:
#include <REGX52.H>
#include "I2C.h"
#define AT24C02_ADDRESS 0xA0 //AT24C02作为从机的地址,AT24C02的固定地址为1010,
//可配置地址本开发板上为000所以SLAVE ADDRESS+W为0XA0,SLAVE ADDRESS+R为OXA1
void AT24C02_WriteByte(unsigned char WordAddress,Data) //向AT24C02中某地址写数据
{
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS); //发送I2C要通信的从机地址,即AT24C02挂载在总线上的地址
I2C_ReceiveAck();
I2C_SendByte(WordAddress); //AT24C02中的地址位,与上面的地址概念不同,不要混淆,
I2C_ReceiveAck(); //可以理解为扇区,可选的地址值为0~255
I2C_SendByte(Data); //写入该地址位的数据
I2C_ReceiveAck();
I2C_Stop();
}
unsigned char AT24C02_ReadByte(unsigned char WordAddress) //读出AT24C02中某地址的数据
{
unsigned char Data;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS|0x01); //读操作时从机地址最低位置1
I2C_ReceiveAck();
Data=I2C_ReceiveByte(); //读某扇区的数据存到Data缓存中
I2C_SendAck(1);
I2C_Stop();
return Data;
}
示例程序:
#include <REGX52.H>
#include "AT24C02.h"
#include "Delay.h"
unsigned char Data1,Data2,Data3;
void main()
{
//分别向0、1、2地址位中写入0xAA、0xBB、0xCC
AT24C02_WriteByte(0,0xAA); Delay(5);
AT24C02_WriteByte(1,0xBB); Delay(5);
AT24C02_WriteByte(2,0xCC); Delay(5);
//分别读出0、1、2地址中的数据并存到Data1,Data2,Data3变量中
Data1=AT24C02_ReadByte(0); Delay(5);
Data2=AT24C02_ReadByte(1); Delay(5);
Data3=AT24C02_ReadByte(2); Delay(5);
while(1){;}
}
总结
对照时序图来理解I2C的代码,每一条语句都是和时序中的每一步一一对应。