Як працює розбиття Flash-пам’яті в ESP32

Організація Flash-памʼяті в ESP32 дуже нагадує мені те, як це було з жорсткими дисками на ПК за часів MBR. Там так само є завантажувач, таблиця розділів і, власне, розділи з даними.

І яким би не був розмір флешу, правила розбиття не змінюються.

Зазвичай таблиця розділів починається за адресою 0x8000 і займає 4 КБ. Але в деяких випадках може знадобитись більший розмір завантажувача, і доводится здвигати таблицю розділів далі.

Cтруктура одного запису в таблиці розділів виглядає так:

struct PartitionEntry 
{
    uint8_t magic;         // Магічне число для валідації запису
    uint8_t type;          // Тип розділу
    uint8_t subtype;       // Підтип розділу
    uint8_t reserved;      // Зарезервовано
    uint32_t offset;       // Зсув розділу у флеш-пам'яті
    uint32_t size;         // Розмір розділу
    char name[16];         // Ім'я розділу
    uint32_t flags;        // Прапори розділу
};

Проте не варто жорстко на неї спиратися, у різних версіях фреймворку вона може змінюватися. Для надійної роботи краще використовувати функції з API (із заголовного файлу esp_partition.h):

  • esp_partition_find( sp_partition_type_t type, esp_partition_subtype_t subtype, const char label ) - починає пошук розділів. Можна вказати певний тип, підтип та назву розділу. Або ж передати ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL, щоб отримати ітератор для читання інформації про всі наявні розділи.

  • esp_partition_get(esp_partition_iterator_t iterator) — дозволяє отримати детальну інформацію про розділ за допомогою ітератора.

  • esp_partition_iterator_t esp_partition_next(esp_partition_iterator_t iterator) — перехід до наступного елемента. Коли список закінчиться, ітератор дорівнюватиме NULL.

  • esp_partition_iterator_release(esp_partition_iterator_t iterator) — звільняє пам’ять, виділену під ітератор.

Приклад читання розділів в ESP-IDF під час виконання (до речі, ці ж функції аналогічно працюють і в Arduino для ESP32):

    const esp_partition_t *partition = nullptr;
    esp_partition_iterator_t it = esp_partition_find(
        ESP_PARTITION_TYPE_ANY,
        ESP_PARTITION_SUBTYPE_ANY,
        NULL);
    while (it != NULL)
    {
        partition = esp_partition_get(it);
        partition = esp_partition_get(it);
        printf("Partition: %s", partition->label);
        printf(", Address: 0x%lx", partition->address);
        printf(", Size: %ld", partition->size);
        printf(" bytes, type: %d \n", partition->type);
        it = esp_partition_next(it);
    }
    esp_partition_iterator_release(it);

У мене результат такий:

Partition: nvs, Address: 0x9000, Size: 24576 bytes, type: 1 
Partition: phy_init, Address: 0xf000, Size: 4096 bytes, type: 1 
Partition: factory, Address: 0x10000, Size: 1048576 bytes, type: 0 

Щоб краще розуміти цей вивід, давайте коротко розглянемо основні типи розділів в екосистемі ESP32 та їхнє призначення:

Назва розділу Призначення
nvs Призначений для зберігання логінів та паролів до Wi-Fi, калібрувальних даних та інших користувацьких налаштувань.
factory, otaXX, appXX Розділи для збереження самої прошивки (їх може бути декілька). Це основа для механізму OTA (Over-The-Air) оновлень.
otadata Містить інформацію для завантажувача про те, яку саме версію прошивки (з якого розділу) потрібно завантажити зараз. Використовується, коли в нас є кілька версій.
spiffs,littlefs,fat Розділи для зберігання користувацьких даних у відповідних файлових системах.
coredump Розділ для збереження дампу пам’яті при критичних помилках. Згодом ці дані можна використати для налагодження та пошуку багів у коді прошивки.

Якщо проаналізувати мій результат через призму цієї таблиці, можна помітити одну деталь: під основну прошивку (factory) виділено рівно 1 МБ. При тому що на флешці є доступно цілих 4 Мб. Чому так відбувається і як це виправити, щоб використати всю пам’ять? Давайте розбиратись.

Як змінити розподіл в ESP-IDF?

ESP-IDF дозволяє конфігурувати проєкт різними засобами — є графічний конфігуратор для VSCode, є консольний menuconfig, і, нарешті, ніхто не 
забороняє відкрити файл конфігурації sdkconfig в улюбленому текстовому 
редакторі та змінити його напряму. Де знайти ці конфігуратори?

-  Графічний можна знайти SDK Configuration editor.


 

- Консольний можна запустити командою idf.py menuconfig (в VSCode це робится через ESP-IDF: New Terminal).

 

Він має ідентичні розділи та налаштування, як і графічний конфігуратор. Власне, ми можемо налаштувати:

  • Тип таблиці розділів - можна вибрати якусь стандартну розбивку (наприклад, Single factory app, no OTA або Factory app, two OTA). Або ж взагалі вказати власний CSV-файл.

  • Нестандартне зміщення (offset) для таблиці розділів.

Спробуємо створити та використати свій CSV-файл з описом розділів. У корені проєкту я створюю файл mypartitions.csv і виділяю 3 МБ під прошивку:

# Name,   Type, SubType, Offset,  Size, Flags
nvs,      data, nvs,     ,        0x6000,
phy_init, data, phy,     ,        0x1000,
factory,  app,  factory, ,        3M,

Стовпчик Offset можна пропускати, у такому разі він буде розрахований автоматично.

Далі запускаю консольний конфігуратор та обираю в ньому пункт Custom partition table CSV. Після цього в меню з’являється додаткова опція, у якій можна вказати ім’я цього файлу. Вписуємо його, далі тиснемо s для збереження конфігурації, а потім q для виходу.

Давайте подивимось як це було записано в sdkconfig:

#
# Partition Table
#
# CONFIG_PARTITION_TABLE_SINGLE_APP is not set
# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set
# CONFIG_PARTITION_TABLE_TWO_OTA is not set
# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="mypartitions.csv"
# default:
CONFIG_PARTITION_TABLE_FILENAME="mypartitions.csv"
# default:
CONFIG_PARTITION_TABLE_OFFSET=0x8000
# default:
CONFIG_PARTITION_TABLE_MD5=y

Тепер потрібно повністю очистити флеш-пам’ять мікроконтролера, це можна зробити командою idf.py erase-flash. Якщо у вас у цей час відкритий монітор порту, ви отримаєте помилку - тому монітор потрібно заздалегідь вимкнути.

Після запускаю білд та прошивку проєкту - і отримую помилку

Partitions tables occupies 3.1MB of flash (3211264 bytes) which does not fit in configured flash size 2MB. 
Change the flash size in menuconfig under the 'Serial Flasher Config' menu.

За замовчуванням максимальний розмір флешу сконфігуровано як 2 МБ. Це треба виправити: йду в Serial Flasher Config -> Flash Size, обираю 4 MB та зберігаю конфігурацію. Знову запускаю збірку та прошивку.

Тепер тестовий код виводить таку інформацію про розділи:

Partition: nvs, Address: 0x9000, Size: 24576 bytes, type: 1 
Partition: phy_init, Address: 0xf000, Size: 4096 bytes, type: 1 
Partition: factory, Address: 0x10000, Size: 3145728 bytes, type: 0 

Усе вийшло, маємо аж 3 МБ під прошивку.

Як змінити розподіл у PlatformIO + ESP-IDF проєкті

PlatformIO підтримує розробку проєктів на базі як фреймворку Arduino, так і ESP-IDF. 

У випадку ESP-IDF проєкту налаштування розбиття флешу змінюється безпосередньо у конфігураційному файлі platformio.ini. Для підключення власної таблиці достатньо додати параметр board_build.partitions:

[env:lolin32]
platform = espressif32
board = lolin32_lite
framework = espidf
board_build.partitions = mypartitions.csv
board_upload.flash_size = 4MB

У цьому ж конфізі можна відразу вказати й загальний розмір доступної флеш-пам’яті за допомогою параметра board_upload.flash_size.

Важливий нюанс: навіть якщо ви попередньо налаштували інше розбиття чи розмір флешу через консольний menuconfig, PlatformIO проігнорує їх і жорстко перевизначить конфігурацію значеннями з platformio.ini. Тому під час роботи в цьому середовищі саме platformio.ini є головним файлом налаштувань.

Як змінити розподіл у PlatformIO + Arduino проєкті

Для фреймворку Arduino налаштування в PlatformIO абсолютно ідентичні до тих, що ми розглядали для ESP-IDF. Тобто ми так само використовуємо параметри board_build.partitions та board_upload.flash_size у файлі platformio.ini, щоб вказати шлях до власного CSV-файлу та загальний розмір флеш-пам’яті.

Замість створення власного файлу, ви також можете вибрати одну зі стандартних схем розбиття, які вже підготовлені розробниками. Знайти їх можна в директорії пакунків PlatformIO за таким шляхом - .platformio/packages/framework-arduinoespressif32/tools/partitions.

Ось приклад схем які є в моєму PlatformIO:

app3M_fat9M_16MB.csv
app3M_fat9M_fact512k_16MB.csv
app3M_spiffs9M_fact512k_16MB.csv
bare_minimum_2MB.csv
boot_app0.bin
default_16MB.csv
default_8MB.csv
default_ffat_8MB.csv
default_ffat.csv
default.csv
ffat.csv
huge_app.csv
large_fat_32MB.csv
large_ffat_8MB.csv
large_littlefs_32MB.csv
large_spiffs_16MB.csv
large_spiffs_8MB.csv
max_app_8MB.csv
min_spiffs.csv
minimal.csv
no_ota.csv
noota_3g.csv
noota_3gffat.csv
noota_ffat.csv
rainmaker.csv

Зверніть увагу: якщо в конфігураційному файлі platformio.ini жодна схема не вказана явно, під час збірки PlatformIO за замовчуванням використає конфігурацію default.csv.

Використані матеріали

Офіційна документація Espressif: Partition Tables



Коментарі

Популярні дописи з цього блогу

Огляд DC-DC Step-down Buck перетворювачів

ESP8266 модуль з OLED екраном (HW-364A)

Модуль PD тригер IP2721 на 15 та 20 вольт