硬件原理图:
AT24C02数据图:
I2C电路规范:
- 所有I2C设备的时钟线SCL和数据线SDA连在一起
- 设备的SCL和SDA都要配置为开漏输出模式
- SCL和SDA各需要添加一个 上拉电阻,阻值一般为4.7k
- 开漏输出模式和上拉电阻共同作用实现了线与的功能,解决了多机通信的问题
首先根据原理图定义对应引脚:
cpp
sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;
I2C时序图:
I2C起始信号:
cpp
/**
* @brief I2C起始
* @param 无
* @retval 无
*/
void I2C_Start(void)
{
I2C_SDA=1;//这里SDA置1是因为可能数据不是刚开始才发送,可能是从停止位开始,SDA可能为0,
I2C_SCL=1;//因为SCL每次最后都是低电平,这个时候SDA可以改变,起始条件是开始都为1
I2C_SDA=0;
I2C_SCL=0;
}
I2C停止信号:
cpp
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void I2C_Stop(void)
{
I2C_SDA=0;//保证SDA拉高前是0
I2C_SCL=1;
I2C_SDA=1;
}
I2C发送一个字节:
写入一个字节时序分析:
cpp
/**
* @brief I2C发送一个字节
* @param Byte
* @retval 无
*/
void I2C_SendByte(unsigned char Byte)//由时序图可知在调用该函数是SCL为低电平,
{ //此时直接改变SDA的电平
unsigned char i;
for( i=0;i<8;i++)
{
I2C_SDA=Byte&(0x80>>i); ////I2C协议先存的是最高位然后依次存
I2C_SCL=1; //由数据手册可知时间可以不加延时
I2C_SCL=0;
}
}
I2C接收一个字节:
-
在SCL低电平期间从机将数据位依次放到SDA线上还是高位在前,然后拉高SCL,读取数据位,在该期间SDA不能变化
-
在主机接收时,主机需要释放SDA
cpp
/**
* @brief I2C接收一个字节
* @param 无
* @retval Byte
*/
unsigned char I2C_ReceiveByte(void)
{
unsigned char Byte=0x00;
unsigned char i;
I2C_SDA=1;//释放总线
for( i=0;i<8;i++)
{
I2C_SCL=1;//I2C协议规定在SCL=1时读取
if(I2C_SDA){Byte|=(0x80>>i);}//如果读取到SDA=1,就把Byte的最高位置1
I2C_SCL=0;//读完后拉低
}
return Byte;
}
I2C发送应答:
- 发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
cpp
/**
* @brief I2C发送应答
* @param AckBit 应答位为0应答,为1无应答
* @retval 无
*/
void I2C_SendAck(unsigned char AckBit)
{
I2C_SDA=AckBit;
I2C_SCL=1;
I2C_SCL=0;
}
I2C接收应答:
- 接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
cpp
/**
* @brief I2C接收应答
* @param 无
* @retval AckBit 应答位为0应答,为1无应答
*/
unsigned char I2C_ReceiveAck(void)
{
unsigned char AckBit;
I2C_SDA=1;//主机接收前,先释放SDA
I2C_SCL=1;//读取
AckBit=I2C_SDA;
I2C_SCL=0;
return AckBit;//为0应答,为1无应答
}
AT24C02的设备地址:
固定地址是1010,原理图A2A1A0是000,最低位为0时是Write,最低位为1时是Read.
cpp
#define AT24C02_ADDRESS 0XA0 //定义AT24C02的写入时的地址
AT24C02写入一个字节:
分析:
cpp
/**
* @brief AT24C02写入一个字节
* @param WordAddress,要写入字节的地址
* @param Data要写入的数据
* @retval 无
*/
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
/*
//unsigned char Ack;用来测试是否应答
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);//1010固定,000原理图,0为w,1为r,写为0XA0,读为0XA1
//Ack=I2C_ReceiveACK();用来测试是否应答
//if(Ack==0){P2=0x00;}用来测试是否应答
*/
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);//1010固定,000原理图,0为w,1为r,写为0XA0,读为0XA1
I2C_ReceiveACK();
I2C_SendByte(WordAddress);
I2C_ReceiveACK();
I2C_SendByte(Data);
I2C_ReceiveACK();
I2C_Stop();
}
AT24C02读取一个字节:
分析:
cpp
/**
* @brief AT24C02读出一个字节
* @param WordAddress,要读出字节的地址
* @retval Data要读取对应地址存入的数据
*/
unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
unsigned char Data;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);//1010固定,000原理图,0为w,1为r,写为0XA0,读为0XA1
I2C_ReceiveACK();
I2C_SendByte(WordAddress);
I2C_ReceiveACK();
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS|0x01);
I2C_ReceiveACK();
Data=I2C_ReceiveByte();
I2C_SendAck(1);
I2C_Stop();
return Data;
}
主函数测试:
写入完后未加延时立即读取数据:读出的数据有问题
cpp
unsigned char Data;
int main()
{
LCD_Init();
AT24C02_WriteByte(1,66);
Data=AT24C02_ReadByte(1);
LCD_ShowNum(1,1,Data,3);
while(1)
{
}
}
查看数据手册可知:
写入完后加延时5ms读取数据: 数据正常
cpp
unsigned char Data;
int main()
{
LCD_Init();
AT24C02_WriteByte(1,66);
Delayms(5);
Data=AT24C02_ReadByte(1);
LCD_ShowNum(1,1,Data,3);
while(1)
{
}
}