1. はじめに
RTT(Real-Time Transfer)は、Segger社のJ-Linkデバッガが提供するリアルタイムでのデータ転送機能です。組み込み開発において、ターゲットマイコン(MCU)とホストPCの間で、デバッグ中に高速かつノンブロッキングでログやデータをやりとりできます。
RTTの特徴は以下のとおりです:
- JTAG/SWD経由でデータ転送するため、追加の通信ハードウェア不要
- CPUの実行を止めずにログを出力可能(従来のprintfのように待ち時間が発生しない)
- リアルタイム性が求められる組み込みシステムに特に適している
J-Linkだけでなく、OpenOCD経由でもRTTが使用可能で、さらにRaspberry Pi Pico SDKにもRTT用ライブラリが含まれていることから、実際に使い方を調べてみました。
本記事では、以下の開発環境での利用方法について解説します:
- Visual Studio Code + PlatformIO
- Platform:earlephilhower版 Arduino-Picoを使用
- デバッグプローブ:Picoprobe
2. RTTの動作仕組み
RTTは以下のような仕組みでデータ転送を実現しています:
-
メモリ上のリングバッファ
MCU側のRAMにリングバッファ(RTTバッファ)を配置し、そこにログやデータを記録します。
このバッファはホストPCとターゲットMCUが直接読み書き可能です。 -
J-Linkによるポーリング
J-Linkが定期的にバッファをポーリング(監視)してデータを取得します。
UARTなどと違ってCPUが割り込み処理を行う必要がなく、MCUに負担がかかりません。 -
複数チャネルの活用
RTTバッファはチャネルごとに独立しており、用途に応じたデータ転送の分離が可能です。
3. 組み込み手順
earlephilhower版 Arduino-PicoのPlatformIO環境では、RTT関連ファイルがビルド対象になっていません。
そのため、必要なファイルをプロジェクトから直接参照する形で利用します。
(将来的なライブラリアップデートに備え、コピーせずインクルード推奨)
以下のようにインクルードします:
#include <../../pico_stdio_rtt/SEGGER/RTT/SEGGER_RTT.c>
#include <../../pico_stdio_rtt/SEGGER/RTT/SEGGER_RTT.h>
#include <../../pico_stdio_rtt/SEGGER/Config/SEGGER_RTT_Conf.h>
これだけでRTTが使えるようになりますが、printfなどが使えるようにするためのラッパークラス(Stream対応)を用意するのが便利です。
以下のファイルをプロジェクトに追加します:
#pragma once
#include <Stream.h>
#include <array>
#include <vector>
#include <optional>
#include "SEGGER_RTT.h"
class SerialRTT : public Stream
{
public:
enum class RTT_MODE {
NO_BLOCK_SKIP = SEGGER_RTT_MODE_NO_BLOCK_SKIP, // Skip. Do not block, output nothing. (Default)
NO_BLOCK_TRIM = SEGGER_RTT_MODE_NO_BLOCK_TRIM, // Trim: Do not block, output as much as fits.
BLOCK_IF_FIFO_FULL = SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL, // Block: Wait until there is space in the buffer.
DEFAULT = SEGGER_RTT_MODE_DEFAULT,
};
SerialRTT(uint32_t bufIndex = 0, RTT_MODE upMode = RTT_MODE::DEFAULT, RTT_MODE downMode = RTT_MODE::DEFAULT);
~SerialRTT();
virtual bool begin();
virtual bool begin(const char* upName, uint32_t upBufferSize, const char* downName, uint32_t downBufferSize);
virtual void end();
virtual int available(void) override;
virtual int peek(void) override;
virtual int read(void) override;
virtual void flush(void) override;
virtual size_t write(uint8_t) override;
using Print::write;
virtual operator bool() const;
inline void setTranslateCrlf(bool translate) { translateCrlf = translate; }
int32_t setFlagsUpBuffer(RTT_MODE mode) { rttUpMode = mode; return SEGGER_RTT_SetFlagsUpBuffer(bufferIndex, static_cast<uint32_t>(mode)); }
int32_t setFlagsDownBuffer(RTT_MODE mode) { rttDownMode = mode; return SEGGER_RTT_SetFlagsDownBuffer(bufferIndex, static_cast<uint32_t>(mode)); }
protected:
uint32_t bufferIndex;
RTT_MODE rttUpMode;
RTT_MODE rttDownMode;
uint32_t writeBufferSIze;
bool translateCrlf;
std::optional<uint8_t> peekedByte;
static bool initialized;
std::vector<uint8_t> upBuffer;
std::vector<uint8_t> downBuffer;
static std::array<bool, SEGGER_RTT_MAX_NUM_UP_BUFFERS> upUsed;
static std::array<bool, SEGGER_RTT_MAX_NUM_DOWN_BUFFERS> downUsed;
};
extern SerialRTT SerialRTT0;
#include <Arduino.h>
#include "SerialRTT.hpp"
#include <FreeRTOS.h>
#include <algorithm>
#ifndef portCHECK_IF_IN_ISR
#define portCHECK_IF_IN_ISR() (xPortIsInsideInterrupt() == pdTRUE)
#endif
bool SerialRTT::initialized{false};
std::array<bool, SEGGER_RTT_MAX_NUM_UP_BUFFERS> SerialRTT::upUsed;
std::array<bool, SEGGER_RTT_MAX_NUM_DOWN_BUFFERS> SerialRTT::downUsed;
SerialRTT::SerialRTT(uint32_t bufIndex, SerialRTT::RTT_MODE upMode, SerialRTT::RTT_MODE downMode)
: bufferIndex{bufIndex}, rttUpMode{upMode}, rttDownMode{downMode},
writeBufferSIze{0}, translateCrlf{false},
upBuffer{0}, downBuffer{0}
{
if (!initialized) {
initialized = true;
SEGGER_RTT_Init();
upUsed.fill(false);
downUsed.fill(false);
}
}
SerialRTT::~SerialRTT()
{
end();
}
bool
SerialRTT::begin()
{
if (bufferIndex != 0) {
return false;
}
if (upUsed[bufferIndex] || downUsed[bufferIndex]) {
return false;
}
upUsed[bufferIndex] = true;
downUsed[bufferIndex] = true;
peekedByte.reset();
writeBufferSIze = SEGGER_RTT_GetAvailWriteSpace(bufferIndex);
return true;
}
bool
SerialRTT::begin(const char* upName, uint32_t upBufferSize, const char* downName, uint32_t downBufferSize)
{
if ((bufferIndex == 0) || (bufferIndex >= SEGGER_RTT_MAX_NUM_DOWN_BUFFERS)) {
return false;
}
if (upBufferSize > 0) {
upBuffer.resize(static_cast<std::size_t>(upBufferSize));
int ret = SEGGER_RTT_ConfigUpBuffer(bufferIndex, upName, upBuffer.data(), upBuffer.size(), static_cast<unsigned>(rttUpMode));
if (ret < 0) {
upBuffer.resize(0);
return false;
}
upUsed[bufferIndex] = true;
}
if (downBufferSize > 0) {
downBuffer.resize(static_cast<std::size_t>(downBufferSize));
int ret = SEGGER_RTT_ConfigDownBuffer(bufferIndex, downName, downBuffer.data(), downBuffer.size(), static_cast<unsigned>(rttDownMode));
if (ret < 0) {
upUsed[bufferIndex] = false;
upBuffer.resize(0);
downBuffer.resize(0);
return false;
}
downUsed[bufferIndex] = true;
peekedByte.reset();
}
return true;
}
void
SerialRTT::end()
{
if (bufferIndex > 0) {
upBuffer.resize(0);
downBuffer.resize(0);
}
upUsed[bufferIndex] = false;
downUsed[bufferIndex] = false;
}
int
SerialRTT::available(void)
{
return SEGGER_RTT_GetBytesInBuffer(bufferIndex);
}
int
SerialRTT::peek(void)
{
if (peekedByte.has_value()) {
int data = static_cast<int>(peekedByte.value());
return data;
}
int data = read();
if (data < 0) {
return -1;
}
peekedByte = static_cast<uint8_t>(data);
return data;
}
int
SerialRTT::read(void)
{
if (peekedByte.has_value()) {
int data = static_cast<int>(peekedByte.value());
peekedByte.reset();
return data;
}
if (SEGGER_RTT_GetBytesInBuffer(bufferIndex) == 0) {
return -1;
}
uint8_t data;
SEGGER_RTT_Read(bufferIndex, &data, sizeof(data));
return static_cast<int>(data);
}
void
SerialRTT::flush(void)
{
while (writeBufferSIze > SEGGER_RTT_GetBytesInBuffer(bufferIndex)) {
if (!portCHECK_IF_IN_ISR()) {
delay(1);
}
}
}
size_t
SerialRTT::write(uint8_t c)
{
if (c == '\n' && translateCrlf) {
SEGGER_RTT_PutChar(bufferIndex, '\r');
}
return SEGGER_RTT_PutChar(bufferIndex, static_cast<char>(c));
}
SerialRTT::operator bool() const
{
return (writeBufferSIze == SEGGER_RTT_GetBytesInBuffer(bufferIndex));
}
SerialRTT SerialRTT0{0};
4. 使い方
4.1 テスト用プログラム
テスト用のメインプログラムを作成します。
#include <Arduino.h>
#include <stdlib.h>
#include "SerialRTT.hpp"
SerialRTT serialRTT;
void
setup()
{
serialRTT.begin();
serialRTT.setTranslateCrlf(true);
serialRTT.printf("build [%s %s]\n", __DATE__, __TIME__);
}
void
loop()
{
serialRTT.printf("SEGGER Real-Time-Terminal Sample\n");
delay(1000);
}
4.2 platformio.iniの設定
platformio.iniの設定は以下の通りです。
[platformio]
default_envs = pico
[env:pico]
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
platform_packages = framework-arduinopico@https://github.com/earlephilhower/arduino-pico.git
board = pico
board_build.core = earlephilhower
framework = arduino
upload_protocol = cmsis-dap
debug_tool = cmsis-dap
debug_init_break = tbreak setup
4.3 デバッグ開始
PlatformIOからデバッグを開始します。上記の設定では、setup関数の先頭で停止するように指定しています。
4.4 RTT設定
停止後、RTTを有効にするため以下のコマンドを実行します。
4.4.1 RTTバッファの設定
ターゲット上のバッファアドレスとサイズを指定します。
eval "monitor rp2040.core0 rtt setup %p 1024 SEGGER\\ RTT", &_SEGGER_RTT
4.4.2 RTT通信の開始
先ほど設定したRTTバッファを使って、RTTセッションを開始します。
monitor rp2040.core0 rtt start
4.4.3 利用可能なRTTチャネルの確認
チャネルの状態や数(名前、サイズ、方向など)を表示します。
monitor rp2040.core0 rtt channels
4.4.4 RTTサーバ起動
ホストPC上に、RTT経由のデータを転送するTCPサーバを作成します。以下の例では、チャネル 0 に対してポート番号 5555 を割り当てています。
monitor rtt server start 5555 0
4.5 ターミナルの接続
rtt serverに接続します。以下はteratermでの指定例です。
4.6 実行再開
実行を再開すると、teratermにログが出力されます。