Хабрахабр

[Из песочницы] Перенаправляем printf() из STM32 в консоль Qt Creator

kdpv.svg

При этом хочется, чтобы и вывод был побыстрее, и чтобы строки отображались не где-нибудь, а прямо в IDE — не отходя от кода, так сказать. Нередко при отладке ПО микроконтроллера возникает необходимость вывода отладочных сообщений, логов, захваченных данных и прочего на экран ПК. Собственно, об этом и статья — как я пытался printf() выводить и отображать внутри любимой, но не очень микроконтроллерной, среды Qt Creator.

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

Поэтому обычно, я отдаю предпочтение последним двум — использование UART и ITM. Semihosting — довольно медленный, RTT — завязан на программно-аппаратные решения Segger, USB — есть не в каждом микроконтроллере. О них и пойдёт ниже речь.

В качестве ОС сейчас у меня Fedora 28, а текущей связкой ПО для работы с микроконтроллерами являются: И сразу некоторое пояснение по тому софту, что будет использоваться далее.

Перенаправление printf() в GCC

Итак, чтобы в GCC перенаправить вывод printf() необходимо добавить в ключи линкера

-specs=nosys.specs -specs=nano.specs

Если будет необходим вывод чисел с плавающей запятой, то нужно не забыть ключ

-u_printf_float

Например, примерно так И реализовать функцию _write().

int _write(int fd, char* ptr, int len)
i++; } return len;
}

где retarget_put_char() — это функция, которая будет загружать символ непосредственно в нужный интерфейс.

printf() -> ITM -> Qt Creator

Для реализации printf() об ITM необходимо знать следующее: Instrumentation Trace Macrocell (ITM) — это блок внутри ядра Cortex-M3/M4/M7, используемый для неинвазивного вывода (трассировки) различного вида диагностической информации.

  • Использует тактовый сигнал TRACECLKIN, частота которого обычно равна частоте работы ядра
  • Имеет 32 штуки так называемых stimulus ports для вывода данных
  • CMSIS имеет в своем составе функцию ITM_SendChar(), которая загружает символ в stimulus port 0
  • Данные выводятся наружу либо через синхронную шину (TRACEDATA, TRACECLK), либо по асинхронной однопроводной линии SWO (TRACESWO)
  • Линия SWO обычно мультиплексирована с JTDO, а значит работает только в режиме отладки по SWD
  • Вывод по SWO осуществляется либо с использованием кода Манчестер, либо NRZ (UART 8N1)
  • Данные передаются фреймами определенного формата — нужен парсер на приёмной стороне
  • Настраивается ITM обычно из IDE или соответствующей утилиты (однако, никто не запрещает настроить в коде программы — тогда вывод в SWO будет работать без поднятой отладочной сессии)

Наиболее удобным способом использования ITM является вывод через SWO с иcпользованием NRZ кодирования — таким образом, нужна всего одна линия, и принимать данные можно будет не только с помощью отладчика со специальным входом, но и обычным USB-UART переходником, пусть и с меньшей скоростью.

Далее всё просто — подключаем JTDO/TRACESWO микроконтроллера к соответствующему пину отладчика, и идём настраивать софт. Я пошел по пути с использованием отладчика, и был вынужден доработать свой китайский STLink-V2, чтобы он стал поддерживать SWO.

Так например, использование аргументов В openocd есть команда "tpiu config" — с помощью неё можно настроить способ вывода трассировочной информации (более подробно в OpenOCD User’s Guide).

tpiu config internal /home/esynr3z/itm.fifo uart off 168000000

А ещё одна команда настроит вывод в файл /home/esynr3z/itm.fifo, использование NRZ кодирования, и рассчитает максимальную скорость передачи, исходя из частоты TRACECLKIN 168 МГц — для STLink это 2МГц.

itm port 0 1

включит нулевой порт для передачи данных.

В состав исходников OpenOCD входит утилита itmdump (contrib/itmdump.c) — с помощью неё можно осуществить парсинг строк из полученных данных.

Чтобы скомпилировать вводим

gcc itmdump.c -o itmdump

При запуске указываем необходимый файл/pipe/ttyUSB* и ключ -d1 для того, чтобы выводить полученные байты данных как строки

./itmdump -f /home/esynr3z/itm.fifo -d1

Чтобы отправить символ по SWO, дополняем _write(), описанный выше, функцией И последнее.

int retarget_put_char(int ch)
{ ITM_SendChar((uint32_t)ch); return 0;
}

Безусловно, существует и более элегантный способ решения поставленной задачи — написать соответствующий плагин для Qt Creator. Итак, общий план такой: внутри Qt Creator конфигурируем openocd на сохранение всей получаемой информации по SWO в предварительно созданный named pipe, а чтение pipe, парсинг строк и вывод на экран выполняем с помощью itmdump, запущенной как External Tool. Однако, надеюсь, что и описанный ниже подход окажется кому-нибудь полезным.

Заходим в настройки плагина Bare Metal (Tools->Options->Devices->Bare Metal).

config_baremetal.png

Выбираем используемый GDB-сервер и добавляем в конец списка команд инициализации строки

monitor tpiu config internal /home/esynr3z/itm.fifo uart off 168000000
monitor itm port 0 1

Теперь, непосредственно перед тем как отладчик поставит курсор в самое начало main() будет происходить настройка ITM.

Добавляем itmdump в качестве External Tool (Tools->External->Configure...).

external_itmdump.png

Не забываем установить переменную

QT_LOGGING_TO_CONSOLE=1

для отображения вывода утилиты в консоль Qt Creator (панель 7 General Messages).

Однако, если прервать отладку, исполнение itmdump завершится, и на вкладке General Messages появятся все выведенные через printf() строки. Теперь включаем itmdump, активируем режим дебага, запускаем исполнение кода и… ничего не происходит.

Модифицированную версию itmdump я залил на GitHub. Путём недолгих изысканий было установлено, что строки из itmdump необходимо буферизировать и выводить в stderr — тогда они появляются в консоли интерактивно, во время отладки программы.

Отладка при запуске будет зависать на выполнении команды "monitor tpiu config ...", если не будет предварительно запущен itmdump. Есть есть еще один нюанс. Происходит это из-за того, что открытие pipe (/home/esynr3z/itm.fifo) внутри openocd на запись — блокирующее, и дебагер будет висеть до тех пор, пока pipe не откроется на чтение с другого конца.

Поэтому пришлось немного поковырять исходники openocd и найти то место, куда нужно подставить небольшой костыль. Это несколько неприятно, особенно, если в какой-то момент ITM не будет нужен, но придется вхолостую запускать itmdump, либо постоянно переключать GDB-сервер или удалять/добавлять строки в его настройках.

В файле src/target/armv7m_trace.c есть строка с искомой процедурой открытия

armv7m->trace_config.trace_file = fopen(CMD_ARGV[cmd_idx], "ab");

её нужно заменить на

int fd = open(CMD_ARGV[cmd_idx], O_CREAT | O_RDWR, 0664);
armv7m->trace_config.trace_file = fdopen(fd, "ab");

А значит можно оставить настройки Bare Metal в покое, а itmdump запускать только когда это нужно. Теперь наш pipe будет открываться сразу и не отсвечивать.

В итоге, вывод сообщений во время отладки выглядит так

debug.png

printf() -> UART -> Qt Creator

В этом случае всё примерно так же:

  • Добавляем в код функцию с инициализацией UART
  • Реализуем retarget_put_char(), где символ будет отправляться в буфер приемопередатчика
  • Подключаем USB-UART адаптер
  • Добавляем в External Tools утилиту, которая будет читать строки из виртуального COM-порта и выводить их на экран

Использование довольно простое — нужно указать лишь имя порта и баудрейт. Я набросал такую утилиту на C — uartdump.

external_uartdump.png

Работа этой утилиты не зависит от отладки, а Qt Creator не предлагает никаких опций для закрытия запущенных External Tools. Однако, стоит отметить одну особенность. Поэтому, для прекращения чтения COM-порта я добавил ещё один внешний инструмент.

external_uartdump_close.png

Ну и на всякий случай приложу ссылку на шаблон CMake проекта, который фигурировал на скринах — GitHub.

Теги
Показать больше

Похожие статьи

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

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

Кнопка «Наверх»
Закрыть