0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

rp2040でRTTを使用する

Posted at

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が直接読み書き可能です。

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

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

3. 組み込み手順

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

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

EGGER_RTT.c
#include <../../pico_stdio_rtt/SEGGER/RTT/SEGGER_RTT.c>
SEGGER_RTT.h
#include <../../pico_stdio_rtt/SEGGER/RTT/SEGGER_RTT.h>
SEGGER_RTT_Conf.h
#include <../../pico_stdio_rtt/SEGGER/Config/SEGGER_RTT_Conf.h>

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

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

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;

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 テスト用プログラム

テスト用のメインプログラムを作成します。

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の設定は以下の通りです。

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

4.5 ターミナルの接続

rtt serverに接続します。以下はteratermでの指定例です。
telnet01.png

4.6 実行再開

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

5. 実行結果

以下は、実行再開後の表示結果です。
telnet02.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?