Внутри Ардуины есть EEPROM, конечно же. Много места не надо, чтобы хранить пяток длинных целых, но есть нюанс. EEPROM имеет слишком ограниченный ресурс на запись. Хотелось бы писать данные раз в несколько секунд хотя бы. Ресурс же EEPROM позволяет это делать вполне обозримое время, то есть, встроенная память явно не вечна.
Сначала я хотел обмануть судьбу записывая структурку данных в разные места 1К памяти чипа по кругу. Упёрся в то, что указатель надо где-то хранить тоже, а данные достаточно случайные, чтобы использовать какой-то маркер для последовательного поиска.
Необходимость хранения указателя можно обмануть разными способами. Например так:
struct MarkedSavedData { byte marker; // показывает, занято место или нет. struct SavedData { // Собственно данные для сохранения } } data;
Структуркой MarkedSavedData заполняется eerpom или флеш или что-то по кругу. Чтобы не писать указатель, в свободных записях делаем data.marker=0x00, а в занятой текущей data.marker=0xff, например. В процессе работы, конечно же, запись идёт по указателям, а при старте контроллера просто поиском по всей памяти ищется структура с data.marker==0xff — это последние правильные данные. Плохо, что каждый раз две записи получаются тк надо обнулить data.marker освобождаемой записи.
Есть вариант с последовательным счётчиком.
struct MarkedSavedData { unsugned int counter; // последовательный номер записи. struct SavedData { // Собственно данные для сохранения } } data;
На каждую запись увеличивать счётчик на единицу забив на переполнение. При старте контроллера искать самый большой счётчик с учётом возможного переполнения, что не так уж и трудно, а сэкономить sram можно сделав для этого функцию и поместив промежуточные структурки в стеке в локальных переменных её.
Всё это хорошо, но это припарки.
Коллеги из НТЦ Метротек подсказали поискать FRAM. Это ферритовая память с бешеным быстродействием и 1014 циклами записи.

Услужливый Aliexpress привёз мне вот такой модуль. Память в виде модуля дорогая весьма, кстати. Сам же чип стоит 16р/шт. В микросхеме 512 байт, то есть, вроде и немного, но с учётом бесконечному числу записей вполне достаточно.
Погуглив на тему готового чего-то для этого чипа я не нашёл ничего. Отличная кошка, решил я, буду на ней тренироваться! Открыл доку по Wire, даташит по FM24, чей-то проект EEPROM/I2C с похожим интерфейсом и набросал класс для FRAM.

Проект на гитхабе: github.com/nw-wind/FM24I2C
Пример прилагается вот такой.
#include "FM24I2C.h" // Объект для платы. Адрес в i2c. FM24I2C fm(0x57); void setup() { Wire.begin(); Serial.begin(9600); char str1[]="12345678901234567890"; char str2[]="qwertyuiopasdfghjklzxcvbnm"; int a1=0x00; // Первый в FRAM int a2=0x40; // Второй адрес в FRAM fm.pack(a1,str1,strlen(str1)+1); // Пишем в память delay(5); fm.pack(a2,str2,strlen(str2)+1); // Пишем вторую строку delay(5); char buf[80]; fm.unpack(a2,buf,strlen(str2)+1); // Читаем вторую Serial.println(str2); fm.unpack(a1,buf,strlen(str1)+1); // Читаем первую Serial.println(str1); }
Протокол i2c для FRAM сильно проще, чем для EEPROM. Память работает быстрее передачи данных по шине и можно лить хоть все 2К ардуининых мозгов за один раз. Польза от моего кода хоть в том, что нет лишнего разбиения на блоки по 32 байта или вообще побайтной передачи.
class FM24I2C { private: int id; public: FM24I2C(int id_addr); ~FM24I2C(); void pack(int addr, void* data, int len); // Упаковать данные в FRAM int unpack(int addr, void* data, int len); // Распаковать из FRAM. Возвращает количество переданных байтов. // Это уже специально для меня, пишет беззнаковые длинные целые. void inline writeUnsignedLong(int addr, unsigned long data) { pack(addr, (void*)&data, sizeof(unsigned long)); } // И читает. unsigned long inline readUnsignedLong(int addr) { unsigned long data; return unpack(addr, (void*)&data, sizeof(unsigned long)) == sizeof(unsigned long) ? data : 0UL; } // Можно для других типов сделать чтение/запись, но мне было не нужно, а флеш занимает. // Каждый же может унаследовать класс и дописать сам. };
Кода же немножко совсем.
void FM24I2C::pack(int addr, void* data, int len) { Wire.beginTransmission(id); Wire.write((byte*)&addr,2); Wire.write((byte*)data,len); // Наверное, стоит всё же unsigned int использовать :) Wire.endTransmission(true); } int FM24I2C::unpack(int addr, void* data, int len) { int rc; byte *p; Wire.beginTransmission(id); Wire.write((byte*)&addr,2); Wire.endTransmission(false); Wire.requestFrom(id,len); // Здесь можно поспорить про замену rc на p-data :) for (rc=0, p=(byte*)data; Wire.available() && rc < len; rc++, p++) { *p=Wire.read(); } return(rc); }
Так как на модуле, кроме чипа и разъёмов, практически ничего нет, уже хочу купить несколько микросхем. Нравятся.

