nRF5x: отладка с помощью RTT Печать
Добавил(а) microsin   

В SDK 12.0.0 или более новой версии встроена функция вывода отладочных сообщений в терминал реального времени (logging/debugging over Real Time Terminal, сокращенно RTT) соответствующий код находится в logger module NRF_LOG. Чтобы разрешить вывод в лог через RTT, разрешите опции NRF_LOG_ENABLED и NRF_LOG_BACKEND_SERIAL_USES_RTT в sdk_config.h (опции файла sdk_config.h можно редактировать как напрямую, так и через утилиту CMSIS Configuration Wizard [2]). Таким образом, это руководство (перевод [1]) больше подходит для SDK 11 или его более ранней версии.

При создании программы для MCU обычно возникает необходимость отладки и мониторинга поведения кода в реальном времени. Есть несколько способов организации такого мониторинга - управление уровнем ножки порта GPIO, подключение отладчика, вставка в код точек останова (breakpoints), выполнение операторов по шагам. Однако наиболее информативный способ отладки приложений, критичных ко времени выполнения (таких, как приложения Bluetooth) - вывод информационных сообщений через последовательный порт в реальном времени, без остановки программы. Хороший вариант организации такого вывода - текстовый лог с использованием Real Time Terminal (RTT) от SEGGER. В этом руководстве показано, как эту функцию отладки добавить в любой существующий проект.

Что потребуется:

• J-Link software pack v4.98 (или более свежей версии) [3].
• Файлы RTT [4].
• Keil uVision 5.14 (или более свежей версии).

Как добавить файлы RTT в любой проект nRF5x (на примере проекта ble_app_uart):

• Загрузите zip-архив [4] и распакуйте его.
• Скопируйте папки RTT и Syscalls, распакованные из zip-архива, в каталог C:\Keil_v5\ARM\Pack\NordicSemiconductor.
• Зайдите в меню Keil Project -> Options for target.
• На закладке C/C++ добавьте C:\Keil_v5\ARM\Pack\NordicSemiconductor\RTT в пути поиска заголовков (include path).
• Подключите SEGGER_RTT.h к проекту, добавив строчку #include "SEGGER_RTT.h" в начало модуля main.c проекта.

Мы добавили файл заголовка для функций RTT, но еще нужно добавить в проект модули *.c.

• В Keil сделайте правый клик на папку проекта, и кликните Add Group, введите RTT для имени группы.
• Сделайте правый клик на созданной новой группе, и выберите Add existing files to group 'RTT'.
• В диалоге выбора файлов перейдите в C:\Keil_v5\ARM\Pack\NordicSemiconductor\RTT, и добавьте SEGGER_RTT.c.

Теперь у Вас должна работать отправка простых строк через интерфейс RTT. Добавьте в функцию main сразу перед входом в бесконечный цикл следующую строку:

SEGGER_RTT_WriteString(0, "Hello World!\n");

Первый аргумент задает, в какой канал записывать текст. Канал по умолчанию 0, более подробно про каналы можно прочитать на сайте SEGGER.

[Использование Real Time Terminal]

Наш код посылает строку сообщения в RTT, но у нас также должна быть возможность её прочитать. Есть несколько способов, описанных на страничке SEGGER [5]. Самый простой из них - использовать J-Link RTT Viewer, который входит в пакет программ для J-Link (J-link software package).

• Откройте J-Link RTT Viewer, появится окно, как на скриншоте ниже. Если подключено больше одного адаптера J-Link, то поставьте галочку "Serial no.", и введите серийный номер устройства, к которому нужно подключиться.

nRF5x Debug RTT Viewer

• Кликните Ok. Появится окно J-Link RTT Viewer:

nRF5x Debug RTT Terminal0

Загрузите скомпилированный код, и после его запуска Вы увидите текст "Hello World!". Обратите внимание, что если открыть окно терминала с последовательным соединением UART (Termite, putty, HyperTerminal и т. п.), то Вы также увидите в нем текст "Start..", который все еще печатается через последовательный порт, как и раньше. Теперь Вы можете использовать проект ble_app_uart, как это предполагалось изначально, однако также можете посылать текст через RTT, и использовать эту возможность для отладки. Имейте в виду, что SEGGER_RTT_WriteString() работает намного быстрее, чем printf, так что можно безопасно вызывать эту функцию без влияния на параметры поведения в реальном времени своего приложения.

[Передача текста в приложение]

Вы также можете посылать текст через RTT своему MCU, как альтернативу использованию UART. Измените главный цикл функции main в проекте ble_app_uart так, как показано ниже. Для использования функции задержки необходимо также подключить заголовок nrf_delay.h.

char c = 0;
for (;;)
{
   c = SEGGER_RTT_WaitKey(); // блокировка, пока не поступят данные
   if(c == 'r'){
      SEGGER_RTT_WriteString(0, "Resetting..\n");
      nrf_delay_ms(1000);
      sd_nvic_SystemReset();
   }
   //power_manage();
}

• Скомпилируйте проект, и прошейте код в свое устройство.
• В RTT Viewer перейдите в меню Input -> Sending и кликните Send on Enter (иначе при каждой нажатой Вами клавише будет происходить отправка соответствующего символа).
• Введите букву r в текстовое поле и нажмите Enter. Устройство перезагрузится.

nRF5x Debug RTT Terminal0 output example

nRF5x Debug RTT UART output

[Более продвинутый вывод]

Мы только что рассмотрели вывод сырого текста с помощью функции SEGGER_RTT_WriteString(). Также есть функция с мощной поддержкой форматированного вывода, SEGGER_RTT_printf(). Чтобы её использовать, нужно дополнительно подправить проект.

• Добавьте в проект модуль SEGGER_RTT_printf.c точно так же, как мы добавляли ранее SEGGER_RTT.c.

• Убедитесь, что в проекте определен макрос NRF_LOG_USES_RTT:

#define NRF_LOG_USES_RTT 1

• Точно так же добавьте RTT_Syscalls_KEIL.c из папки C:\Keil_v5\ARM\Pack\NordicSemiconductor\Syscalls.

• Сделайте правый клик на nRF_Libraries в проекте, и выберите Options.

nRF5x Debug RTT options nRF Libraries

• В списке Software Components кликните на retarget (перенаправление), и затем на кнопку Remove.

• Перейдите в меню Project -> Options for target, и снимите галочку "Use MicroLIB".

nRF5x Debug RTT options nRF Libraries retarget remove

• Отредактируйте бесконечный цикл main следующим образом:

char c = 0;
for (;;) {
   c = SEGGER_RTT_WaitKey(); // блокировка, пока не поступят данные
   if(c == 'r'){
      SEGGER_RTT_printf(0,
                        "%sResetting in %d second..%s\n",
                        RTT_CTRL_BG_BRIGHT_RED,
                        1,
                        RTT_CTRL_RESET);
      nrf_delay_ms(1000);
      sd_nvic_SystemReset();
   }
   //power_manage();
}

Скомпилируйте и запустите проект. Теперь вывод текста может быть разными цветами, что удобно для визуального выделения отладочных сообщений. В RTT Viewer перейдите в Terminal 0, чтобы увидеть раскрашенный текст.

nRF5x Debug RTT Terminal0 output example colored

Теперь функция printf перенаправляет свой вывод в RTT, поэтому для отладки стал доступен стандартный форматированный вывод. Проект ble_app_uart все еще будет работать так же, как и раньше, потому что в нем вывод app_uart_put осуществляется через последовательный порт.

Пример использования форматированного вывода printf:

SEGGER_RTT_printf(0, "Значение переменной: %d\n", variable);

Здесь первый параметр указывает, что вывод должен осуществляться в Terminal 0 утилиты J-Link RTT Viewer, второй параметр это строка для печати с форматом, и третий параметр это переменная, преобразованное в текст значение которой будет подставлено вместо "%d".

Для дополнительной информации по RTT и различным функциям см. главу 10 руководства J-Link [6].

[2 простых шага для включения вывода через RTT в IDE Keil]

Поскольку модули SEGGER_RTT.c и SEGGER_RTT_printf.c уже добавлены почти во всех примерах из SDK для Keil (они находятся в папке nRF_Segger_RTT дерева модулей проекта), то добавить вывод отладочных сообщений RTT становится очень просто. Процесс по шагам:

1. Добавьте директивой #include заголовочный файл SEGGER_RTT.h в код, где нужно использовать функции вывода через RTT.

#include "SEGGER_RTT.h"

2. Перейдите в свойства проекта (меню Project -> Options for ...), и на закладке C/C++ в поле ввода Preprocessor Symbols Define добавьте макроопределение NRF_LOG_USES_RTT.

После выполнения этих двух шагов можно в коде выполнять вывод произвольных сообщений с помощью функции SEGGER_RTT_printf. Чтобы не вводить каждый раз такое длинное имя функции, я для себя сделал удобный макрос umsg:

#include "SEGGER_RTT.h"
 
#define umsg(...) \
        { \
            SEGGER_RTT_printf(0, __VA_ARGS__); \
        }

[Перенаправление вывода NRF_LOG_INFO в RTT]

Поменяйте в файле sdk_config.h макроопределение NRF_LOG_BACKEND_SERIAL_USES_RTT, установив его в 1 (по умолчанию оно определено как 0):

// < e > NRF_LOG_BACKEND_SERIAL_USES_RTT - Если разрешено (1),
//       то вывод функций NRF_LOG_ будет печататься через RTT
//============================================================
#ifndef NRF_LOG_BACKEND_SERIAL_USES_RTT
//#define NRF_LOG_BACKEND_SERIAL_USES_RTT 0
#define NRF_LOG_BACKEND_SERIAL_USES_RTT 1
#endif

Как вариант это макроопределение можно установить в отдельном подключаемом файле настроек app_config.h. После этого функции NRF_LOG_INFO, NRF_LOG_DEBUG и NRF_LOG_WARNING будут выводить свои сообщения через RTT.

[Размер буфера вывода]

Среди настроечных параметров есть очень важный параметр NRF_LOG_BACKEND_RTT_OUTPUT_BUFFER_SIZE, который стоит учитывать при выводе большого количества символов через RTT. По умолчанию в файле sdk_config.h приложения он определен как 512:

#if  NRF_LOG_BACKEND_SERIAL_USES_RTT
// < o > NRF_LOG_BACKEND_RTT_OUTPUT_BUFFER_SIZE - размер выходного буфера RTT.
// < i> Он должен быть равным или меньшим, чем значение \ref NRF_LOG_BACKEND_MAX_STRING_LENGTH.
// < i> Это значение используется в конфигурации Segger RTT configuration для установки
// < i> размера буфера, если он больше, чем значение размера буфера RTT по умолчанию.
#ifndef NRF_LOG_BACKEND_RTT_OUTPUT_BUFFER_SIZE
#define NRF_LOG_BACKEND_RTT_OUTPUT_BUFFER_SIZE 512
#endif
 
#endif //NRF_LOG_BACKEND_SERIAL_USES_RTT

Если Вы выведете через RTT большее количество символов, чем размер буфера RTT, то это приведет к ошибочному, непредсказуемому выводу через RTT.

На примере приложений из SDK 12.3.0 (nRF5_SDK_12.3.0_d7731ad), пошаговая инструкция:

1. sdl_config.h -> nRF_Log -> поставить галочку NRF_LOG_ENABLED.

2. sdl_config.h -> nRF_Log -> выбрать уровень вывода NRF_LOG_DEFAULT_LEVEL (по умолчанию Info, Debug самый подробный).

3. sdl_config.h -> nrf_log_backend -> снять галочку NRF_LOG_BACKEND_SERIAL_USES_UART и поставить галочку NRF_LOG_BACKEND_SERIAL_USES_RTT.

4. Создать в папке pca10040\s132\config файл settings.h с таким содержимым:

#define NRF_LOG_USES_RTT 1

5. Добавить в начало external\segger_rtt\SEGGER_RTT_printf.c строчку:

#include "settings.h"

Альтернативно можно пункты 4 и 5 исключить, если добавить в поле Define символов препроцессора NRF_LOG_USES_RTT=1 (свойства проекта -> закладка C/C++ -> Preprocessor Symbols -> в поле ввода Define добавить NRF_LOG_USES_RTT=1).

Для собственного форматированного вывода:

6. sdl_config.h -> nRF_Log -> выбрать уровень вывода Off.

7. Переписать в каталог external\segger_rtt\ макрос umsg.h.

8. Добавить в любой файл #include "umsg.h" и использовать вызовы umsg.

Не забыть записать SoftDevice, если проект его использует!

Содержимое заголовка external\segger_rtt\umsg.h:

#include "settings.h"
#include "SEGGER_RTT.h"
#ifdef NRF_LOG_USES_RTT
#define umsg(...) \
        { \
            SEGGER_RTT_printf(0, __VA_ARGS__); \
        }
 
#else
#define umsg(...) 
#endif

[Ток потребления]

Для приложений, у которых потребление энергии - критичный параметр (например носимые устройства BLE), в стоит иметь в виду, что включение RTT при не интенсивном выводе не добавляет дополнительный ток потребления. Просто по понятным причинам растет объем используемой памяти.

Однако если Вы подключитесь консолью J-Link, чтобы считывать отладочные сообщения, то ток потребления возрастет примерно на 3.6 мА.

[Ссылки]

1. Debugging with Real Time Terminal site:nordicsemi.com.
2. Конфигурационный заголовок nRF5x SDK.
3. J-Link / J-Trace Downloads site:segger.com.
4. RTT_Implementation_141217.zip - файлы RTT.
5. J-Link RTT – Real Time Transfer.
6. UM08001 J-Link / J-Trace User Guide site:segger.com.