AT24C02芯片存储及I2C总线传输数据(主要是I2C)

AT24C02介绍

AT24C02是一个2K位串行CMOS E2PROM,内部含有256个8位字节,采用先进CMOS技术实质上减少了器件的功耗。AT24C02有一个8字节页写缓冲器,该器件通过IIC总线接口进行操作,有一个专门的写保护功能。是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息,储存介质是E2PROM

AT24C02的引脚图

应用电路

内部框架图

其主要主体是用于电可擦除可编程ROM,意思就是可以通过上电并且编程的模式来实现数据的存储以及读出,存储在内部的地址总共有256个,每个地址可以存储八位字节的数据

AT24C02是通过I2C来进行数据的传输进去以及读取出来,了解I2C也是学会使用AT24C02的重要一环,下面我来介绍I2C的主要内容及传输数据时的时序图及具体可实现代码

I2C总线的介绍

I2C是什么

在消费电子,工业电子等领域,会使用各种类型的芯片,如微控制器,电源管理,显示驱动,传感器,存储器,转换器等,他们有着不同的功能,有时需要快速的进行数据的交互,为了使用最简单的方式使这些芯片互联互通,于是I2C诞生了,I2C(Inter-Integrated Circuit)是一种通用的总线协议。它是由Philips(飞利浦)公司,现NXP(恩智浦)半导体开发的一种简单的双向两线制总线协议标准。

通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度

工作原理图,通过I2C配制地址可以控制多个结点的数据传输

所有I2C设备的SCL连在一起,SDA连在一起

设备的SCL和SDA均要配置成开漏输出模式 SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右 开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相干扰的问题

下面为电路的结构框图

I2C主要是操控SDA和SCL来控制输出为,SCL是串行时钟接口,通过SCL的时序变化,相应的数据就会传到SDA上,这取决于不同模式下是虚的不同

I2C的时序结构

最重要的一句话是:当SCL处于高电平SDA的值不能改变(数据传输时),因为SCL在高电平时SDA的值如果改变那就证明这时I2C处于起始条件或者终止条件的判断中,这时不会向某个地址出传某个数据,如果在传输数据时出现这样的情况可能会导致数据的丢失。

I2C起始条件(发送开始信号)

SCL高电平期间,SDA从高电平切换到低电平

先把SDA置1(高电位)保证串行数据线的初始状态为1,随后把SCL置1,他们两同时保证高电位时按图上先把SDA置0,再把SCL置0,这样开始信号就发送完成了

I2C终止条件(发送终止信号)

SCL高电平期间,SDA从低电平切换到高电平

首先把SDA置低电平,再把SCL置高电平,这样在SCL高电平时SDA再置高电平,在SCL为搞得时候SDA发生了低到高的信号,此时向I2C发送了终止信号

I2C发送一个字节

SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可发送一个字节

这里主机先把要发送的数据位先放到SDA上,再拉高SCL,从机就会自动读取SDA上的数据

void I2C_SendByte(unsigned char Byte)  //发送一个字节
{
	unsigned char i=0;
	for(i=0;i<8;i++)
	{
		I2C_SDA=Byte&(0x80>>i);    //逐位发送
		I2C_SCL=1;                 //拉高电位发送这一位的数据
		I2C_SCL=0;                 //恢复电位以便下一个发送时能拉高产生上升沿
	}
}

I2C接收一个字节

SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA)

在接收数据时是依靠从机控制我们的数据位,我们只要控制串行时钟即可按位读取到数据,这里把串行时钟置1就可以把数据从SDA上读取(SCL置高电位后,从机会把数据转移到SDA上,这时我们只需要取值即可)

unsigned char I2C_ReceiveByte()
{
	unsigned char i,Data=0x00;    //定义一个变量来接收数据
	
	I2C_SDA=1; //这里读取数据SDA的控制权已经交给了AT24C02芯片只需一开始置1,在读数据时把SCL拉高表示主机释放SDA
	
	for(i=0;i<8;i++)
	{
		I2C_SCL=1;        //串行时钟置1接收数据
		if(I2C_SDA)    //当接收到的数据是1时,把对应位变成1
		{
			Data|=(0x80>>i);
		}
		I2C_SCL=0;        //置零方便下次上升沿出现
	}
	I2C_SCL=0;        //最后把串行时钟置回低电位
	return Data;
}

在I2C发送数据或者接收数据时需要一个应答位来表示你是否接收到信息

I2C发送应答

在接收完一个字节之后,主机在下一个时钟发送一位数据(这时主机要想从机发送应答位),数据0表示应答,数据1表示非应答

I2C接收应答

接收应答:在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)

void I2C_SendAck(unsigned char AckBit)
{
	I2C_SDA=AckBit;        //发送应答
	I2C_SCL=1;
	I2C_SCL=0;
}



unsigned char I2C_ReceivedAck()
{
	unsigned char AckBit;
	I2C_SDA=1;				//释放SDA让芯片进行操控
	
	I2C_SCL=1;              //拉高串行时钟即接收数据
	AckBit=I2C_SDA;
	I2C_SCL=0;
	return AckBit;
}

I2C的数据帧

I2C数据帧主要完成的任务有:向谁(从机地址(这里我们用的是AT24C02))哪个位置(数据存储的位置)发送/接收什么数据

通过Start激活I2C随后发送从机的位置,发送应答,再发送数据存储的位置,发送应答,再把数据发送过去即写入数据完成

读取数据也是通过Start激活I2C随后发送从机的位置,发送应答,再发送数据存储的位置,发送应答,最后把数据读取出来即读取数据完成

这里介绍完I2C的写入和读出再介绍一下I2C与AT24C02搭配着应用

使用AT24C02主要是为了能把数据存储到AT24C02内部和从内部读出数据,这就需要我们的I2C来配合传输数据

写入数据到AT24C02

写入数据按照上面的步骤用I2C来传输数据,这里用到AT24C02主要就只有它的从机地址

这里复述一遍写入的过程

首先发送I2C的开始条件,发送从机的地址(这里AT24C02的地址是0xA0),接收从机发送的应答,发送数据需要存储在哪个位置上,接收从机应答,最后再发送数据,接收从机应答,即可关闭I2C

还要注意的一点是AT24C02在写入时它需要时间,这个时间内你不能对数据进行读取或者别的操作,这里官方定义的写入最长时间是5ms,所以我们在写入数据时还需要延时一段时间才能进行下一步操作

void AT24C02_WriteByte(unsigned char WordAddress,Data)
{

	I2C_Start();  						//给开始指令进行工作
	I2C_SendByte(AT24C02_Address);  	//先发送从机地址(需要操控的芯片的地址)
	I2C_ReceivedAck();					//接受应答
	I2C_SendByte(WordAddress);			//发送字节存储的地址
	I2C_ReceivedAck();
	I2C_SendByte(Data);					//发送数据
	I2C_ReceivedAck();
	I2C_Stop();
}

从AT24C02读出数据

读出数据也是通过I2C传输

过程如下

首先先发送I2C开始工作的指令,发送从机地址(读的操作要在从机地址的最后一位加上一),应答,发送要读取的数据的地址,接收应答,把数据读取出来后向从机发送应答,最后关闭I2C

unsigned char AT24C02_ReadByte(unsigned char WordAddress)
{
	unsigned char Data;
	I2C_Start();  						//给开始指令进行工作
	I2C_SendByte(AT24C02_Address);  	//先发送从机地址(需要操控的芯片的地址)
	I2C_ReceivedAck();					//接受应答
	I2C_SendByte(WordAddress);			//发送字节存储的位置(在哪里)
	I2C_ReceivedAck();
	
	I2C_Start();
	I2C_SendByte(AT24C02_Address|0x01); //发送向AT24C02读取数据的命令(这里与上0x01就是最后一位置1表示读数据)
	I2C_ReceivedAck();
	Data=I2C_ReceiveByte();
	I2C_SendAck(1);
	I2C_Stop();
	return Data;
}

这里再通过按键的功能尝试一下数据的写入和读出功能在AT24C02中的效果

AT24C02实践

#include <REGX52.H>
#include "LCD1602.h"
#include "AT24C02.h"
#include "Delay.h"
#include "Key.h"
unsigned char Data,KeyNum;
unsigned int Num;
void main()
{
	LCD_Init();
	
	/*
	//测试读写功能
	AT24C02_WriteByte(1,233);
	Delay(5);
	Data=AT24C02_ReadByte(1);
	LCD_ShowNum(2,1,Data,3);
	
	*/
	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中
		{
			AT24C02_WriteByte(0,Num%256);
/*这里的unsigned int 的Num分来两位来存储,因为存储器中是以八个字节大小的ROM来存储数据的,而unsigned int的大小是16个字节,这里低位存储在地址0内
高位存储在地址1上*/
			Delay(5);    //写完数据之后要等待它写入,Delay个5ms不进行其他操作
			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);
			LCD_ShowNum(1,1,Num,5);
			LCD_ShowString(2,1,"Read OK ");
			Delay(1000);
			LCD_ShowString(2,1,"        ");
		}
	}

}

再附上按键的代码

#include <REGX52.H>
#include "Delay.h"

unsigned char Key()  //获取按键键码
{
	unsigned char KeyNum=0;
	if(P3_1==0)  //按键1
	{
		Delay(20);
		while(P3_1==0);
		Delay(20);
		KeyNum=1;
	}
	if(P3_0==0)  //按键2
	{
		Delay(20);
		while(P3_0==0);
		Delay(20);
		KeyNum=2;
	}
	if(P3_2==0)  //按键3
	{
		Delay(20);
		while(P3_2==0);
		Delay(20);
		KeyNum=3;
	}
	if(P3_3==0)	//按键3
	{
		Delay(20);
		while(P3_3==0);
		Delay(20);
		KeyNum=4;
	}
	return KeyNum;
}

还有LCD显示

#include <REGX52.H>

//引脚配置:
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');
	}
}

延时函数

#include <REGX52.H>
void Delay(int xms)
{
		unsigned char i, j;
		while(xms--)
		{
				i = 2;
				j = 239;
				do
				{
					while (--j);
				} while (--i);
		}
}

  • 34
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值