AT24C02引脚介绍与使用
首先AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息
在早起时候,我们的储存器的是出场的时候就储存好内容的,这种储存介质也叫Mask ROM,看下面的图片也可以知道,二极管(具有单向导通性)的方向是确定的。
后来,就发明了一种叫做PROM的储存器,看下面的图片可以知道,他一条线上有2个二极管,在制造出来后,我们进行写入程序,利用高电压击穿,使其烧毁,使得其一条线上只具有一个二极管,但是这种写入程序的方式是不可逆的,所以写入的时候又叫烧写。
再到后来就有了EPROM(在外线的照射下,擦除之前编写的程序)
和E2PROM也就是我们现在常用的写入储存器,其通电后就可以擦去不要的内容,进行重新编写
AT24C02介绍
存储介质:E2PROM
通讯接口:I2C总线
容量:256字节
AT24C02内部结构图
?引脚介绍:
在写入AT24C02的代码中要做适当延时,确保数据写入!!!
I2C通信介绍
I2C总线介绍
I2C总线(Inter IC BUS)是由Philips公司开发的一种通用数据总线
两根通信线:SCL(Serial Clock)、SDA(Serial Data)
同步、半双工,带数据应答
通用的I2C总线,可以使各种设备的通信标准统一,对于厂家来说,使用成熟的方案可以缩短芯片设计周期、提高稳定性,对于应用者来说,使用通用的通信协议可以避免学习各种各样的自定义协议,降低了学习和应用的难度
I2C电路规范
所有I2C设备的SCL连在一起,SDA连在一起(在主机和规定的从机进行交换数据的时候,主机要先发送从机的地址)
设备的SCL和SDA均要配置成开漏输出模式
SCL和SDA各添加一个上拉电阻,阻值一般为4.7KΩ左右
开漏输出和上拉电阻的共同作用实现了“线与”的功能,此设计主要是为了解决多机通信互相干扰的问题
?什么是开漏输出?
? ? ? 开漏输出就是不输出电压,控制输出低电平时引脚接地,控制输出高电平时引脚既不输出高电平,也不输出低电平,为高阻态。如果外接上拉电阻,则在输出高电平时电压会拉到上拉电阻的电源电压。这种方式适合在连接的外设电压比单片机电压低的时候。
I2C通信时序
手册上一段完整的时序是这样的:
但是问题是他这种时序图的可读性不强,即对于每一个部分作用区分的不是很明显,而且高低电平表示的1,0数据不是很清晰
所以下面的介绍类似于心电图的方式,使其更加清晰
起始条件与终止条件
起始条件:SCL高电平期间,SDA从高电平切换到低电平
终止条件:SCL高电平期间,SDA从低电平切换到高电平
发送一个字节(主机发送到从机)
SCL低电平期间,主机将数据位依次放到SDA线上(高位在前),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化(也就是说我们只有在SCL低电平的时候才能变化SDA的数据),依次循环上述过程8次,即可发送一个字节
一般是用8次循环的“ & ”运算,依次将数据赋给SDA
?
接受一个字节(从机发送到主机)
SCL低电平期间,从机将数据位依次放到SDA线上(高位在前),然后拉高SCL,主机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次,即可接收一个字节(主机在接收之前,需要释放SDA,即将SDA置1,因为单片机上电的时候默认所有的接口都为高电平1)
一般是用8次循环的“ | ”运算,依次将数据读给中间变量
? ??
发送应答与接受应答
发送应答(主机发送到从机):在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答
接收应答(从机发送到主机):在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)
总而言之,写完以后接受应答,读完之后发送应答。
S:--->RA? ? ?R:--->SA
I2C数据帧
发送一帧数据(向谁发送什么)
?AT24C02的固定地址为1010(SLAVE ADDRESS),可配置地址本开发板上为000(A0,A1,A2)
所以SLAVE ADDRESS+W为0xA0,SLAVE ADDRESS+R为0xA1
接受一帧数据(向谁接受什么)
先发送后接受数据帧,复合格式书写
应用:字节写与随机读
随机读包括两个步骤,首先是确认读的位置(发送对AT24C02进行写入的指令+发送读入地址)
其次是读出数据(发送对AT24C02进行读出的指令)之前已经确认数据的地址
对应的手册上面的时序图是这样的:
?
?代码演示:
main主函数
#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);//一页中最多存储8个字节,2的8次方等于256,所以分高8位和低8位存储
Delay(5);//须要5ms写入AT24C02中
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;//将高位先向左移8位后,“或”上Num即可
LCD_ShowNum(1,1,Num,5);
LCD_ShowString(2,1,"Read OK ");
Delay(1000);
LCD_ShowString(2,1," ");
}
}
}
I2C代码(AT24C02底层代码)
#include <REGX52.H>
sbit I2C_SCL=P2^1;//相当于晶振,高电平时写入/读出,低电平时SDA进行改变
sbit I2C_SDA=P2^0;//高低电平传输2进制数据
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void I2C_Start(void)
{
I2C_SDA=1;//SDA可能为1也可能为0,又因为我们在SCL低电平的时候变换数据(SCL在传输结束后都为0),所以先将SDA置1
I2C_SCL=1;
I2C_SDA=0;//按照拼图开始时SDA先置0后SDL置0
I2C_SCL=0;
}
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void I2C_Stop(void)
{
I2C_SDA=0;//在停止前可能是RA:0(收到数据应答)也可能是SA:1(发送数据不应答),所以将SDA置1
I2C_SCL=1;
I2C_SDA=1;//按照拼图开始时SCL先置1后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);//在SCL为0的时候变换数据
I2C_SCL=1;//查芯片手册可知,最大延时小于单片机执行一条语句的时间,所以中间不用加延时
I2C_SCL=0;
}
}
/**
* @brief I2C接收一个字节
* @param 无
* @retval 接收到的一个字节数据
*/
unsigned char I2C_ReceiveByte(void)
{
unsigned char i,Byte=0x00;//局部变量,手动定义
I2C_SDA=1;//接收时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;//接收时SDA首先要释放,即置1
I2C_SCL=1;
AckBit=I2C_SDA;
I2C_SCL=0;
return AckBit;
}
AT24C02储存与读出
#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;
}
LCD1602显示
//引脚配置:
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);//光标复位,清屏
}
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');
}
}
Delay延时
void Delay(unsigned int xms)
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
|