Хорошие инструменты для отладки встраиваемого ПО микроконтроллеров давно стали делом привычным. Возможности таких инструментов определяются как архитектурой ядра, так и выбором отладчика. Рассмотрим три понятия: DAP (Debug access port), ITM (Instrumentation Trace Macrocell) и RTT (Real-Time Transfer). Всё это «механизмы» позволяющие выводить отладочную информацию в том или ином виде. DAP – это аппаратный блок, который дает доступ к шинам и ядру микроконтроллера. ITM – это специальный блок внутри Cortex-M (начиная с M3 и выше), предназначенный для сообщений с минимальными потерями времени. RTT – технология компании SEGGER, построенная на использовании кольцевого буфера внутри RAM. Именно о ней и пойдет речь в публикации.
RTT Viewer
RTT (Real-Time Transfer) от SEGGER J-Link — это механизм быстрого обмена данными между микроконтроллером и компьютером без остановки CPU. Работает это дело через кольцевые буферы, размещаемые в RAM целевого МК. Технология RTT поддерживает двустороннюю передачу данных, но обычно используются для вывода отладочной информации, попросту говоря нужна для функции printf.
Для настройки необходимо скачать файлы с репозитория и добавить их к проекту. В качестве примера я выбрал устройство, работающее с OpenTherm. Во время отладки потребовался вывод лога сообщений о текущем состоянии газового котла. Проект собран в IDE Keil uVision, но нужно помнить, что RTT не привязан к выбору среды разработки.

Собственно, на этом все, в файле SEGGER_RTT определяются механизмы работы с кольцевым буфером, название файла SEGGER_RTT_printf говорит само за себя. Скомпилируем и запустим проект. В отладке это выглядит примерно так:

Блок управления RTT (CB, Control Block) содержит идентификатор, количество каналов и описание кольцевых буферов на чтение и запись. В памяти МК это хранится в виде структуры SEGGER_RTT, которая служит «точкой входа» для отладчика J-Link при обмене данными с целевым устройством, для запуска RTT J-Link должен найти в оперативной памяти эту структуру. В большинстве случаев поиск происходит автоматически, но, при необходимости, есть механизмы указания определённого адреса.
Перейдем к практической части. Подключаем заголовочные файлы:
#include <stdio.h> #include "SEGGER_RTT.h"
Добавляем «обертки» под разные типы сообщений (опционально):
#define LOG_INFO(msg, ...) { SEGGER_RTT_TerminalOut(0, "INFO: "); SEGGER_RTT_printf(0, msg, ##__VA_ARGS__); } #define LOG_ERR(msg, ...) { SEGGER_RTT_TerminalOut(0, "ERROR: "); SEGGER_RTT_printf(0, msg, ##__VA_ARGS__); } void LOG_FLOAT(const char* prefix, float value, const char* suffix) { char buf[64]; snprintf(buf, sizeof(buf), "%s%.2f%s", prefix, value, suffix); SEGGER_RTT_TerminalOut(0, buf); }
Вызываем функцию инициализации CB
SEGGER_RTT_Init();
Выводим лог:
LOG_INFO("Central Heating: on\r\n", Boiler.isCentralHeatingActive(response) ? "on" : "off"); LOG_ERR("Error: OpenTherm is not initialized");
Утилита RTT Viewer устанавливается вместе с пакетом драйверов для программатора J-Link. При запуске утилиты необходимо выбрать целевой МК, интерфейс подключения программатора (USB, TCP/IP) и интерфейс отладки (SWD, JTAG).

Обратите внимание, что макросы LOG_INFO, LOG_ERR используют функции SEGGER_RTT_TerminalOut(…) и SEGGER_RTT_printf(…) которые позволяют выбрать канал и терминал. Номер терминала и канала это не одно и то же. Первое служит лишь визуальной составляющей при работе с утилитой RTT Viewer, т.е. позволяет выводить информацию на разные вкладки. Второе относится к числу структур CB (SEGGER_RTT) в памяти МК и позволяет организовать более продвинутую передачу пользовательских данных.
Технология SEGGER RTT имеет ряд существенных преимуществ:
позволяет выводить лог printf через SWD, не задействуя дополнительных выводов МК, т.е. не требуется настройка ножки SWO;
может использоваться параллельно работе отладчика IDE, поскольку использует аппаратный блок DAP (работает на Cortex-M0/M0+ где нет блока ITM);
проста в настройке, базовая структура занимает около 24 байт (ID) + по 24 байта на каждый объявленный канал в RAM + размеры буферов;
-
имеет очень высокую скорость работы:
![Рисунок 4 - Сравнение скорости работы RTT с другими технологиями [1] Рисунок 4 - Сравнение скорости работы RTT с другими технологиями [1]](https://habrastorage.org/r/w780/getpro/habr/upload_files/6a1/0af/f10/6a10aff1094d187e6cc237889bf7d1bc.png)
Рисунок 4 - Сравнение скорости работы RTT с другими технологиями [1]
System Viewer
На механизме кольцевых буферов RTT работает ещё одна крайне полезная утилита, которая использует стандартную библиотеку SEGGER RTT как транспортный уровень. SystemView – это инструмент для анализа и визуализации работы встраиваемых систем в режиме реального времени. Настройка проекта для работы с SystemView чуть сложнее, чем в случае с RTT. Разобьём последовательность действий на несколько шагов.
Шаг 1. Скачиваем необходимые файлы из репозитория и подключаем к проекту. Для примера я взял контроллер адресных светодиодов, работающий на плате We Act STM32F411.

SystemView работает, перехватывая макросы трассировки FreeRTOS (trace-макросы), которые находятся в ключевых точках ядра FreeRTOS (например, при создании задачи или переключении контекста). По умолчанию эти макросы пусты, поэтому не влияют на работу программы. SystmView определяет этим макросы и ядро автоматически вызывает код в нужный момент. Определения макросов можно найти в файле SEGGER_SYSVIEW_FreeRTOS.h, выглядят они примерно так:
#define traceTASK_SWITCHED_IN() SEGGER_SYSVIEW_OnTaskStartExec((U32)pxCurrentTCB) #define traceTASK_SWITCHED_OUT() SEGGER_SYSVIEW_OnTaskStopExec() #define traceISR_ENTER() SEGGER_SYSVIEW_RecordEnterISR() // и т.д.
Шаг 2. В самом конце файла FreeRTOSConfig.h подключаем заголовочный файл SEGGER_SYSVIEW_FreeRTOS.h. Убеждаемся, что:
#define configUSE_TRACE_FACILITY 1,
для FreeRTOS V10+ добавляем макрос
#define INCLUDE_xTaskGetIdleTaskHandle 1
Шаг 3. Cortex-M3/M4 имеют встроенный модуль DWT, который содержит 32-битный счетчик циклов (CYCCNT). SystemView использует его как эталон времени. Для того, чтобы предоставить SystemView доступ к счётчику вносим свой код в функцию SEGGER_SYSVIEW_Config():
void SEGGER_SYSVIEW_Conf(void) { // ---- Setup DWT (for Cortex-M3/M4) ---- #define DEMCR (*(volatile U32*)0xE000EDFCu) #define DWT_CTRL (*(volatile U32*)0xE0001000u) #define DWT_CYCCNT (*(volatile U32*)0xE0001004u) #define DEMCR_TRCENA (1u << 24) #define DWT_CTRL_CYCCNTENA (1u << 0) DEMCR |= DEMCR_TRCENA; // Enable access to TRCENA DWT_CYCCNT = 0; // Clear counter DWT_CTRL |= DWT_CTRL_CYCCNTENA; // Start DWT count SEGGER_SYSVIEW_Init(SYSVIEW_TIMESTAMP_FREQ, SYSVIEW_CPU_FREQ, &SYSVIEW_X_OS_TraceAPI, _cbSendSystemDesc); SEGGER_SYSVIEW_SetRAMBase(SYSVIEW_RAM_BASE); }
Поле установки регистра DWT_CTRL |= DWT_CTRL_CYCCNTENA счётчик DWT_CYCCNT будет инкрементироваться каждый так процессора.
На этом шаге также проверьте макрос #define SYSVIEW_RAM_BASE в соответствии с адресом RAM на выбранном МК.
Шаг 4. В отличие от переключения задач, которое SystemView перехватывает через макросы ядра FreeRTOS, прерывания нужно «подсвечивать». Для этого в обработчики прерываний добавляем вызовы функций SEGGER_SYSVIEW_RecordEnterISR() и SEGGER_SYSVIEW_RecordExitISR(). Ориентировочно это выглядит так:
void EINT15_10_IRQHandler() { SEGGER_SYSVIEW_RecordEnterISR(); if(EINT->IPEND & EINT_IPEND_11) { // Ваш код } SEGGER_SYSVIEW_RecordExitISR(); }
Шаг 5. Для того, чтобы SystemView знала имена прерываний, ей необходимо об этом сообщить, дополнив функцию _cbSendSystemDesc(). Она находится в файле SEGGER_SYSVIEW_Config_FreeRTOS.c.
static void _cbSendSystemDesc(void) { SEGGER_SYSVIEW_SendSysDesc("N="SYSVIEW_APP_NAME",D="SYSVIEW_DEVICE_NAME",O=FreeRTOS"); SEGGER_SYSVIEW_SendSysDesc("I#15=SysTickIRQ"); SEGGER_SYSVIEW_SendSysDesc("I#46=ModbusIRQ"); //... }
Шаг 6. Информацию, подготовленную на предыдущих шагах необходимо передать SystemView. Для этого добавим две в main (или другое удобное место) дополнительные функции:
// A function to get the number of the current active interrupt (ISR) U32 SEGGER_SYSVIEW_X_GetInterruptId(void) { // In the Cortex-M core, the interrupt number is stored in the lower 9 bits of the IPSR register. return ((*(volatile U32*)(0xE000ED04u)) & 0x1FFu); } // In Cortex-M3/M4, we use the DWT_CYCCNT core clock counter register U32 SEGGER_SYSVIEW_X_GetTimestamp(void) { return (*(volatile U32*)(0xE0001004u)); }
Шаг 7. Теперь мы готовы к запуску. После инициализации аппаратной части (тактирование и прочей периферии) в функции main размещаем SEGGER_SYSVIEW_Conf(); И только после этого запускаем диспетчер vTaskStartScheduler();
InitPhy(); SEGGER_SYSVIEW_Conf(); // Segger SystemView Initialization CreateDeviceTasks(); vTaskStartScheduler(); // Start the real time scheduler
Остается лишь запустить утилиту SystemView (можно скачать на сайте SEGGER), в случае успешной настройки получаем достойную награду за приложенные усилия.

Изучение интерфейса утилиты и доступных опций остаются заинтересованному читателю. Вероятно настройка под ваш микроконтроллер не пойдет гладко, но к счастью даже бесплатные нейросети дают правильные ответы по выбранной теме. Уверен, вы без труда сможете подключить SystemView к своему проекту. Преимущества от использования SystemView очевидны, но хочу упомянуть следующую фишку: утилита работает даже в том случае, если целевой МК не находится в режиме отладки. Т.е. вы можете разместить своё устройство в реальных условиях эксплуатации, а когда что-то пойдет не так, просто подключить JTAG и заглянуть внутрь прошивки. Как по мне, это просто фантастика.
Целью этой небольшой публикации было знакомство читателя с технологиями RTT и SystmView и общим паттерном их настройки. Тем кому тема показалась интересной, предлагаю ответить на следующие вопросы:
Почему SystemView использует DWT_CYCCNT вместо того, чтобы считывать значения SysTick?
Через какой канал (буфер) RTT работает SystemView? Можно ли его менять?
Какую из двух функций SEGGER_RTT_printf или SEGGER_SYSVIEW_Print лучше использовать в проектах с RTOS?
Можно ли использовать SystemView без RTOS?
Какие существуют инструменты, аналогичные SystemView?
EmCreatore
Инструмент конечно хороший, но его окупаемость под вопросом.
Если проект под бесплатными GCC или LLVM, то это будет наверно оправдано.
Хотя если с самого начала сидят на бесплатных тулсах, то врядли разработчику купят платный отладчик, да ещё адаптер J-Link стоит немало.
А если проект на IAR, то его C-Spy даст больше преимуществ чем System Viewer.
Чтобы в C-Spy отслеживать события прерывавний в реальном времени не нужно вообще никаких программных вставок в обработчики прерываний. Это сильно упрощает наблюдение за живучестью системы.
С другой стороны то, что в System Viewer показано на демо-скриншотах далеко от реальности.
Не поверю что кто-то вот так сидит и смотрит за таким огромным количеством событий и переменных. А перед этим еще рутинно конфигурирует это.
Нет, смотрят за узкими участками , парочка переменных, желательно видеть сразу все прерывания, и не искаженные программными отладочными вставками. Желательно видеть состояния задач и всех объектов синхронизации (ивенты, семафоры, мьютексы, пайпы и т.д.) Это все есть в C-Spy через аддоны к десятку разных RTOS.
Ну и в последнее время кардинально поменялся сам рабочий процесс отладки. Больше нет глупых ошибок типа: не там запятая, не то имя, неправильное условие в if, пустой указатель, утечка памяти и тому подобные мелочи. Т.е. нет больше тех абсурдных ошибок из-за которых надо было перерывать все исходники сверху до низу. Cloude Opus 4.6 просто не делает таких ошибок в принципе.
Остались только ошибки высокого порядка, когда непонятно как работает API многоуровневых библиотек и плоходокументированная внутреняя периферия микроконтроллера или внешняя переферия. И это решается в основном логами. И самый полезный инструмент тогда RTT Viewer. Но он идет с J-Link и System Viewer для этого не нужен.