EEPROM介绍
EEPROM(Electrically Erasable Programmable Read-Only Memory,电可擦写可编程只读存储器)是一种非易失性存储器(断电后仍能保留数据),可以多次写入和擦除数据。EEPROM广泛应用于需要永久存储数据的电子设备中, 常用于存储设备工作模式、用户偏好设置等信息。
虽然叫做只读存储器(ROM),但EEPROM是即可读又可写的,这种看似矛盾的命名源于其历史发展。最早期的ROM是在制造过程中被一次性编程,出厂之后便只能读取,不能再修改,后期的各种可编程(可写)的ROM,都是基于最早期的ROM发展而来的,所以ROM这个名称就被一直沿用下来了。现在的ROM基本指非易失性存储器。
这里使用的EEPROM型号是AT24C02CN,其存储容量为2K(2048位,256字节),采用I2C协议进行读写。
引脚功能
内存结构
写操作
- Byte Write
Byte Write用于写入一个字节,其时序图如下。
注意
接收到停止信号后,EEPROM 就会开启内部的写周期,将刚刚接收到的一个字节的数据写入存储器的物理介质,在此期间,所有外部输入均会被拒绝,直到当前字节写入完成。这个写入周期的时间可从手册查询,通常用 t 表示。因此如果使用 Byte write 连续写入多个字节,在每个字节写入完成后,至少需要等待 t 之后才能写入下一字节。
- Page Write
Page Write用于连续写入一页数据,其时序图如下。
注意
- Page Write一次最多写入1页(16个字节)的数据
- Page Write会从主设备发送的字地址开始,逐字节顺序写入
- 当 Page write 写到页边界时,下一个字节不会进入下一页,而是会回到当前页的起始位置
- 当EEPROM在接收到最后的结束信号后,才会开启内部的写周期,将刚刚收到的一页数据统一写入存储 器的物理介质,在此期间,所有外部输入均会被拒绝,直接当前页写入完成。内部写周期的时长仍为 t
读操作
- Current Address Read
EEPROM内部有一个Address Register(地址寄存器),用于记录当前操作(读/写)的字节地址,每当完成一个字节的操作后(读/写),该地址会自动指向下一个字节。
Current Address Read读取的就是Address Register中的地址所指向的这一个字节,具体读时序如下。
- Random Read
Random Read用于读取任意指定地址的一个字节,时序图如下。
- Sequential Read
Sequential Read用于读取连续的多个字节。其起始位置可以是Address Register记录的当前地址,也可以是用户指定的任意位置,指定起始位置的方式仍然是在Sequential Read前增加一个Dummy Write操作。下图是从指定位置开始的连续读操作的时序图。
注意
连续读时,读到页边界后,会自动进入下一页,若读到最后一页的页边界,则会进入第一页
页写时,当 Page write 写到页边界时,下一个字节不会进入下一页,而是会回到当前页的起始位置
原理图
代码实现
Int_EEPROM.h
#ifndef __INT_EEPROM_H__
#define __INT_EEPROM_H__
#include <STC89C5xRC.H>
#define DEV_ADDR 0xA0
#define PAGE_SIZE 8
#include "Dri_IIC.h"
#include "Com_Util.h"
/**
* @brief 向EEPROM指定位置写入多个字节
*
* @param addr 起始地址
* @param bytes 要写入的字节
* @param len 要写入的字节个数
* @return bit 0:写入成功 1:写入失败
*/
bit Int_EEPROM_WriteBytes(u8 addr, u8 *bytes, u8 len);
/**
* @brief 从EEPROM指定位置读取多个字节
*
* @param addr 起始地址
* @param bytes 存储读取的字节用的数组指针
* @param len 要读取的字节个数
* @return bit 0:读取成功 1:读取失败
*/
bit Int_EEPROM_ReadBytes(u8 addr, u8 *bytes, u8 len);
#endif /* __INT_EEPROM_H__ */
Int_EEPROM.c
#include "Int_EEPROM.h"
bit Int_EEPROM_WritePage(u8 addr, u8* bytes, u8 len) {
u8 i;
bit ack = 0;
// 发送起始信号
Dri_IIC_Start();
// 发送设备地址
Dri_IIC_SendByte(DEV_ADDR);
ack |= Dri_IIC_ReceiveAck();
// 发送字地址
Dri_IIC_SendByte(addr);
ack |= Dri_IIC_ReceiveAck();
// 发送数据
for (i = 0; i < len; i++) {
Dri_IIC_SendByte(bytes[i]);
ack |= Dri_IIC_ReceiveAck();
}
// 发送结束信号
Dri_IIC_Stop();
// 等待EEPROM内部写入完成
Com_Util_Delay1ms(5);
return ack;
}
bit Int_EEPROM_WriteBytes(u8 addr, u8* bytes, u8 len) {
// 当前页剩余空间
u8 page_remain;
bit ack = 0;
while (len > 0) {
page_remain = PAGE_SIZE - (addr % PAGE_SIZE);
if (len > page_remain) {
// 当前页空间不足,先写满当前页
ack |= Int_EEPROM_WritePage(addr, bytes, page_remain);
// 剩余内容写到下一页
len -= page_remain;
bytes += page_remain;
addr += page_remain;
} else {
// 当前页空间足够,直接写入剩余内容
ack |= Int_EEPROM_WritePage(addr, bytes, len);
len = 0;
}
}
return ack;
}
bit Int_EEPROM_ReadBytes(u8 addr, u8* bytes, u8 len) {
bit ack = 0;
u8 i;
// 发送起始信号
Dri_IIC_Start();
// 发送设备地址
Dri_IIC_SendByte(DEV_ADDR);
ack |= Dri_IIC_ReceiveAck();
// 发送字地址
Dri_IIC_SendByte(addr);
ack |= Dri_IIC_ReceiveAck();
// 再次发送起始信号
Dri_IIC_Start();
// 发送设备地址
Dri_IIC_SendByte(DEV_ADDR + 1);
ack |= Dri_IIC_ReceiveAck();
// 读取数据
for (i = 0; i < len; i++) {
bytes[i] = Dri_IIC_ReceiveByte();
Dri_IIC_SendAck(i == len - 1 ? 1 : 0);
}
// 发送结束信号
Dri_IIC_Stop();
return ack;
}
main.c
#include "Int_MatrixLED.h"
#include "Dri_Timer0.h"
#include "Int_EEPROM.h"
u8 pic[26] = {
0xF8, 0x0A, 0xEC, 0xAF, 0xEC, 0x8A, 0xF8, 0x00, // 尚
0x10, 0xF9, 0x97, 0xF1, 0x88, 0xAA, 0xFF, 0xAA, 0x88, 0x00, // 硅
0x14, 0x0A, 0xF5, 0x92, 0x92, 0xF5, 0x0A, 0x14, // 谷
};
u8 tem[26];
void main() {
u8 i = 0;
Dri_Timer0_Init();
Int_MatrixLED_Init();
Int_EEPROM_WriteBytes(0, pic, 26);
Int_EEPROM_ReadBytes(0, tem, 26);
while (1) {
for (i = 0; i < 26; i++) {
Int_MatrixLED_ShiftPic(tem[i]);
Com_Util_Delay1ms(200);
}
}
}