Хорошие инструменты для отладки встраиваемого ПО микроконтроллеров давно стали делом привычным. Возможности таких инструментов определяются как архитектурой ядра, так и выбором отладчика. Рассмотрим три понятия: 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/w1560/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?
