Зачем ставить внешнюю IC памяти или SD карту если в микроконтроллере осталось много свободной Flash памяти!
Микроконтроллеры семейства STM32H снабжены двумя независимыми банками Flash памяти и это очень удобно. В одном банке можно расположить программный код, а в другом временные перезаписываемые данные.
Как сделать из внутренней Flash подобие EEPROM сравнительно неплохо написано в этом апноуте от ST. Но с некоторого уровня комплексности встроенного ПО хранить данные в именованных файлах удобнее чем в жёстких структурах. Файлы упрощают реюзинг, облегчают поддержку преемственности версий, апгрейды и даунгрейды. Файлы освобождают от хлопот с планированием размещения во флэш и разруливанием конфликтов размещения, особенно если приложение модульное и модули разрабатываются отдельно.
В такой файловой системе можно хранить разные версии наборов параметров устройства, сертификаты и ключи к разным облачным сервисам, диагностические или аварийные дампы, временные файлы при распаковке, стандартные ресурсы, такие как шрифты и пиктограммы для HMI или скрипты и стили WEB страниц и проч.
Здесь представлена линейная файловая система относящаяся к классу журналируемых. Такие файловые системы известны давно. Быстротой они не блещут, потому эффективны на сравнительно небольших объемах памяти. Но им присуще крайне малая потребность в ОЗУ, малый размер кода и сравнительная простота. При этом по своей природе линейные файловые системы обеспечивают равномерный износ Flash и им не нужен дополнительный программный слой FTL, как например для FAT на базе NAND.
Это продолжение развития открытого проекта платы контроллера резервного питания BACKPMAN v2.0. Вот тут и тут написано как началась разработка BACKPMAN v2.0. Движение идет по пути наращивания функциональности. Поэтому представленный здесь следующий проект с пафосным названием Example2 является продолжением проекта Example1 с некоторым рефакторингом и c демонстрацией линейной файловой системы STfs (не расшифровывается, просто префикс в сорсах).
Истоки STfs
Выше упомянул апноут от ST. На самом деле там все довольно угловато. Главная функция записи там может записывать только 2-х байтными словами сопровождаемыми тэгом под называемым виртуальный адрес. Что делать если надо сохранять структуры или не выровненные блоки неясно.
Как вариант - просто записывать все это во Flash по фиксированным адресам, макросам обеспечивая бесконфликтную упаковку. Потом чуть усложнить и организовать циклический сдвиг всего по сектору при перезаписи, чтобы выровнять износ Flash. Тут появляются зачатки API. И естественным образом мысль приходит к файловой абстракции. Один шаг остается до реализации API имитирующего обычную файловую систему.
Источником вдохновения послужила файловая система из среды Keil для SPI Flash. Но там все аскетично и нет поддержки RTOS.
В предыдущих статьях было описано портирование Azure RTOS, и STfs использует сервисы этой RTOS. Как любая файловая система STfs не предоставляет жесткого детерминизма. Поэтому чтобы внедрить ее в среду задач с жестким реальным временем приходится использовать RTOS. Однако количество сервисов требующихся файловой системе от RTOS невелико, и портирование её на другую операционку не должно вызывать проблем.
Почему всё не так просто с STM32Hxxx
Особенностью семейства STM32H является наличие механизма контроля и исправления целостности блоков внутренней Flash памяти и разбивка Flash на стираемые секторы сравнительно большого размера. Конкретно у STM32H753VIH цельные блоки имеют размер 32 байта, а минимальные стираемые секторы 128 Кбайт. Т.е. можно записывать за одну итерацию во Flash только блоками по 32 байта называемыми Flash word, не больше и не меньше, только один раз в одно место после последнего стирания, но стирать можно только блоками по 128 Кбайт. Если попытаться повторно записать что-то во Flash, даже если это будет один байт и он просто поменяет один бит с 1 на 0, то 32-байтный блок, на который придется такая запись, будет с высокой вероятностью обозначен системой как сбойный и последующее чтение в границах этого блока вызовет ошибку доступа к шине и вызов исключения BusFault, которое нарушит ход выполнения программы. И такое поведение никак отключить нельзя. Т.е. после некорректной записи всего в один байт в системе станет недоступным блок Flash памяти размером в 32 байта и его невозможно будет читать ни внутренней программой, ни с помощью отладочного адаптера, пока не будет стерт весь сектор128 Кбайт содержащий этот блок. Указанные ограничения препятствует использованию файловых систем типа SPIFFS или littlefs. Требуется более специфичный подход.
Описание STfs.
Технология проста. В секторы памяти размером 128 Кб последовательно записываются данные файлов. Каждый раз при открытии файлов на запись, фиксации данных и закрытии этих файлов создаются дескрипторы и записываются в тот же сектор. Данные файлов пишутся от нижней границы сектора вверх, а дескрипторы пишутся от верхней границы сектора вниз, как показано на рисунке ниже
Когда данные и дескрипторы сходятся в одной точке запись переносится в следующий сектор Flash.
Ниже описание структуры дескриптора:
// Размер структуры дескриптора фрагмента файла - 64 байта
// Дескриптор состоит из двух 32 байтных частей.
// Вторая часть дописывается когда в дескрипторе ставится тэг(штамп) стертого файла
typedef struct
{
uint32_t file_id; // Идентификационный номер файла. Если здесь находится EMPTY_PTTRN, то это чистый блок без данных о фрагменте
int32_t start_addr; // Абсолютный адрес начала фрагмента
int32_t size; // Размер фрагмента. Запись ведется 32 байтными блоками поэтому этот размер должен быть кратным 32
uint32_t number; // Порядковый номер фрагмента, начинается с 0. В фрагменте с номером 0 хранится имя файла
int32_t data_size; // Количество данных реально записанных во фрагменте
uint32_t version; // Номер версии файла. Используется для безопасного переименования файла. По номеру версии определяется какая версия актуальна.
uint32_t reserv1[2];
} T_stfs_file_normal_descriptor;
typedef struct
{
uint32_t deleted; // Тэг удаленного файла
uint32_t reserv2[7];
} T_stfs_file_erase_descriptor;
typedef struct
{
T_stfs_file_erase_descriptor e;
T_stfs_file_normal_descriptor n;
} T_stfs_file_descriptor;
Дескриптор имеет размер 64 байта и состоит из двух половин по 32 байта. Это позволяет записывать дескрипторы всегда выровненными по границе 32-байтного Flash word и ровно соответствующего размера.
Дескриптор создается всегда, когда записывается имя файла и закрывается файл. Но если требуется надежно зафиксировать данные файла, а закрывать его ещё долго не придётся, то вызывают функцию STfs_flush , и в этом случае также будет создан дескриптор. Т.е. дескриптор окончательно валидирует данные и они не будут потеряны после выключения питания.
При удалении файлов ничего не стирается, но только в дескрипторы файлов записывается тэг удаленных. Когда в секторе остаются только удаленные файла он автоматически стирается. Если сектора полностью заполнены и в них остались вперемешку удаленные и актуальные файлы, то существует функция дефрагментации, которая переносит и дефрагментирует актуальные файлы в один или группу секторов, стирая освободившиеся сектора.
Технические характеристики файловой системы STfs
32-битная линейная многозадачная для среды Azure RTOS адаптированная под 32-батное Flash word
Размер и количество секторов произвольное. Назначается через массив секторов на этапе компиляции
Длина имени файла не более 31 символа и не менее 1
Количество дисков произвольное.
Поддержки директорий нет, поддержки даты создания нет (но можно добавить).
Есть поддержка поиска файлов по имени, по шаблону, по порядку создания.
Количество одновременно открытых файлов на чтение произвольное. Определяется на этапе компиляции.
Количество одновременно открытых файлов на запись не должно превышать количества секторов.
-
Нельзя одновременно открывать файлы на запись и на чтение.
Как видно есть некие ограничения по сравнению с той же FAT32, но с другой стороны все что надо для встраиваемого софта малых систем все есть.
Описание API STfs
Название функции |
Описание |
Инициализация файловой системы. Инициализирует драйвер Flash, проверяет целостность, форматирует если целостность нарушена |
|
Поиск файла на виртуальном диске по строковому шаблону и получение сведений о файле |
|
Поиск самого раннего файла на виртуальном диске и получение сведений о файле |
|
Продолжение поиска файлов от ранних к поздним |
|
Открытие файла на запись или чтение |
|
Переименование файла |
|
Чтение данных из файла |
|
Запись данных в файл |
|
Установка позиции чтения из файла от его начала |
|
Получение размера файла |
|
Закрытие файла |
|
Удаление файла |
|
Фиксация записанных данных во Flash |
|
Получение информации о количестве свободного пространства на виртуальном диске |
|
Форматирование виртуального диска. Физически просто стирание всех секторов |
|
Проверка целостности файловой системы на виртуальном диске и получение информации о системе |
|
Дефрагментация файловой системы |
|
Освобождение захваченных задачей RTOS ресурсов у файловой системы. |
Симулятор STfs на персональном компьютере
Тестировать на реальном железе логику файловой системы слишком долго и неудобно.
Поэтому была написана программа симулятор на C++ под Windows 10.
Программа включает те же исходники STfs что и рабочая версия для микроконтроллера, только слой драйвера Flash памяти был заменен на эмуляцию в оперативной памяти компьютера. Но при этом эмулятор строго проверяет все ограничения по размеру и выравниванию записываемых данных и чистоте целевой области записи. Был создана функция тестовой записи и считывания файлов с регулярной проверкой целостности и дефрагментацией по необходимости.
Все записи, считывания производятся на основе генератора случайных чисел. Каждый 10-й файл не удаляется для имитации фрагментации. Если дефрагментация не удаётся, то чтобы не прерывать тест производится выборочное удаление более старших файлов.
Симулятор легко проводит миллион и больше итераций, что на обычной Flash уже вызвало бы неминуемые проблемы износа. Однако такое количество итераций оправдано при поиске глубоко закопанных ошибок логики файловой системы и это сильно помогло при отладке.
Симулятор накапливает лог в котором можно просмотреть всю историю прохождения тестов.
Симулятор написан в среде Embarcadero RAD Studio 11 Alexandria. Но легко скомпилируется и на более ранних версиях, поскольку использует только минимальный набор стандартных компонентов. Подойдет триальная версия.
И наконец скоростные характеристики STfs
Сначала об износе. Как уже писал выше выравнивание износа присуще линейным FS и это показала симуляция случайного потока записей, удалений и дефрагментаций .
Вот результат после 100 тыс. итераций теста:
Номер сектора |
Количество стираний |
0 |
2961 |
1 |
3442 |
2 |
3541 |
3 |
3941 |
4 |
4386 |
5 |
3653 |
6 |
3798 |
7 |
3166 |
Как видно цифры одного порядка. При том что предельный жизненный цикл у Flash очень размыт то более точного совпадения цифр и не требуется. Надо напомнить что в STM32H работает механизм исправления одиночных ошибок Flash и предупреждения о двойных ошибках. Т.е. пользователь имеет дополнительную возможность проактивно среагировать на износ в своем прикладном коде.
Теперь цифры измеренные на реальном микроконтроллере STM32H753VIH после 200 итераций
Тип операции |
Минимальное время, мкс |
Максимальное время, мкс |
Стирание сектора |
855691 |
930729 |
Открытие файла на запись |
179 |
768 |
Время записи файла размером 10 Кбайт |
27100 |
30742 |
Время чтения файла размером 10 Кбайт |
25 |
203 |
Проверка целостности файловой системы |
189 |
8983 |
Итог
Скорость записи в файл колеблется в пределах 325...369 Кбайт в секунду. Это типичная цифра для NOR памяти и тут лучшего ожидать не приходится. Но надо отметить неплохую детерминированность.
Ложку дёгтя вносит время стирания сектора. А значит дефрагментация может затянуться до 8 сек. Это надо учитывать.
Но чтение будет идти со скоростью не менее 40 Мбайт в секунду. И этого более чем достаточно.
Все проекты хранятся здесь.
Комментарии (17)
fk01
07.11.2021 18:02+1Непонятно, зачем дескрипторы хранить отдельно от данных. По-моему всё давно изобретено есть так называемые log file systems, где идея в том, что данные просто пишутся "по кругу" на некий носитель (новые данные просто перезаписывают наиболее старые со временем). Так обеспечивается идеальный wear leveling. Например JFFS, разработанная фирмой Axis для камер наблюдения. Файлы, ясное дело, при этом не исчезают и не портятся, просто данные которые не должны быть удалены перезаписываются заново. Преимущество такого решения, что в случае сбоя при записи происходит автоматический откат на предыдущую версию. Файловая система получает свойство быть устойчивой к отказам, в частности к отключению питания. Разумеется, для работы с такой файловой системой после включения питания она вся считывается и строится индекс, который занимает относительно большой объём ОЗУ. Последнее впрочем не проблема, т.к. и индекс можно хранить в самой файловой системе, а в ОЗУ только последние не записанные изменения.
А в вашей системе получается, успели записать дескриптор, например, а файл не успели -- сбой. Или наоборот. Система не обладает отказоустойчивостью. А это критично не только в случае отключения питания, но и в случае сбоев ПО, когда процессор уходит в перезагрузку, в случае внешних воздействий на вычислительную систему, таких как статическое электричество, радиация, электромагнитное излучение. Так или иначе на большом периоде времени перезапуски вызванные перечисленными факторами -- будут. И будут потери данных.
Indemsys Автор
07.11.2021 18:17В моей файловой системе системе данные также пишутся по кругу. Посмотрите на анимацию, там это явно видно. Дескрипторы для данных по любому нужны в любой файловой системе. Называться только может по другому. Иначе нельзя будет найти где данные начинаются, а где кончаются.
Индекс в моей системе не нужен, она настолько мала, что любая операция способна прочитать всю файловую систему менее чем за 10 мс если ей требуется что-то найти. Это резко снижает потребность в RAM.Если дескриптор не записали, то файл от этого не теряется. Теряется только последняя запись. И да моя файловая система обеспечивает такой же антисбойный механизм как и любые другие журналируемые системы. Только я его не рекламирую потому что не делал для этого тестовый проект и не делал исследования состояний не до конца стёртых секторов. Это дополнительная большая работа, поэтому у меня на этот счёт есть свои эвристики, но они не стоят того чтобы публиковать.
Нельзя забывать и о наличии механизма исправления одиночных ошибок в STM32 и предупреждения о них. Механизм работает, поскольку я сохранил в системе нативный формат записи 32-байтными словами. Такой фичи нет, скажем в SPI флэш или в JFFS для общего случая.
Поэтому я могу считать свою систему даже потенциально более надёжной чем обычные. Но требуется конечно доработки.
spam-receiver
08.11.2021 22:28Примеры Embedded Flash хранилищ, которые прекрасно работают:
https://docs.zephyrproject.org/latest/reference/storage/nvs/nvs.html
https://infocenter.nordicsemi.com/topic/sdk_nrf5_v17.1.0/lib_fds.html
Indemsys Автор
09.11.2021 15:49Да, наверно я плохо объяснил преимущества именованных файлов, поиска по имени и переименований.
Надеюсь в следующем Example3 это продемонстрировать.
Sun-ami
Если верить Reference manual на STM32H753, при ошибке контроля ECC Programm Flash генегируется не Hard Fault, а Bus Fault, и только в случае, если была обнаружена двойная ошибка, если была обнаружена одинарная — она исправляется, и может произойти маскируемое прерывание, если оно включено. А Hard Fault возникает, если обнаружена двойная ошибка, и Bus Fault маскировано. При этом, Bus Fault можно обработать, ориентируясь на адрес и флаги от блока контроля ECC. Это, конечно, слабо поможет повторной записи, ведь блок ECC может исказить данные при чтении, пытаясь исправить одиночную ошибку — писать повторно имеет смысл разве что несколько значений из фиксированной последовательности, каждое из которых имеет корректный ECC. Или практика расходится с Reference manual?
Indemsys Автор
Да все верно написали.
Я просто ошибочно написал название функции в которую у меня все немаскируемые прерывания прилетают. Исправлю.
По поводу одиночной ошибки при изменении одного бита не уверен.
Мне кажется логичным что при каждой записи записывается и ECC. И ECC пишется в ту же Flash. Т.е. искажается и бит и ECC, что в результате приведёт сразу к двойной ошибке.
Sun-ami
Думаю, да, записывается и ECC. Но думаю, существует несколько последовательностей из 256-битных данных с кодом ЕСС, в которых следующий элемент отличается набором дополнительных нулей в данных и ECC. А если не смотря на Bus Fault всё-таки можно прочитать дескриптор — наверное можно писать маркер стирания прямо в 32-байтный дескриптор, в виде поля из нескольких нулей. Это позволит уменьшить расход памяти на запись одного байта в файл в полтора раза.
Indemsys Автор
Я некоторое время думал об этом и искал алгоритм ECC от ST, но не нашёл.
Опять же вычислительная сложность не ясна. Если это потребует итераций или больших таблиц, то тоже не вариант.
Проще уж рационально писать файлы. Не писать файлы из одного байта.
Sun-ami
Не совсем понимаю, как Вы хотели применить алгоритм ECC. Если он нужен — возможно, стоит попробовать предположение, что он принципиально такой же, какой применялся в IBM-370 ещё в 70-х, и отличается только разрядностью. Но для записи маркера стирания в 32-байтный дескриптор он не нужен — достаточно в обработчике Bus Fault игнорировать это исключение, если установлен флаг двойной ошибки ECC, а в дескрипторе первым делом проверять маркер стирания. Это может быть байт, который для нестёртого дескриптора равен FF. Для стёртого — писать туда 0, и проверять на равенство FF. Это добавит время на обработку Bus Fault по каждому удалённому дескриптору — но это немного времени.
Indemsys Автор
Алгоритм ECC нужен для того чтобы сформировать дескриптор для подходящей ECC чтобы не было ошибки или была только одна.
Потому что если будет две, то я просто не смогу прочитать ничего в дескрипторе.
Bus Fault возникает до того как выполниться команда чтения. Чтобы ни пытался читать в 32-байтном блоке будет Bus Fault и я не узнаю содержимое ни одного байта.
Кстати пустые места в дескрипторах не такая уж и проблема, а скорее фича.
Туда позже можно добавлять дату или другую метаинформацию, для шифрования например
Sun-ami
Так из обработчика Bus Fault можно вернуться как из обычного прерывания, и данные скорее всего будут прочитаны
Indemsys Автор
Так адрес возврата указывается до команды чтения вызывающей ошибку. Попытка просто вернуться мгновенно вызывает новый Bus Fault.
Т.е. чтобы корректно вернуться после этой команды надо будет провести такой не слабый парсинг на предмет что там дальше за коды чтобы попасть в безопасное место после команды вызвавшей исключение.
Я пришёл к выводу что в таком случае уже нужно сгребать все логи и ресетиться.
Sun-ami
Но из Bus Fault нужно возвращаться не в общем случае, а в конкретном. Поэтому чтобы узнать на сколько прирастить адрес возврата достаточно глянуть в дизассемблер в этом месте. Правда, шансов получить значение в этом случае остаётся немного.
Indemsys Автор
Хорошая идея для новой статьи.