Comments 29
Основная проблема использования Flash как EEPROM - это относительно малое кол-во циклов перезаписи. У F0 это около 10тыс циклов. Если за этим не следить - ресурс может очень быстро улететь.
Лучше уж EEPROM развести дополнительно. Либо FRAM у которой кол-во циклов почти неограничено.
Согласен. Внешняя микросхема всегда предпочтительнее в этом случае. Но всё же бывают случаи, когда нет места для нее. Я сталкивался с подобной ситуацией, когда делал миниатюрное устройство, там каждый мм был на счету. Решал задачу логгирования данных.
Да и к тому же использование 2 страниц увеличивает количество циклов, но в итоге приведет к смерти сразу 2х страниц. Перспективы печальны.
Если все же места нет, либо девайс уже готов, то можно выделить чуть больше страниц и сделать аналог выравнивания износа. Или подключить littleFS (выравние износа из коробки) и использовать часть внутренней памяти как "внешний" носитель. Примеры вроде даже попадались с таким.
у тех же нордиков есть FDS (flash data storage), идея та же что и у вас.
выделить чуть больше страниц и сделать аналог выравнивания износа
Конечно, почему бы и нет. Всё зависит от того, на сколько толстый FLASH и сколько объема можно пожертвовать.
Я не стал усложнять текущий пример, хотел сделать его максимально простым. Выравнивание износа можно прикрутить как надстройку для текущего драйвера. Сделать это достаточно просто.
Не 10000, а 1000.
10000 это гарантированное количество циклов, а сохранность данных в течение указанных в даташите времени хранения и температуре только 1000.
Невозможность «дозаписи» в ранее записанное 16 бит слово - 100%? Дозаписывать нули в отдельные биты (и уместить состояние в одно слово) данный flash не позволяет?
Нет. Проверено экспериментально и в Reference Manual об этом сказано.
The Flash memory interface preliminarily reads the value at the addressed main Flash memory location and checks that it has been erased. If not, the program operation is skipped and a warning is issued by the PGERR bit in FLASH_SR register. The only exception to this is when 0x0000 is programmed. In this case, the location is correctly programmed to 0x0000 and the PGERR bit is not set.
Изначально я так и хотел -- обойтись словом. Я вот прям помню, что встречал где-то FLASH с возможностью сбросов отдельных битов, но не в этом случае
Самый сок в том, что я только сейчас о нем узнал :) В целом подходы похожи (а есть ли другие?). Мне нужно больше времени разобраться в нем, возможно получится улучшить мой вариант
Но что-то мне подсказывает, что 3 состояний будет недостаточно. Я пытался уложиться в меньшее количество и в некоторых моментах возникали неоднозначности -- поэтому у меня их больше
Под состоянием bad имеется ввиду, когда страницу дотерли до дыр (циклы записи исчерпаны)?
Вообще сейчас в голову пришла идея каким-то образом копировать не целые страницы, а хранить диффы в виде "адрес - дифф" (разумеется блоками). Это как минимум позволит не перетирать страницы при каждом чихе. Нужно будет подумать
Под состоянием bad имеется ввиду, когда страницу дотерли до дыр (циклы записи исчерпаны)?
кстати, а может кто прояснить, правильно ли я понял: по исчерпанию лимита (допустим 10к) происходит примерно следующие:
доступ к странице становиться дольше (как по чтению, так и по записи);
не гарантируется что записанное значение сохранит свое значение в течении гарантированного времени (для флешей 5-10 лет).
что-то ещё может быть?
Кстати состояние VALID у вас содержит 16 бит, и можно эти 16 бит использовать как указатель того, сколько блоков занято. Делим страницу в 4к на 16 блоков, получаем 256 байт. Итого если в позиции «VALID» стоит один ноль — занят первый блок, два — занято два блока. Все 16 — вся страница занята. И обновление всегда идет от 1 к 0.
А внутри блока уже хранить CRC и реальный размер занятого в заголовке.
В апноуте выше, насколько я помню, предлагается дописывать на страницу. Идея в том, чтобы писать новые данные на ту же страницу, а при чтении брать последнюю "версию".
Я по этому апноуту делал реализацию на Rust (https://github.com/idubrov/eeprom). Я правда, с потерей питания особо не заморачивался -- при записи, если кончилось место, была вероятность потерять "новые" данные пока мы копируем страницу.
Почему-то мне тоже казалось, что можно биты с 1 на 0 по одному менять, но в моей реализации я вообще всего два состояния использую: активная страница и пустая.
В ситуации, когда у нас две активных страницы я просто выбирал любую. Наверное, ход мыслей был такой, что в такой ситуации их содержимое должно совпадать. Что не совсем верно -- если питание пропало в момент "спасения", возможна ситуация двух активных страниц, причём мы ещё и можем начать дописывать в одну из них (и тогда они будут отличаться). Наверное, можно было починить тем, что при инициализации всегда затирать остальные "активные" страницы. А, ну и при копировании я не проверяю, что новая страница на самом деле пустая (может оказаться, что копировать начали, а в середине всё сломалось). Но по идее, это можно проверить просто чтением всей страницы, чтоб убедиться, что она вся пустая.
>Почему-то мне тоже казалось, что можно биты с 1 на 0 по одному менять, но в моей реализации я вообще всего два состояния использую: активная страница и пустая.
А, вот почему. Они в своей реализации сами используют это:
https://documentation.help/AN2594-EEPROM/eeprom_8h-source.html#l00057
Состояние страницы переходит из 0xffff (пустая) => 0xeeee (копируем на эту страницу) => 0x0000 (активная).
Так что попробуйте одно и то же слово несколько раз программировать (но важно что новое значение может только сбрасывать биты 1 в 0, но не наоборот), может, получится ? (P.S. упс, я вижу, что вы написали, что уже пробовали...).
P.P.S. Хм, если буквально читать, что они пишут, то 0x0000 они всегда разрешают записать? Может, три состояния всё-таки можно (0xffff => что-нибудь отличное от 0x0000 => 0x0000)?
Rust для микроконтроллеров вообще очень интересная тема. Странно что популярность не приходит к языку в этой области.
А зачем он там? С/С++ прекрасно справляются с задачами в этой области. Пока нет ощущения нехватки чего-либо, по крайней мере у меня. Это мое субъективное мнение.
Да, я в полном восторге от Rust на микроконтроллерах (STM32). Я от C++ уйти хотел ещё даже когда про Rust слыхом не слыхивал; C++ -- это всё-таки не тот язык на котором я бы хотел хобби-проект делать. Старый он, и кривой просто ужасно.
В Rust https://rtic.rs/ понравилось (тогда оно ещё RTFM называлась). Вообще, идея языка где компилятор будет находить ошибки типа гонок состояний, в том числе и на микроконтроллерах, -- очень интересно. Даже если это и будет работать только для менее "требовательных" к производительности случаев.
Но я так, сварщик не настоящий (на Rust три года коммерческого кода писал, но без микроконтроллеров -- REST сервисы).
Профессиональная разработка для микроконтроллеров -- там, наверное, уже по-другому. Rust сырой, библиотек или нет или постоянно меняются, сертификации нет, опыта в индустрии -- тоже. Может быть, вот эти ребята https://oxide.computer/ что-то поменяют -- по-моему, они для микроконтроллеров тоже что-то делают.
А еще можно писать лог транзакций прямо во флеш. Например я поддерживаю RAM в 1024 байта. Каждый доступ на запись пишу во флеш( "адрес, дата" или "адрес , дата, дата" ) и в RAM напрямую по адресу. При рестарте процессора раскручиваю записи в обратном порядке. Раз в какое то время( когда записей становится много) содежимое страницы сохраняется полностью , а записи сбрасываются. Можно прилично сэкономить ресурс флеша при некоторых паттернах доступа.
А можно ведь просто добавить конденсатор покрупнее и не сохранять информацию при уже отключенном питании и работе от конденсатора. Тогда либо информация будет записана корректно, либо не будет записана вообще.
В целом, хранить во Flash можно, разве что, настройки устройства, которые, в случае чего, без проблем сбрасываются в дефолт и задаются пользователем заново. В иных случаях лучше установить микросхему EEPROM.
Всё же это не 100% решает проблему сохранности данных, т.к. при старте МК мы не сможем определить, какая из страниц содержит актуальные данные
Возможно я вас не понял, но разве постоянно растущий счетчик - не решение проблемы? Переписали все данные. на лету изменив нужную ячейку, и в последнюю ячейку записали увеличенное значение счетчика. При следующем старте определили, какая версия свежее - с ней и работаем. При этом заодно можно на лету считать CRC/XOR/Parity и писать туда же в конец.
Пишем драйвер виртуального EEPROM для STM32F030