GR-CITRUSのMTU/TPU機能ピン

GR-CITRUSはピン数の少ないボードに機能を詰め込むため、1つのピンがRXマイコンの複数のピンに接続されていることがある。またDAC出力の9番ピンおよびADC入力のA0~A3ピンは、ジャンパJ1~J5をショートすることでRXマイコンの他のピンにも接続される。(アナログ信号のピンのため、デフォルトでは不要な負荷が接続されないようにジャンパがオープンになっているものと思われる。)

そのため、RXマイコンペリフェラルArduinoライブラリを介さずに直接制御して利用したい場合、GR-CITRUSのどのピンがRXマイコンのどのピンとどのピンに接続されていて、どの機能が割り当て可能かをよくよく調べる必要がある。RXマイコンペリフェラルの中でもとりわけ用途が多岐にわたるMTUとTPUについて、ピン機能の対応表を作成した。

詳細は、「RX63Nグループ、RX631グループ ユーザーズマニュアル ハードウェア編」の「22. マルチファンクションピンコントローラ(MPC)」を参照。

2020/11/13 追記

GR-ROSEについても同様のピン機能対応表を作成した。

GR-SAKURA/GR-CITRUSのタイマリソース

GR-SAKURAおよびGR-CITRUSArduinoライブラリで使用されるタイマリソースについてまとめる。Arduino環境でRXマイコンのタイマリソースを直接使用する場合、Arduinoライブラリとの競合に注意する。

GR-ROSEについてはこちら ↓
GR-ROSEのタイマリソース - 滴了庵日録

(1) PWM出力

ServoクラスおよびanalogWrite関数で使用されるタイマリソースは以下の通り。詳細はisHardwarePWMPin関数, setPinModeHardwarePWM関数, setPinModeSoftwarePWM関数のソースを参照。

GR-SAKURA
ピン番号 使用するタイマリソース 備考
0 TPU3 GR-CITRUSと異なる
1 MTU1 GR-CITRUSと異なる
2 TPU3
3 TPU3
4 MTU4
5 TPU4
6 TPU0
7 TPU0
11 MTU3
上記以外 TPU2 ソフトウェアPWM, 割り込み使用
GR-CITRUS
ピン番号 使用するタイマリソース 備考
0 MTU1 GR-SAKURAと異なる
1 TPU3 GR-SAKURAと異なる
5 TPU4
7 TPU0
8 TPU0
11 MTU3
上記以外 TPU2 ソフトウェアPWM, 割り込み使用

また、tone関数はピン番号にかかわらずソフトウェアPWMであり、TPU2を使用する。

(2) PWM以外

ライブラリ GR-SAKURA GR-CITRUS
millis関数
micros関数
delay関数
delayMicroseconds関数
CMT0 CMT0
Ethernetライブラリ CMT1 非対応
SoftwareSerialクラス CMT2 ※1 CMT2 ※1
MsTimer2クラス TPU1 TPU1
WavMp3pクラス MTU0 MTU1
attachIntervalTimerHandler関数 TPU5 TPU5
attachCyclicHandler関数 CMT0 ※2 CMT0 ※2

※1: TPU5を使用するようにも変更できる。 (ライブラリのビルドが必要)
※2: millis関数に依存

タイマリソース別まとめ

MTU0 WavMp3pクラス(SAKURA)
MTU1 ハードウェアPWM, WavMp3pクラス(CITRUS)
MTU2 未使用
MTU3 ハードウェアPWM
MTU4 ハードウェアPWM(SAKURA)
MTU5 未使用
TPU0 ハードウェアPWM
TPU1 MsTimer2クラス
TPU2 ソフトウェアPWM
TPU3 ハードウェアPWM
TPU4 ハードウェアPWM
TPU5 attachIntervalTimerHandler関数
TPU6~11 未使用
CMT0 システムタイマ
CMT1 Ethernetライブラリ(SAKURA)
CMT2〜3 未使用
TMR0〜1 未使用

ハードウェアPWM: ServoクラスおよびanalogWrite関数
ソフトウェアPWM: ServoクラスおよびanalogWrite関数, tone関数
システムタイマ: millis関数, micros関数, delay関数, delayMicroseconds関数

MCUXpressoでUART printfの改善

前回の記事でUARTでのprintfの速度は実装しだいと書いた。

UARTの送信をブロッキング処理でおこなうと相応の時間を要するが、バッファにためて割り込みやDMAで送信すればprintf関数の処理時間を短くすることができる。ただし、当然ながら通信のスループットはUARTのボーレートで決まる。

ブロッキング処理でのUART printf

たとえばMCUXpressoでLPCOpenプロジェクトの場合、下記のようなソースでprintf関数の出力をUARTに送信できる。実験にはLPC1857マイコンを使用した。ボーレートは115200。

#include <stdio.h>
#include "board.h"

// called every 1msec
void SysTick_Handler(void)
{
    static int cnt = 0;
    static int x = 0;
    if (x++ > 1000) {
        x = 0;
        cnt++;
        Board_LED_Set(0, false);

        printf("Count up! %d\n", cnt);

        Board_LED_Set(0, true);
    }
}

int main(void)
{
    SystemCoreClockUpdate();
    Board_Init();
    Board_Debug_Init(); // initialize Debug UART

    // call SysTick_Handler every 1msec
    SysTick_Config(SystemCoreClock / 1000);

    printf("Hello, world!\n");

    while(1) {
        ;
    }
    return 0 ;
}

このときprintf関数で10文字送信するのに要する時間は約710usecであった。ボーレート115200で1文字送信するのに要する時間は86.8usecであるから、これは(10-2)文字送信する時間 + printf関数の書式処理時間とみられる。-2文字はUARTの送信データレジスタと送信シフトレジスタのぶんである。

このように、セミホスティングよりマシではあるが、IMTやRTTに比べるとprintf関数の所要時間が長い。IMTやRTTが利用できるならそちらを検討したほうが良いだろう。

割り込み処理による改善

ただし、どうしてもUARTにログを出したい場合もあるだろう。その場合には送信データをリングバッファにためて割り込み処理で送信すればprintf関数の所要時間を短縮できる。たとえばMCUXpressoでLPCOpenプロジェクトの場合、下記のような修正でUART printfを割り込み処理にできる。

(1) デバッグUARTの割り込み番号と割り込みハンドラ名を定義

ボードライブラリのboard.hに下記のような定義を追加する。ただし、UART番号はターゲットの基板に合わせること。

#define DEBUG_UART LPC_USART3
// 以下を追記
#define DEBUG_UART_IRQn        USART3_IRQn
#define DEBUG_UART_IRQHandler  UART3_IRQHandler
(2) デバッグUART用のリングバッファと割り込みハンドラを定義

ボードライブラリのboard.cに下記の定義を追加する。バッファサイズは適宜調整すること。

// Ring buffer
#define UART_RB_SIZE 256
static uint8_t txbuff[UART_RB_SIZE], rxbuff[UART_RB_SIZE];
static RINGBUFF_T txring, rxring;

// Debug UART interrupt handler
void DEBUG_UART_IRQHandler(void)
{
    Chip_UART_IRQRBHandler(DEBUG_UART, &rxring, &txring);
}
(3) Board_Debug_Init関数を修正

board.cのBoard_Debug_Init関数を下記のように修正する。

/* Initialize debug output via UART for board */
void Board_Debug_Init(void)
{
#if defined(DEBUG_UART)
    Board_UART_Init(DEBUG_UART);

    Chip_UART_Init(DEBUG_UART);
    Chip_UART_SetBaud(DEBUG_UART, 115200);
    Chip_UART_ConfigData(DEBUG_UART, UART_LCR_WLEN8 | UART_LCR_SBS_1BIT | UART_LCR_PARITY_DIS);

    /* Enable UART Transmit */
    Chip_UART_TXEnable(DEBUG_UART);

    // 以下を追記
    // Initialize ring buffer
    RingBuffer_Init(&txring, txbuff, 1, UART_RB_SIZE);
    RingBuffer_Init(&rxring, rxbuff, 1, UART_RB_SIZE);
    // Enable UART transmit interrupt
    Chip_UART_IntEnable(DEBUG_UART, UART_IER_RBRINT);
    NVIC_SetPriority(USART3_IRQn, 1);
    NVIC_EnableIRQ(USART3_IRQn);
#endif
}
(4) Board_UARTPutChar関数を修正

board.cのBoard_UARTPutChar関数を下記のように修正する。

/* Sends a character on the UART */
void Board_UARTPutChar(char ch)
{
#if defined(DEBUG_UART)

//  /* Wait for space in FIFO */
//  while ((Chip_UART_ReadLineStatus(DEBUG_UART) & UART_LSR_THRE) == 0) {}
//  Chip_UART_SendByte(DEBUG_UART, (uint8_t) ch);

    // 以下に変更
    Chip_UART_SendRB(DEBUG_UART, &txring, &ch, 1);

#endif
}

以上の変更を施したところ、10文字送信するprintf関数の処理時間は710usecから80usecに短縮された。ただし最初に述べたように、UARTの送信時間じたいはボーレートで決まり、約870usec(86.8usec×10文字)かかることに留意されたい。

ARMで4種類のprintfデバッグ方式まとめ

これまで4回にわたってARMマイコンデバッグプローブにprintf出力する方法を説明した。

各方法の比較を表にまとめる。

UART セミホスティング ITM RTT
出力先 シリアルポート デバッガ デバッガ Telnet
速度
実装しだい
×
msec単位

usec単位

usec単位
接続ピン
UARTが必要

SWD 2線のみ

SWOも必要

SWD 2線のみ
デバッガ切断時 ×
スタックする
対応コア
コアは不問

全てのCortex

Cortex-M3以上

全てのCortex
対応デバッガ
不要

CMSIS-DAP

CMSIS-DAP

J-Linkのみ

このようにどの方式にも一長一短がある。ただし、セミホスティングに関しては速度が非常に遅く、またデバッガを接続していないと処理がスタックしてしまうという致命的な欠点があるので、ITMかRTTを利用できるならそちらを検討したほうが良いと思われる。

MCUXpressoでRTT printf (その2)

前回はMCUXpressoでRTTを使用する方法を説明した。今回はprintf等の標準入出力関数をRTTにリダイレクトする方法を説明する。前回の内容の準備を前提とする。

MCU Xpressoで標準入出力をRTTにリダイレクトする設定

(1) 標準入出力をUARTへリダイレクトしない

ボードライブラリのプロジェクトのboard.c内の #include "retarget.h" をコメントアウトする。

#include <string.h>
#include "board.h"
// #include "retarget.h"
(2) 標準入出力をRTTへリダイレクトする

アプリケーションのプロジェクトに下記のretarget_rtt.cを追加する。

なお、標準入力関数(scanf関数やgetchar関数など)にもいちおう対応したが、ブロッキング処理であることに注意。実際にこれらを使うことはあまりないだろう。

(3) nohostのライブラリを選択する

アプリケーションのプロジェクトの[Properties] > [C/C++ Build] > [Settings] > [Tools Settings]タブ > [MCU Linker] > [Managed Linker Script] で Redlib(nohost) を選択する。

サンプルコード

下記のようなサンプルコードでprintf関数の出力がRTTに表示される。

#include <stdio.h>
#include "board.h"
#include "SEGGER_RTT.h"

// called every 1msec
void SysTick_Handler(void)
{
    static int cnt = 0;
    static int x = 0;
    if (x++ > 1000) {
        x = 0;
        cnt++;
        Board_LED_Set(0, false);

        printf("Count up! %d\n", cnt);

        Board_LED_Set(0, true);
    }
}

int main(void)
{
    SystemCoreClockUpdate();
    Board_Init();

    // call SysTick_Handler every 1msec
    SysTick_Config(SystemCoreClock / 1000);

    printf("Hello, world!\n");

    while(1) {
        ;
    }
    return 0 ;
}

速度の比較

セミホスティング、ITM、RTTのprintfの速度を比較する。
条件は以下の通りとする。

  • ターゲット: LPC1857, 180MHz
  • デバッグプローブ: LPC-Link2 (RTTの場合はJ-Link化、それ以外はCMSIS-DAP)
  • printf関数で10文字を出力 ( printf("123456789\n"); )

結果は以下のようになった。

方式 時間
セミホスティング 40~70 msec
ITM 20 usec
RTT 12 usec

セミホスティングは論外な遅さ(しかも一定しない)であるが、ITMとRTTは思ったほど差がなかった。これは、printf関数は書式処理のオーバーヘッドが大きいためと思われる。検証のために下記のような各処理の時間を計測した。

処理 時間
printf("%d\n", 123456789); 30 usec
printf("123456789\n"); 12 usec
SEGGER_RTT_printf(0, "123456789\n"); 10 usec
SEGGER_RTT_Write(0, "123456789\n", 10); 2.6 usec

以上のように、RTTの処理よりもprintf関数の書式処理の時間のほうが支配的であった。

MCUXpressoでRTT printf (その1)

前々回と前回でセミホスティングによるprintfとITM printfについて説明した。

今回はITM printfよりもさらに高速なRTT printfについて説明する。

RTT printfとは?

RTT(Real Time Transfer)はJ-Linkが提供する高速転送技術であり、ITMよりもさらに高速なprintfの出力が可能である。ITMとは異なりSWOの接続が不要であり、またCortex-M0/M0+にも対応している。またセミホスティングのようにデバッガ未接続時にスタックすることもない。

いっぽうで、セミホスティングやITMはCMSIS-DAP等のデバッグプローブでも利用可能だが、RTTはJ-Linkでのみ利用できる。そのためJ-LinkのデバッグプローブとJ-Linkに対応した開発環境が必要である。

開発環境

LPCXpressoはJ-Linkに対応していないので、MCUXpressoを用いる。(そもそもLPCXpressoはすでに非推奨の環境である。)

MCUXpressoがインストールされたPCであれば、J-Linkのドライバ等はすでにインストール済みのはずである。J-Linkのインストールフォルダ (例えば、C:/Program Files (x86)/SEGGER/JLink )に下記のファイルがあることを確認する。

  • JLink/Samples/RTT/SEGGER_RTT_V686.zip :RTTのソース
  • JLink/Doc/Manuals/UM08001_JLink.pdf :J-Linkのユーザーガイド
  • JLink/JLinkRTTViewer.exe :J-Link RTT Viewerの実行ファイル

ユーザーガイドの「Chapter 16. RTT」でRTTについて解説されている。

MCUXpressoでのRTT printfの使い方

(1) RTTのソースをインポート

上記の SEGGER_RTT_V686.zip を適当なファルダに解凍し、SEGGER_RTT_V686/RTT/ フォルダ内のファイルをMCUXpressoのプロジェクトにインポートする。([File] > [Import] > [General] > [File System])

f:id:licheng:20201102142624p:plain:w600

(2) RTT版のprintf関数を使用する

今回は標準入出力関数のかわりにRTT版の入出力関数を使用する。標準入出力をRTTにリダイレクトする方法は次回説明する。

まず、"SEGGER_RTT.h"をインクルードする。

#include "SEGGER_RTT.h"

そしてprintf関数のかわりにSEGGER_RTT_printf関数を使用する。

    SEGGER_RTT_printf(0, "Hello, world!\n");
(3) J-Linkデバッグの開始

ターゲットとPCをJ-Linkで接続する。LPC-Link2をJ-Link化する方法については下記の記事を参照。

ツールバーの[Debug As]から[SEGGER J-Link probes]を選択し、ターゲットと接続してデバッグを開始する。

f:id:licheng:20201102145430p:plain

(4) J-Link RTT Viewerで出力を確認

RTT printfの出力はJLinkRTTViewer、JLinkRTTClient、JLinkRTTLoggerなどで表示できる。ここではJLinkRTTViewerを用いる。実行ファイルは前述のようにJ-Linkのインストールフォルダにある。Windowsであればスタートメニューからも実行できる。

起動時に接続方法(通常はUSB)とターゲットを選択する。

f:id:licheng:20201102152153p:plain

ターゲットと接続できると、printfの出力がJLinkRTTViewerに表示される。

f:id:licheng:20201102153938p:plain

または、ホスト名「localhost」、ポート番号「19021」にTelnet接続することで、Teraterm等のターミナルソフトやMCUXpressoのTerminalに表示することもできる。

f:id:licheng:20201102161152p:plain