Как стать автором
Обновить

FRAM через I2C для Arduino как замена EEPROM

Время на прочтение3 мин
Количество просмотров26K
Продолжу рассказывать про приборную панель для мотоцикла. Это замечательное устройство содержит одометр, то есть, счётчик пройденного пути в километрах, а у того есть плохое свойство — он должен сохранять данные и при выключенном питании. Ой, ещё есть моточасы, их тоже надо хранить как-то энергозависимо.

Внутри Ардуины есть 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 циклами записи.

image

Услужливый Aliexpress привёз мне вот такой модуль. Память в виде модуля дорогая весьма, кстати. Сам же чип стоит 16р/шт. В микросхеме 512 байт, то есть, вроде и немного, но с учётом бесконечному числу записей вполне достаточно.

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

image

Проект на гитхабе: 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);
}

Так как на модуле, кроме чипа и разъёмов, практически ничего нет, уже хочу купить несколько микросхем. Нравятся.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Есть в этом смысл?
44.04% Хорошо сделано, воспользуюсь.48
5.5% Фигня.6
8.26% Надо было гуглить тщательнее, всё уже есть.9
14.68% Опять говнокод.16
27.52% Что такое FRAM?30
Проголосовали 109 пользователей. Воздержались 46 пользователей.
Теги:
Хабы:
+19
Комментарии47

Публикации

Изменить настройки темы

Истории

Ближайшие события

PG Bootcamp 2024
Дата16 апреля
Время09:30 – 21:00
Место
МинскОнлайн
EvaConf 2024
Дата16 апреля
Время11:00 – 16:00
Место
МоскваОнлайн
Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн