03.ESP8266与EEPROM

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();
}

本笔记参考博哥博客 如果对你有帮助,可以赏我几块硬币可好?

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注

Title - Artist
0:00