一、AT24C02
1.AT24C02介绍
·AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息
· 存储介质:E2PROM
· 通讯接口:12C总线
· 容量:256字节
2.引脚即应用电路
本开发板AT24C02原理图
12C地址全接地,即全为0
WE接地,没有写使能
SCL接P21 SDA接P20
没有外接上拉电阻是因为单片机的每个10口都接了上拉电阻
AT24C02内部结构框图如图所示
简单来说,就是通过SCL和SDA接口获得数据,经过一定的逻辑,数据存储到EEPROM(通过X和Y来控制数据的存储位置),并且可以通过一定的逻辑,将数据输出出来
3.I2C总线
1.I2C总线介绍
· 12C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线
· 两根通信线:SCL(Serial Clock)、SDA (Serial Data)
· 同步、半双工,带数据应答
· 通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度
2.I2C电路规范
·所有I2C设备的SCL连在一起,SDA连在一起
· 设备的SCL和SDA均要配置成开漏输出模式
· SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
· 开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相干扰的问题
本单片机所有IO口都是弱上拉模式,如图
开关闭合,则输出0
开关打开,则输出1
高电平驱动能力弱,低电平驱动能力强
开漏模式如图,没有接上拉电阻
开关闭合,输出0
开关断开,引脚呈浮空状态(什么都没接),电平不稳定
3.I2C时序结构
一帧标准的写数据帧
一帧写数据是由时钟线和数据线共同作用的,在同一时间,要么在发送信息,要么在读取信息
当处于空闲状态时,数据线和时钟线都处于高电平状态
当开始传递信息时,比如传递第一位起始位。此时必须要在时钟信号为高电平期间,数据信号完成由高到低的跳变,也就是下降沿
接下来是7位设备地址码,因为每一个从设备的地址码都是唯一的,为了区别要和哪一个从设备通讯,需要先发送出7位地址码,7位不同的0和1的排列组合,一共表可以表示128种结果
当时钟线为高电平时,数据线上的数据必须保持稳定(时钟线为高时,数据线上的数据始终为高),这样就完成了逻辑1的传输。如果数据线上始终是低电平,则表示逻辑0
若发送1010000,且24c02的地址为1010000,此时就是单片机和24c02通讯
接下来是读写数据位,如果要写数据帧,就给它置0,读数据帧置1
再下面一位是应答信号,这个信号是由从机发送给主机的,如果从机收到了之前的信息,它会回复0,没有收到或者(主机)读取接收完成回复1
下面的8位是设备寄存器的地址,因为是给24C02通讯的,24C02是一个存储器,它可以存储256个字节,而发送的8位寄存器地址正好可以访问这256个字节
比如写的寄存器地址是0x01(二进制为0000 0001),它就会往0x01里写入数据
然后单片机需要存储器返回一个应答信号
接下来的8位是给这个存储器的寄存器要写入的数据
比如发送0000 1111,则这8个位就会存储这8位信息
即使后来断电依旧保存,发送写数据之后,需要再给主机发送应答信号0,告诉主机写入成功
最后再写入停止位,它和起始位相反,是当时钟信号为高时,数据信号需要由低到高的跳变
这样一个标准的写数据帧就完成了
以下是一个标准的读数据帧
首先写入设备地址,然后是写数据。接下来写的是寄存器的地址,在收到从机的应答信号之后,主机需要再发送一个起始信号。然后需要再发送一遍设备的地址,然后才能发送读数据。接下来存储器就会把寄存器里面的数据发送给单片机,这样就完成了一帧数据的读取
二、AT24C02数据存储
I2C.c
#include <REGX52.H>
sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void I2C_Start(void)
{
I2C_SDA=1;
I2C_SCL=1;
I2C_SDA=0;
I2C_SCL=0;
}
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void I2C_Stop(void)
{
I2C_SDA=0;
I2C_SCL=1;
I2C_SDA=1;
}
/**
* @brief I2C发送一个字节
* @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 I2C接收一个字节
* @param 无
* @retval 接收到的一个字节数据
*/
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;
}
/**
* @brief I2C发送应答
* @param AckBit 应答位,0为应答,1为非应答
* @retval 无
*/
void I2C_SendAck(unsigned char AckBit)
{
I2C_SDA=AckBit;
I2C_SCL=1;
I2C_SCL=0;
}
/**
* @brief I2C接收应答位
* @param 无
* @retval 接收到的应答位,0为应答,1为非应答
*/
unsigned char I2C_ReceiveAck(void)
{
unsigned char AckBit;
I2C_SDA=1;
I2C_SCL=1;
AckBit=I2C_SDA;
I2C_SCL=0;
return AckBit;
}
I2C.h
#ifndef __I2C_H__
#define __I2C_H__
void I2C_Start(void);
void I2C_Stop(void);
void I2C_SendByte(unsigned char Byte);
unsigned char I2C_ReceiveByte(void);
void I2C_SendAck(unsigned char AckBit);
unsigned char I2C_ReceiveAck(void);
#endif
AT24C02.c
#include <REGX52.H>
#include "I2C.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();
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 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);
I2C_Stop();
return Data;
}
AT24C02.h
#ifndef __AT24C02_H__
#define __AT24C02_H__
void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);
#endif
main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "Key.h"
#include "AT24C02.h"
#include "Delay.h"
unsigned char KeyNum;
unsigned int Num;
void main()
{
LCD_Init();
LCD_ShowNum(1,1,Num,5);
while(1)
{
KeyNum=Key();
if(KeyNum==1) //K1按键,Num自增
{
Num++;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==2) //K2按键,Num自减
{
Num--;
LCD_ShowNum(1,1,Num,5);
}
if(KeyNum==3) //K3按键,向AT24C02写入数据
{
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) //K4按键,从AT24C02读取数据
{
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," ");
}
}
}