EEPROM(Electrically Erasable Programmable Read-Only Memory),电可擦可编程只读存储器——一种掉电后数据不丢失的存储芯片。
EEPROM可以在不使用文件和文件系统的情况下用来固化一些数据,常见的比如用来保存SSID或者Password,保存用户设置等数据,这样就可以不用每次都通过烧写程序来改变系统运行时的初始值。
Arduino提供了完善的eeprom库,不过需要注意的是ESP8266没有硬件EEPROM,使用的是flash模拟的EEPROM。
原理
EEPROM库在Arduino中经常用于存储设定数据。当然基于Arduino的ESP8266也不例外。但是,和真正的Arduino板子不一样的是,ESP8266采用的方式是将flash中某一块4K的存储模拟成EEPROM。至于为什么是4K呢?主要原因是flash是以sector为一个单位,1 sector等于4096Bytes(4KB),操作flash时是以sector为一个整体来操作。
读取操作是通过ESP8266 SDK提供的API将flash中的内容读取到Buffer中是没有限制一次就要将4K全读完,Buffer的大小由EEPROM.begin(size)决定,但是由于Buffer大小会占用内存RAM,所以务必按照实际需要来定义大小。
写入操作是通过commit将flash eeprom地址的4K 存储内容删除后才将Buffer写入flash中(也就是说就算你buffer只有4个字节,但是最终还是会刷新整个sector)
官方介绍可以简化成以下几点:
- ESP8266 EEPROM的操作其实和Arduino EEPROM的操作核心思想很像,但是又有所不同。
- 和标准的EEPROM库不一样的是,你需要在读或者写操作之前先通过
- EEPROM.begin(size) 来声明你需要操作的存储大小,size取值范围为4~4096字节。
EEPROM.write() 不会立刻把内容写进flash,如果你希望保持到flash去,那么你必须调用 EEPROM.commit()。当然, EEPROM.end() 不仅也能完成commit,同时会释放申请的eeprom ram资源。 - EEPROM库跟在SPIFFS文件系统的后面(那么读者就得考虑不同的SPIFFS大小对应的地址是不一样)。
具体函数:
1.申请内存
函数: begin(size)
参数:
size:要申请的内存大小。
返回值: 无;
2.写数据
函数: write(address,value)
参数:
address:要写入的地址位置,取值范围为内存空间的地址0~size。
val:写入的数据。
3.读数据
函数: read(address)
参数:
address:要读取的地址位置,取值范围为内存空间的地址0~size。
返回值: 返回存储数据;
4.把内存空间的数据覆盖到flash eeprom块去。
函数: commit()
参数: 无;
返回值: 返回bool值,表示是否覆盖成功;
注意:写回flash之前会把整一块sector全部擦除掉,也就意味着就算我们begin(1)最终也是会擦除4096字节空间。但是size的大小决定了内存空间的剩余量以及回写的快慢,所以根据具体情况来设置size。
5.写入flash,并且释放内存空间。
函数: end()
参数: 无;
返回值: 无;
建议操作完EEPROM之后,必须调用这个方法,回收内存空间很重要。
实例讲解:
//写数据
#include <EEPROM.h>
int addr = 0; //EEPROM数据地址
void setup()
{
Serial.begin(9600);
Serial.println("");
Serial.println("Start write");
EEPROM.begin(100);
for(addr = 0; addr<100; addr++)
{
int data = addr;
EEPROM.write(addr, addr); //写数据
}
EEPROM.end(); //保存更改的数据
Serial.println("End write");
}
void loop()
{
}
//读数据
#include <EEPROM.h>
int addr = 0;
void setup()
{
Serial.begin(9600);
Serial.println("");
Serial.println("Start read");
EEPROM.begin(100);
for(addr = 0; addr<100; addr++)
{
int data = EEPROM.read(addr); //读数据
Serial.print(data);
Serial.print(" ");
delay(2);
}
//释放内存
EEPROM.end();
Serial.println("End read");
}
void loop()
{
}
//清除数据
#include <EEPROM.h>
void setup() {
EEPROM.begin(100);
// write a 0 to all 512 bytes of the EEPROM
for (int i = 0; i < 100; i++) {
EEPROM.write(i, 0);
}
//释放内存
EEPROM.end();
}
void loop() {
}
在没有应用结构体之前,不管是写入还是读取操作,我们都需要记住具体的存储位置。特别是当配置数据越来越多的时候或者别人维护的时候,非常容易出错。那么有没有办法优雅地解决这种问题呢?当然有,那就是结构体的妙用,我们不需要关注具体的位置,只需要关注数据本身。看以下代码:
#include <EEPROM.h>
#define DEFAULT_STASSID "danpianjicainiao"
#define DEFAULT_STAPSW "boge"
struct config_type
{
char stassid[32];
char stapsw[64];
};
config_type config;
/*
* 保存参数到EEPROM
*/
void saveConfig()
{
Serial.println("Save config!");
Serial.print("stassid:");
Serial.println(config.stassid);
Serial.print("stapsw:");
Serial.println(config.stapsw);
EEPROM.begin(1024);
uint8_t *p = (uint8_t*)(&config);
for (int i = 0; i < sizeof(config); i++)
{
EEPROM.write(i, *(p + i));
}
EEPROM.commit();
}
/*
* 从EEPROM加载参数
*/
void loadConfig()
{
EEPROM.begin(1024);
uint8_t *p = (uint8_t*)(&config);
for (int i = 0; i < sizeof(config); i++)
{
*(p + i) = EEPROM.read(i);
}
EEPROM.commit();
Serial.println("-----Read config-----");
Serial.print("stassid:");
Serial.println(config.stassid);
Serial.print("stapsw:");
Serial.println(config.stapsw);
}
/*
*初始化
*/
void setup() {
Serial.begin(115200);//注意需要设置波特率
ESP.wdtEnable(5000);
strcpy(config.stassid, DEFAULT_STASSID);
strcpy(config.stapsw, DEFAULT_STAPSW);
saveConfig();
}
/*
*主循环
*/
void loop() {
ESP.wdtFeed();
loadConfig();
}
本笔记参考博哥博客 如果对你有帮助,可以赏我几块硬币可好?