Pull to refresh

Comments 29

При записи флэш обнуляются битики изначально стертые до FF. Поэтому при необходимости обновления параметров затираем нулями старую структуру, в конец дописываем новую. Для поиска используем ненулевой идентификатор. Если структуру выровнять по границам секторов, то еще проще. И не надо заморачиваться с XML и произвольным порядком.

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

У вас же калибровка идет с участием большого брата(ПК) соответственно нет сложностей писать сразу все изменения за раз, а не по одному параметру?

ну да калибровка идет под управлением ПК, но бывает и перекалибровка, и просто какие то эксперименты, а память то ограниченная, и каждый байт во флеш пишется по процедуре - то есть относительно долго. Я ж говорю у меня получилось как то очень рационально все проблемы решить в совокупности при описанном подходе.Дело в том что если всю структуру переписывать, еще и надежность падает (сложно конечно оценить, но логичено ведь?), надо на достаточно длительное время записи блокировать программу на функции записи, все таки во флеш лучше поменьше писать.

Проще добавить в идентификатор счётчик с числом отсчётов больше количества умещающихся блоков и при старте искать идентификатор с максимальным значением счётчика (с учётом возможного переполнения). Или, если в системе есть RTC — добавить время. Тогда не надо ничего затирать — тупо пишем поверх устаревших данных.

так не будет это работать если какие то данные не изменялись с самого начала, если по моей концепции идти. Я для себя пришел к выводу, что надо при переходе в начало сохранять всю структуру с начального адреса. Если вы про другой метод организации записи то вообще ничего не понятно.

Концепция круга не единственная в таких вещах. Можно применить концепцию встречно растущих стеков. Она позволяют хранить много разных блоков настроек и калибровок независимо друг от друга.

в моей задаче вроде как только один блок настроек-калибровок.

А картинка прикольная :).

Бывает что выбранная концепция ограничивает функциональность. Линейные связные структуры вместо кольцевой дают больше возможностей и гибкости в управлении настройками, только и всего.

Кстати на заметку, в некоторых контроллерах в частности в Renesas, как в этой статье, после стирания FLASH в ней не 0xFF будут, а случайные числа, причем при каждом следующем чтении разные.

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

если память не подводит, то в stm32l04 / stm32l07 стертая флешка забивалась 00.

для флешки типа NAND "пустое" значение FF, так как из 1 сделать 0 можно
для флешки типа NOR "пустое" значение 00, так как из 0 можно сделать 1

а случайно содержимое для какого типа памяти характерно?

Ну неужели нельзя какую-нибудь другую картинку для заставки подобрать ну ей богу уже достала она меня

картинка мне тоже не очень нравится, но я не специалист по картинкам, к сожалению

Насколько я знаю у этой проблемы нет решения на уровне С или даже С++ компиляторов, нельзя подсунуть вызов функции на все операции присваивания полям некоторой структуры

В С++ можно использовать в качестве типов параметров свой класс с перегрузкой оператора присваивания, а для чтения определить приведение этого класса к uint16_t

Или пойти более классическим путем и присваивать значения параметрам не напрямую, а через метод Set

ну да только тогда у вас все поля должны поменять тип с uint16_t -типа на тип специального класса с перегрузкой. То есть в структуре у нас будут не Шорты, а объекты специального класса, как это чудо будет компилироваться в указанных целях вы замучаетесь проверять.

Поэтому предложение это может рассматриваться как чисто теоритическое, требующее большой практической проработки.

В принципе, тут ни чего сложного нет, и что сделает компилятор вполне очевидно. Но если очень хочется проконтролировать что получилось, то можно пройти в отладке по шагам по ассемблерному коду. Там кода будет строк 30 на ассемблере, за пол часа можно во всех деталях рассмотреть.
Размер и расположение объектов можно посмотреть в map файле, который выдаст линкер.

С тем что ничего сложного нет нельзя не согласиться! Я даже считаю что во всем программировании ничего сложного нет, просто берешь и пишешь программу, и она просто работает, а если не работает дебажишь ее, исправляешь, и она в конце концов все равно просто работает.

А вот LittleFS никто не разбирал? Они обещают Wear Leveling, как раз то, что нужно?

К предлагаемой автором концепции неплохо бы добавить контроль целостности. Дополнительно появится возможность записав любой бит в 0 вместо 1, испортить строку данных.

Если запись приближается к концу доступной памяти, нужно собрать данные в структуру, очистить кадры (*), и записать структуру целиком с начала.

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

LittleFS прикольная, но не стабильная в первой версии. Склонна к зависаниям после некоторых неудачных сбросов питания во время записи. С выравниваем износа тоже не все однозначно. По второй версии ничего не скажу, можно почитать ишью на гитхабе что бы оценить. Кроме того есть неприятные ограничения, связанные с дизайном. Она хорошо пишет в конец файла, но очень не любит переписывать данные внутри файла. В общем я пользуюсь, но если бы была возможность начать проект сегодня сначала, то внимательно посмотрел бы на LevelX от AzureRTOS или на Reliance Edge

Я использовал в одном проекте версию 2.4.0 - работает на >500 устройствах: хранение настроек в json и некоторые оперативные данные в бинарном формате. Операций стирания\записи не сильно много. Медленнее FatFS от Чана. И снижается скорость открытия при большом количестве файлов в директории - по крайней мере у меня так проявлялось. В остальном за почти 2 года эксплуатации каких-либо проблем пока не заметил.

upd: SPIFFS была еще медленнее.

"Не переписывать же всю структуру при изменении отдельного параметра"
Эту строчку надо разместить в тексте статьи во избежание.

TL/DR:
Си-структура конфига (настроек/параметров) хранится в RAM микроконтроллера.
WRITE.
В FLASH каждый раз записывается не вся си-структура целиком, но только изменившиеся поля структуры в виде цепочки {id1, data1}, {id2, data2}, ... где в качестве id выступает смещение поля от начала структуры. Размер data в байтах равен размеру поля и однозначно определяется по id. Запись возможна в произвольном порядке. Указатель записи нигде не хранится, свободное место отыскивается по "пустым" байтам 0xFF, (...) после операции ERASE страницы NAND FLASH (не так для EEPROM без bulk erase). Так как каждое сохранение во FLASH лишь дописывается друг за другом, выполняется техника распределения износа ячеек - Wear leveling. При исчерпании свободного места запись зацикливается с конца в начало по принципу Circular Buffer.
READ.
При чтении всегда начинаем с начала, пробегая по всей цепочке когда-либо сохраненных во FLASH пар {id,data} ... {id,data}. В зависимости от встретившегося id, считываются и многократно перезаписываются соответствующие поля си-структуры в RAM. Признаком конца служат байт(ы) 0xFF. В итоге в полях си-структуры в RAM останутся лишь последние сохраненные во FLASH значения, т.е. последний актуальный конфиг.

все правильно, только это:

 При исчерпании свободного места запись зацикливается с конца в начало по принципу Circular Buffer.

не надежно, потому что есть вероятность что какие то данные не переписывались с самого начала, поэтому при возвращении в начало надо( придется) начать с записи полной структуры. У меня так и сделано только не в прошивке, а через ПК эта операция производится (сброс памяти- перезапись структуры в начало), дело в том что такая операция все равно нужна и производится не только когда достигли конца памяти.

Получается что я понял почему это сделано именно так, только когда написал что же у меня сделано :), хорошо когда выясняется что что то сделано правильно :)

Хороший пример того что :

писать полезно не только код, но и описание того что этот код делает

Один из интересных вариантов - это хранить размер блока данных и его версию. Ну и писать данные по кругу.
Ну а при старте контроллера - считывать весь фелш в поиске последнего (самого нового) блока данных.
Из плюсов - можно откатываться на предыдущий конфиг, если текущий невозможно прочитать. Из минусов - "лишние" 1-4 байта под хранение версии данных.

версия имеет смысл когда у версии есть какое то описание:

зачем она создана, что в ней интересного, чем она отличается от предыдущей версии,

Описание вы не засунете в контроллер, поэтому вывод: версии должны регистрироваться на ПК и там и подписываться! Просто плодить версии по времени не очень умно (из моей практики).

если вы кокаго то подобного описания про версию не знаете то перебор версий будет приводить к эффекту как в анекдоте (замените "приборы" на "версия"):

летят Петька с Василий Иванычем в самолете.

Василий Иваныч спрашивает: "Петька, приборы!"

Петька: "Двадцать!"

Василий Иваныч : "ЧО двадцать?"

Петька: "а ЧО приборы?" "

Примерно на этой идеологии делал сохранение последнего состояние прибора и его настроек (после перезагрузки необходимо было подняться ровно в том же состоянии, что и до пропадания питания). Ради экономии ресурса носителя записывался не каждый чих по изменению состояния, а обрабатывалось прерывание пропадания питания. Времени было как раз записать подготовленный сектор. Очистка производилась заранее. Запись по кругу, блоки с растущей нумерацией, в конце блока последней записывалась его контрольная сумма. Автоматически решается проблема записи битого блока (не хватило времени на запись) - при загрузке берется самый свежий корректно подписанный блок. Использование устройства с предыдущими настройками тоже допускалось по ТЗ. Размер данных не обязательно кратен сектору - очистка была посекторная, а в чистый сектор можно было писать хоть по одному байту, главное держать вперед резерв достаточный для записи очередного блока настроек. В эту концепцию хорошо укладываеся и инкрементивная запись - пишем не весь блок данных, а только те, которые изменились от предыдущего. Естественно, контролируем размер кольца, чтобы в кольце был хотя бы один ключевой кадр.
Плюс функция перешагивания битого блока - исходим из того, что его размер тоже не корректен. Идет поиск очищенной области после последнего корректного блока и пометка начала после сбоя уникальным идентификатором (при чтении если блок не прочитался, идет поиск этой метки для позиционирования в следующий корректный блок). Или, если потеря размера сектора не страшна, то просто переход на следующий сектор начинающийся с FF.

Кстати, тоже вариант, если данных мало: В начало сектора пишем целый блок данных, далее дописываем только изменения.

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

если находите что это похоже, считайте что это перевод с обзором нюансов практической реализации :). Велосипеды нам не нужны.

Sign up to leave a comment.

Articles