AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息
- 存储介质:E2P ROM
- 通讯接口:I2C
- 总线容量:256字节
I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线
- 两根通信线:SCL(Serial Clock)、SDA(Serial Data)
- 同步、半双工,带数据应答
- 通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度
I2C电路规范:
- 所有I2C设备的SCL连在一起,SDA连在一起
-
设备的SCL和SDA均要配置成开漏输出模式(目的是当某设备和CPU通讯时其他设备不影响通信)
-
SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
-
开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相干扰的问题
I2C时序结构:
为什么SDA =1是释放?为什么不是SDA=0?
I2C的IO是开漏或者开集电极模式,即I2C对应的IO口只能输出两种状态:
- “低电平(逻辑0,IO被接在GND上,只能是低电平)”
- “高阻态(逻辑1,IO可以被拉高或者拉低)”
而所谓的“高电平”其实是在输出逻辑1、IO为高阻态时,IO被外部或者内部上拉电阻拉高的。
SDA=0时,SDA脚在IC内部被直接接在GND上,它只能是低电平,外部器件也无法把SDA脚拉高,这叫拉低。
SDA=1时,SDA脚为高阻态,(相当于在万用板上焊了一根IO,这个IO谁都不接,这就是高阻了),这时SDA可以被别的器件拉低或者拉高,即,这时SDA脚的控制权是“释放状态”,如果谁都不去管这个高阻态的脚,这个脚会被上拉电阻拉高以保证不会被空气里的电磁波干扰而高高低低乱跳。
I2C数据帧:
- 页写:字节写的加强版,可以发多个8位数据,只要在数据后继续S:DATA RA:0即可,但是超过8个8位数据就会回到第一个8位数据处覆盖
- 顺序读:随机读的加强版,可以读多个8位数据,超过第8个也不会循环到第一个8位
#include <REGX52.H>
sbit I2C_SCL = P2^1;
sbit I2C_SDA = P2^0;
/**
* @brief 发送起始条件
* @param
* @retval
*/
void I2C_Start(){
I2C_SDA = 1; // 确保SDA为高电平
I2C_SCL = 1;
I2C_SDA = 0; // 起始条件
I2C_SCL = 0;
}
/**
* @brief 发送终止条件
* @param
* @retval
*/
void I2C_Stop(){
I2C_SDA = 0; // 确保SDA为低电平
//I2C_SCL = 0;
I2C_SCL = 1;
I2C_SDA = 1; // 终止条件
}
/**
* @brief 发送字节
* @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;
I2C_SCL = 0;
}
}
/**
* @brief 接收字节
* @param
* @retval 接收到的字节
*/
unsigned char I2C_ReceiveByte(){
unsigned char i, result = 0;
I2C_SDA = 1; // 主机释放SDA
for(i = 0; i < 8; i++){
I2C_SCL = 1;
if(I2C_SDA){result |= (0x80 >> i);}
I2C_SCL = 0;
}
return result;
}
/**
* @brief 发送发送应答(主机接收完字节后发送)
* @param AckBit 应答内容(0表应答,1表非应答)
* @retval
*/
void I2C_SendAck(unsigned char AckBit){
I2C_SDA = AckBit;
I2C_SCL = 1;
I2C_SCL = 0;
}
/**
* @brief 接收接收应答(主机发送完字节后接收)
* @param
* @retval 从机发送来的应答内容(0表应答,1表非应答)
*/
unsigned char I2C_ReceiveAck(){
unsigned char AckBit;
I2C_SDA = 1; // 主机释放SDA
I2C_SCL = 1;
AckBit = I2C_SDA;
I2C_SCL = 0;
return AckBit;
}
#include "I2C.H"
#include <REGX52.H>
#define AT24C02_ADDRESS 0xA0
/**
* @brief AT24C02写入一个字节
* @param WordAddress 要写入字节的地址
* @param Data 要写入的字节
* @retval
*/
void AT24C02_WriteByte(unsigned char WordAddress, Data){
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck(); // 0应答
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_SendByte(Data);
I2C_ReceiveAck();
I2C_Stop();
}
/**
* @brief AT24C02读取一个字节
* @param WordAddress 要读取字节的地址
* @retval 要读取的字节的内容
*/
unsigned char AT24C02_ReadByte(unsigned char WordAddress){
unsigned char result;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS | 0x01);
I2C_ReceiveAck();
result = I2C_ReceiveByte();
I2C_SendAck(1);
I2C_Stop();
return result;
}
void main(){
LCD_Init();
AT24C02_WriteByte(1000, 70);
Delay(5); // AT24C02写周期5ms
num = AT24C02_ReadByte(1000);
LCD_ShowNum(2, 1, num, 3);
while(1){}
}