rp2040でRTTを使用する

## 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は以下のような仕組みでデータ転送を実現しています：

1. メモリ上のリングバッファ
MCU側のRAMにリングバッファ（RTTバッファ）を配置し、そこにログやデータを記録します。
このバッファはホストPCとターゲットMCUが直接読み書き可能です。

1. J-Linkによるポーリング
J-Linkが定期的にバッファをポーリング（監視）してデータを取得します。
UARTなどと違ってCPUが割り込み処理を行う必要がなく、MCUに負担がかかりません。

1. 複数チャネルの活用
RTTバッファはチャネルごとに独立しており、用途に応じたデータ転送の分離が可能です。

## 3. 組み込み手順
earlephilhower版 Arduino-PicoのPlatformIO環境では、RTT関連ファイルがビルド対象になっていません。
そのため、必要なファイルをプロジェクトから直接参照する形で利用します。
（将来的なライブラリアップデートに備え、コピーせずインクルード推奨）

以下のようにインクルードします：

```cpp:EGGER_RTT.c
#include <../../pico_stdio_rtt/SEGGER/RTT/SEGGER_RTT.c>
```

```cpp:SEGGER_RTT.h
#include <../../pico_stdio_rtt/SEGGER/RTT/SEGGER_RTT.h>
```
```cpp:SEGGER_RTT_Conf.h
#include <../../pico_stdio_rtt/SEGGER/Config/SEGGER_RTT_Conf.h>
```

これだけでRTTが使えるようになりますが、printfなどが使えるようにするためのラッパークラス（Stream対応）を用意するのが便利です。

以下のファイルをプロジェクトに追加します：

```cpp:SerialRTT.hpp
#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;

```
```cpp:SerialRTT.cpp
#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 テスト用プログラム
テスト用のメインプログラムを作成します。
```cpp:main.cpp
#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の設定は以下の通りです。

```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**

以下は実際に入力した例です。
![setup.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193997/20c9dbf3-4111-4c6b-9713-1ca2d611687a.png)

### 4.5 ターミナルの接続
rtt serverに接続します。以下はteratermでの指定例です。
![telnet01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193997/68d32615-1394-477e-85e0-059b80372010.png)



### 4.6 実行再開

実行を再開すると、teratermにログが出力されます。

## 5. 実行結果
以下は、実行再開後の表示結果です。
![telnet02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193997/5c36dfc7-410f-4a94-bd40-b95e226e78a8.png)