Одна flash-микросхема памяти ESP32 может содержать как несколько приложений, так и несколько разновидностей данных (данные калибровки, файловые системы, хранилище параметров и т. д.). По этому таблица разделов прошивается во flash по адресу 0x8000 (смещение по умолчанию).
Длина таблицы разделов составляет 0xC00 байт (что дает максимальное колиество записей 95). Контрольная сумма MD5, которая используется для проверки целостности таблицы разделов, прикрепляется в конце данных таблицы.
Каждая запись в таблице разделов имеет имя (метку, label), тип (app, data, или что-нибудь еще), подтип (subtype) и смещение во flash-памяти, куда загружен раздел.
Самый простой способ использовать таблицу разделов - открыть меню конфигурации проекта (idf.py menuconfig) и опцией CONFIG_PARTITION_TABLE_TYPE выбрать одну из предварительно определенных таблиц разделов:
• Single factory app, no OTA (одно заводское приложение, без OTA) • Single factory app (large), no OTA (одно большое заводское риложение, без OTA) • Factory app, two OTA definitions (заводское приложение, два определения OTA) • Custom partition table CSV (пользовательская таблица разделов в файле CSV)
Заводское приложение прошивается со смещением 0x10000. Если выполнить команду idf.py partition-table, то она напечатает общую информацию таблицы разделов (перед выполнением этой команды надо сделать текущей директорию проекта).
[Встроенные таблицы разделов]
Пример вывода общей информации для конфигурации "Single factory app, no OTA":
• Со смещением 0x10000 (64 KB) во flash записано приложение помеченное как "factory". Загрузчик (bootloader) будет по умолчанию запускать это приложение. • Также здесь есть 2 региона данных, определенных в таблице раздела для сохранения раздела библиотеки NVS [4] (NVS library partition) и данных инициализации PHY (PHY init data).
Пример вывода общей информации для конфигурации "Factory app, two OTA definitions":
• Здесь присутствует определение трех разделов приложений (тип app): приложение factory по адресу 0x10000 и следующие два OTA-приложения. Обратите внимание, что подтипы приложений отличаются. • Также присутствует новый слот otadata, который хранит данные для обновлений OTA. Загрузчик консультируется с этими данными, чтобы узнать, какое приложение запускать. Если слот данных ota пустой, то будет запущено приложение factory.
[Создание пользовательских таблиц]
Если в menuconfig выбрано "Custom partition table CSV", то можно также ввести имя CSV-файла (находящегося в директории проекта), чтобы использовать его для пользовательской таблицы разделов. CSV-файл может описывать любое количество определений для той таблицы, которая Вам нужна.
Формат CSV-файла такой же, как показанный в примерах выше вывод информации по команде idf.py partition-table. Однако в CSV требуются не все поля. Например, может быть следующий входной CSV-файл для таблицы разделов OTA:
• Пробелы между полями игнорируются, как и любая строка, начинающаяся на символ # (комментарий). • Каждая строка, не начинающаяся на #, определяет раздел. • Здесь поле Offset разделов ota_0, ota_1 и nvs_key пустое. Утилита gen_esp32part.py заполнит каждое пустое поле Offset, начинаясь после таблицы раздела, с проверкой корректности выравнивания каждого раздела.
Поле Name. Это поле имени, в нем может быть любое осмысленное значение. Для ESP32 содержимое поля имени не имеет значения. Имена длиннее 16 символов обрезаются.
Поле Type. Это поле типа, в котором может быть app (0x00) или data (0x01). Или же здесь может быть число в диапазоне 0-254 (hex 0x00-0xFE). Типы 0x00-0x3F зарезервированы для функций ядра ESP-IDF. Если Вашему приложению нужно сохранить данные в формате, который не поддерживается ESP-IDF, то добавляйте раздел с типом в диапазоне 0x40-0xFE. См. перечисление esp_partition_type_t для определения разделов app и data.
Если приложение пишется на C++, то указание типа раздела, определяемого для приложения, требует приведения типа от целого значения в значение esp_partition_type_t, чтобы тип можно было использовать в API разделов. Например:
Загрузчик (bootloader) ESP-IDF игнорирует любые типы разделов, которые отличаются от app (0x00) и data (0x01).
Поле SubType. 8-битное поле подтипа специфическое для каждого типа раздела. ESP-IDF в настоящее время указывает осмысленные значения поля subtype только для типов разделов app и data. Перечисление esp_partition_subtype_t, где приведен полный список подтипов, определенных средой разработки ESP-IDF, включает следующее:
• Когда тип app, поле subtype может быть указано как factory (0x00), ota_0 (0x10) … ota_15 (0x1F), или test (0x20).
- factory (0x00) это раздел приложения по умолчанию. Загрузчик выполнит приложение factory за исключением случаев, когда он увидит раздел типа data/ota. Для этого раздела загрузчик прочитает его, чтобы определить, какой образ OTA загружать. OTA никогда не обновляет раздел factory. Если необходимо экономить использование flash-памяти в проекте OTA, то можно удалить раздел factory, и использовать вместо него ota_0. - ota_0 (0x10) .. ota_15 (0x1F) это слоты приложений OTA. Когда используется OTA, раздел OTA data конфигурируется, чтобы определить, какой app slot должен загрузить bootloader. При использовании OTA приложение должно определить как минимум 2 слота приложений OTA (ota_0 и ota_1). Для дополнительной информации см. документацию по OTA [2]. - test (0x20) зарезервированный подтип для процедур заводского тестирования. Он будет использоваться в качестве резервного загрузочного раздела (fallback boot partition), если не найден другой допустимый раздел app. Можно также сконфигурировать загрузчик, чтобы он опрашивал входы GPIO при каждой загрузке, и загружать этот раздел, если ножка GPIO удерживается на уровне лог. 0, см. раздел "Boot from Test Firmware" документации [5].
• Когда у раздела тип data, поле subtype может быть указано как ota (0x00), phy (0x01), nvs (0x02), nvs_keys (0x04), либо оно может быть подтипом, специфичным для компонента (см. описание перечисления esp_partition_subtype_t для значений subtype [6]).
- ota (0) это раздел данных OTA, где находится информация о том, какой в настоящий момент выбран слот приложения (OTA app slot). У этого раздела должен быть размер 0x2000 байт, подробнее см. раздел "OTA Data Partition" документации по OTA [3]. - phy (1) этот раздел хранит информацию данных инициализации PHY, что позволяет для каждого устройства конфигурировать PHY вместо того, чтобы это было реализовано в firmware.
В конфигурации по умолчанию раздел phy не используется, и данные инициализации PHY компилируются в составе самого приложения. В этом случае раздел phy может быть удален из таблицы разделов, чтобы экономить место flash.
Для загрузки данных PHY из этого раздела откройте меню конфигурации проекта (idf.py menuconfig), и разрешите опцию CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION. Также понадобится прошивка вашего устройства данными phy init, поскольку система разработки ESP-IDF не делает это автоматически.
- nvs (2) это раздел энергонезависимого хранилища (Non-Volatile Storage, NVS), доступное через соответствующее API [4].
NVS используется для хранение данных калибровки PHY для каждого устройства (эти данные отличаются от данных инициализации).
NVS используется для хранения данных WiFi, если используется функция инициализации esp_wifi_set_storage(WIFI_STORAGE_FLASH).
NVS API может также использоваться для других данных приложения.
Настоятельно рекомендуется включить в проект раздел NVS размером как минимум 0x3000 байт. Если для хранения большого количества данных используется NVS API, то размер по умолчанию 0x6000 байт (24K) может оказаться недостаточным, и его следует увеличить.
- nvs_keys (4) это раздел для ключа, подробнее см [4].
В разделе nvs_keys хранятся ключи шифрования NVS, когда разрешена функция NVS Encryption. Размер этого раздела должен быть равен 4096 байт (минимальный размер раздела).
Существуют и другие предопределенные подтипы данных для хранилища данных, поддерживаемые ESP-IDF. Эти типы включают файловую систему FAT (ESP_PARTITION_SUBTYPE_DATA_FAT), SPIFFS (ESP_PARTITION_SUBTYPE_DATA_SPIFFS), и т. д. Другие подтипы данных зарезервированы для будущего использования в ESP-IDF.
Если у типа раздела задано значение, определяемое приложением (в диапазоне 0x40-0xFE), то поле subtype может быть любого значения, выбранного приложением (в диапазоне 0x00-0xFE).
Обратите внимание, что когда приложение пишется на C++, определяемое приложением значение субтипа должно быть приведено к типу esp_partition_subtype_t, чтобы его можно было использовать в API-функциях разделов.
Поля Offset и Size. Разделы, в описании CSV-файла которых поле смещения пустое, будет начинаться после предыдущего раздела, или после таблицы разделов в случае первого раздела.
Разделы типа app должны быть размещены со смещением, выровненным на блок размером 0x10000 байт (64K). Если оставить поле offset пустым, то скрипт gen_esp32part.py автоматически выровняет смещение раздела. Если было указано не выровненное смещение для раздела app, то скрипт возвратит ошибку.
Размеры (Size) и смещения могут задаваться десятичными числами, в hex-виде с префиксом 0x, или с помощью мультипликаторов единиц размера K или M (единицы 1024 или 1024*1024 байт соответственно).
Если необходимо, чтобы разделы в таблице разделов работали относительно любого размещения (CONFIG_PARTITION_TABLE_OFFSET) самой таблицы, то оставьте поле смещения (в файле CSV) для всех разделов пустым. Аналогично, при изменении смещения таблицы разделов следует иметь в виду, что все пустые смещения разделов могут быть изменены, и любые фиксированные значения смещений могут столкнуться с таблицей разделов (что вызовет ошибку).
Поле Flags. В настоящее время поддерживается только один флаг, encrypted. Если это поле установлено в encrypted, то этот раздел будет зашифрован, если разрешена функция Flash Encryption.
Замечание: разделы типа app всегда будут незашифрованными, независимо от того, установлен ли этот флаг, или нет.
[Генерация двоичной таблицы разделов]
Таблица разделов, которая прошивается в ESP32, имеет двоичный формат, не CSV. Для преобразования CSV в двоичный формат используется утилита partition_table/gen_esp32part.py.
Если Вы конфигурируете имя таблицы разделов CSV в конфигурации проекта (idf.py menuconfig), и затем выполняете сборку проекта или запускаете idf.py partition-table, то это преобразование осуществляется как часть процесса сборки.
Для преобразования CSV в двоичный формат вручную, запустите следующую команду:
Для отображения содержимого двоичной таблицы разделов в stdout (таким образом генерируются суммарные отчеты, когда при запуске idf.py partition-table генерируется таблица разделов:
python gen_esp32part.py binary_partitions.bin
[Проверки размера разделов]
Подсистема сборки ESP-IDF будет автоматически проверять, соответствуют ли сгенерированные двоичные файлы доступному пространству раздела, и произойдет ошибка, если двоичные данные для раздела слишком большие.
В настоящее время эти проверки выполняются для следующих двоичных файлов:
• Бинарный код загрузчика (bootloader) должен поместиться в пространство перед таблицей загрузчика (см. раздел "Bootloader Size" документации [5]). • Бинарный код приложения, который должен поместиться как минимум в один из разделов типа app. Если бинарник приложения не умещается ни в один раздел app, то сборка завершится неудачей. Если бинарник приложения помещается только в некоторые разделы app, то будет выведено предупреждающее сообщение.
Важное замечание: хотя процесс сборки завершится неудачей, если проверка размера вернет ошибку, то двоичные файлы все равно будут сгенерированы и могут быть прошиты во flash-память (хотя они могут не работать, если слишком большие для доступного пространства).
Контрольная сумма MD5. Двоичный формат таблицы разделов содержит контрольную сумму MD5, вычисленную по содержимому таблицы разделов. Эта контрольная сумма используется для проверки целостности таблицы разделов во время загрузки (boot).
Генерация контрольной суммы MD5 может быть запрещена опцией --disable-md5sum скрипта gen_esp32part.py, или опцией конфигурацией CONFIG_PARTITION_TABLE_MD5. Это полезно, например, когда используется bootloader из ESP-IDF версии до v3.1, который не мог обрабатывать контрольные суммы MD5, и загрузка завершится неудачей с сообщением об ошибке invalid magic number 0xebeb.
[Прошивка таблицы разделов]
Таблицу разделов можно прошить в память flash следующими командами:
idf.py partition-table-flash: эта команда прошьет таблицу раздела с помощью утилиты esptool.py. idf.py flash: эта команда прошивает сразу все, включая таблицу разделов.
Результаты команды ручной прошивки также печатаются как часть вывода idf.py partition-table.
Важное замечание: обратите внимание, что обновление таблицы разделов не стирает данные, которые были сохранены в старую таблицу разделов. Вы можете использовать команду idf.py erase-flash (или esptool.py erase_flash) для полной очистки содержимого памяти flash.
[Partition Tool (parttool.py)]
Компонент partition_table предоставляет скрипт parttool.py для операций, связанных с разделами на целевом устройстве. Эта утилита может выполнять следующее:
• Чтение раздела и сохранение его содержимого в файл (read_partition). • Запись содержимого файла в раздел (write_partition). • Стирание раздела (erase_partition). • Получение информации по указанному разделу, такой как name, offset, size и flag (get_partition_info).
Утилита может быть импортирована и использоваться из другого скрипта Python, или может запускаться из командной строки (shell-скрипта) для пользователей, которые хотят программно выполнять операции с разделами. Этому способствует интерфейс Python API и интерфейс командной строки соответственно.
Раздел, на котором производится операция, указывается с помощью либо PartitionName, либо PartitionType, либо PARTITION_BOOT_DEFAULT. Как подразумевает имя, они позволяют ссылаться на раздел по определенному имени, комбинации type-subtype, или через указание default boot partition.
Дополнительную информацию по Python API можно получить из строк документирования утилиты.
- command-args - аргументы, которые нужны для выполнения основной команды (parttool.py). В основном они относятся к целевому устройству. - subcommand - выполняемая операция. - subcommand-args - аргументы, специфические для выбранной операции.
# Стирание раздела с именем 'storage'
parttool.py --port "/dev/ttyUSB1" erase_partition --partition-name=storage
# Чтение раздела типа 'data' и подтипа 'spiffs', и сохранение в файл 'spiffs.bin'
parttool.py --port "/dev/ttyUSB1" read_partition --partition-type=data
--partition-subtype=spiffs --output "spiffs.bin"
# Запись в раздел 'factory' содержимого файла 'factory.bin'
parttool.py --port "/dev/ttyUSB1" write_partition --partition-name=factory
--input "factory.bin"