Probably the best way to add emulated EEPROM to your Arduino project:
- easy to use
- extremely low flash memory wear
- any number of independent EEPROM objects
- ESP32, ESP8266 (STM32 is planned)
struct EE {
int devID = 0XEFBEADDA; //it will be a default value
short step; //at the first init will be equal to zero
};
EEBoom<EE> ee;
void setup() {
ee.begin(); //will use two default sectors
}
void loop() {
ee.data.step++; //change data in RAM
ee.commit(); //fix it in flash memory
delay(1000);
}
We don't erase the entire memory page every time we need to update the data, we write a modified copy to the next available space on the same page.
And only when we run out of space on the current page will we move on to the next page.
Thus:
- data is protected against any power failures
- physical wear and tear on memory tends to be as low as possible
The sectors are counted from larger to smaller:
ee.begin(1000, 3); //will fill 1000 -> 999 -> 998 -> 1000 -> ...
-
ESP8266 - arduino framework provides one page of flash for EEPROM emulation. For
4MB
this is sector1019
(total has1024
). The sectors have a size of4096 byte
. Here you can see more details.If we want to use more than one page, we have to sacrifice the ability to work with SPIFFS/LittleFS and OTA Updates, since they use the same pages (
1018
,1017
, ...).On the other hand, the library has an option to return to the base sector. To do this,
ee.goZero()
must be called before attempting an updateBy default
ee.begin()
assumes the use of sectors1019
and1018
(for4MB
). But you can always use any configuration - for exampleee.begin(1000, 100)
initializes pages1000
-901
for operation. -
ESP32 - unlike ESP8266 memory is divided into partitions. Partitions have different types and purposes. For more details see here.
ESP32 has a built-in solution for data storage - NVS (non-volatile storage). A special partition is allocated for storage, and sectors are formatted in a special way. We won't describe advantages and disadvantages here, we will only say that the versatility of NVS library makes it very wasteful.
All information about partition structure is stored in
partition_table
, which is usually located at0x8000
. You can download it with the command:esptool.py read_flash 0x8000 0xC00 partition.img
There is special software that allows you to convert this image to a text file, but we can also look at it in a regular HEX editor On the right side you can see the text labels of the partitions. There are 32 bytes allocated for information about each partition. For example, we can see that the partition labeled
nvs
starts from address0x900
(00
90
00
00
00
) and has size0x5000
(00
50
00
00
00
), which corresponds to5
sectors.There are a total of 6 partitions.
spiffs
- for working with file systems,app0
- current firmware.app1
- this is where firmware will arrive during OTA Update,coredump
- for framework needs.nvs
- should not be used for storage - various libraries and the kernel may use this area implicitly, which may lead to data lossapp1
- a good candidate if you don't plan to use updatesspiffs
- we use by default.ee.begin()
without parameters initiates the last 2 sectors of this partition for storage.Worth mentioning is the ability to create custom partitions. For example like this. To initialize the last 10 sectors in a
mypartition
partition, you can callee.begin("mypartition", 10)
// Includes =================
#include "Arduino.h" //no need in Arduino IDE
#include "EEBoom.h"
//---------------------------
EEBoom<int> ee; //just int
ee.begin();
ee.data = 55;
ee.commit(); //save to flash
//---------------------------
//for more convenience, you can wrap the data in a structure
//note how to set the values for the first zero init run
struct AppParam {
uint32_t devID = 0x12345678;
uint16_t minSpeed = 10;
uint16_t maxSpeed = 60;
uint16_t maxTemp = 85;
uint8_t lastOffSrc = PWR_BTN;
uint8_t arr[10];
char ssid[20] = "Empty_SSID";
char pass[20] = "Empty_PASS";
};
EEBoom<AppParam> ee;
ee.begin(1000, 5); //5 sectors 1000 -> 996
Serial.println(ee.data.devID); //get data from our EEPROM
ee.data.devID = getID(); //change from some func
ee.data.maxSpeed = 70; //change limits
ee.data.arr[0] = 0x55;
strcpy(ee.data.ssid, "Awesom_SSID"); //new credentials
strcpy(ee.data.ssid, "Strong_PASS");
ee.commit(); //don't forget to save it to flash
//---------------------------
#define FIRST_SECT 1000
#define NMB_SECT 3
ee.begin(); //default first sector, 2 sectors
ee.begin(NMB_SECT); //default first sector, 3 sectors
ee.begin(FIRST_SECT, NMB_SECT);
ee.begin("app1"); //last 2 sectors of app1 partition
ee.begin("mypartition", 10); //last 10 sectors of custom partition
//esp8266 default first sector = total secrots - 5 (4MB - 1019)
//esp32 default first sector - last spiffs sector
//---------------------------
//If you're using OTA Update you can go back to your home sector
ee.goZero(); //go to first ee sector
tryOTA(); //go OTA
//---------------------------
struct BadAligned {
short varShort; //2 bytes
int varInt; //4 bytes
char varCh; //1 bytes
}; //so total 7? No, this case 12 bytes
struct GoodAligned {
int varInt;
short varShort;
char varChar;
} //total 8 bytes
#pragma pack(push, 1) //some directives
struct BadAligned {
short varShort;
int varInt;
char varCh;
}; //total really 7 bytes
#pragma pack(pop) //try reducing the data structure
// Some tools ===============
ee.setPort(Serial); //you need provide desired port
ee.printInfo(); //some info about current EE object
ee.printInfo(1); //commit interval in minutes
// ↓ ↓ ↓
// User data size: 64 //real data struct size
// Whole slot size: 68 //user data + crc + dummy bytes
// First sector: 1019 //base sector
// Sector count: 20 //more sectors - longer life
// Slots per sector: 60 //how many slots on a sectore
// Current sect: 1019 //where is current slot
// Approximate flash life: 8333 days //well not bad for a minute-by-minute commit.
//---------------------------
ee.setPort(Serial); //you need provide desired port
ee.dump(Sect, //desired sector (-1 for current)
FirstLine, //first line (-1 for current)
NumbLine, //number of lines (each is 32 bytes)
FirstColumn, //first from the left column (total 32)
NumbColumn); //number of columns
ee.dump(1000, 0, 4, 0, 22);
// ↓ ↓ ↓
// Sector 1000:
// 000: DE AD BE EF 00 00 00 00 00 00 14 ED DE AD BE EF 01 00 00 00 00 00
// 001: 00 00 B2 45 DE AD BE EF 03 00 00 00 00 00 01 F2 DE AD BE EF 04 00
// 002: 05 00 00 00 00 00 9F 4A FF FF FF FF FF FF FF FF FF FF FF FF FF FF
// 003: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
ee.dump(); //equal ee.dump(-1, -1, 3)
ee.dump(1000); //entire 1000 sector - 32column x 128line
ee.dump(1000, 5, 10); //1000 sector 5-14 lines 32 columns
ee.dump(1000, 5, 10, 10, 10); //1000 sector 5-14 lines 10-19 columns
//---------------------------
ee.currentSect(); //returns current sector - like 1000
//---------------------------
ee.setPort(Serial);
ee.begin();
ee.printMsg(); //can print -> EEBoom init ok
ee.commit();
ee.printMsg(); //can print -> EEBoom can't write
if(ee.begin() == false) сantInitEE(); //you can check begin() and commit()
//---------------------------
EETool::spiffsLastSector(); //return the sector number like 1023
EETool::app1LastSector(); //you can use it with begin()
EETool::lastSectorByName("mycstmpart"); //return last sector of custom partition
EETool::printRanges(Serial); //print ranges of spiffs and app1 partitions
//---------------------------
- The library can be found by the name EEBoom and installed through the library manager in:
- Arduino IDE
- Arduino IDE v2
- PlatformIO
- Download Library .zip archive for manual installation
If you find bugs, create an Issue, or better yet, email x-radio@bk.ru
The library is open for improvements and your Pull Requests
When reporting bugs or incorrect library operation, be sure to include:
- Library version
- Which MCU is used
- SDK version (for ESP)
- Arduino IDE version
- Whether the built-in examples that use functions and constructs that cause the bug in your code are working correctly
- What code was loaded, how it was expected to work, and how it actually works
- Ideally attach minimal code where the bug is observed.