51单片机--AT24C02数据存储

文章目录

  • 存储器的介绍
  • AT24C02
  • I2C总线
  • I2C时序结构
  • AT24C02数据帧
  • AT24C02数据存储实例

存储器的介绍

存储器是计算机系统中的一种重要设备,用于存储程序和数据,它可以通过电子、磁性介质等技术来记录和保持数据。在这里,主要介绍的是随机存储器(RAM)和只读存储器(ROM)。
随机存储器(Random Access Memory,RAM)是一种可以读取和写入数据的存储器。 它的特点是可以随机访问其中的任意位置,并且读写速度较快。RAM通常由电子芯片制成,用于临时存储计算机运行时所需的程序和数据。
RAM可以分为静态随机存储器(Static RAM,SRAM)和动态随机存储器(Dynamic RAM,DRAM)两种类型。
静态随机存储器(SRAM):SRAM使用了触发器电路来存储数据,每个存储单元由多个触发器组成,具有较高的稳定性和快速的读写速度。由于使用了更多的电子元件,SRAM的密度较低,成本相对较高。它常用于高速缓存(Cache)等需要快速访问的场景。
动态随机存储器(DRAM):DRAM使用了电容器和开关电路来存储数据,每个存储单元由一个电容器和一个访问开关组成。由于电容器的电荷会随时间衰减,DRAM需要周期性地进行刷新操作来保持数据的稳定。DRAM具有较高的存储密度和较低的成本,但相对而言读写速度较慢。它常用于主存储器(内存)等需要较大容量的场景。
51单片机--AT24C02数据存储_第1张图片

只读存储器(Read-Only Memory,ROM)是一种只能读取数据而无法写入或修改数据的存储器。ROM中的数据在制造时被写入,通常用于存储固化的程序代码、系统设置、固件等不需要频繁修改的数据。ROM的数据在断电后依然可以保持,具有非易失性
ROM根据存储数据的方式可以分为多种类型,如只读存储器(ROM)、可编程只读存储器(PROM)、可擦除可编程只读存储器(EPROM)和电可擦除可编程只读存储器(EEPROM)等。这些ROM的特点是在制造或编程后,其存储的数据无法修改或只能经过特定方式修改。对于我们这款单片机来说,AT24C02就是E2PROM,电可擦除可编程ROM,可以通过编程来进行存储数据,数据根据具体的地址存储;断电之后,他还会保存在地址中,当我们需要读取时,可以通过相对应的地址取出数据;

AT24C02

AT24C02是一种2K位(256字节)的串行EEPROM(Electrically Erasable Programmable Read-Only Memory),常用于51单片机系统中的数据存储
AT24C02采用I2C总线协议进行通信,通过两根线路(SCL和SDA)与单片机进行连接。它具有8位的设备地址,可通过引脚A0、A1和A2进行编程设置,从而允许多个AT24C02共存在同一I2C总线上。
51单片机--AT24C02数据存储_第2张图片
对于AT24C02,在读写速度上支持400kHz标准模式,数据保存能力可达10年之久,而且有写入保护,可以防止误操作;
在单片机系统中,可以使用AT24C02来存储各种数据,如配置参数、校准数据、历史记录等。单片机可以使用相应的I2C库函数来向AT24C02写入或读取数据,以实现对其存储空间的读写操作。

需要注意的是,AT24C02是一种非易失性存储器,即使在断电情况下也可以保持存储的数据。因此,它适用于需要长期保存数据的应用场景。
51单片机--AT24C02数据存储_第3张图片
上图时AT24C02的内部结构框图,红色部分为它的存储位置;

I2C总线

I2C,全称为Inter-Integrated Circuit,是一种串行通信协议用于在电子设备之间进行数字数据传输。它由飞利浦半导体(现在的NXP半导体)公司在1980年代开发,并于1982年发布。I2C采用了两根导线(一根用于传输数据,另一根用于传输时钟信号)来连接设备,这使得多个设备可以通过同一组导线进行通信。
I2C协议有两个重要的角色:主设备(Master)和从设备(Slave)主设备负责控制通信的序列和时钟信号的生成,而从设备则被动响应主设备的指令。

I2C支持多主设备和多从设备的连接,每个设备都有一个唯一的地址标识。主设备可以向从设备发送数据或者接收从设备的数据。在传输数据时,数据被分为多个字节,每个字节都会被从设备确认。
51单片机--AT24C02数据存储_第4张图片

在51单片机中,AT24C02通过I2C总线与单片机进行数据通信
通过I2C总线,单片机可以发送读取或写入命令给AT24C02,然后AT24C02会响应相应的操作。例如,单片机可以向AT24C02发送写入命令,并指定要写入的地址和数据,AT24C02会将数据写入指定地址的存储单元。而当单片机需要读取AT24C02中的数据时,它可以向AT24C02发送读取命令和要读取的地址,然后AT24C02会返回相应的数据给单片机。

I2C时序结构

I2C总线的时序结构一般分为6部分:起始条件、终止条件、发送数据、接收数据、接收应答和发送应答;

起始条件主设备通过SCL(串行时钟线)保持高电平的同时,将SDA(串行数据线)由高电平转换为低电平,表示要启动一次通信
51单片机--AT24C02数据存储_第5张图片
代码:

void I2C_Start()
{
	I2C_SDA=1;//表示SCL高电平之前SDA就是高电平了
	I2C_SCL=1;//SCL高电平期间
	I2C_SDA=0;//SDA降为低电平
	I2C_SCL=0;//SCL回到低电平
	
}

发送一个字节:SCL低电平期间,主设备将数据位依次放到SDA线上(高位在前),然后拉高SCL,在SCL上升沿时从设备将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节;
51单片机--AT24C02数据存储_第6张图片
代码:

void I2C_SendByte(unsigned char byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		I2C_SDA=byte&(0x80>>i);//先将数据放到SDA上
		I2C_SCL=1; //SCL为上升沿时,从设备读取数据
		I2C_SCL=0;
	}
}

接收一个字节:SCL低电平期间,从设备将数据位依次放到SDA线上(高位在前),然后拉高SCL,主设备将在SCL上升沿期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA
51单片机--AT24C02数据存储_第7张图片
代码:

unsigned char I2C_ReceiveByte()
{
	unsigned char i,byte=0x00;
	I2C_SDA=1; //释放SDA,规定置于高电平
	for(i=0;i<8;i++)
	{
	//在SCL上升沿期间,主设备读取SDA的数据,
		I2C_SCL=1;
		if(I2C_SDA){byte|=(0x80>>i);
		//通过判断SDA位的数据是否为1,进行位赋值
		//为1执行条件内容,将位变为1,为0则不变,仍为0;
		I2C_SCL=0;
	}
	
	return byte;
	
}

发送应答:在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答 ;
51单片机--AT24C02数据存储_第8张图片
代码:

void I2C_SendAck(unsigned char AckBit)
{
	I2C_SDA=AckBit;
	//SDA发送应答,应答或者不应答
	I2C_SCL=1;
	//在SCL上升沿期间,从设备接收主设备发送应答
	I2C_SCL=0;
	
}

接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA);主设备发送一个高电平的SDA并保持SCL线为高电平,使得从设备可以拉低SDA线来应答。如果接收方正确收到数据,它会发一个低电平的SDA信号应答
51单片机--AT24C02数据存储_第9张图片
代码:

unsigned char I2C_ReceiveAck()
{
	unsigned char AckBit;
	I2C_SDA=1; //释放SDA
	I2C_SCL=1;  //在SCL高电平期间
	AckBit=I2C_SDA; //接收SDA的应答数据
	I2C_SCL=0; 
	return AckBit;
	
}

51单片机--AT24C02数据存储_第10张图片

终止条件:将SCL线保持高电平的同时,将SDA线由低电平转换为高电平,表示通信结束;
51单片机--AT24C02数据存储_第11张图片

代码:

void I2C_Stop()
{
	I2C_SDA=0;// 在SCL高电平之前SDA就为低电平
	I2C_SCL=1; //SCL处于高电平期间
	I2C_SDA=1;
}

AT24C02数据帧

数据帧是在通信中用于传输数据的基本单元
对于AT24C02,数据帧包括一个地址字节和一个或多个数据字节

51单片机--AT24C02数据存储_第12张图片
上图为AT24C02数据帧的格式:

  1. 起始位(Start Bit):数据传输开始时,为低电平。
  2. 7位地址(Address):表示要访问的内存地址。AT24C02共有256个地址,即0x00到0xFF。
  3. R/W位(Read/Write Bit):用于选择读(高电平)或写(低电平)操作。
  4. 数据字节(Data Byte):在写操作中,数据字节表示要写入到指定地址的数据。在读操作中,数据字节表示从指定地址读取到的数据。
  5. 应答位(Acknowledge Bit):用于发送方向接收方确认数据传输的成功。

而这些数据帧的格式是遵循I2C总线的通信协议的;所以我们只需要运用I2O的时序结构,将它们对应进AT24C02的数据帧即可;

对于读操作,数据帧的格式如下:
起始位 + 7位地址 + R/W位(设置为高电平)+ 应答位 + 存储的地址标识 + 应答位 + 数据字节 + 应答位 + 起始位 +读取数据+ 应答位 + 停止位

对于写操作,数据帧的格式如下:
起始位 + 7位地址 + R/W位(设置为低电平)+ 应答位 + 存储的地址标识 + 应答位 + 数据字节 + 应答位 + 停止位

代码(利用到I2C总线时序写的代码):

写入字节:

#define AT24C02_ADDRESS 0xA0 //AT24C02的写操作地址
void AT24C02_WriteByte(unsigned char WordAddress,Data)
{
	
	I2C_Start(); //起始条件
	I2C_SendByte(AT24C02_ADDRESS); //主设备发送AT24C02的地址
	I2C_ReceiveAck(); //从设备接收应答(确认地址)
	I2C_SendByte(WordAddress); // 主设备向从设备发送地址
	I2C_ReceiveAck(); //从设备接收应答
	I2C_SendByte(Data); //主设备向从设备发送数据
	I2C_ReceiveAck(); //从设备接收应答
	I2C_Stop(); //终止条件
}

读取字节:

unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
	unsigned char Data;
	I2C_Start(); //起始条件
	I2C_SendByte(AT24C02_ADDRESS); //主设备发送AT24C02的地址
	I2C_ReceiveAck();//从设备接收应答(确认地址)
	I2C_SendByte(WordAddress);// 主设备向从设备发送要读取字节的地址
	I2C_ReceiveAck();//从设备接收应答
	//以上操作完成了主设备向从设备要读取字节的地址的操作,是主设备
	//告诉从设备的地址
	//下面操作是从设备向主设备发送对应的字节操作,主设备从写入变
	//为读取,所以要重新开始定起始条件
	I2C_Start(); //起始条件
	I2C_SendByte(AT24C02_ADDRESS|0x01); //主设备发送AT24C02的读取地址
	I2C_ReceiveAck(); //从设备接收应答(确认地址
	Data=I2C_ReceiveByte();//读取从设备对应地址的数据字节
	I2C_SendAck(1); //主设备发送应答(地址对应正确发送回应给从设备)
	I2C_Stop(); //终止条件
	return Data;
	
}

AT24C02数据存储实例

写一个能通过独立按键让数字逐渐增加或者减少的程序,并且让这个程序能记住某一个数字,断电或者置0的时候可以恢复到这个数字
代码:
Delay.h

#ifndef __DELAY_H__
#define __DELAY_H__
//获取一个毫秒为单位的延迟函数,填入对应数字表示延迟多少毫秒
void Delay(unsigned int xms);

#endif

Delay.c

void Delay(unsigned int xms)
{
	unsigned char i, j;
	while(xms--)
	{
		i = 2;
		j = 239;
		do
		{
			while (--j);
		} while (--i);
	}
}

Key.h

#ifndef __KEY_H__
#define __KEY_H__

unsigned char Key();

#endif

Key.c

#include 
#include "Delay.h"

/**
  * @brief  获取独立按键键码
  * @param  无
  * @retval 按下按键的键码,范围:0~4,无按键按下时返回值为0
  */
unsigned char Key()
{
	unsigned char KeyNumber=0;
	
	if(P3_1==0){Delay(20);while(P3_1==0);Delay(20);KeyNumber=1;}
	if(P3_0==0){Delay(20);while(P3_0==0);Delay(20);KeyNumber=2;}
	if(P3_2==0){Delay(20);while(P3_2==0);Delay(20);KeyNumber=3;}
	if(P3_3==0){Delay(20);while(P3_3==0);Delay(20);KeyNumber=4;}
	
	return KeyNumber;
}

LCD1602.h

#ifndef __LCD1602_H__
#define __LCD1602_H__

//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);

#endif

LCD1602.c

#include 

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

AT24C02.h

#ifndef __KEY_H__
#define __KEY_H__

void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);
#endif

AT24C02.c

#include 
#include"I2C.h"

#define AT24C02_ADDRESS 0xA0
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();
}

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;
	
}

I2C.h

#ifndef __KEY_H__
#define __KEY_H__

void I2C_Start();//¶ÔI2C³õʼ»¯
void I2C_Stop();//¶ÔI2CÄ©¶Ë
void I2C_SendByte(unsigned char byte);//I2CÖ÷»ú·¢ËÍÒ»¸ö×Ö½Ú
unsigned char I2C_ReceiveByte();  //½ÓÊÕÒ»¸ö×Ö½Ú
void I2C_SendAck(unsigned char AckBit);  //·¢ËÍÓ¦´ð
unsigned char I2C_ReceiveAck();   //½ÓÊÕÓ¦´ð
#endif

I2C.c

#include 


sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;

void I2C_Start()
{
	I2C_SDA=1;
	I2C_SCL=1;
	I2C_SDA=0;
	I2C_SCL=0;
	
}

void I2C_Stop()
{
	I2C_SDA=0;
	I2C_SCL=1;
	I2C_SDA=1;
}

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;
	}
}

unsigned char I2C_ReceiveByte()
{
	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()
{
	unsigned char AckBit;
	I2C_SDA=1;
	I2C_SCL=1;
	AckBit=I2C_SDA;
	I2C_SCL=0;
	return AckBit;
	
}


main.c

#include 
#include"AT24C02.h"
#include"I2C.h"
#include"LCD1602.h"
#include"Delay.h"

unsigned char KeyNum;
unsigned short 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,"        ");
		}
		
	}
}

对于无符号的short类型数字,范围是0~65535,对应的有两个字节,所以写入数据要分别两部分,前8位和后8位,存储的地址可由自己选取,但在写入多个数据之间要延迟5ms,否则将会报错

你可能感兴趣的:(51单片机,51单片机,AT24C02,数据存储)