本专栏内容经修订后,已在豆瓣阅读集结成书出版:
![3694bef187de8af4b439cdaa78f05b5b.png](https://i-blog.csdnimg.cn/blog_migrate/5ce8b2c69f7c3bb57cf02c9bee716906.jpeg)
EEPROM (Electrically Erasable Programmable Read-Only Memory),电可擦可编程只读存储器 ,它是一种掉电后数据不丢失的存储器。
在Arduino开发设计中,非常重要的存储介质。比如,作为智能空调的控制板,关闭空调后,我们希望空调存储关闭前设置的温度、风力。此时可以把设置参数写入EEPROM,再次开启时读取。再如,小型气象站,利用它暂存气象数据,定时通过GSM或者Wi-Fi,发送到服务器,不用长时间开启通信模组,非常省电。
但是Arduino 上的EEPROM不是Flash,后者用于存储程序,前者用户暂存数据,共同点时掉电后,数据都不会消失。 它们之间有什么区别呢?这要从“史前”的ROM讲起。
最初的ROM 是不能编程的,出厂什么内容就永远什么内容。后来出现了PROM,可以写入一次,但要是写错了,只能换一片,自认倒霉。
人类文明不断进步,终于出现了可多次擦除写入的EPROM。不过,每次擦除要把芯片拿到紫外线上照一下。试想一下,你往单片机上写入一个程序后,发现有个小地方要改,为此你要把单片机放紫外灯下照半小时,然后才能再下一次,这么折腾一天也改不了几次。
最终,可以随意修改的 EEPROM 出现了,拯救了一大批程序员。很多人将它与FLASH闪存相比较,实际上FLASH也是EEPROM一种。为了方便区分,我们指EEPROM都是狭义的EEPROM,与FLASH的区别如下:
- FLASH 按扇区操作,EEPROM则按字节操作
- FLASH 写入时间长,EEPROM写入时间短
- FLASH 擦写次数少(TLC 200次 ~ SLC 10000次),EEPROM次数多(1,000,000次)
- FLASH 电路结构简单,成本低,EEPROM工艺复杂,成本高
- 运行时,EEPROM可以修改,FLASH则不行。因此EEPROM可以存储一些参数,FLASH 存储程序代码和不需修改的数据。
Arduino 上既有FLASH,也有EEPROM。存放程序的区域就是FLASH,Arduino Uno 的空间大小为32KB,而存储一些需要随时修改,但又能掉电保存的数据,比如:数字收音机的频率、智能家居各模块的开关状态等,则放在EEPROM,空间大小为1KB。
EEPROM 的操作,官方提供了8个示例,我们先看看最基本的三个 Write、Read、Clear示例。
![71d70b44eb5bb60d7ed0304c03e44c74.png](https://i-blog.csdnimg.cn/blog_migrate/47c705d9abac56191c992c5dbe2e9092.jpeg)
先对 eeprom_write 程序略微修改一下(程序22.1):
/*
程序22.1
作者:Ardui.co
效果:EEPROM 写入数据
*/
#include <EEPROM.h>
int addr = 0; //初始化地址变量
void setup() {
pinMode(13, OUTPUT);
}
void loop() {
int val = analogRead(0) / 4; //从A0读取结果0~1023,转换为0~255,空载输入为随机数
EEPROM.write(addr, val); //按照 EEPROM 地址,写入数值
if (addr <= EEPROM.length()) { // 如果地址达到末尾,从头开始
addr = addr + 1; //前一个地址写入数据后,进入下一个地址
}
else {
digitalWrite(13, HIGH); // 全部写入完毕,点亮板载LED
}
delay(10);//延时100ms,注意每次写入需要3.3ms,因此不能少于4ms;
}
EEPROM.write(addr, val) 是根据地址来写入数据的:addr:写入字节的地址,整数变量;val:写入的字节,范围0 ~ 255。
现在反过来把这些数据从EEPROM读出来(程序22.2):
/*
程序22.2
作者:Ardui.co
效果:EEPROM 读取数据
*/
#include <EEPROM.h>
int address = 0;
byte value;
void setup() {
Serial.begin(9600);
}
void loop() {
value = EEPROM.read(address);
Serial.print(address);
Serial.print("t");
Serial.print(value, DEC);
Serial.println();
address = address + 1;
if (address == EEPROM.length()) {
address = 0;
}
delay(500);
}
打开串口监视器,可以看到刚才写进入的随机数据:
![7875944b3bc254f05abcb07004697d37.png](https://i-blog.csdnimg.cn/blog_migrate/ecf48fcd4befedd9d83bc4cb99e16a85.jpeg)
EEPROM一般用于存储设置参数等数据,如果我们想把设备RESET,很简单设置一个Clear程序即可(程序22.3):
/*
程序22.3
作者:Ardui.co
效果:EEPROM 清零
*/
#include <EEPROM.h>
void setup() {
pinMode(13, OUTPUT);
for (int i = 0 ; i < EEPROM.length() ; i++) {
EEPROM.write(i, 0);
}
digitalWrite(13, HIGH);
}
void loop() { // 这循环很闲,啥事情都不用干
}
现在让Arduino不停地从0号地址位开始的读取EEPROM,把读取到的数值输出到串口监视器上。如果向串口监视器输入数字,更新从0号地址位开始的EEPROM信息。并间接的更新了输出到监视器上的数值。
由于官方EEPROM 每次只能写入或读取一个地址位,因此我们设置了两个函数 EEPROM_write 与 EEPROM_read,形参跟官方一样,但可以连续读取或写入(程序22.4):
/*
程序序1.22.4
作者:Ardui.co
效果:读取串口数据流,写入EEPROM
*/
#include <EEPROM.h>
#define EEPROM_write(address, p) {int i = 0; byte *pp = (byte*)&(p);for(; i < sizeof(p); i++) EEPROM.write(address+i, pp);}
#define EEPROM_read(address, p) {int i = 0; byte *pp = (byte*)&(p);for(; i < sizeof(p); i++) pp=EEPROM.read(address+i);}
unsigned long incomingByte = 0; // 定义无符号长整数型变量incomingByte初始值为0
unsigned long incomingByteTemp = 0;
void setup() {
Serial.begin(9600); // 打开串口,设置数据传输速率9600
}
void loop() {
if (Serial.available() > 0) {
incomingByte = Serial.parseInt(); //读取串口传入的下一个有效整数,把该整数赋值给incomingByte变量
EEPROM_write(0, incomingByte) //把incomingByte变量从0地址位开始写入EEPROM
}
if(incomingByte != incomingByteTemp) {
EEPROM_read(0, incomingByte) //从0地址位开始读取EEPROM,把值写入变量incomingByte
Serial.println(incomingByte); //通过串口输出
incomingByteTemp = incomingByte;
delay(1000);
}
}
串口命令 Serial.parseInt() 的作用是查找传入的串行数据流中的下一个有效整数。Serial.read()只能每次读取一个字节,Serial.parseInt() 一次能读取一个数据流。
byte *pp=(byte*)&p 的意思是:定义了一个字节型数组指针pp,将它指向了变量 p 。(byte*) 是为了将&p 强制转换为字节型指针,以便赋值给*pp 。