1、EEPROM简介
(1)存储设备类型:ROM、RAM、PROM(可编程ROM)、EPROM(可擦除ROM)、EEPROM(电可擦除ROM)。
(2)为什么需要EEPROM?
某些数据内容我们需要掉电不丢失且在程序运行中可以修改这些数据内容,这就需要用到EEPROM。
(3)EEPROM和flash(闪存)的区别。
EEPROM是按功能分类的一种存储设备类型;flash是存储设备生产的一种工艺。EEPROM就可能采用了flash工艺,常见的U盘也是采用flash工艺。
(4)这里的EEPROM芯片具体型号是AT24C02,采用I2C时序进行读写。
2、AT24C02数据手册
(1)引脚介绍
A0、A1、A2 | I2C总线上器件地址 |
GND | 电源地 |
VCC | 电源正极 |
WP | 写保护引脚 |
SCL | 串行时钟输入 |
SDA | 串行地址和数据输入/输出 |
(2)AT24C02中02表示容量。
3、原理图
EEPROM芯片使用AT24C02。
(1)E0、E1、E2标识I2C总线的从地址。
(2)VDD表示电源正,VSS接电源地。
(3)WE(Write Enable)写保护引脚。(上划线代表低电平使能,低电平状态才能写)
(4)SDA(Serial Data)串行数据)引脚接单片机引脚P2^0。
(5)SCL(Serial Clock 串行时钟)引脚接单片机引脚P2^1。
4、I2C总结
(1)主CPU和其附属芯片(I2C设备)之间最常用的接口,尤其是各种传感器,因此在物联网时代非常重要。
(2)三根线:GND、SCL、SDA,串行通信(只有一根数据传输线),电平式通信(相对于差分信号)。
(3)总线式结构,可以一对多,总线上可以挂上百个器件,用从地址来区分。
(4)主从式,由主设备来发起通信及总线仲裁,从设备被动响应。
(5)通信速率一般(kbps级别),不适合语音、视频等信息类型。
5、EEPROM编程思路
主要包含下面两部分:
(1)I2C接口底层时序。起始信号、停止信号、发送字节、读取字节。
(2)EEPROM的寄存器读写时序。
6、I2C从设备的地址
(1)从设备的地址是由从设备自身定义的,不同的从器件的地址定义方式是不同的,要查具体的芯片数据手册来确定。
(2)同一个I2C总线上只有一个主设备,但是从设备可以有多个。这多个从设备的从地址不能相同(硬件设计工程师必须保证这一点。因为从地址是不能通过软件设定的)
(3)分析原理图和AT24C02的数据手册,得出: 从设备地址是:读地址:0xA1,写地址:0xA0.
7、示例程序
(1)app.c文件
#include <reg52.h>
#include "drv_uart.h"
#include "at24c02.h"
#include "intrins.h"
/*函数声明*/
void Delay20ms(); //@12.000MHz
void Delay500ms(); //@12.000MHz
void main(void)
{
unsigned char ReadData[10] = "aaaaaaaaaa"; //保存从EP读取的数据
unsigned char WriteData[10] = "zjd6661234"; //保存写入EP的数据
unsigned char AddrBase = 0x05; //EP内存操作基地址
unsigned char i = 0; //用于for循环
UartInit(); //串口初始化
/*注释写操作,检测断电是否丢失*/
for(i=0; i<10; i++)
{
AT24C02_WriteByte(AddrBase+i,WriteData[i]);
Delay20ms();
}
for(i=0; i<10; i++)
{
ReadData[i] = AT24C02_ReadByte(AddrBase+i);
Delay20ms();
}
for(i=0; i<10; i++)
{
UartSendByte(ReadData[i]);
}
while(1)
{
UartSendString("hello world.\r\n");
Delay500ms();
}
}
void Delay20ms() //@12.000MHz
{
unsigned char i, j;
i = 39;
j = 230;
do
{
while (--j);
} while (--i);
}
void Delay500ms() //@12.000MHz
{
unsigned char i, j, k;
_nop_();
i = 4;
j = 205;
k = 187;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
(2)drv_i2c.c
#include "drv_i2c.h"
#include <intrins.h>
#include <reg52.h>
sbit SCL = P2^1;
sbit SDA = P2^0;
/*函数声明*/
void Delay10us(); //@12.000MHz
/*
*功能:I2C总线起始信号
*参数:无
*返回值:无
*注:
(1)起始信号:在SCL时钟信号在高电平期间SDA信号产生一个下降沿
(2)起始之后SDA和SCL都为0
*/
void I2C_Start()
{
SCL = 1;
SDA = 1;
Delay10us();
SDA = 0; /*SDA下降延*/
Delay10us();
SCL = 0; /*为后续读写做好准备*/
Delay10us();
}
/*
*功能:I2C总线停止信号
*参数:无
*返回值:无
*注:
(1)终止信号:在SCL时钟信号高电平期间I2C_SDA信号产生一个上升沿
(2)结束之后保持SDA和SCL都为1,表示总线空闲
*/
void I2C_Stop()
{
SCL = 1;
SDA = 0;
Delay10us();
SDA = 1;
Delay10us();
}
/*
*功能:I2C发送一个字节
*参数:
Data: 待发送数据
ack: 1表示等待应答信号,0表示不等待应答信号
*返回值:发送成功返回1,发送失败返回0。
*注:
(1)发送完一个字节I2C_SCL=0, 需要应答则应答设置为1,否则为0。
(2)SCL低电平时,MCU把数据准备好,SCL高电平,从设备把数据读走。
*/
uchar I2C_SendByte(uchar Data, uchar ack)
{
uchar i = 0; /*用于for循环*/
uchar TimeCount = 0; /*等待应答信号计数*/
SCL = 0; /*预防I2C起始信号最后没有把SCL拉低*/
for(i=0; i<8; i++) /*发送8bit,从高位到低位*/
{
SDA = Data >> 7; //起始信号之后I2C_SCL=0,所以可以直接改变I2C_SDA信号
Data = Data << 1;
Delay10us();
SCL = 1;
Delay10us();
SCL = 0;
Delay10us();
}
SDA = 1; /*主设备释放SDA线给从设备去操作*/
Delay10us(); /*延时5uS*/
SCL = 1; /*SCL第9个周期*/
while(SDA && (ack == 1)) /*等待应答,也就是等待从设备把SDA拉低*/
{
TimeCount++;
if(TimeCount > 400) /*如果超过2000us没有应答发送失败,或者为非应答,表示接收结束*/
{
SCL = 0;
Delay10us();
return 0;
}
}
SCL = 0; /*为发送下一字节做准备*/
Delay10us();
return 1;
}
/*
*功能:I2C读取一个字节
*参数:无
*返回值:读取的一字节数据
*/
uchar I2C_ReadByte()
{
uchar i = 0; /*用于for循环*/
uchar Data = 0; /*保存读取的数据*/
SCL = 0; /*预防起始信号或发送字节后SCL不是0*/
SDA = 1; /*起始和发送一个字节之后I2C_SCL都是0*/
Delay10us();
for(i=0; i<8; i++) /*接收8个bit*/
{
SCL = 1; /*通知从设备MCU要开始读了,可以放1bit数据到SDA*/
Delay10us();
Data <<= 1; /*读取高位在前*/
Data |= SDA;
Delay10us();
SCL = 0; /*为下bit传输做准备*/
Delay10us();
}
return Data;
}
void Delay10us() //@12.000MHz
{
unsigned char i;
_nop_();
i = 2;
while (--i);
}
(3)drv_i2c.h
#ifndef __DRV_I2C_H__
#define __DRV_I2C_H__
#define uchar unsigned char
#define uint unsigned int
/*函数声明*/
void I2C_Start(); /*I2C总线起始信号*/
void I2C_Stop(); /*I2C总线停止信号*/
uchar I2C_SendByte(uchar Data, uchar ack); /*I2C发送一个字节*/
uchar I2C_ReadByte(); /*I2C读取一个字节*/
#endif
(4)drv_uart.c
#include "drv_uart.h"
#include <reg52.h>
/*
*功能:串口初始化函数,8数据位,1停止位,无校验位,波特率4800
*参数:无
*返回值:无
*/
void UartInit(void)
{
SCON = 0x50; //串口工作在模式1,8位数据位,允许串行接收
PCON = 0x80; //波特率加倍
TMOD = 0x20; //设置T1为模式2
TH1 = 243; //波特率4800 ,TH1 = 晶振频率/12/32/波特率
TL1 = 243; //8位自动重装,意识是TH1用完了之后下一个周期TL1会自动重装到TH1去。
TR1 = 1; //开启定时器1
//ES = 1; //打开串口中断
//EA = 1; //打开总中断
}
/*
*功能:通过串口发送一个字节数据
*参数:需要发送的内容
*返回值:无
*/
void UartSendByte(unsigned char Dat)
{
SBUF = Dat; //准备好需要发送的一个字节
while(TI == 0); //确认串口发送没有再忙,while循环需要加超时判断
TI = 0; //软件复位TI标志位
}
/*
*功能:通过串口发送字符串
*参数:待发送的字符串
*返回值:无
*/
void UartSendString(unsigned char *str)
{
while(*str != '\0') //等待字符串发完*/
{
UartSendByte(*str); //发送一个字符
str++; //指针指向下一个字符
}
}
/*
*功能:串口中断接收函数
*参数:无
*返回值:无
*注意:中断函数通过中断编号识别,中断编号可通过查数据手册得到
*/
void Uart_Isr() interrupt 4
{
unsigned char ReceiveBit;
if(RI == 1)
{
ReceiveBit = SBUF; //读取SBUF,读取串口接收到的一个字节
RI = 0;
}
UartSendByte(ReceiveBit); //接收到的内容原封不动发回去
}
(5)drv_uart.h
#ifndef __DRV_UART_H__
#define __DRV_UART_H__
/*函数声明*/
void UartInit(void); /*串口初始化函数*/
void UartSendByte(unsigned char Dat); /*通过串口发送一个字节数据*/
void UartSendString(unsigned char *str); /*通过串口发送字符串*/
#endif
(6)at24c02.c
#include "at24c02.h"
/*
*功能:AT24C02写入一个字节
*参数:
Addr:AT24C02存储数据的地址
Data:写入的数据
*返回值:无
*/
void AT24C02_WriteByte(uchar Addr, uchar Data)
{
I2C_Start();
I2C_SendByte(0xA0, 1); // 发送AT24C02写地址
I2C_SendByte(Addr, 1); // 发送要写入数据的内存地址
I2C_SendByte(Data, 1); // 发送要写入的数据
I2C_Stop();
}
/*
*功能:AT24C02读取一个字节
*参数:
Addr:读取内容存放的内存地址
*返回值:读取到的内容
*/
uchar AT24C02_ReadByte(uchar Addr)
{
uchar Data = 0;
I2C_Start();
I2C_SendByte(0xA0, 1); // 发送AT24C02写地址
I2C_SendByte(Addr, 1); // 发送读取内容存放地址
I2C_Start();
I2C_SendByte(0xA1, 1); // 发送AT24C02读地址
Data = I2C_ReadByte(); // 读取数据
I2C_Stop();
return Data;
}
(7)at24c02.h
#ifndef __AT24C02_H__
#define __AT24C02_H__
#include "drv_i2c.h"
/*AT24C02写入一个字节*/
void AT24C02_WriteByte(uchar Addr, uchar Data);
/*AT24C02读取一个字节*/
uchar AT24C02_ReadByte(uchar Addr);
#endif