Перейти к содержимому

Настройка таблицы разделов FLASH-памяти для ESP32

Метки:

Добрый день, уважаемые читатели! Практически любой проект автоматики требует применения настраиваемых во время работы программы параметров – ну например желаемая температура для термостата или пароль для подключения к сети WiFi. Получить эти данные с сервера или с панели управления не особо сложно, но сразу же возникает следующий вопрос – а что делать после перезагрузки или выключения и включения устройства? Нужно где-то хранить последнее установленное значение непосредственно на ESP.

 

Самое простое, что можно придумать в этом случае – воспользоваться свободным пространством на встроенной в модуль ESP flash-памяти. Забегая немного вперед, сразу оговорюсь, что для ESP32 разработчики предусмотрели архи-мега-супер-удобнейший способ для хранения различных переменных – раздел NVS, но об нем конкретно мы поподробнее поговорим в одной из следующих статей. Ну а в этой статье поговорим о том, как распределить все доступное пространство на подключенной к чипу flash-памяти между собственно прошивкой и вашими прикладными данными. Речь в данной статье пойдет о только о фреймворке ESP-IDF.

Собственно говоря, делать свою “разбивку” flash-памяти – не строго обязательно. По умолчанию ESP-IDF предоставляет две таблицы “по умолчанию”:

  • Single factory app, no OTA – одно приложение, без OTA раздела. Выглядит оно так: 24 килобайт отдано под NVS-раздел, 4 килобайта данные инициализации и 1 МБ отдано – под прошивку. Куда девались остальные мегабайты? А никуда, их просто никто не использует.
Single factory app, no OTA

Single factory app, no OTA

  • Factory app, two OTA definitions – одно приложение, но два OTA раздела для обеспечения возможности обновления прошивки по воздуху. Здесь немного интереснее, память используется уже полностью, но опять же – далеко не оптимально.
Factory app, two OTA definitions

Factory app, two OTA definitions

Вы вполне можете воспользоваться одним из этих двух вариантов и не “делать себе моск”. Чем мы, собственно и занимались во всех предыдущих статьях, посвященных ESP-IDF (окромя серии статей про термостат – там используется моя кастомная таблица).

Я лично предпочитаю использовать “свою” таблицу разделов. Почему? Просто я не люблю выбрасывать деньги на ветер (а кто любит?) свободное пространство на flash в пустоту, ибо на мой взгляд, встроенные в ESP-IDF варианты далеки от идеальных. И в этой статье я расскажу как…

Это была присказка, сказка будет впереди. Читать опять придется изрядно – а вы что думали, в сказку попали?

 


Правила формирования разделов

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

Файл custom таблицы разделов представляет собой обычный текстовый CSV-файл с разделителями “запятая” (в кодировке UTF-8 без BOM, но это не имеет особого значения, так как в нём допускаются только кошерные символы латиницы, а они в любой кодировке занимают ровно 1 байт).

Каждая запись в таблице разделов имеет имя (метку), тип (приложение, данные или что-то еще), подтип и смещение во флэш-памяти, куда будет загружен этот раздел. Имя – это просто текстовое обозначение, ни на что практически не влияющее. Главное – чтобы было понятно программисту. А вот с типами посложнее.

При создании таблицы разделов необходимо учитывать следующие ограничения:

  • Каждый сектор flash памяти имеет минимальный размер 4 КБ или 0x1000 байт в шестнадцатеричном представлении. Здесь и далее все размеры – именно в шестнадцатеричном виде. То есть задать размер раздела 0x0100 или 0x0500 у вас не выйдет.
  • Начальная область Flash-памяти по умолчанию занята под загрузчик (конечно, если вы его используете, но он используется в большинстве случаев). Поэтому прикладные разделы должны располагаться начиная с адреса, заданного в константе CONFIG_PARTITION_TABLE_OFFSET – это так называемое смещение по умолчанию. На момент написания статьи это 0x8000. Однако, в некоторых случаях загрузчик не впихивается в отведенное ему место (например если вы включили отладочные сообщения в лог для загрузчика) и вам потребуется увеличить это значение.
  • Длина таблицы разделов составляет 0xC00 байт, то есть допускается максимум 95 записей. Контрольная сумма MD5, используемая для проверки целостности таблицы разделов во время запуска, добавляется сразу же после данных таблицы. Таким образом, таблица разделов занимает целый сектор флэш-памяти. В результате любой следующий за ним раздел должен располагаться как минимум по адресу смещение по умолчанию + 0x1000. То есть начало “нашей” таблицы разделов увеличивается до 0x9000.

 


Типы разделов

Тип раздела можно указать с помощью строкового идентификатора или как 8-битное число в HEX-формате. В принципе, типы разделов могут быть самыми разными, в том числе и пользовательскими. За подробностями – прошу в справочную систему. Но по большому счету эта информация для простого обывателя на мой взгляд лишняя, так как загрузчик ESP-IDF игнорирует любые типы разделов, кроме двух:

  • app, он же ESP_PARTITION_TYPE_APP (цифровое значение 0x00) – сюда могут быть записаны данные вашей прошивки.
  • data, он же ESP_PARTITION_TYPE_DATA (цифровое значение 0x01) – а сюда можно поместить любые прикладные данные.

С другими типами разделов мне лично работать ещё не приходилось.

Подтип раздела

Тип раздела это ещё не всё. Нужно дополнительно указать, какого подтипа данные будут располагаться в разделе. ESP-IDF позволяет задать следующие подтипы в зависимости от типа:

Для разделов типа app (0x00)

Когда type равен app, поле SubType может быть указано как:

  • factory (0x00) – это раздел приложения по умолчанию. Не обязательный раздел, если вы используете OTA и обязательный – если OTA не используется. Сюда “по умолчанию” ESP-IDF помещает скомпилированный код во время прошивки ESP32 кабелем. Но если этот раздел отсутствует, прошивка будет записана в свободный ota-раздел. Загрузчик запустит это “заводское” приложение, если не найдет разделы типа ota. ОТА никогда не перезаписывает этот раздел, поэтому вы можете быть уверены, что ваше устройство гарантировано запустится, даже если вы путем сложной комбинации кривых рук ушатали все свои ota-разделы.
  • ota_0 (0x10) … ota_15 (0x1F) – слоты для OTA-прошивок. Сюда будет помещен код прошивки, полученный “на лету” через WiFi и OTA. Как работает этот механизм, я упоминал ранее. Сюда же упадет ваш код “с кабеля” в случае, если раздел factory отсутствует. Если вы планируете использовать OTA – то вам нужно как минимум два таких раздела.
  • test (0x20) – это зарезервированный подтип для процедур заводских испытаний. Он будет использоваться в качестве резервного загрузочного раздела, если не будет найден другой допустимый раздел приложения (factory или ota). Можно настроить загрузчик на чтение специального strapping pin во время каждой загрузки и загрузку этого раздела, если соответствующее GPIO удерживается на низком уровне, см. загрузка тестовой прошивки.

Важно! Для разделов типа app размер и посадочное место (смещение) должны быть выровненными до 0x10000 (64 КБ)! Ну и конечно же, размер раздела должен быть не меньше размера скомпилировано вами кода с запасом на будущие модификации.

 

Для разделов типа data (0x01)

Когда тип равен data, поле подтипа может быть указано как ota (0x00), phy (0x01), nvs (0x02), nvs_keys (0x04) или несколько других подтипов (см. subtype enum)

  • ota (0x00) — это раздел данных OTA , в котором хранится информация о выбранном в данный момент слоте приложения OTA. Этот раздел обязателен при наличии OTA разделов типа app и должен иметь размер ровно 0x2000 байт. Дополнительные сведения смотрите в документации OTA.
  • phy (0x01) предназначен для хранения данных инициализации устройства PHY. Это позволяет настраивать константы для каждого устройства без внесения изменений в прошивку. В конфигурации по умолчанию раздел phy не используется, а данные инициализации PHY компилируются в само приложение. Таким образом, этот раздел можно удалить из таблицы разделов для экономии места. Особенно если вы не выпускаете свои устройства крупными партиями.
  • nvs (0x02) предназначен для API энергонезависимого хранилища (NVS).
    NVS используется для хранения данных калибровки PHY для каждого устройства (отличных от данных инициализации) и для хранения данных WiFi, если используется функция инициализации esp_wifi_set_storage (WIFI_STORAGE_FLASH). NVS API также можно (и нужно!) использовать для данных вашего приложения. Разработчики ESP-IDF настоятельно рекомендуют включить в проект раздел NVS размером не менее 0x3000 байт. Если вы используете NVS API для хранения большого количества данных, увеличьте размер раздела NVS со значения по умолчанию 0x6000 байт.
  • nvs_keys (0x04) – это ключевой раздел NVS и используется для хранения ключей шифрования NVS, когда включена функция шифрования NVS.
    Размер этого раздела должен быть ровно 0x1000 байт. Если шифрование данных не используется, то и этот раздел не нужен.
  • fat (ESP_PARTITION_SUBTYPE_DATA_FAT) – позволяет организовать файловую систему FAT
  • spiffs (ESP_PARTITION_SUBTYPE_DATA_SPIFFS) – позволяет организовать файловую систему SPIFFS

Существуют и другие предопределенные подтипы данных для хранения данных, поддерживаемые ESP-IDF. Более подробно смотрите в документации.

 


Создаем свою таблицу разделов

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

  • Учитывая вышесказанное, таблицу разделов начинаем с адреса 0x9000.
  • Я активно использую технологию OTA, потому мне нужно 2 раздела ota_0 и ota_1. Создавать их больше двух – не вижу в этом особого смысла. За несколько лет зверских и бесчеловечных экспериментов над бедными и несчастными ESP мне так и не удалось добиться того, чтобы убить обе прошивки сразу. Но какие наши годы! Попытки не прекращаются.
  • Раздел factory я не использую. Зачем? Хранить там когда-то прошитую версию, которая через несколько обновлений через OTA безнадежно устареет? Такое себе… Впрочем – на вкус и цвет фломастеры разные.
  • Раздел phy тоже в топку. Я не выпускаю устройства на конвейере, поэтому оно мне без надобности.
  • Fat и spiffs я тоже еще ни разу не использовал. NVS пока что перекрывает все мои потребности. Поэтому я отдаю место под NVS по максимуму. Зачем так много? Это станет понятно потом, когда я расскажу, как устроен и работает этот раздел.

Для разных размеров flash, разумеется, я использую разные таблицы. Приведу вам свои.

Для FLASH 4 МБ

Большинство популярных плат и модулей на китайской площадке имеют объем микросхемы памяти размером в 4 МБ. Итак, моя таблица разделов для “стандартного” flash 4 МБ выглядит так:

Теги: #ESP32 #ESP-IDF #FLASH Добрый день, уважаемый читатель!-3

Объясняю как это получилось.

Четыре мегабайта – это 4194304 байт или 0x400000 в HEX формате.

  • Учитывая, что размер слота под ota должен быть выровнен по 0x10000, я принял размер одного ota-раздела 0x180000 – это 1,5 МБ, такого размера пока хватает на все мои потребности даже для самых сложных прошивок.
  • Не забываем добавить раздел data / ota размером 0x2000 как описано выше.
  • Все остальное – отдано под раздел NVS. Это чуть меньше 1 мегабайта. Куда так много? Это станет ясно, когда вы узнаете, как устроено NVS API (по умному, кстати). Причем, чтобы выровнять app / ota – разделы, я расположил его между data / ota и первым app / ota_0.

Вот и все хитрости. Ничего лишнего, все в дело!

Для FLASH 16 МБ

Другое дело, если у вас на руках память гораздо большего размера. Например у меня есть ESP32-WROWER-I16 с 16 Мб на борту. В этом случае таблица разделов выглядит так:

Теги: #ESP32 #ESP-IDF #FLASH Добрый день, уважаемый читатель!-4

  • Здесь я увеличил размеры слотов под пришивку аж до 3 МБ.
  • NVS занимает полноценные 3.5 МБ. Что позволяет не задумываться о том, когда заполнятся все страницы.
  • От избытка чувств и по широте душевной в конце таблицы расположился раздел SPIFFS на будущие хотелки. Но пока в прошивке не используется.

Собственно WROWER я брал не из-за большого FLASH, а из-за дополнительной SPIRAM – для хранения данных когда нет доступа к сети интернет.

 

Совет: Как посчитать все эти шестнадцатеричные размеры? На Win10 – очень просто. Берем самый обычный калькулятор windows и включаем на нем режим “Программист”. Вводим числа в десятичной системе – сразу же видим в hex. Можно считать смещения сразу в hex и т.д.

Вычисление размера памяти для 4МБ модуля в качестве примера

Вычисление размера памяти для 4МБ модуля в качестве примера


 


Подключение таблицы разделов к проекту ESP-IDF

Хорошо, файлик с описанием разделов мы создали, что дальше? А дальше запускаем menuconfig и ищем раздел Partition Table → Partition Table:

После того, как мы выбрали пункт, указывающий на то, что мы хотим всё сломать настроить своими руками, необходимо указать имя файла, который мы создали:

Ну вот, собственно и всё. Да, так просто. И никаких утилит запускать не нужно. Система сборки сделает все самостоятельно.

А в PlatformIO?

А в PlatformIO это не работает!!! – скажут опытные читатели. И будут правы. Дабы PlatformIO “увидел” этот файл, нужно указать его в специальном пункте файла platformio.ini, по аналогии с embed files:

Вот теперь ваш проект должен скомпилироваться с новыми разделами!


Ссылки

1. Partitions API

2. Storage API

 

На том и сказочке конец! Кто всё дочитал – молодец, возьми с полки огурец! Ну лето же…

💠 Полный архив статей вы найдете здесь


Пожалуйста, оцените статью:
[ 3 из 5, всего 2 оценок ]

2 комментария для “Настройка таблицы разделов FLASH-памяти для ESP32”

  1. добрый. наверно опечатка строка:
    “Четыре мегабайта – это 4194304 байт или 0x40000 в HEX формате.”

    и ниже показываете калькулятор где не 40 000, а 40 0000 (группировка страная но видно что 400 тыщ).. да и по суммам не сходится.. должно быть 400 тыщ, а не 40… иначе сложно в 40 уместить раздел под ОТА 180 тыщ )

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *