Замена EEPROM для ESP8266/32 для хранения любых данных в файлах
- Механизм автоматического "флага" первой записи
- Поддержка всех файловых систем (LittleFS, SPIFFS, SDFS)
- Поддержка любых типов статических данных
- Отложенная запись по таймауту
- "Обновление" данных - файл не перезапишется, если данные не изменились
Примечание: библиотека сохраняет бинарные, т.е. непригодные для чтения человеком данные. Если нужно хранение данных в файле в читаемом формате - используйте библиотеку PairsFile
ESP8266, ESP32
- Библиотеку можно найти по названию FileData и установить через менеджер библиотек в:
- Arduino IDE
- Arduino IDE v2
- PlatformIO
- Скачать библиотеку .zip архивом для ручной установки:
- Распаковать и положить в C:\Program Files (x86)\Arduino\libraries (Windows x64)
- Распаковать и положить в C:\Program Files\Arduino\libraries (Windows x32)
- Распаковать и положить в Документы/Arduino/libraries/
- (Arduino IDE) автоматическая установка из .zip: Скетч/Подключить библиотеку/Добавить .ZIP библиотеку… и указать скачанный архив
- Читай более подробную инструкцию по установке библиотек здесь
- Рекомендую всегда обновлять библиотеку: в новых версиях исправляются ошибки и баги, а также проводится оптимизация и добавляются новые фичи
- Через менеджер библиотек IDE: найти библиотеку как при установке и нажать "Обновить"
- Вручную: удалить папку со старой версией, а затем положить на её место новую. "Замену" делать нельзя: иногда в новых версиях удаляются файлы, которые останутся при замене и могут привести к ошибкам!
FileData;
FileData(fs::FS* fs);
FileData(fs::FS* fs, const char* path);
FileData(fs::FS* fs, const char* path, uint8_t key);
FileData(fs::FS* fs, const char* path, uint8_t key, void* data, uint16_t size);
FileData(fs::FS* fs, const char* path, uint8_t key, void* data, uint16_t size, uint16_t tout);
// fs - файловая система, адрес (&LittleFS, &SDFS..)
// path - путь (имя) файла. Может быть любым, как и расширение ("/myData", "/data/settings.dat")
// key - ключ первой записи. Не рекомендуется задавать 0 и 255. Рекомендуется использовать символы ('A', 'F')
// data - ссылка на переменную (массив, структуру, класс)
// size - размер переменной, можно передать как sizeof(переменная)
// tout - таймаут обновления в миллисекундах (умолч 5000)
В ESP8266/32 EEPROM память эмулируется из Flash памяти, реализация EEPROM во встроенной библиотеке имеет следующие минусы:
- Размер ограничен 4 кБ
- Весь указанный в
EEPROM.begin()
объём памяти дублируется в оперативной памяти до вызоваEEPROM.end()
- При любом изменении в "EEPROM" памяти (даже 1-го байта) и вызове
EEPROM.commit()
полностью стирается и перезаписывается весь сектор 4 кБ. То есть износ памяти происходит не по ячейкам, а полностью всей EEPROM области! Около 10-20 тысяч перезаписей данных и EEPROM памяти больше нет
Предлагается использовать для хранения данных файловую систему (например встроенную LittleFS), которая сама заботится о перезаписи памяти и занимается ротацией файлов по отведённой области, что многократно увеличивает ресурс памяти и надёжность хранения данных. Также это позволит "скачивать" сохранённые данные, делать бэкапы и так далее. Данная библиотека является аналогом EEManager и имеет схожие с ней механизмы и возможности:
- "Подключение" статических переменных любого типа, библиотека сама будет читать и писать их содержимое в файл
- Механизм "ключа первого запуска" - если файл не существует или не содержит указанный ключ - в файл будут записаны данные "по умолчанию"
- Механизм отложенной записи по таймауту - после изменения данных достаточно дать библиотеке команду на обновление, и она обновит данные по истечению таймаута
// установить файловую систему и путь к файлу
void setFS(fs::FS* nfs, const char* path);
// установить ключ
void setKey(uint8_t key);
// подключить данные (переменную)
void setData(void* data, uint16_t size);
// установить таймаут записи
void setTimeout(uint16_t tout);
// прочитать файл в переменную
// возврат: FD_FS_ERR/FD_FILE_ERR/FD_WRITE/FD_ADD/FD_READ
FDstat_t read();
// обновить сейчас
// возврат: FD_FS_ERR/FD_FILE_ERR/FD_WRITE/FD_NO_DIF
FDstat_t updateNow();
// отложить обновление на заданный таймаут
void update();
// тикер, обновит данные по таймауту
// возврат: FD_FS_ERR/FD_FILE_ERR/FD_WRITE/FD_NO_DIF/FD_WAIT/FD_IDLE
FDstat_t tick();
// записать данные в файл
// возврат: FD_FS_ERR/FD_FILE_ERR/FD_WRITE
FDstat_t write();
// сбросить ключ
// возврат: FD_FS_ERR/FD_FILE_ERR/FD_RESET
FDstat_t reset();
// включить режим увеличения данных с добавлением в файл без очистки
void addWithoutWipe(bool addw);
// ============== СТАТУСЫ ==============
FD_IDLE // 0 - холостая работа
FD_WAIT // 1 - ожидание таймаута
FD_FS_ERR // 2 - ошибка файловой системы
FD_FILE_ERR // 3 - ошибка открытия файла
FD_WRITE // 4 - запись данных в файл
FD_READ // 5 - чтение данных из файла
FD_ADD // 6 - добавление данных в файл
FD_NO_DIF // 7 - данные не отличаются (не записаны)
FD_RESET // 8 - произведён сброс ключа
Для хранения "настроек" программы в глобальной области, чтобы всегда иметь к ним доступ:
- Создать глобальную переменную и объект
FileData
- Передать переменную в объект
- Прочитать данные
read()
при запуске - Вызывать
tick()
вloop()
- После изменения данных вызвать
update()
- По истечению таймаута данные сами запишутся в файл
Таким образом безопасно вызывать
update()
несколько раз подряд, например при изменении "настройки" кнопками - данные обновятся только после окончания ввода и таймаута
Для хранения настроек в файле и чтении/изменении внутри функций в программе:
- Создать переменную и объект
FileData
- Передать переменную в объект
- Прочитать данные
read()
- Если данные изменились и их нужно сохранить - вызвать
updateNow()
void func() {
Data mydata;
FileData data(&LittleFS, "/data.dat", 'A', &mydata, sizeof(mydata));
data.read();
// ...
data.updateNow();
}
- При изменении размера данных (при изменении количества полей в структуре) будет выполнен сброс при чтении - в файл запишется новая структура
- При установке флага
addWithoutWipe(true)
и при увеличении размера данных: данные старого размера прочитаются из файла, затем в файл будут дописаны новые данные (разница со старым размером). Это удобно при разработке проекта - добавление новых "настроек" не будет сбрасывать старые
#include <Arduino.h>
#include <FileData.h>
#include <LittleFS.h>
struct Data {
uint8_t val8;
uint16_t val16;
uint32_t val32 = 12345;
char str[20];
};
Data mydata;
FileData data(&LittleFS, "/data.dat", 'B', &mydata, sizeof(mydata));
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println();
LittleFS.begin();
// прочитать данные из файла в переменную
// при первом запуске в файл запишутся данные из структуры
FDstat_t stat = data.read();
switch (stat) {
case FD_FS_ERR: Serial.println("FS Error");
break;
case FD_FILE_ERR: Serial.println("Error");
break;
case FD_WRITE: Serial.println("Data Write");
break;
case FD_ADD: Serial.println("Data Add");
break;
case FD_READ: Serial.println("Data Read");
break;
default:
break;
}
Serial.println("Data read:");
Serial.println(mydata.val8);
Serial.println(mydata.val16);
Serial.println(mydata.val32);
Serial.println(mydata.str);
}
void loop() {
// data.tick(); // вызывать тикер в loop
// можно отловить момент записи
if (data.tick() == FD_WRITE) Serial.println("Data updated!");
// запишем в данные строку из монитора порта
// а также присвоим остальным переменным случайные значения
if (Serial.available()) {
int len = Serial.readBytes(mydata.str, 20);
mydata.str[len] = '\0';
mydata.val8 = random(255);
mydata.val16 = random(65000);
Serial.println("Update");
// отложить обновление
data.update();
}
}
- v1.0
При нахождении багов создавайте Issue, а лучше сразу пишите на почту alex@alexgyver.ru
Библиотека открыта для доработки и ваших Pull Request'ов!
При сообщении о багах или некорректной работе библиотеки нужно обязательно указывать:
- Версия библиотеки
- Какой используется МК
- Версия SDK (для ESP)
- Версия Arduino IDE
- Корректно ли работают ли встроенные примеры, в которых используются функции и конструкции, приводящие к багу в вашем коде
- Какой код загружался, какая работа от него ожидалась и как он работает в реальности
- В идеале приложить минимальный код, в котором наблюдается баг. Не полотно из тысячи строк, а минимальный код