Pull to refresh

Comments 29

Основная проблема использования Flash как EEPROM - это относительно малое кол-во циклов перезаписи. У F0 это около 10тыс циклов. Если за этим не следить - ресурс может очень быстро улететь.

Лучше уж EEPROM развести дополнительно. Либо FRAM у которой кол-во циклов почти неограничено.

Согласен. Внешняя микросхема всегда предпочтительнее в этом случае. Но всё же бывают случаи, когда нет места для нее. Я сталкивался с подобной ситуацией, когда делал миниатюрное устройство, там каждый мм был на счету. Решал задачу логгирования данных.

Да и к тому же использование 2 страниц увеличивает количество циклов, но в итоге приведет к смерти сразу 2х страниц. Перспективы печальны.

Опыт подсказывает что под EEPROM в корпусе SOT23-5 место в 99% случаев можно найти, единственное когда требуеться такое решение это если устройство уже готово и как обычно вдруг потребовался EEPROM.

Если все же места нет, либо девайс уже готов, то можно выделить чуть больше страниц и сделать аналог выравнивания износа. Или подключить 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 с возможностью сбросов отдельных битов, но не в этом случае

Да вот как раз потому и удивило - для flash дозапись битов (только в одну сторону - 1->0) вообще норма и «сама получается». А тут в явном виде добавили проверку зачем-то. Причём, судя по комментариям ниже о F103, проверка эта есть не во всех семействах. Ну будем знать.

UFO just landed and posted this here

Самый сок в том, что я только сейчас о нем узнал :) В целом подходы похожи (а есть ли другие?). Мне нужно больше времени разобраться в нем, возможно получится улучшить мой вариант

Но что-то мне подсказывает, что 3 состояний будет недостаточно. Я пытался уложиться в меньшее количество и в некоторых моментах возникали неоднозначности -- поэтому у меня их больше

Основной вопрос думаю в принципиальной ненадежности flash. Чтобы с ней уверенно работать лучше сделать wear levelling (страниц эдак 20-30 и писать в следующую циклично) + неплохо бы прям из flash-драйвера понимать что страница перешла в состояние «bad» и никогда не читать/не писать из нее, «обходить».

Под состоянием bad имеется ввиду, когда страницу дотерли до дыр (циклы записи исчерпаны)?

Вообще сейчас в голову пришла идея каким-то образом копировать не целые страницы, а хранить диффы в виде "адрес - дифф" (разумеется блоками). Это как минимум позволит не перетирать страницы при каждом чихе. Нужно будет подумать

Под состоянием bad имеется ввиду, когда страницу дотерли до дыр (циклы записи исчерпаны)?

кстати, а может кто прояснить, правильно ли я понял: по исчерпанию лимита (допустим 10к) происходит примерно следующие:

  1. доступ к странице становиться дольше (как по чтению, так и по записи);

  2. не гарантируется что записанное значение сохранит свое значение в течении гарантированного времени (для флешей 5-10 лет).

что-то ещё может быть?

Она перестает стираться и записываться. У всех flash это происходит по-своему конечно, но обычно выглядит как «драйвер возвращает ошибку при записи/стирании» (если он умеет понимать что произошел bad) или «считанные данные после стирания и записи не меняются» (это обычно при тупом драйвере происходит).
Предметно-специфичное хранилище в этом плане имеет ощутимое преимущество в том, что можно выделить т. н. writesize и разделить страницы на блоки размером «writesize + заголовок», и писать в ту же страницу кратно этому блоку.
Кстати состояние 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.

Если есть место под "конденсатор покрупнее", то значит есть место и под EEPROM.

Иногда вопрос в цене, а не в свободном месте

EEPROM не решает проблемы отключения питания посередине записи

Всё же это не 100% решает проблему сохранности данных, т.к. при старте МК мы не сможем определить, какая из страниц содержит актуальные данные

Возможно я вас не понял, но разве постоянно растущий счетчик - не решение проблемы? Переписали все данные. на лету изменив нужную ячейку, и в последнюю ячейку записали увеличенное значение счетчика. При следующем старте определили, какая версия свежее - с ней и работаем. При этом заодно можно на лету считать CRC/XOR/Parity и писать туда же в конец.

Sign up to leave a comment.