Открытый проект файловой системы для внутренней памяти STM32H
Зачем ставить внешнюю 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-байтный блок, на который придется такая запись, будет с высокой вероятностью обозначен системой как сбойный и последующее чтение в границах этого блока вызовет ошибку доступа к шине и вызов исключения HardFault, которое нарушит ход выполнения программы. И такое поведение никак отключить нельзя. Т.е. после некорректной записи всего в один байт в системе станет недоступным блок 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
Название функции | Описание |
STfs_init | Инициализация файловой системы. Инициализирует драйвер Flash, проверяет целостность, форматирует если целостность нарушена |
STfs_find | Поиск файла на виртуальном диске по строковому шаблону и получение сведений о файле |
STfs_find_first_file | Поиск самого раннего файла на виртуальном диске и получение сведений о файле |
STfs_find_next_file | Продолжение поиска файлов от ранних к поздним |
STfs_open | Открытие файла на запись или чтение |
STfs_rename | Переименование файла |
STfs_read | Чтение данных из файла |
STfs_write | Запись данных в файл |
STfs_setpos | Установка позиции чтения из файла от его начала |
STfs_len | Получение размера файла |
STfs_close | Закрытие файла |
STfs_delete | Удаление файла |
STfs_flush | Фиксация записанных данных во Flash |
STfs_free_space | Получение информации о количестве свободного пространства на виртуальном диске |
STfs_format | Форматирование виртуального диска. Физически просто стирание всех секторов |
STfs_check | Проверка целостности файловой системы на виртуальном диске и получение информации о системе |
STfs_defrag | Дефрагментация файловой системы |
STfs_free_task_cbls | Освобождение захваченных задачей RTOS ресурсов у файловой системы. |
Симулятор STfs на персональном компьютере
Тестировать на реальном железе логику файловой системы слишком долго и неудобно.
Поэтому была написана программа симулятор на C++ под Windows 10.
Программа включает те же исходники STfs что и рабочая версия для микроконтроллера, только слой драйвера Flash памяти был заменен на эмуляцию в оперативной памяти компьютера. Но при этом эмулятор строго проверяет все ограничения по размеру и выравниванию записываемых данных и чистоте целевой области записи. Был создана функция тестовой записи и считывания файлов с регулярной проверкой целостности и дефрагментацией по необходимости.
Все записи, считывания производятся на основе генератора случайных чисел. Каждый 10-й файл не удаляется для имитации фрагментации. Если дефрагментация не удаётся, то чтобы не прерывать тест производится выборочное удаление более старших файлов.
Внешний вид программы симулятора STfs Каждый пиксел в столбце отображает состояние байта в секторе. Каждый столбец это отдельный сектор. Адреса растут слева направо и снизу вверх.
Зелёным цветом обозначаются блоки данных актуальных файлов, желтым обозначаются дескрипторы актуальных файлов. Красным и тёмно-красным обозначаются блоки уделенных файлов и их дескрипторов. Серый цвет обозначает чистые области Flash, куда еще ничего не записывалось после последнего стирания сектора.
Симулятор легко проводит миллион и больше итераций, что на обычной 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 Мбайт в секунду. И этого более чем достаточно.
Все проекты хранятся здесь — https://github.com/Indemsys/Backup-controller_BACKPMAN-v2.0/tree/main/Software