I2c - AT24C02实验
1.1 I2C总线介绍
I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线 两根通信线:SCL(Serial Clock)、SDA(Serial Data) 同步、半双工,带数据应答 通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度.
1.2 引脚及电路
1.3 I2C电路规范
所有的I2C设备的SCL连在一起,SDA连在一起 设备的SCL和SDA均要配置成开漏输出模式 SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右 开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相干扰的问题。
2.1 I2C时序结构
起始条件:SCL高电平期间,SDA从高电平切换到低电平。
终止条件:SCL高电平期间,SDA从低电平切换到高电平。
2.2 发送一个字节
发送一个字节:SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节.
2.3 接收一个字节
接收一个字节:SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)。
2.4 应答信号
发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答。 接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
2.5 I2C数据帧
S:起始位 标志。 SLAVE ADDRESS+W:地址写(高四位固定值1010),如果是写则是0xA0 。 SLAVE ADDRESS+W:读则是0xA1. RA:接收应答,0表示应答,1表示非应答 。 SA:发送应答 S:BYTE:发送的字节 。 R:BYTE:读字节 P:停止标志。
解读:主机发送一条数据--->从机收到(RA:0)--->主机写一个字节(S:BYTE 1)--->从机收到(RA:0)--->.....
主机读取一条数据--->从机收到(此时控制权交给从机)--->从机接收一个字节--->主机未应答-->结束
3.1 代码实现
分为三个模块:I2C.C,AT24CO2.C,min.c三个文件。
void I2C_Start(void)
{
I2C_SDA=1;//先给数据(SDA)拉高,因为它的状态不稳定
I2C_SCL=1;//从机读取SDA
I2C_SDA=0;
I2C_SCL=0;
}
/**
* @brief 停止位
* @param 无
* @retval 无
*/
void I2C_Stop(void)
{
I2C_SDA=0;
I2C_SCL=1;
I2C_SDA=1;
}
/**
* @brief 发送一个字节 I2C_SendByte
* @param Byte 发送的字节
* @retval 无
*/
void I2C_SendByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
//发送的数据就是传进来的数据
I2C_SDA=Byte&(0x80>>i);//先取出最高位,因为高位在前
I2C_SCL=1;//上升沿(sda不可变化),读取数据
I2C_SCL=0;//下降沿,SDA开始变化右移
}
}
/**
* @brief 接收一个字节
* @param 无
* @retval Byte
*/
unsigned char I2C_ReceiveByte(void)
{
unsigned char i,Byte=0x00;
//这里置1,主机放掉控制权,交给从机
I2C_SDA=1;//主机在接收之前要释放SDA
for(i=0;i<8;i++)
{
I2C_SCL=1;//主机读取
if(I2C_SDA){Byte|=(0x80>>i);}
I2C_SCL=0;
}
return Byte;
}
/**
* @brief 发送应答
* @param AckBit应答位 0表示应答,1表示非应答
* @retval 无
*/
void I2C_SendAck(unsigned char AckBit)
{
I2C_SDA=AckBit;
I2C_SCL=1;
I2C_SCL=0;
}
/**
* @brief 接收应答
* @param 无
* @retval AckBit
*/
unsigned char I2C_ReceiveAck(void)
{
unsigned char AckBit;
I2C_SDA=1;//释放应答,控制权交给从机
I2C_SCL=1;//主机读取数据
AckBit=I2C_SDA;//收到0表示应答,1表示非应答
I2C_SCL=0;
return AckBit;
}
根据上面的AT24C02数据帧来写AT24CO2.C文件
#define AT24C02_ADDRESS 0xA0
/**
* @brief AT24C02_WriteByte 写一个字节
* @param WordAddress 要写入字节的地址
Data 要写入字节数据
* @retval
*/
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
I2C_Start();//起始位
I2C_SendByte(AT24C02_ADDRESS);//主机地址写
I2C_ReceiveAck();//从机接收应答
I2C_SendByte(WordAddress);//字地址
I2C_ReceiveAck();//从机接收应答
I2C_SendByte(Data);//主机发送数据
I2C_ReceiveAck();
I2C_Stop();//结束位
}
/**
* @brief AT24C02_ReadByte 读取一个字节
* @param WordAddress 读取字节的地址
* @retval Data来接收读出的数据
*/
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
unsigned char Data;
I2C_Start();//开始位
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS|0x01);//主机地址读
I2C_ReceiveAck();//在此接收完,从机将获取总线控制权
Data=I2C_ReceiveByte();//从机接收数据
I2C_SendAck(1);//主机发送应答,为了不让主机应答所以置1
I2C_Stop();//结束位
return Data;
}
最后在主函数里测试
unsigned char KeyNum;
unsigned int Num;
void main()
{
LCD_Init();
LCD_ShowNum(1,1,Num,5);
while(1)
{
KeyNum=Key();
if(KeyNum==1)
{
Num++;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==2)
{
Num--;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==3)//写数据
{
AT24C02_WriteByte(0,Num%256);//写入低八位
Delay(5);
AT24C02_WriteByte(1,Num/256);//写入高八位
Delay(5);
LCD_ShowString(2,1,"Write Ok");
Delay(1000);
LCD_ShowString(2,1," ");//清空
}
if(KeyNum==4)//读数据
{
/*
Num=AT24C02_ReadByte(0);
Num|=AT24C02_ReadByte(1)<<8;
1.先读取0地址的数据存到Num中
2.再读取1地址的数据然后左移8位(后面补零),即得到16位的二进制数
3.再将1地址的数据按位或0地址的数据
4.此时高八位是1的地址,低八位是0的地址,即得到一个完整的16位数
*/
Num=AT24C02_ReadByte(0);//读取低八位
Num|=AT24C02_ReadByte(1)<<8; // << 优先级高于 |
LCD_ShowNum(1,1,Num,5);
LCD_ShowString(2,1,"Read Ok");
Delay(1000);
LCD_ShowString(2,1," ");
}
}
}
新手入行C51,如有错误请指正!