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

NVRAM из EEPROM

Уровень сложностиПростой
Время на прочтение3 мин
Количество просмотров2.6K
Всего голосов 10: ↑5 и ↓5+3
Комментарии24

Комментарии 24

Мне кажется, от такой абстракции больше вреда, чем пользы. Вот у нас есть интерфейс, который даёт доступ кратно dword. И мы понимаем, что наши структуры данных должны быть выровнены по 4 байта, и обновляться по 4 байта и вышележащие алгоритмы к этому готовы.

А вот нам дали интерфейс с доступом побайтно. И вот мы написали некую структуру, и теперь решили её положить в EEPROM. Получается, чтобы записать 4 байта, нам нужно сделать 4 вызова побайтного интефейса, и 4 раза переписать слово в EEPROM. А если эти 4 байта ещё и не ровно по границам слов EEPROM выровнены, то мы трижды перепишем одно слово и ещё один раз -- соседнее.

Дело в том что уровнем выше (с другой стороны) уже есть готовый программный компонент черный ящик, которому нужен именно byte API.
И HiLevel черный ящик уже никак не переделать, так как он задействован в других проектах.


Тогда возможны сюрпризы в виде "истёртой до дыр" EEPROM, ведь у неё ресурс конечен, в отличие от микросхемы SRAM, питаемой от батарейки. Так себе ситуация, да.

возможны сюрпризы в виде "истёртой до дыр" EEPROM, ведь у неё ресурс конечен

Об равномерном износе полей EEPROM у меня заботится как раз компонент уровнем выше.
https://habr.com/ru/articles/706972/

Байтовый интерфейс должен предоставлять как раз уровень FTL, а не кривые обертки.

Что Вы подразумеваете под акронимом FTL?

Если придётся этот интерфейс делать поверх флеш-памяти, то вы тоже будете на каждый байт читать страницу, стирать её и переписывать? Но это ещё ладно, если страница предварительно стёрта, не так всё плохо.

А вот стирание будет выглядеть, как изнасилование с особо тяжкими:
сначала вы читаете страницу, стираете, заменяете первый байт на 0xFF, записываете.
потом снова читаете страницу, стираете, заменяете второй байт на 0xFF, записываете,
и так всю страницу.

Второй момент: запись и стирание EEPROM - обычно не моментальные операции. У вас многозадачная среда выполнения? Внутриeeprom_dword_erase и eeprom_dword_writeможно переключить контекст и выполнять другие действия?

У меня NoRTOS прошивка. Да и в Rtos прошивке можно просто отключить прерывания на время манипуляций с eeprom .

Этот алгоритм только для EEPROM. Причем специфического МСU.

С NOR Flash алгоритм кардинально другой.

Если у верхнего уровня есть API для работы с флеш, не проще ли было использовать его, а не заниматься перекладыванием байтов?

Стоп. А я тогда не понял, что в интерфейсе NVRAM делает byte_erase? Ведь вы же NVRAM эмулируете.

Верхне уровневому Api нужна функция записи произвольного массива байт по произвольному адресу. А eeprom api от вендора такого люкса не даёт.

надо попробовать по другому: функция записи байта должна записывать в статическую переменную, а перед этим проверять если в этой статической переменной уже есть значение и запись происходит по адресу другой ячейки Епром, то сохраненное значение надо предварительно записать в Епром и это значт что надо хранить и адрес этой ячейки (в кэш) и количество байт перезаписанных в этой ячейке (хотя про это есть варианты), получается гораздо сложнее, но может быть гораздо эффективнее. Но надо проверять конечно, а чтобы проверять надо изобрести метод который работает для конкретной задачи (для готовый-ого программный-ого компонент-а черный ящик, которому нужен именно byte API. )

А вот нам дали интерфейс с доступом побайтно. И вот мы написали некую структуру, и теперь решили её положить в EEPROM. Получается, чтобы записать 4 байта, нам нужно сделать 4 вызова побайтного интефейса, и 4 раза переписать слово в EEPROM. А если эти 4 байта ещё и не ровно по границам слов EEPROM выровнены, то мы трижды перепишем одно слово и ещё один раз -- соседнее.

Что нам теперь программную cache память разворачивать что-ли?

И думать, что случится, когда данные уже записались в кэш, но ещё не записались в EEPROM, и внезапно отключилось питание.

Тут и так защитой от креша не пахнет даже на уровне байтов - что будет, если питание кончится/процессор сбросится/повиснет в процессе стирания слова? Попортятся 4 байта - не просто недозапишется часть данных, а и уже находящиеся в хранилище данные будут запорчены.

Ну, в общем-то, решаемая задача идентична работе с памятью в CPU. Тот тоже общается в внешним миром в терминах кеш-линий или транзакций шины, а изнутри - в терминах байтов. Так что да, по хорошему стоило бы сделать что-то типа write combining. Надо смотреть, какие паттерны работы с NVRAM у вашего черного ящика. Если более-менее последовательный доступ к памяти, то может быть достаточным будет "кеша" на единственный dword (придерживать запись, пока не будет запись в другое слово, собирать все байты воедино и тогда уже писать). Минимальная доп. работа, и потенциально в 4 раза меньше стираний.

По-моему, в коде есть проблемы поболее, чем "затёртая до дыр" eeprom :(

Что с порядком байт: little-endian vs big_endian? (У вас little-endian);

Также, возможно, если предположить перенос кода на C++ (или вдруг в чистом Си расширят UB), то лучше не использовать запись в одно поле union и чтение из другого (type-pinning).

Что с порядком байт: little-endian vs big_endian? (У вас little-endian);

У меня little-endian

, если предположить перенос кода на C++ (или вдруг в чистом Си расширят UB), то лучше не использовать запись в одно поле union и чтение из другого (type-pinning).

Почему в C++ не любят объединения? Union в купе с битовыми полями - это же фундаментальная основа embedded программирования.

Потому что такое использование union - это UB в C++. А битовые поля - это до кучи еще полностью implementation-defined упаковка их по байтам (с какой стороны в байтики суем, как они между байтиками разбиваются).

Надо просто модульный тест написать на битовые поля и все ответы у вас будут.

А битовые поля - это до кучи еще полностью implementation-defined упаковка их по байтам (с какой стороны в байтики суем, как они между байтиками разбиваются).


Вот битовым полем взвели 13ы бит. И результат совпал с маской.

typedef union{
	uint32_t dword;
	struct{
        uint8_t bit0 : 1;
        uint8_t bit1 : 1;
        uint8_t bit2 : 1;
        uint8_t bit3 : 1;
        uint8_t bit4 : 1;
        uint8_t bit5 : 1;
        uint8_t bit6 : 1;
        uint8_t bit7 : 1;
        uint8_t bit8 : 1;
        uint8_t bit9 : 1;
        uint8_t bit10 : 1;
        uint8_t bit11 : 1;
        uint8_t bit12 : 1;
        uint8_t bit13 : 1;
        uint8_t bit14 : 1;
        uint8_t bit15 : 1;
        uint8_t bit16 : 1;
        uint8_t bit17 : 1;
        uint8_t bit18 : 1;
        uint8_t bit19 : 1;
        uint8_t bit20 : 1;
        uint8_t bit21 : 1;
        uint8_t bit22 : 1;
        uint8_t bit23 : 1;
        uint8_t bit24 : 1;
        uint8_t bit25 : 1;
        uint8_t bit26 : 1;
        uint8_t bit27 : 1;
        uint8_t bit28 : 1;
        uint8_t bit29 : 1;
        uint8_t bit30 : 1;
        uint8_t bit31 : 1;
	};
} BitField32_t;

bool test_bit32_fields(void){
    LOG_INFO(TEST, "%s():", __FUNCTION__);
    bool res = true;
    BitField32_t Field32;
    Field32.dword = 0;
    Field32.bit13 = 1;
    EXPECT_EQ( 0x2000, Field32.dword);
    return res;
}

Ваши опасения напрасны. Просто надо вести разработку с модульными тестами.

Я не особо в теме и поэтому вопрос. Для МК, где данные будут постоянно менятся real-time сутки напролет, хватит ли ресурса циклов "запись - чтение" EEPROM? Или я зря беспокоюсь и этих циклов хватит на годы эксплуатации?

Смотрите даташит на количество циклов записи, считаете для своей пиковой нагрузки реальный ресурс. Если ресурса не хватает, а памяти с избытком, то реализуете (или берете готовый, благо реализаций уже много под разные архитектуры написано) механизм выравнивания износа. Самый простой: пишете данные в виде пары "токен-значение", держите таблицу токенов и состояния страниц (активная, заполнена, пустая) для инициализации отдельно в памяти. Пишете данные в одну страницу, при считывании для получения актуальных данных ищете последнюю запись искомого токена в активной странице. После заполнения страницы переносите актуальные пары на другую страницу и продолжаете запись в нее, стираете старую страницу (желательно делать это не сразу, а когда израсходуете текущую, может помочь в случаях когда по какой-либо причине дропнулась запись в активной странице). Таким образом жонглируете несколькими страницами памяти, выравнивая износ и увеличивая общий ресурс. Как и любой другой вариант записи данных чувствителен к падениям питания в моменты записи, но тут если нужно, можно дублировать данные/пытаться прочитать с прошлой записанной страницы.

Как вариант - использовать FRAM. Больше 10 триллионов циклов перезаписи на ячейку точно хватит.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории