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

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

    Внутри Ардуины есть 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);
    }
    

    Так как на модуле, кроме чипа и разъёмов, практически ничего нет, уже хочу купить несколько микросхем. Нравятся.
    Есть в этом смысл?

    Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 45
    • +1
      Странно, что готовые модули продаются по 10 баксов штука. Сам FM24CL16 стоит $1 в розницу и $0.5 оптом на али. Он имеет настолько простую схему подключения, что платить в 10-20 раз за модуль это слишком.

      P.S. А почему бы в данной ситуации вместо оперативки microsd? Туда же можно писать уже что угодно — хоть GPS хоть коды ODB2 хоть данные гироскопа-акселерометра.
      • 0
        Там I2C, перемычки, ручное изготовление…
        • 0
          берем breakout ценой примерно ноль, перемыкиваем прямо на внешний контактах, останется только напряжение подтянуть и все.
      • +1
        Да, меня тоже поразила разница в цене микрухи и сборки.
        За чип хотят 16р/штука.

        SD хорошо, но там слишком много контактов. Это же мотоцикл, более того, эндуро. Там такие вибрации, что контакты противопоказаны.

        Ну и жрёт память SD в ардуине и флеш жрёт и быстродействие совсем не то. Здесь же i2c одна на всех, две ножки всего. Никаких файловых систем, просто пиу и готово.
        • 0
          P.S. А почему бы в данной ситуации вместо оперативки microsd?

          насколько я понимаю, только промышленные micro sd карты (с соответствующей ценой) сравнимы по циклам записи с eeprom. у дешевых карт — циклов записи меньше.
          тут, конечно, может помочь больший размер карты — можно устраивать wear leveling с циклическими буферами.
          но в этом случае возникает вопрос про хранение указателя ) или поиска последней записи по сигнатуре )
          в принципе, указатель в данном случае:
          а. можно хранить в памяти, а записывать только по прерыванию на понижении питания
          б. использовать код грея для указателя — чтобы уменьшить износ младших бит в ячейке

          недавно был пост про малинку и readonly fs — там обсуждали износостойкость и были ссылки на информацию по картам
          https://geektimes.ru/post/283802/
          • 0
            Про беду с хранением указателя я писал выше. Можно писать блоками на sizeof некую структуру и в ней держать байт маркера. О, сейчас напишу в самой статейке.
            • 0
              Написал в тексте статьи два способа организации циклической записи без хранения указателя.
          • +2
            Обмануть судьбу можно, как мне кажется, просто зануляя предыдущее значение. Суммарное снижение ресурса получится в 2 раза или даже чуть меньше.
            А моточасы, надо полагать, величина всегда возрастающая, поэтому можно просто писать в следующую ячейку и потом искать наибольшее значение.

            Впрочем, попробовать новые штуки — тоже хорошо :]
            • +1
              МЧ и одометры растут конечно, но им тоже нужен сброс. Есть одометр поездки, сбрасывается перед стартом. Есть МЧ после ТО, тоже сбрасываются после того самого ТО.
              • 0
                Хорошо, при сбросе (записи нуля) просто занулить все ячейки.
                • 0
                  Я думал занулить в sram, а запишет оно уже по расписанию.
            • 0
              Я не понял — надо два занчения хранить при выключении питания? И ради этого сыр бор?
              В чем проблема хранить показания одометра в еепроме (не обязательно внутреннем — их на любой плате россыпь обычно, спаял да прикрутил на соплях) по смещению 0x00, отвести на это целых четыре байта, например, а часы хранить в структуре по смещению 0x04???
              • 0
                Там больше. Три одометра, два счётчика МЧ.
                Может ещё что-то всплывёт.
                Микросхема стоит 16р/ш при покупке от 10 штук. Я её потом напаяю прямо на плату, когда буду разводить. Главное — код мизерный, память не ест, гемора нет.
                • 0
                  может хранить в микросхеме часов с батарейкой, и изредка сбрасывать на епром?
                  вот например, RTC + i2c + 64 bytes RAM backed by battery
                  http://www.nxp.com/documents/data_sheet/PCF85363A.pdf

                  батарейки живут долго, там успеешь и имплементировать отслеживание разряда батареии и приглашение её поменять.
                  • 0
                    А что будет с памятью в pcf85363a, если сядет батарейка или надо просто её поменять?
                    Батарейка от вибрации (мотоцикл) может и отскочить на ходу. Лишиться часов не так страшно, как одометров и моточасов.
                    Бакап в епроме будет ээээ сильно устаревшим же.

                    Опять гемор, зачем, если есть fram?
                • 0
                  У EEPROM время жизни скажем ~100 тысяч циклов, если обновлять одну и ту же ячейку раз в секунду, то хватит примерно на сутки. Идея заключается в том, чтобы использовать новую позицию для записи каждый раз, 1К памяти хватит на 3 года.

                  Простейший алгоритм реализации — это ввести некий счетчик, который увеличивается каждый раз при сохранении структуры (содержащей данные и сообственно счетчик). Если счетчик дошел до 100.000 — инкрементируется позиция структуры (которую можно хранить по фиксированному адресу, т.к. запись туда происходит редко).
                  • 0
                    Как раз примерно такое я описал только что в статье по просьбе комментирующего.
                    • +1
                      У EEPROM время жизни скажем ~100 тысяч циклов

                      не надо забывать, что это применимо к одному биту. и лет 50-60 назад господин Грей придумал, как уменьшить износ физических переключателей для бинарных счетчиков.

                      если мы просто инкременитируем байт — то каждый раз младший бит мигает. и в конце концов умирает, а старшие разряды живы.
                      в итоге, добавив минимальную логику на преобразование gray-binary мы можем растянуть срок жизни еще в 8 раз )

                      Википедия — Код Грея
                      • +1
                        Это понятно.
                        Но использование fram добавляет 16р к цене устройства и позволяет не париться с проблемами еепрома навсегда.
                      • 0
                        Идея заключается в том, чтобы использовать новую позицию для записи каждый раз, 1К памяти хватит на 3 года.

                        Всё хорошо, если стирание/запись не постраничная, как это часто бывает. Но на таких размерах это обычно не так.

                        • 0
                          Даже три года — это очень очень мало.
                          Вы видели автомобиль с электронным одометром, который дохнет через три года? Я нет.
                      • +1
                        Причины сыра-бора описаны во втором абзаце — ограниченный ресурс еепрома. Если в мегу писать раз в 10 секунд, то гарантированно проработает она только ~11 дней(100 000 циклов/ячейку / 3600 / 24 * 10) и даже установка внешней еепромки увеличит ресурс всего в 10 раз (для первой загуглившейся AT24C16B). Так что проблему это не решит.
                        Хотя всё же проще кольцевой буфер или, ИМХО, правильнее детектировать отключение питания и тогда всё сохранять, например, как тут http://radiokot.ru/forum/viewtopic.php?p=1120034#p1120034
                        • 0
                          Писать по пропаданию питания — хорошая идея, но… ненадёжно.
                          Я это планирую сделать, но с FRAM как-то заметно проще получается.
                      • +1
                        Уже было напрягся увидеть что-то такое, а оказывается вон оно как
                        • 0
                          Я про это думал. У нас есть в музее в конторе.
                        • 0
                          Сначала я хотел обмануть судьбу записывая структурку данных в разные места 1К памяти чипа по кругу. Упёрся в то, что указатель надо где-то хранить тоже, а данные достаточно случайные, чтобы использовать какой-то маркер для последовательного поиска.

                          Можно было, наверное, два кольцевых буфера завести, один с указателем (увеличивается равномерно, при поиске искать самое большое число, которое не FF), а второй собственно с данными.
                          • 0
                            Есть средство лучше. Описал в статейке, посмотри обновление.
                            • 0
                              Я делаю примерно так. Два однобайтовых каунтера — глобальный в ячейке 0 и локальный в самой структуре (которую будем двигать по eeprom). Глобальный задает номер блока в который пишем наши данные (ну то есть смещение блока данных). После каждой записи увеличиваем локальный счетчик и перезаписываем его в месте с данными. Как только дошли до 256 увеличиваем глобальный счетчик и переходим к следующему блоку сбросив локальный счетчик в 0. Дошли до конца памяти — глобальный счетчик в 0 и дальше по циклу. Глобальный счетчик перезаписывается только 1 раз на 256 записей. Локальный каждый раз, но он после каждых 256 записей переезжает на новое место.
                            • +2
                              Из личного опыта FRAM память довольно чувствительна к помехам по напряжению питания. Если использовать для установки на мотоцикл, это необходимо учесть.
                              • 0
                                Я на входе ставлю стабилизатор (китайский импульсный понижатель постоянного напряжения с ШИМ) и толстый конденсатор для дополнительного сглаживания и питания в случае чего. На вход, где 12В, через делитель подключаю ногу ардуины. Если на ней пропало напряжение, значит у нас есть несколько миллисекунд на запись состояния и самоубийство. Экранчик питается отдельно. Думаю как лучше сделать.
                                • +1
                                  А еще лучше к пакету записываемых данных добавить контрольную сумму CRC16 (чтобы быстрее считать можно использовать табличный метод). При скачке питания или внезапном отключении питания может записаться только часть данных, а не весь пакет.
                                  • 0
                                    Хорошая мысль, кстати.
                                    Добавлю в проект, спасибо.
                                    Тогда надо писать в циклический буфер как минимум на две структурки таких. Для бакапа.
                              • 0
                                Обновил статью. Добавил ссылки на алиекспресс и примеры решения с EEPROM или Flash.
                                • 0
                                  Зачем постоянно в EEPROM писать — при включении прочитали последнее значение, при выключении записали.
                                  Нужно только реализовать «реакцию» на отключение питания, и схему на поддержание питания Ардуины, что бы успела скинуть последнее значение в EEPROM.
                                  • 0
                                    Если бы было плевать на потерю этих данных, я бы так и сделал. Вероятность потери же весьма велика. Права на ошибку нет, что-то сглючило с еепромом и прощай. Это всё-таки не микроволновка, а мотоцикл.
                                    • 0
                                      И Ваш способ не избавляет от вероятности потери данных:
                                      ...«что-то сглючило с FRAM и прощай»...

                                      Помехи обмена по I2C, блокирующая реализация Wire библиотеки…
                                      • 0
                                        i2c с подтверждением работает. Я в библиотеке верю в качество передачи, но надо убеждаться. Это в ближайшее время пофиксю.
                                        Всё глючит, потом бакап в eeprom — это хорошо, но только как бакап.
                                      • 0

                                        Я в своём проекте ambox.me делаю именно так как предложенной выше. Плюс еже простейшая схема из электролита и диода на входечтобы питание не пропадало неожиданно. АЦП и источник опорного в Ардуине, благо есть. Пр правильной организации кольцевого буфера можно не только повысить ресурс eeprom на 2.5 порядка, но и гарантировать откат на предыдущее значение, если что.

                                        • 0
                                          Да, я всё это уже и сам придумал, когда статью написал. Я просто не стал бороться с еепромом, а решил проблему тупо железкой, с которой биться не нужно. :)
                                          Вот, рекомендую.
                                    • 0
                                      Любопытная память, спасибо. Хотя раз уж тут речь о микроконтроллере, то это десятки миллиампер потребляемого тока, а значит, если добавить хороший емкий конденсатор в цепь питания, то при выключении внешнего питания у вас будет несколько секунд на то, чтобы разобрать все данные в SRAM и записать в EEPROM. При таком подходе срок её жизни существенно увеличится.
                                      • 0
                                        Нескольких секунд не будет, будут миллисекунды. Писать надо по прерыванию так как до чтения ноги питания может не дойти. Прерываний внешних всего два, оба заняты.
                                        Способ с FRAM надёжнее заметно. Конечно же, намного проще.
                                        Одно другое не исключает, даже наоборот. :) Я собираюсь писать и туда и туда. Просто fram на i2c и шина тоже может сглючить даже на 100кГц.
                                        • 0
                                          по поводу «Прерываний внешних всего два, оба заняты.» не совсем верно ))
                                          прерываний больше, просто их на одну строчку кода сложнее обрабатывать))))
                                          не ограничивайте себя int0 & int1
                                          ловите pin change на порт и просто проверяйте нужную ногу

                                          для атмеги328
                                          4 Pin Change Interrupt Request 0 (pins D8 to D13) (PCINT0_vect)
                                          5 Pin Change Interrupt Request 1 (pins A0 to A5) (PCINT1_vect)
                                          6 Pin Change Interrupt Request 2 (pins D0 to D7) (PCINT2_vect)

                                          тут есть прекрасный саммари http://gammon.com.au/interrupts, чтобы толстый даташит не перекапывать
                                          • 0
                                            Я в курсе про пинченч и векторы.
                                            Я к тому, что те два без всякого вектора, хардкорного программирования, группирования, разбора в функции, с какой ноги пришло оно.
                                            Если нет фрама, то да, надо упарываться со схемотехникой и ломать голову над живучестью еепрома. А если есть, то не надо :)
                                          • 0
                                            Это какой конденсатор поставите. На 33000 микрофарад Ардуина живет больше 5 секунд. Вполне достаточно для сохранения, можно даже и меньше конденсатор поставить.

                                        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.