При разработке устройств настает момент, когда перестаешь использовать dev kit и переходишь на свою плату. Соответственно, меняются и механизмы взаимодействия устройства с компьютером. Я использую программаторы ARM-USB-OCD и ARM-USB-OCD-H. Они отличаются друг от друга только скоростью. Для STM32 вполне достаточно первого, а вот для ESP32 уже хочется второй…
Начнем с теории. На официальном сайте есть руководство по установке всего необходимого софта (компиляторы, библиотеки, отладчики и примеры кода). И рядом имеется руководство по отладке при помощи openocd. Этот софт через программатор подключается к чипу. Различными командами можно записать прошивку на чип, стереть его или считать. В общем, манипуляции с памятью, команды на перезагрузку и прочие низкоуровневые функции. Далее, openocd и предоставляет отладчику GDB доступ чипу. GDB уже понимает что переменная X лежит по адресу 0xff и так далее. Можно прыгать по прошивке, читать переменные… Можно сказать, высокоуровневая отладка. Как-то так оно и работает.
Теперь о железе. В первую очередь требуется подключить плату к программатору. На официальном же сайте есть схема подключения.
ESP32 Pin | JTAG Signal |
---|---|
CHIP_PU | TRST_N |
MTDO / GPIO15 | TDO |
MTDI / GPIO12 | TDI |
MTCK / GPIO13 | TCK |
MTMS / GPIO14 | TMS |
GND | GND |
Но я дополню эту схему. Не знаю, для всех ли программаторов это актуально. В моем случае, в программаторе содержатся буферы, которые «запитывает» TARGET устройство. Таким образом, если мы подаем на пин V_TARGET/VREF (1 контакт 20-пинового разъема) напряжением 3.3 вольта, то и все сигнальные линии будут с уровнями 3.3 вольта. Так же и для 5 вольт. Таким образом программатор становится универсальным и подходит для работы с чипами, работающими на различных уровнях сигналов. Если V_TARGET не подключать, то openocd просто не обнаружит чип.
Теперь сам процесс прошивки. По умолчанию команда make flash скрывает вызовы компилятора и различных утилит для прошивки. В качестве примера возьмем проект работы с GPIO.
Но, если добавить ключ -e, то она их покажет.
Теперь мы видим, что на самом деле прошивается не только build/gpio.bin, а ещё и build/bootloader/bootloader.bin c build/partitions_singleapp.bin. Причем в определенном порядке и с определенными смещениями.
Если посмотреть файл с описанием esp32 в скриптах openocd (openocd-esp32/share/openocd/scripts/target/esp32.cfg), то можно найти в нем команду program_esp32, которая в качестве аргумента принимает имя файла и смещение. Можно воспользоваться и ей, но все равно нужно прошивать все три bin файла.
На самом деле эта команда просто проверяет входные данные и вызывает встроенные в openocd функции. Поэтому перейдем сразу к их описанию в документации. В ней описаны команды для работы с flash: erase_sector, erase_sector, write_bank, read_bank, verify_bank, write_image. Сейчас интересует только последняя. С остальными разобраться не проблема, если вдруг понадобятся.
Требуется указать имя файла с прошивкой, смещение и нужно ли предварительно стереть используемую область памяти и проводить проверку после записи. Загружать bin файлы скорее всего нужно в том же порядке, который получили из вывода make flash.
В моем случае, в домашней директории лежит скрипт ~/.openocd.sh, в котором находятся команды для запуска openocd со всеми параметрами. Это удобно.
[walhi@walhi] ~ $ cat ~/.openocd.sh
#!/bin/sh
#OCD=ocd-h
OCD=ocd
case `pwd` in
*stm32*)
exec openocd -f interface/ftdi/olimex-arm-usb-${OCD}.cfg\
-f interface/ftdi/olimex-arm-jtag-swd.cfg\
-f target/stm32f1x.cfg\
-c 'init'\
"$@"
*esp*)
exec ~/esp/openocd-esp32/bin/openocd -s ~/esp/openocd-esp32/share/openocd/scripts\
-f interface/ftdi/olimex-arm-usb-${OCD}.cfg\
-f board/esp-wroom-32.cfg\
"$@"
esac
[walhi@walhi] ~ $
Поясню один момент. Конструкция «$@» позволяет передать все параметры, которые передали этому скрипту, в openocd.
Теперь можно попробовать прошить ESP32. Сформируем команду:
~/.openocd.sh
Для начала нужно проинициализировать чип и остановить выполнение текущей программы. Добавим команды для этого:
-c "init" -c "reset halt"
Прошивка файла build/bootloader/bootloader.bin:
-c "flash write_image erase build/bootloader/bootloader.bin 0x1000"
Прошивка программы build/gpio.bin:
-c "flash write_image erase build/gpio.bin 0x10000"
Прошивка файла build/partitions_singleapp.bin:
-c "flash write_image erase build/partitions_singleapp.bin 0x8000"
На этом формирование команды на прошивку завершено. Но программа не начнет выполняться и openocd останется запущенным. Для решения этих проблем добавим ещё две команды
-c "reset run" -c "exit"
В результате полная команда на прошивку выглядит следующим образом:
~/.openocd.sh -c "init" -c "reset halt" -c "flash write_image erase build/bootloader/bootloader.bin 0x1000" -c "flash write_image erase build/gpio.bin 0x10000" -c "flash write_image erase build/partitions_singleapp.bin 0x8000" -c "reset run" -c "exit"
Учтите, что при копировании строки с сайта, могут возникнуть проблемы из-за символов юникода. Выглядит это так:
Набрать команду не так уж и сложно. А вообще, лучше бы оформить это в скриптик.
Результат успешного выполнения команды выглядит следующим образом:
А теперь давайте сравним скорости загрузки через UART Bootloader и через JTAG. Для справки приведу размеры прошивки:
[walhi@walhi] ~/esp/esp-idf/examples/peripherals/gpio $ make size
Toolchain path: /home/walhi/esp/xtensa-esp32-elf/bin/xtensa-esp32-elf-gcc
Toolchain version: crosstool-ng-1.22.0-80-g6c4433a
Compiler version: 5.2.0
App "gpio" version: v4.0-dev-73-gf1e9078cb
Python requirements from /home/walhi/esp/esp-idf/requirements.txt are satisfied.
Total sizes:
DRAM .data size: 7852 bytes
DRAM .bss size: 4128 bytes
Used static DRAM: 11980 bytes ( 168756 available, 6.6% used)
Used static IRAM: 32839 bytes ( 98233 available, 25.1% used)
Flash code: 78932 bytes
Flash rodata: 38508 bytes
Total image size:~ 158131 bytes (.bin may be padded larger)
Программатор | Время (сек) |
UART Bootloader | 14,245 |
Olimex ARM-USB-OCD | 16,093 |
Olimex ARM-USB-OCD-H | 4,084 |
Надеюсь, статья поможет освоиться.