Программирование ARM Протокол загрузчика ESP32 Sun, October 06 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.

Протокол загрузчика ESP32 Печать
Добавил(а) microsin   

В этой статье приведен перевод документации [1], описывающей протокол UART-загрузчика микроконтроллеров ROM Loader (ESP32 UART bootloader ROM), заглушка загрузчика stub loader, работающая в ОЗУ, и утилита для работы с загрузчиком esptool.

UART bootloader запускается сразу после деактивации сигнала сброса, если удерживаются соответствующем логическом уровне так называемые выводы управления загрузкой (strapping pins [4]).

Протокол ESP32 ROM-загрузчика подобен протоколу ESP8266, хотя у ESP32 добавлены некоторые дополнительные команды, и в нем несколько отличается поведение.

По умолчанию утилита esptool выгрузит программный загрузчик (stub loader) в IRAM чипа. После этого stub loader заменит ROM-загрузчик для всех последующих операций взаимодействия. Это во многом стандартизирует поведение процесса перепрошивки. Можно передать опцию --no-stub в командную строку esptool, чтобы запретить использование stub loader (подробнее см. документацию [3]).

Замечание: существуют различия последовательного протокола между чипами ESP! Чтобы переключиться на оригинал документации по описанию протокола, на страничке [1] в левом верхнем углу из выпадающего списка выберите нужную модель чипа.

UART bootloader Serial Protocol fig01

Примечание: исходный код stub loader можно найти в папке components/esptool_py/esptool/flasher_stub/ среды программирования ESP-IDF версии 4.4.1, либо на Github в репозитории [8].

[Описание пакета]

Компьютер хоста посылает чипу ESP пакет команды запроса (Command Packet). Чип ESP отвечает на эти команды пакетом (Response Packet), в котором включена информация статуса и любые данные в качестве полезной нагрузки.

Протокол низкого уровня. Протокол загрузчика использует пакет кадра SLIP для передачи данных в обоих направлениях. Каждый пакет SLIP начинается и заканчивается байтом 0xC0. Внутри пакета все появляющиес байты 0xC0 и 0xDB заменяются соответственно на последовательности байт 0xDB 0xDC и 0xDB 0xDD. Эта замена происходит после вычисления контрольной суммы и длины пакета, поэтому длина пакета может быть больше, чем значение поля Size, описанного далее (см. таблицу 1).

Command Packet. Каждая команда протоколе SLIP загрузчика инициируется хостом с помощью пакета команды (Command Packet), и в ответ на него приходит пакет ответа (Response Packet). Внутри пакет состоит из заголовка и тела данных переменной длины. Все многобайтные поля представлены в последовательности старшинства байт little-endian [7].

Таблица 1. Формат пакета команды (запрос).

Байт Имя Описание
0 Direction Для запросов тут всегда 0x00.
1 Command Идентификатор команды (см. далее раздел "Команды загрузчика").
2 .. 3 Size Длина поля данных (Data) в байтах.
4 .. 7 Checksum Простая контрольная сумма, вычисляемая от поля данных (используется только для некоторых команд, см. далее раздел "Контрольная сумма").
8 .. n Data Полезная нагрузка данных переменной длины (0 .. 65535 байт, как указано в параметре Size). Назначение данных зависит от команды.

Response Packet. Каждая принятая загрузчиком команда приводит к передаче ответа в виде SLIP-пакета от чипа ESP к хосту. Содержимое пакета ответа следующее:

Таблица 2. Формат пакета ответа.

Байт Имя Описание
0 Direction Для ответа здесь всегда 0x01.
1 Command То же самое значение, что было в идентификаторе команды запроса (см. таблицу 1).
2 .. 3 Size Размер поля данных. Значение здесь равно как минимум длине байтов состояния Status Bytes (2 или 4 байта, см. описание ниже).
4 .. 7 Value Значение ответа, используемое командой READ_REG (см. далее), иначе здесь 0.
8 .. n Data Полезная нагрузка данных переменной длины. Длина показана полем Size.

Байты состояния (Status Bytes). Последние байты полезной нагрузки Data показывают статус команды.

Для stub loader последние 2 байта статуса следующие (большинство команд возвратят как минимум 2 байта полезной нагрузки Data):

Таблица 3. Байты состояния команды для stub loader.

Байт Имя Описание
Size-2 Status Флаг состояния, показывающий успех (0) или неудачу (1).
Size-1 Error Если Status == 1, то этот байт указывает на тип ошибки (см. далее описание ошибок).

Для загрузчика ESP32 ROM (только для него, это не относится к stub loader) используются последние 4 байта, но только первые два из них содержат информацию статуса:

Байт Имя Описание
Size-4 Status Флаг состояния, показывающий успех (0) или неудачу (1).
Size-3 Error Если Status == 1, то этот байт указывает на тип ошибки (см. далее описание ошибок).
Size-2 - Зарезервировано.
Size-1 - Зарезервировано.

ROM Loader Errors. ROM-загрузчик посылает следующие коды ошибок.

Таблица 4. Коды ошибок ROM Loader.

Значение Описание
0x05 "Received message is invalid" (неправильные поля параметра или длины).
0x06 "Failed to act on received message" (не получилось предпринять нужные действия для полученного сообщения).
0x07 "Invalid CRC in message" (ошибка контрольной суммы полученного сообщения).
0x08 "Flash write error" (ошибка записи flash) - после записи блока данных в память flash, код ROM Loader вычитывает записанные значения обратно, и вычисляет 8-битную CRC, проверяя данные на соответствие. Если проверка не показала целостность данных, то будет возвращена эта ошибка.
0x09 "Flash read error" - неудачное чтение SPI.
0x0a "Flash read length error" - запрос на чтение SPI был слишком большой длины.
0x0b "Deflate error" (ошибка распаковки) - относится только к сжатым выгружаемым данным.

Stub Loader Status & Error. Если используется stub loader:

• В статусе ответа всегда используются 2 байта, независимо от типа чипа.
• Коды ошибки полностью отличаются от кодов ошибки ROM Loader. У всех этих кодов форма 0xC*, или 0xFF для обозначения не реализованной команды.

/* Коды ошибки Stub Loader (см. [8]) */
typedef enum {
  ESP_OK = 0,
 
  ESP_BAD_DATA_LEN = 0xC0,
  ESP_BAD_DATA_CHECKSUM = 0xC1,
  ESP_BAD_BLOCKSIZE = 0xC2,
  ESP_INVALID_COMMAND = 0xC3,
  ESP_FAILED_SPI_OP = 0xC4,
  ESP_FAILED_SPI_UNLOCK = 0xC5,
  ESP_NOT_IN_FLASH_MODE = 0xC6,
  ESP_INFLATE_ERROR = 0xC7,
  ESP_NOT_ENOUGH_DATA = 0xC8,
  ESP_TOO_MUCH_DATA = 0xC9,
 
  ESP_CMD_NOT_IMPLEMENTED = 0xFF,
} esp_command_error;

После отправки команды хост должен продолжать чтение пакетов ответа, пока не будет получен пакет ответа, где в поле Command окажется то же самое значение, что и в поле Command запроса, либо пока не истечет таймаут приема пакетов ответа.

[Команды загрузчика]

Таблица 5. Команды, поддерживаемые stub loader и ROM loader.

Байт Имя Описание Входные данные Выходные данные
0x02 FLASH_BEGIN Начать загрузку flash. 4 слова по 32 бита в каждом: размер для стирания, количество пакетов данных, размер данных в одном пакете, смещение в памяти flash.  
0x03 FLASH_DATA Загрузка данных flash. 4 слова по 32 бита в каждом: размер данных, последовательный номер, 0, 0. Затем идут данные. Используется контрольная сумма.  
0x04 FLASH_END Завершение загрузки flash. Одно 32-битное слово: 0 для reboot, 1 для запуска кода пользователя. Эту команду посылать необязательно, если вы хотите остаться в загрузчике.  
0x05 MEM_BEGIN Begin RAM Download Start 4 слова по 32 бита в каждом: общий размер, количество пакетов данных, размер данных в одном пакете, смещение (адрес) в памяти.  
0x06 MEM_END Finish RAM Download 2 слова по 32 бита: флаг выполнения, адрес точки входа для запуска.  
0x07 MEM_DATA RAM Download Data 4 слова по 32 бита в каждом: размер данных, последовательный номер, 0, 0. Затем идут данные. Используется контрольная сумма.  
0x08 SYNC Кадр синхронизации 36 байт: 4 байта 0x07 0x07 0x12 0x20, затем еще 32 байта 0x55.  
0x09 WRITE_REG Запись по 32-разрядному адресу в памяти. 4 слова по 32 бита в каждом: адрес, значение, маска и задержка (в микросекундах).  
0x0a READ_REG Чтение по 32-разрядному адресу в памяти. Адрес как 32-битное слово. Прочитанные данные как 32-битное слово в поле Value пакета ответа.
0x0b SPI_SET_PARAMS Конфигурация SPI flash. 6 слов по 32-бита каждое: id, общий размер в байтах, размер блока, размер сектора, размер страницы, маска статуса.  
0x0d SPI_ATTACH Подключение SPI flash. 32-битное слово: 0 для обычной SPI flash. Второе 32-битное слово (которое должно быть 0) передается только для ROM Loader.  
0x0f CHANGE_BAUDRATE Изменение скорости. 2 слова по 32 бита каждое: новая скорость UART (здесь 0, если мы говорим оставить текущую/старую скорость.  
0x10 FLASH_DEFL_BEGIN Начать загрузку упакованных данных (compressed flash). 4 слова по 32 бита каждое: размер после распаковки, количество пакетов данных, размер пакета данных, смещение flash. Для stub loader размер после распаковки точно соответствует количеству записываемых байт, в то время как для ROM bootloader это количество округляется вверх до размера стираемого блока flash.  
0x11 FLASH_DEFL_DATA Сжатые загружаемые данные flash. 4 слова по 32 бита каждое: размер данных, последовательный номер, 0, 0. Затем идут данные. Используется контрольная сумма. Код ошибки 0xC1 в случае ошибки контрольной суммы.
0x12 FLASH_DEFL_END Завершение загрузки сжатых данных flash. одно 32-разрядное слово: 0 для reboot, 1 для загрузки кода пользователя. Эту команду посылать необязательно, если вы хотите остаться в загрузчике.  
0x13 SPI_FLASH_MD5 Вычисленный хэш MD5 от региона flash. 4 слова по 32 бита каждое: адрес, размер, 0, 0. Тело данных содержит 16 сырых байт MD5 за которыми идут 2 байта состояния (stub loader), или 32 hex-символа ASCII (ROM loader) вычисленной MD5.

Примечание: память SPI flash это необязательно внешняя микросхема, это может быть память, размещенная в корпусе микроконтроллера, но она все равно подключается через SPI.

Таблица 6. Команды, которые поддерживает только stub loader.

Байт Имя Описание Входные данные Выходные данные
0xd0 ERASE_FLASH Полностью стереть весь чип flash.    
0xd1 ERASE_REGION Стереть регион flash. 2 слова по 32 бита: смещение во flash для стирания, размер стираемой области в байтах. Оба эти слова должны нацело делиться на размер сектора flash.  
0xd2 READ_FLASH Прочитать flash. 4 слова по 32 байта: смещение во flash, количество читаемых данных, размер сектора flash, размер пакета чтения, максимальное количество не подтвержденных пакетов.  
0xd3 RUN_USER_CODE Выйти из загрузчика и запустить код пользователя.    

Контрольная сумма. Поле контрольной суммы игнорируется (может быть нулем) для всех команд, кроме MEM_DATA, FLASH_DATA и FLASH_DEFL_DATA.

У каждого из пакетов команды _DATA (наподобие FLASH_DEFL_DATA, MEM_DATA) используется одинаковый формат данных полезной нагрузки ("data payload"):

Байт Имя Формат
0 .. 3 Длина данных для записи. 32-разрядное слово в формате little endian.
4 .. 7 Последовательный номер. 32-разрядное слово в формате little endian. Этим числом нумеруются блоки, начиная от 0.
8 .. 15 0 Два 32-разрядных нулевых слова, эти байты не используются.
16 .. Данные для записи. Длина записываемых данных указана в начале полезной нагрузки (байты 0 .. 3).

Контрольная сумма вычисляется только от последних данных для записи, но не от первых 16 байт полезной нагрузки.

Чтобы вычислить контрольную сумму, начните с seed-значения 0xEF, и выполняйте на нем операцию XOR (исключающее ИЛИ) для каждого отдельного байта поля "данных для записи". 8-разрядный результат сохраняется в поле контрольной суммы заголовка пакета (в виде 32-битного значения little endian).

Замечание: из-за того, что такое вычисление контрольной суммы не является адекватной гарантией целостности данных, была добавлена команда SPI_FLASH_MD5 для проверки содержимого flash после прошивки. Всегда рекомендуется использовать эту команду, см. далее "Проверка выгруженных данных".

[Функциональное описание]

Начальная синхронизация

• Чип ESP после сброса переходит в режим последовательной загрузки (UART bootloader mode). Хост начинает передавать команды SYNC. В этих командах находится поле достаточной длины, чтобы которое чип ESP использует для автодетекта сконфигурированной скорости. ESP32 всегда инициализируется с начальной скоростью 115200 бит/сек (bps). Однако пакеты синхронизации могут быть отправлены на любой скорости, и по ним автодетект скорости настроит периферийное устройство UART.
• Хост должен ждать поступления правильного ответа на команду SYNC, что покажет корректный обмен с чипом ESP.
• После этого esptool (по умолчанию) использует последовательность загрузки "RAM Download", чтобы поместить в IRAM чипа код stub loader. Команда MEM_END содержит адрес точки входа для запуска stub loader. Затем stub loader пошле свой пакет SLIP последовательности OHAI (0xC0 0x4F 0x48 0x41 0x49 0xC0), показывающий, что он работает. И это единственный пакет, который ESP отправляет не в ответ на запрос. Если для esptool указан аргумент --no-stub, то весь этот шаг пропускается.
• После этого esptool использует команды READ_REG для чтения различных адресов в чипе: чтобы идентифицировать подтип чипа, его ревизию и т. п.
• Для команд, которым нужен доступ к flash, коды ESP32 ROM и stub loader требуют команд SPI_ATTACH и SPI_SET_PARAMS (см. далее "Команды конфигурации SPI").
• Для stub loader и/или ESP32 ROM Loader хост может послать команду CHANGE_BAUD для установки скорости обмена (baud rate) в определенное значение. По сравнению с автодетектом по пакету импульсов SYNC, этот способ установки скорости более надежен для очень высоких скоростей обмена. Утилита esptool пытается синхронизироваться на (максимум) 115200bps, и затем посылает эту команду для перехода на повышенную скорость, если это запросил пользователь.

Запись данных (включает режимы RAM Download, Flash Download, Compressed Flash Download).

• RAM Download (MEM_BEGIN, MEM_DATA, MEM_END) загружает данные в оперативную память чипа ESP и (опционально) запускает их.
• Flash Download (FLASH_BEGIN, FLASH_DATA) прошьет данные в ESP SPI flash.
• Compressed Flash Download делает то же самое, но в этом случае передаваемые данные сжаты алгоритмом gzip Deflate, чтобы уменьшить накладные расходы по передаче данных через UART.

Все три перечисленные выше режима следуют одному и тому же шаблону действий:

• Посылается команда _BEGIN (FLASH_BEGIN, и т. п.), которая содержит базовые параметры для размера стираемой области flash, начального адреса для записи и т. д. Коду выгрузки также нужно указать, сколько "блоков" данных (т. е. отдельных пакетов данных) будет отправлено, и какой размер у каждого такого пакета.
• Посылаются одна или большее количество команд _DATA (FLASH_DATA, и т. п.), где полезная нагрузка содержит реальные данные для записи во flash/RAM. В случае режима Compressed Flash Downloads, передаваемые данные сжимаются алгоритмом gzip Deflate. Количество команд _DATA указывается в команде _BEGIN, как и размер полезной нагрузки каждой команды _DATA. Последний блок данных должен быть дополнен до размера блока байтами 0xFF.
• Команда _END (FLASH_END, и т. п.) посылается для выхода из загрузчика и опционально для сброса чипа (или для переход по адресу в RAM, в случае команды MEM_END). Команду _END не надо посылать после завершения процесса прошивки, если вы хотите продолжать посылать другие команды.

Нет необходимости перед командой записи flash посылать команды стирания. ROM-загрузчики стирают записываемый регион в ответ на команду FLASH_BEGIN. Код stub loader делает стирание flash по мере записи данных, чтобы максимизировать общую эффективность прошивки (каждый блок данных считывается в RAM, в то время как предыдущий блок одновременно записывается во flash, и операции стирания 4KB и 64KB осуществляются перед записью во flash).

Размер блока должен быть выбран достаточно малым, чтобы поместиться в RAM чипа. Утилита esptool использует 16KB, что дает хорошую производительность при использовании stub loader.

Проверка выгруженных данных. В протоколе выгрузке используется 8-разрядная контрольная сумма, которая недостаточна для гарантии получения корректного содержимого памяти flash после выгрузки. Посылающие данные код должен использовать команду SPI_FLASH_MD5 или другой надежный метод верификации содержимого flash.

Команда SPI_FLASH_MD5 передает начальный адрес в flash и размер данных для вычисления хэша MD5. В полезной нагрузке ответа передается значение MD5 перед байтами статуса.

Обратите внимание, что ESP32 ROM Loader возвращает md5sum как 32 байта ASCII, кодирующие HEX-значение MD5, в то время как stub loader возвратит md5sum как 16 сырых байт данных MD5, за которыми идут 2 байта статуса.

[Команды конфигурации SPI]

Команда SPI Attach. Команда SPI_ATTACH разрешает интерфейс SPI flash. Она принимает 32-битную полезную нагрузку данных, определяющую, какое периферийное устройство и какие выводы должны использоваться для подключения к SPI flash.

Для ESP32 stub loader требуется послать эту команду перед началом взаимодействия с памятью SPI flash.

Value Что означает
0 Интерфейс SPI flash по умолчанию.
1 Интерфейс HSPI.
(другие значения) Номера выводов как 6-разрядные значения, упакованные в 30 разрядов (6 x 5). Порядок следования (начиная со старшего бита, MSB): ножка HD, ножка Q, ножка D, ножка CS, ножка CLK.

Вариант "Интерфейс SPI flash по умолчанию" использует выводы, сконфигурированные через efuse-биты SPI_PAD_CONFIG_xxx (если не установлены, то все они соответствуют нулям, и тогда используются выводы по умолчанию, определенные в даташите).

Когда записываются 6-битные значения для каждой выбранной ножки, то это значение кодируется следующим образом:

• Номера выводов 0 .. 30 кодируются их номерами.
• Номера выводов 32 и 33 кодируются значениями 30 и 31 соответственно.
• Нет возможности указать выводы 30 и 31, или выводы с номером больше 33. То же самое 6-битное представление номеров ножек используется в efuse-битах SPI_PAD_CONFIG_xxx.

Только для ESP32 ROM Loader у этой команды существуют дополнительные 4 байта в полезной нагрузке. Все эти байты должны быть установлены в 0.

Установка параметров SPI. Команда SPI_SET_PARAMS устанавливает некоторые параметры подключенного чипа SPI flash (его размер, и т. д.).

Все значения, которые передаются, за исключением общего размера, жестко вшиты в код (hardcoded), и большинство не используется при записи во flash. См. функцию flash_set_parameters в утилите esptool для значений, которые она посылает.

32-bit Read/Write. Команды 32-битных чтения и записи (READ_REG, WRITE_REG) позволяют выполнять выровненные на 32-разрядное слово операции обращения к данным памяти и регистров микроконтроллера.

Эти команды могут использоваться для произвольных манипуляций периферийными устройствами микроконтроллера. Например, реализована функциональность "flash id" утилиты esptool путем манипуляции регистрами периферийного устройства SPI, чтобы отправить команду JEDEC flash ID в кристалл памяти flash, и прочитать ответ от неё.

Чтение flash. В коде stub loader реализована команда READ_FLASH. Эта команда ведет себя по-другому в сравнении с другими командами, включая READ_FLASH команду ROM Loader-а:

• Хост посылает команду READ_FLASH, и полезная нагрузка данных содержит смещение, размер чтения, размер каждого отдельного пакета данных, и максимальное количество "не подтвержденных" пакетов данных, которые могут быть отправлены одновременно.
• Stub loader пошлет стандартный пакет ответа, без дополнительной полезной нагрузки данных.
• Теперь stub loader начнет посылать пакеты SLIP с сырыми данными (размер был запрошен в команде). В этих пакетах SLIP метаданные отсутствуют.
• После приема каждого пакета SLIP хост должен послать обратно 4 байта сырого SLIP-пакета подтверждения с общим количеством принятых байт. Здесь в этих пакетах SLIP нет заголовка или других метаданных.
• Stub loader может послать до максимального количества пакетов данных (оно было указано в команде READ_FLASH) перед ожиданием первого пакета подтверждения. Нельзя передавать сразу больше чем "max in flight" ("максимум находящихся в полете") не подтвержденных пакетов.
• После того, как все пакеты данных были приняты и подтверждены, stub loader пошлет 16 байт MD5 хэша от всех данных, которые были прочитаны из flash. Это также отправляется как сырой SLIP-пакет, без метаданных.

После завершения процесса чтения flash код stub loader вернется к нормальному функционированию по типу команда/ответ.

Команда чтения у ROM Loader более "нормальная", то работает намного медленнее.

[Последовательный обмен esptool для трассировки]

Утилита esptool поддерживает опцию --trace, которая может быть предоставлена в первой группе аргументов (перед командой). Эта опция приведет к выводу дампа всего передаваемого и принимаемого трафика, который проходит в консоль через последовательный порт.

Вот кусок трассировки, показывающий команду READ_REG и ответ на неё:

TRACE +0.000 command op=0x0a data len=4 wait_response=1 timeout=3.000 data=1400f43f
TRACE +0.000 Write 14 bytes: c0000a0400000000001400f43fc0
TRACE +0.005 Read 1 bytes: c0
TRACE +0.000 Read 11 bytes: 010a0200620100000000c0
TRACE +0.000 Received full packet: 010a0200620100000000
The +X.XXX value is the time delta (in seconds) since the last trace line.

Печатаемые значения выводятся в HEX-формате. Если больше 16 байт выводится одновременно, то производится разделение отображения на 2 части, где байты в HEX-формате показаны слева, а соответствующие им символы ASCII справа. "Не печатаемые" символы при этом отображаются ASCII-символом точки '.'.

Обратите внимание, что в логах присутствуют несколько слоев протокола. Строки "Write X bytes" показывают, какое точное количество байт отправлено "по проводу", включая оформление кадров SLIP. Подобным образом строки "Read X bytes" показывают, сколько байт было прочитано по последовательному каналу, включая оформление кадров SLIP. Как только прочитан полный пакет SLIP, те же самые байты - что и полезная нагрузка SLIP с любыми удаленными дополнительными кодами - появятся в строках лога "Received full packet".

Вот второй пример, показывающий начальную последовательность синхронизации (порция байт 0x55, которые соответствуют ASCII-коду буквы 'U'):

TRACE +0.000 Write 46 bytes:
    c000082400000000 0007071220555555 | ...$........ UUU
    5555555555555555 5555555555555555 | UUUUUUUUUUUUUUUU
    5555555555555555 5555555555c0     | UUUUUUUUUUUUU.
TRACE +0.011 Read 1 bytes: c0
TRACE +0.000 Read 63 bytes:
    0108040007122055 00000000c0c00108 | ...... U........
    0400071220550000 0000c0c001080400 | .... U..........
    0712205500000000 c0c0010804000712 | .. U............
    205500000000c0c0 01080400071220   |  U............
TRACE +0.000 Received full packet: 010804000712205500000000
TRACE +0.000 Received full packet: 010804000712205500000000

Важное замечание: если вы не планируете использовать stub loader с утилитой esptool, то передайте --no-stub --trace, чтобы увидеть взаимодействие только с внутренним ПЗУ-загрузчиком чипа (built-in ROM loader). Иначе трассировка покажет полный бинарный поток загрузчика.

В дополнение к этой функции трассировки у большинства операционных систем есть фича "system call trace" или "port trace", которую можно использовать для получения дампа обмена данными через последовательный интерфейс.

[Ссылки]

1. ESP32 bootloader ROM Serial Protocol site:espressif.com.
2. ESP32: проблемы с прошивкой SPI Flash и загрузкой.
3. esptool.py Flasher Stub site:espressif.com.
4. Выбор режима загрузки ESP32-C3.
5. ESP32 Bootloader.
6. espressif / esp-serial-flasher site:github.com.
7. Порядок следования байт (endianness).
8. espressif / esptool site:site:github.com.

 

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


Защитный код
Обновить

Top of Page