ラベル TOPPERS/ASP(Arduino UNO R4版) の投稿を表示しています。 すべての投稿を表示
ラベル TOPPERS/ASP(Arduino UNO R4版) の投稿を表示しています。 すべての投稿を表示

2025年4月19日土曜日

TOPPERS/ASP - Arduino UNO R4版 その10

前回からの続きです。

このテーマを最初からご覧になる場合はこちらからどうぞ。


TOPPERS/ASPとFSPを組み合わせて使う~その2

前回ビルドまで通した「OBJ_template」に対し、実際にシリアル通信プログラムを肉付けしていきましょう。

TOPPERS/ASP - Arduino UNO R4版 その8」で作ったベアメタル版と同じ動作のプログラムをTOPPERS/ASP上に実装します。

接続例


結果「OBJ_template」の「main.c」は、以下のようなものとなります。

  • ...
  • #include <kernel.h>
  • #include <t_syslog.h>
  • #include <t_stdlib.h>
  • #include "kernel_cfg.h"
  • #include "hal_data.h"    // 追記!
  • #include "main.h"


  • /*
  •  * 割り込みをOSで受け取り FSP のハンドラへ処理を渡す
  •  * ( FSP のハンドラは vector_data.c に記述されている)
  •  */
  • // UART受信割り込みハンドラ
  • void _sci_uart_rxi_isr(void) {
  •     // FSP のUART受信割り込みハンドラ
  •     sci_uart_rxi_isr();
  • }

  • // UART送信割り込みハンドラ
  • void _sci_uart_txi_isr(void) {
  •     // FSP のUART送信割り込みハンドラ
  •     sci_uart_txi_isr();
  • }

  • // UART送信完了割り込みハンドラ
  • void _sci_uart_tei_isr(void) {
  •     // FSP のUART送信完了割り込みハンドラ
  •     sci_uart_tei_isr();
  • }

  • // UARTエラー割り込みハンドラ
  • void _sci_uart_eri_isr(void) {
  •     // FSP のUARTエラー割り込みハンドラ
  •     sci_uart_eri_isr();
  • }


  • /*
  •  * メインタスク
  •  */
  • void main_task(intptr_t exinf)
  • {
  •     uint8_t c;

  •     // 割り込み番号とイベント番号の紐付け
  •     bsp_irq_cfg();

  •     // 各割り込みを有効化
  •     ena_int(INTNO_UART2_RXI);
  •     ena_int(INTNO_UART2_TXI);
  •     ena_int(INTNO_UART2_TEI);
  •     ena_int(INTNO_UART2_ERI);

  •     // シリアル通信を開く
  •     R_SCI_UART_Open(&g_uart2_ctrl, &g_uart2_cfg);

  •     while (1) {
  •         // 受信する
  •         R_SCI_UART_Read(&g_uart2_ctrl, &c, 1);
  •         // タスクを待ち状態にする
  •         slp_tsk();
  •         // 受信した一文字を送信する
  •         R_SCI_UART_Write(&g_uart2_ctrl, &c, 1);
  •     }
  • }
  • ...


まずは「_sci_uart_xxx_isr()」という関数が4つ並んでいますね。

関数名の頭にアンダーバー(_)が付いている点がポイントです。

これらはコメントの通り、シリアル通信ポートの各種割り込みハンドラです。

実は、前々回のFSP(Flexible Software Package)を使用したベアメタルでも同様の働きをする割り込みハンドラを使っていました。

これらは「e2 studio」で「Hinagata」プロジェクトを作成したときにFSPのコンフィギュレーションツールが吐き出したUARTドライバの中で実装されていて、それが影で動いていましたので特に意識はしなくても良かったのです。

その後、吐き出されたソースは前回TOPPERS/ASPのソースツリーへコピーしましたよね?

それらのハンドラは、未だに以下のパスにその実体があります。


C:\cygwin64\home\<ユーザー名>\asp_arduino_uno_r4_gcc-template\target\arduino_uno_r4_gcc\ra\fsp\src\r_sci_uart.c


この「r_sci_uart.c」の中にUARTの各割り込みハンドラがあります。

その名も…


〇void sci_uart_rxi_isr(void) … UART受信割り込みハンドラ

〇void sci_uart_txi_isr(void) … UART送信割り込みハンドラ

〇void sci_uart_tei_isr(void) … UART送信完了割り込みハンドラ

〇void sci_uart_eri_isr(void) … UARTエラー割り込みハンドラ


…関数名の頭にアンダーバー(_)が付いてない点がポイントです。

つまり「main.c」の冒頭に追記した4つのアンダーバー付き割り込みハンドラは、本来の処理を行うアンダーバー無し割り込みハンドラへ処理を渡すだけの関数ということになります。

なんでこんな面倒くさいことをやっているのか?と言えば…。

TOPPERS/ASPを含むμITRON系のRTOSは、複数のタスクを並行して実行可能にする機能に加え、割り込みも管理します。

したがって、割り込みは一度RTOS側で受け取る必要があります。

(そうじゃないと管理できませんよね。)

RTOSのあずかり知らぬところで、頭越しに勝手にFSPの割り込みハンドラを呼び出されては色々とマズいのです。

そのために、本来の割り込みハンドラの関数名の頭にアンダーバーを付けた形で、RTOSが割り込みを受け取るためのハンドラを作っているのです。

各々の本来の割り込みハンドラへバイパスするだけなんですけどね。


次に「main.c」の「main_task()」関数の中に入ります。

「main.cfg」の記述より、RTOSが立ち上がると自動的にこの「main_task()」が実行されることになっています。

そして、いきなり「bsp_irq_cfg()」という関数を呼んでいますね?

これに関しては、ちょっとだけ長い説明が必要でしょう。

一般的なマイコンは、割り込み番号とそれが何の割り込みなのか?(すなわち「イベント」)の関係性は固定されています。

たとえば、以下はSilicon Labs社の「EFM32PG23」というマイコンのデータシートの1ページです。

これは、割り込み(IRQ)番号と、それが引き起されるイベント名の一覧表です。

この中で、このマイコンのシリアル通信の受信割り込み「USART0_RX」の割り込み番号は「9」です。

同様に送信割り込み「USART0_TX」の割り込み番号は「10」ですね?

「EFM32PG23」のデータシート


大半のマイコンは、このように割り込み番号とイベントの関係は固定です。

つまり、この型番のマイコンにおいてはシリアル通信の受信割り込み「USART0_RX」の割り込み番号は「9」って言ったら「9」であり、この世の終わりまでこれが変更されることはありません。

ところが、今回の「Arduino UNO R4」に搭載されているRA4M1マイコンはさにあらず。

上にあげた「EFM32PG23」の割り込み(IRQ)番号とイベント名の一覧表と同内容のRA4M1マイコンの表を以下に示します。

すると、割り込み(IRQ)番号に対し具体的なイベント名が書いておらず「ICU.IELSRxレジスタで選択されたイベント」などと記述されています。

RA4M1マイコンのデータシート - 1


結論から言えば、RA4M1マイコンは割り込み番号とイベントの関係は固定されておらず、自由に設定することができるのです。

例えば、UARTの各種割り込み(IRQ)番号を0~31まで、好きなように割り振ることができるのです。

それを行うためには「ICU.IELSRx」というレジスタに「イベント番号」というものを書き込みます。

では「イベント番号」ってどこで調べればいいの?ってことですが、ちゃんとデータシートに記載されています。

今回使用するシリアル通信ポートは「SCI2」ですので、これが引き起こす各種イベントは赤枠で囲った部分であり、それぞれ左端の「イベント番号」項目の数値に対応します。

イベントテーブル


今回は、以下のようにイベントに割り込み番号(IRQ#)を紐付けます。


〇SCI2_RXI(sci_uart_rxi_isr()) … IRQ#0

SCI2_TXI(sci_uart_txi_isr() … IRQ#1

SCI2_TEI(sci_uart_tei_isr()) … IRQ#2

〇SCI2_ERI(sci_uart_eri_isr()) … IRQ#3


紐づけは「ICU.IELSRx」レジスタに「イベント番号」を書き込むのでしたね?

ですので、本来は以下のようなコードを書かなくてはならないのですが…


R_ICU->IELSR[<割り込み番号>] = <イベント番号>

  • ...
  • R_ICU->IELSR[0] = (uint32_t)0x0A3    // IRQ#0 に SCI2_RXI(イベント番号 : 0x0A3)を設定
  • R_ICU->IELSR[1] = (uint32_t)0x0A4    // IRQ#1 に SCI2_TXI(イベント番号 : 0x0A4)を設定
  • R_ICU->IELSR[2] = (uint32_t)0x0A5    // IRQ#2 に SCI2_TEI(イベント番号 : 0x0A5)を設定
  • R_ICU->IELSR[3] = (uint32_t)0x0A6    // IRQ#3 に SCI2_ERI(イベント番号 : 0x0A6)を設定
  • ...


…この処理をやってくれるのが「bsp_irq_cfg()」という関数なのです。

そもそも、この紐づけはFSPコンフィギュレーションツールで設定したものなので、当然設定内容はFSPが知っています。

なので、同じくFSPコンフィギュレーションツールが吐き出した「bsp_irq_cfg()」をコールします。


「bsp_irq_cfg()」の下の行からは「ena_int(INTNO_UART2_XXX)」という記述が4つ並んでいます。

「ena_int()」は、指定された割り込み番号の割り込みを有効にするというμITRONのシステムコールです。

「INTNO_UART2_XXX」の値は「main.h」で定義されています。

その後はベアメタル版と同様にシリアル通信を開いた後、これまたベアメタル版と似たような処理を持つwhileループに入ります。


次に「OBJ_template」の「main.h」を以下の通り修正します。

(「ここから~ここまで」のコメントの範囲で2ヵ所です。)

  • ...
  • /*
  •  * ターゲット依存の定義
  •  */
  • #include "target_test.h"

  • /*
  •  * 各タスクの優先度の定義
  •  */

  • #define MAIN_PRIORITY    5        /* メインタスクの優先度 */
  •                                 /* HIGH_PRIORITYより高くすること */

  • #define HIGH_PRIORITY    9        /* 並行実行されるタスクの優先度 */
  • #define MID_PRIORITY    10
  • #define LOW_PRIORITY    11

  • /*
  •  * ターゲットに依存する可能性のある定数の定義
  •  */

  • #ifndef TASK_PORTID
  • #define    TASK_PORTID        1            /* 文字入力するシリアルポートID */
  • #endif /* TASK_PORTID */

  • #ifndef STACK_SIZE
  • #define    STACK_SIZE        4096        /* タスクのスタックサイズ */
  • #endif /* STACK_SIZE */

  • #ifndef LOOP_REF
  • #define LOOP_REF        ULONG_C(1000000)    /* 速度計測用のループ回数 */
  • #endif /* LOOP_REF */

  • // ここから ----------------------------------------------------------------
  • #define INHNO_UART2_RXI    (SCI2_RXI_IRQn + 16)
  • #define INTNO_UART2_RXI    (SCI2_RXI_IRQn + 16)
  • #define INHNO_UART2_TXI    (SCI2_TXI_IRQn + 16)
  • #define INTNO_UART2_TXI    (SCI2_TXI_IRQn + 16)
  • #define INHNO_UART2_TEI    (SCI2_TEI_IRQn + 16)
  • #define INTNO_UART2_TEI    (SCI2_TEI_IRQn + 16)
  • #define INHNO_UART2_ERI    (SCI2_ERI_IRQn + 16)
  • #define INTNO_UART2_ERI    (SCI2_ERI_IRQn + 16)
  • #define INTPRI_UART2    -4
  • // ここまで ----------------------------------------------------------------

  • /*
  •  * 関数のプロトタイプ宣言
  •  */
  • #ifndef TOPPERS_MACRO_ONLY

  • extern void    main_task(intptr_t exinf);
  • // ここから ----------------------------------------------------------------
  • extern void _sci_uart_rxi_isr(void);
  • extern void _sci_uart_txi_isr(void);
  • extern void _sci_uart_tei_isr(void);
  • extern void _sci_uart_eri_isr(void);
  • // ここまで ----------------------------------------------------------------

  • #endif /* TOPPERS_MACRO_ONLY */


「main.c」において、RTOSが割り込みを受け取るためのハンドラを4つ追加しました。

そこで、ソースコードの関数の中で「どれが」割り込みハンドラなのか?「何の」割り込みなのかをRTOSに申告する必要があります。

この申告を行うのがコンフィギュレーションファイルです。

「OBJ_template」アプリケーションのコンフィギュレーションファイルは「main.cfg」です。

「main.cfg」を以下の通り修正します。

(「ここから~ここまで」のコメントの範囲です。)

  • ...
  • INCLUDE("target_timer.cfg");
  • INCLUDE("syssvc/syslog.cfg");
  • INCLUDE("syssvc/banner.cfg");
  • INCLUDE("syssvc/serial.cfg");
  • INCLUDE("syssvc/logtask.cfg");

  • #include "main.h"
  • CRE_TSK(MAIN_TASK, { TA_ACT, 0, main_task, MAIN_PRIORITY, STACK_SIZE, NULL });

  • // ここから ----------------------------------------------------------------
  • DEF_INH(INHNO_UART2_RXI, { TA_NULL, _sci_uart_rxi_isr });
  • CFG_INT(INTNO_UART2_RXI, { TA_NULL, INTPRI_UART2 });
  • DEF_INH(INHNO_UART2_TXI, { TA_NULL, _sci_uart_txi_isr });
  • CFG_INT(INTNO_UART2_TXI, { TA_NULL, INTPRI_UART2 });
  • DEF_INH(INHNO_UART2_TEI, { TA_NULL, _sci_uart_tei_isr });
  • CFG_INT(INTNO_UART2_TEI, { TA_NULL, INTPRI_UART2 });
  • DEF_INH(INHNO_UART2_ERI, { TA_NULL, _sci_uart_eri_isr });
  • CFG_INT(INTNO_UART2_ERI, { TA_NULL, INTPRI_UART2 });
  • // ここまで ----------------------------------------------------------------


コンフィギュレーションファイルは冒頭で「#include "main.h"」とされていて「INHNO_UART2_XXX」、「INTNO_UART2_XXX」などのパラメータは、修正を行った「main.h」に定義されています。

各パラメータは、それぞれ以下の意味を持ちます。


〇INHNO_UART2_RXI:
〇INHNO_UART2_TXI:
〇INHNO_UART2_TEI:
〇INHNO_UART2_ERI:

今回使用するシリアル通信ポート「UART2」の各種割り込みハンドラ番号を示します。

なお、今回のターゲットである「Arduino UNO R4」はARM-Mコアです。

ARM-Mコアの場合は、この「割り込みハンドラ番号」と下記の「割り込み番号」は同一の値となります。

ところで「main.c」の項でも説明した通り、4つの割り込み番号は0~3と指定しました。

これらは「main.h」の「SCI2_XXX_IRQn」の値を見ていただければ一致していることがお分かりいただけると思います。

しかし、その後ろの「+16」って何でしょう?

ARM-Mコアを採用しているマイコンの場合、ベンダーや型番に関わらず実装しなければいけない16個の割り込みが予め規定されています。

もう一度RA4M1マイコンの割り込み一覧表を見てみましょう。

例外番号0~15までの16個がその割り込みです。

RA4M1マイコンのデータシート - 2


TOPPERS/ASPは「IRQ番号」ではなく、上の表で言うところの「例外番号」で割り込みを認識する仕様となっています。

なので「+16」なのです。

他のアーキテクチャのマイコンを使って同時開発している時など、これを忘れてハマることが結構ありますのでご注意を。

(つい最近やっちまいました…。)


〇INTNO_UART2_RXI:
〇INTNO_UART2_TXI:
〇INTNO_UART2_TEI:
〇INTNO_UART2_ERI:

今回使用するシリアル通信ポート「UART2」の各種割り込み番号を示します。

前述の通り「割り込み番号」と上記の「割り込みハンドラ番号」は同一の値となります。

「+16」の意味も前述と同様です。


〇TA_NULL

何も指定しないという意味のμITRONで定められた定数です。

今回はあまり重要ではないので、説明を省きます。

興味のある方はこちらのμITRON4.0の仕様書を読んでみてください。


〇INTPRI_UART2

使用するシリアル通信ポート「UART2」の割り込み優先度を示します。

今回の場合はFSPコンフィギュレーションツールで設定した時と同じ値にしておきましょう。

もし違う値が設定された場合は、このコンフィギュレーションファイルの値ではなく、FSPでの設定値が優先されます。

ただし、注意点としては値の変換が必要なことです。

例えば、今回UART2の割り込み優先度は「Hinagata」プロジェクト作成時に「12」としました…っていうか、デフォルトでそうなっています。

FSPでの割り込み優先度の設定


しかし、μITRONの割り込み優先度の数値は以下の計算式で求めたマイナスの値を指定する必要があります。


<μITRONの優先度の数値>=<FSPの優先度の数値>-<1<<割込み優先度のビット幅>


<FSPの優先度の数値>は今回の場合「12」です。

そして<割込み優先度のビット幅>については「Arduino UNO R4」に搭載されているRA4M1マイコンは4ビットですので、「1<<4」で「16」となります。

すなわち、知りたい<μITRONの優先度の数値>PRIは…


PRI = 12 - (1 << 4)

PRI = 12 - 16

PRI = -4


…と求まります。

したがって「main.h」の「INTPRI_UART2」を「-4」で定義しているのです。

因みに、値の変換の前後に関わらず「数の少ない方が優先度が高い」設定となります。


さて、最後の最後。

割り込みコールバックルーチンの修正です。

以下のパスにある「hal_entry.c」の「uart2_callback()」関数に注目です。


C:\cygwin64\home\<ユーザー名>\asp_arduino_uno_r4_gcc-template\target\arduino_uno_r4_gcc\src\hal_entry.c


これを以下のように修正します。

(「ここから~ここまで」のコメントの範囲で2ヵ所です。)

  • // ここから ----------------------------------------------------------------
  • #include <kernel.h>        // 追記!
  • #include "kernel_cfg.h"    // 追記!
  • // ここまで ----------------------------------------------------------------
  • #include "hal_data.h"

  • FSP_CPP_HEADER
  • void R_BSP_WarmStart(bsp_warm_start_event_t event);
  • FSP_CPP_FOOTER

  • volatile bool recieved = false; // 受信完了フラグ
  • void uart2_callback(uart_callback_args_t *p_args)
  • {
  •     if (p_args->event == UART_EVENT_RX_COMPLETE) {
  •         // 受信が完了したら「r_sci_usrt.c」ファイルの
  •         // 「sci_uart_rxi_isr()」割り込みハンドラからここに来る
  • // ここから ----------------------------------------------------------------
  •         //recieved = true;        // コメントアウト!
  •         // 待ち状態のタスクを起床させる
  •         iwup_tsk(MAIN_TASK);    // 追記!
  • // ここまで ----------------------------------------------------------------
  •     }
  •     if (p_args->event == UART_EVENT_TX_COMPLETE) {
  •         // 送信が完了したら「r_sci_usrt.c」ファイルの
  •         // 「sci_uart_tei_isr()」割り込みハンドラからここに来る
  •     }
  •     if (p_args->event == UART_EVENT_RX_CHAR) {
  •         // 一文字受信したら「r_sci_usrt.c」ファイルの
  •         // 「sci_uart_rxi_isr()」割り込みハンドラからここに来る
  •     }
  •     if (p_args->event == UART_EVENT_ERR_FRAMING) {
  •         // フレーミングエラーを検出した時に「r_sci_usrt.c」ファイルの
  •         // 「sci_uart_eri_isr()」割り込みハンドラからここに来る
  •     }
  •     if (p_args->event == UART_EVENT_BREAK_DETECT) {
  •         // ブレークを検出した時に「r_sci_usrt.c」ファイルの
  •         // 「sci_uart_eri_isr()」割り込みハンドラからここに来る
  •     }
  •     if (p_args->event == UART_EVENT_TX_DATA_EMPTY) {
  •         // 送信バッファが空になった時に「r_sci_usrt.c」ファイルの
  •         // 「sci_uart_txi_isr()」割り込みハンドラからここに来る
  •     }
  • }
  • ...


※「hal_entry.c」は「target」ディレクトリ直下のファイルです。

本来、アプリケーション毎に変更の可能性がある関数やファイルの修正は「OBJ~」ディレクトリ直下に存在するものに留めるべきです。

今回は説明を簡便にするために「target」ディレクトリ直下の「hal_entry.c」を書き換えています。


冒頭の「kernel.h」や「kernel_cfg.h」のインクルードは、以降の「iwup_tsk(MAIN_TASK);」という処理のために必要です。

まず「iwup_tsk()」は、指定された番号のタスクを起床させるというμITRONのシステムコールです。

元々「wup_tsk()」という同様のシステムコールが存在しますが、こちらはタスクからコールする時に使用します。

「uart2_callback()」関数はタスクではなく割り込みハンドラから直接跳んできています。

ですので、ここでは頭に「i」を付けた「iwup_tsk()」を使用します。

もし「i」を付けないとどうなるか?

少なくともTOPPERS/ASPのこのバージョンでは、正しく動作しません。

タスクからコールしているのに「i」を付けてしまった場合も同様です。

但し、同じμITRON準拠のRTOSの中でもこれらを区別しない実装もあります。

株式会社ミスポさんの「NORTi」などがそうです。

しかし、移植性を考えるとこれらを正しく使い分けるコーディングを推奨します。

(ちなみに「NORTi」のユーザーズガイドは良くまとまっていて「NORTi」に限らずμITRONのリファレンスとして超分かり易いです!


話を戻して…。

「iwup_tsk()」に指定するタスク番号「MAIN_TASK」は「kernel_cfg.h」で定義されています。

今回の場合は「2」番ですね。

ご想像の通り、この番号が示すタスクは「main.c」の「main_task()」関数がその実体です。

  • /* kernel_cfg.h */
  • #ifndef TOPPERS_KERNEL_CFG_H
  • #define TOPPERS_KERNEL_CFG_H

  • #define TNUM_TSKID    2
  • #define TNUM_SEMID    4
  • #define TNUM_FLGID    0
  • #define TNUM_DTQID    0
  • #define TNUM_PDQID    0
  • #define TNUM_MBXID    0
  • #define TNUM_MPFID    0
  • #define TNUM_CYCID    0
  • #define TNUM_ALMID    0

  • #define LOGTASK    1
  • #define MAIN_TASK    2
  • #define SERIAL_RCV_SEM1    1
  • #define SERIAL_SND_SEM1    2
  • #define SERIAL_RCV_SEM2    3
  • #define SERIAL_SND_SEM2    4

  • #endif /* TOPPERS_KERNEL_CFG_H */


以上で修正作業は完了です。

修正箇所は忘れずに保存してから、こちらの記事を参考にプログラムをビルドしてターゲット上で動かしてみてください。

但し、シリアル通信用の信号線はデバッガ⇔ターゲット・ケーブルから出ているもの(SCI9)ではなく、こちらの記事のようにArduinoのピンソケットから取り出したもの(SCI2)をUSB/シリアル通信変換ケーブルに接続してください。

Arduinoのピンソケット


上手く動けば、ベアメタル版と同じ内容のプログラムが走ります。

「TeraTerm」で入力した文字がそのまま表示されるプログラムでしたね!

TeraTerm


次回は、よ~やく最終回。

このRTOS版の解説とベアメタル版との比較です。

ここまで苦労してRTOSを乗っけたけど、それに見合うメリットってあるの?

あるんですよ!(…多分ね。)


<続く>

2025年3月20日木曜日

TOPPERS/ASP - Arduino UNO R4版 その9

前回からの続きです。

このテーマを最初からご覧になる場合はこちらからどうぞ。


TOPPERS/ASPとFSPを組み合わせて使う~その1

さて、前回は「TOPPERS/ASP」を使用せずに完全なベアメタルでのシリアル通信を使用するプログラムを作成しました。

シリアル通信のドライバは、Renesas社純正のドライバ、プロトコル・スタックなどを開発者に提供するソースコードのライブラリであるFSP(Flexible Software Package)を使用しました。

Arduino UNO R4


今回は同様の事を「TOPPERS/ASP」とFSPを組み合わせた形で試用する方法をご紹介します。

前回の「Hinagata」プロジェクトを「TOPPERS/ASP」に移植する形で作業を進めます。

んなもんとっくに消しちゃったよ~って方は、コチラを参考に再度作成してください。

そして、前回のベアメタルでのシリアル通信プログラムが動くところまで作業をお願いします。


まずは「TOPPERS/ASP」のソースコードのダウンロードです。

すでにこのブログを読んでいただいていて、ぶっ通しで作業してくれている方には申し訳ないのですが、なるべく最新のものを使っていただきたいので、再度ダウンロードをお願いします。

以下のページにアクセスしてください。

そうしたら「master」という表示のコンボボックスに注目です。


https://github.com/RyutaroMorita/asp_arduino_uno_r4_gcc

GitHub - 1


このコンボボックスをクリックすると、以下のようにリストが表示されます。

今は「master」が選ばれていますが、その下の「template」をクリックします。

GitHub - 2


すると、コンボボックスが「template」に変わったページに切り替わりましたよね?

次は、そのはるか右側の「<>Code」と書いてあるボタンをクリックしてください。

GitHub - 3


こちらもリストが展開されて、その中に「Download ZIP」というのがあります。

これをクリックすると、Arduino UNO R4版の「TOPPERS/ASP」のソースコードのダウンロードが始まります。

GitHub - 4


ソースコードはZIP形式でダウンロードされますので、展開が必要です。

今回は「Cygwin」傘下の以下のディレクトリに展開したものとして説明します。


C:\cygwin64\home\<ユーザー名>\asp_arduino_uno_r4_gcc-template


このディレクトリ直下は、以下のようになっていることを確認します。

(Winodwsの標準ツールでそのまま展開しちゃうと、もう一層「asp_arduino_uno_r4_gcc-template」ディレクトリが入っちゃうので注意です。)

この中の「OBJ_template」というディレクトリに注目です。

エクスプローラー


「OBJ」ディレクトリの中には、TOPPERS/ASPの純正のサンプルプログラムのソースコードが入っています。

一方「OBJ_template」には、一秒ごとに「main_task is running.」というシリアル通信のメッセージを出力するだけのシンプルなアプリケーションのソースコードが入っています。

これから新しいシリアル通信のアプリケーションを作成する上で、ベースとするソースコードとして用意しました。

「OBJ」をベースにしてしまうと、いろいろ余計な部分を削除する手間がありますので~。

メインプログラムの「main.c」は以下のような感じです。

シンプルでしょ?

  • #include <kernel.h>
  • #include <t_syslog.h>
  • #include <t_stdlib.h>
  • #include "kernel_cfg.h"
  • #include "main.h"

  • /*
  •  * メインタスク
  •  */
  • void main_task(intptr_t exinf)
  • {
  •     while (1) {
  •         syslog(LOG_NOTICE, "main_task is running.");
  •         dly_tsk(1000);
  •     }
  • }


お次は「Hinagata」プロジェクトからFSPを含む必要なソースコードを「TOPPERS/ASP」のソースコードにコピーする作業です。

この作業は「TOPPERS/ASP - Arduino UNO R4版 その4」の記事を参考にしてください。

前回の「Hinagata」プロジェクトの状態ならば、コピーするディレクトリの中にシリアル通信ドライバなども含まれているはずです。

ただし、ビルド作業までやる必要はありません。

後述しますが、どうせエラーが出ます!!

さらに「e2 studio」でのプロジェクトも作っちゃいましょう。

この作業は「TOPPERS/ASP - Arduino UNO R4版 その6」の記事を参考にしてください。

ただし、この記事では「OBJ」ディレクトリに対しての作業でしたが、今回は「OBJ_template」ディレクトリに対する作業となります。

置き換えてお読みください。

以下のように「OBJ_template」をビルドする準備ができましたでしょうか?

ここで「all」というアイコンをダブルクリックすると、ビルドができるはずなのですが…

ビルド・ターゲット


…ビルドの結果は、あえなく撃沈!!

コンソール - 1


エラーメッセージの重要な部分を抜き出すと、以下の通りです。

  • ~/hal_entry.c:53: undefined reference to `R_SCI_UART_Read'
  • ~/hal_entry.c:59: undefined reference to `R_SCI_UART_Write'
  • ~/hal_entry.c:53: undefined reference to `g_uart2_cfg'
  • ~/hal_entry.c:53: undefined reference to `g_uart2_ctrl'


定義されていない関数やら変数やらへの参照があります」と言っています。

これは「Hinagata」プロジェクトのディレクトリから必要なディレクトリやファイルはコピーしましたが、それらがビルドされていないことを意味します。

参照先も名前的に今回追加されたシリアル通信のドライバの関数や変数っぽいですね。

これを改修するには「OBJ_template」ディレクトリの直下にある「Makefile」を修正する必要があります。

「TOPPERS/ASP」の標準的な方法では「Make」コマンドを使用してOSとアプリケーションをビルドすることになっています。

「Make」コマンドは、ビルド対象の「Makefile」を読むことによりコンパイラのオプションや、コンパイルするべきソースコード、参照するインクルードファイルのパスなどを知ります。

したがって、今回のようにシリアル通信ドライバなど、新たに追加されたソースコードをビルドするためには「Makefile」を編集しなければなりません。

今回のエラーメッセージで表示された「R_SCI_UART_Read」などをキーワードに「asp_arduino_uno_r4_gcc-template」ディレクトリを検索すると「Makefile」に加えるべきソースコードが分かります。

その結果を踏まえ「Makefile」を以下のように修正します。

赤い部分が書き加えた箇所です。


C:\cygwin64\home\<ユーザー名>\asp_arduino_uno_r4_gcc-template\OBJ_template\Makefile

  • (153行目から)
  • #
  • # アプリケーションプログラムに関する定義
  • #
  • APPLNAME = main
  • APPLDIR =
  • APPL_CFG = $(APPLNAME).cfg

  • APPL_DIR = $(APPLDIR) $(SRCDIR)/library \
  •     $(TARGETDIR)/ra/fsp/src/r_sci_uart
  • APPL_ASMOBJS =
  • ifdef USE_CXX
  •   APPL_CXXOBJS = $(APPLNAME).o
  •   APPL_COBJS =
  • else
  •   APPL_COBJS = $(APPLNAME).o
  • endif
  • APPL_COBJS := $(APPL_COBJS) log_output.o vasyslog.o t_perror.o strerror.o \
  •     hal_data.o \
  •     r_sci_uart.o \
  •     vector_datat.o
  • APPL_CFLAGS =
  • APPL_LIBS =
  • ifdef APPLDIR
  •   INCLUDES := $(INCLUDES) $(foreach dir,$(APPLDIR),-I$(dir))
  • endif


改行を意味する「¥あるいは\」にご注意ください。

つまり「APPL_COBJS」変数にビルドするべきソースコード(…というか拡張子が「.o」なのでソースコードと同名のオブジェクト)を加え(「hal_data.o」と「r_sci_uart.o」)、その上で、新たに加えられた「~/r_sci_uart」ディレクトリ(「r_sci_uart.c」の置いてある場所)を参照するように「APPL_DIR」変数に加えています。

<追記>

「APPL_COBJS」変数に「vector_data.o」も同時に加えるように修正しました!

理由は次回に説明させてください。

…ちょっと面倒くさいですね。

たとえば「Visual Studio」などのリッチな統合開発環境では、プロジェクトに新しいソースコードを加える場合は全てGUIで行えるようになっていますよね?

それに比べて「Makefile」を編集しなければソースコード一つも追加できないのは、あまりにも前時代的です。


しかし、良いこともあります。


少なくとも余計なソースコードがプロジェクトに入ってしまって、プログラムサイズが大きくなったり、原因不明のバグが出てしまう可能性が減ることです。

楽は、ヒトをダメにします。

組み込みエンジニアたる者、プロジェクトに含まれるソースコードは完璧に把握しなければなりません!!

(…まあ、ぶっちゃけ不便だとは思ってます…。)

ともあれ、これで「e2 studio」の画面右側の「ビルド・ターゲット」ビューから「OBJ_template」ディレクトリ直下の「all」アイコンをダブルクリックすれば、無事ビルドが通るはずです。

コンソール - 2


今のところ「OBJ_template」では、前述したシンプルなプログラムが記述されているのみです。

次回は、これを前回のベアメタル版と同じ内容のプログラムに実装し直し、RTOSを使った利点を明らかにしていきたいと思います。

RTOS使った方がメンドイじゃん?…とか言わんといて…。


<続く>

2024年6月30日日曜日

TOPPERS/ASP - Arduino UNO R4版 その8

前回からの続きです。

このテーマを最初からご覧になる場合はこちらからどうぞ。


「e2 Studio」で普通にFSPを使う

さて、これまでにも何回か言及している通り、この「TOPPERS/ASP Arduinp UNO R4版」を使った「Arduino」は、もはやArduinoではありません。

便利な「Arduino IDE」も使えませんし、豊富なArduino用のライブラリも使えません。

つまり、安価なRenesas RAマイコン評価ボードになってしまったとも言えます。

しかし、単にライブラリという意味では「Arduino IDE」ではなく「e2 studio」を使用した開発でもFSP(Flexible Software Package)が使用できます。

このFSPについては、過去記事を参照してください。

FSPをうまく利用すれば、Arduino用のライブラリとまでは言えませんが、RAマイコンの持つ色々な機能をライブラリとして利用したソフトウェアを楽に開発できます。

いきなり「TOPPERS/ASP Arduinp UNO R4版」上でFSPを使うのは少々ハードルが高いです。

ですので、今回は、OSレスでフツーにFSPを使用する方法をご紹介します。

そのためには、以前作成した「Hinagata」プロジェクトを「e2 studio」で開きます。

んなもんとっくに消しちゃったよ~って方は、コチラを参考に再度作成してください。

「e2 studio」 - 1


今回は、この「Hinagata」プロジェクトでFSPを使用して、シリアル通信ポートを使えるようにライブラリを追加しましょう。

どのピンにシリアルポートを割り当てましょうか?

シリアル通信には、TXD(送信)、RXD(受信)、GNDの計3本の線が必要です。

GNDは適当で良いとして…、TXD(送信)、RXD(受信)は、以下の回路図のピンを使用することにしましょう!

回路図


絵にすると、以下の通りです。

Arduino UNO R4


これで…

SCI2」というシリアルポートの送信(TXD)信号を「P302」というポートに

SCI2」というシリアルポートの受信(RXD)信号を「P301」というポートに、それぞれ設定すれば良いことが分かります。

これを「e2 studio」で「Hinagata」プロジェクトに設定すればよいのですが、マイコンのピンを機能に割り付ける作業は、実は以前の記事で行っています。

その時に、今回使用する「P302」も「P301」も「SCI2」というシリアルポートで使用できるように既に設定されています。

したがって、残りの作業はシリアル通信のライブラリを設定することだけです!

「e2 studio」の左側の「プロジェクト・エクスプローラー」で「Hinagata」プロジェクト以下に「configuration.xml」というファイルがありますので、これをダブルクリックしてください。

すると、画面中央には「[Hinagata]FSP Configuration」というタブが追加されると思います。

「e2 studio」 - 2

次に、開いた「[Hinagata]FSP Configuration」タブの下にもいくつかのタブがありますので、この中から「Stacks」というタブをクリックします。

以下のように、なにやら「HAL Common Stacks」と題された表示に切り替わります。

「e2 studio」 - 3


この画面で必要なスタック…ライブラリとかドライバを追加していきます。

今回はシリアル通信、すなわちUARTのスタックを追加すれば良いわけですね。

HAL Common Stacks」という表示の右側に「New Stack >」というボタンがありますので、これをクリックするとメニューが表示されます。

メニューの中から「Connectivity」を、更に展開されるメニューで今回のお目当てである「UART(r_sci_uart)」を順にクリックします。

「e2 studio」 - 4


すると、以下のように「HAL Common Stacks」の表示が切り替わり、UARTスタックが追加されたことが分かります。

追加されたUARTスタックですが、デフォルトのままでは都合が悪いので、プロパティをいじらないとなりません。

それには「[Hinagata]FSP Configuration」タブの下のビューの中の「プロパティー」タブをクリックします。

「e2 studio」 - 5


以下は、分かりやすいように拡大した「プロパティー」タブです。

ここには、追加されたUARTスタックの各種設定が表示されています。

これらの中から以下の項目を変更します。

前述した通り、今回は「SCI2」というシリアルポートを使用したいので、それに沿った変更を行います。


Name:g_uart2

Channel:2

Callback:uart2_callback

「e2 studio」 - 6


次に、以下の項目を確認しておきます。

ボーレートが115200bpsに設定されていることは、覚えておきましょう。

また、こちらも前述の通り、送信(TXD_MOSI)信号が「P302」というポートに、受信(RXD_MISO)信号が「P301」というポートに、それぞれ設定されていることを確認します。


Baud Rate:115200

TXD_MOSI:P302

RXD_MISO:P301

「e2 studio」 - 7


オッケー。

じゃあ、この状態でUARTスタックを追加した新しい「Hinagata」プロジェクトのソースコードを出力しましょう。

メインビューの「[Hinagata]FSP Configuration」タブの右上にある「Generate Project Content」ボタンをクリックします。

「e2 studio」 - 8


こんなポップアップが出たら「続行」ボタンをクリックで。

そういや、UARTスタックの追加とかプロパティの変更とか、保存してなかったや…。

「Generate Project Content」ポップアップ


処理が終わると、UARTスタックが追加された「Hinagata」プロジェクトが出来上がっているはずです。

試しに画面左側の「プロジェクト・エクスプローラー」で「ra_gen」ディレクトリ以下の「hal_data.c」をダブルクリックして、ソースコードを見てみましょう。

ソースコードの中にUARTスタックのプロパティで設定した「g_uart2」という文字列がいくつか確認できますね?

「e2 studio」 - 9


更に、新しい「Hinagata」プロジェクトのソースツリーの中には「uart」と記述されたソースコードやヘッダファイルがいくつも見つかります。

これらが追加されたUARTスタックの本体です。

「e2 studio」 - 10


さて、UARTスタックを手に入れた新しい「Hinagata」プロジェクトですが、これをビルドして動かしても何も起こりません。

UARTスタックを使う…すなわちシリアル通信を行うアプリケーションを実装しないと本当に正しく動くのか分かりませんよね?

というわけで、簡単なプログラムを実装して動作確認をしてみましょう。

プログラムを記述するのは「src」ディレクトリ以下の「hal_entry.c」です。

さっそく開いてみましょう。

「e2 studio」 - 11


この「hal_entry.c」ファイルに、以下のように「received」というフラグと「uart2_callback()」という関数を記述します。

  1. #include "hal_data.h"
  2. FSP_CPP_HEADER
  3. void R_BSP_WarmStart(bsp_warm_start_event_t event);
  4. FSP_CPP_FOOTER
  5. // ここから ----------------------------------------------------------------
  6. volatile bool recieved = false; // 受信完了フラグ
  7. void uart2_callback(uart_callback_args_t *p_args)
  8. {
  9.     if (p_args->event == UART_EVENT_RX_COMPLETE) {
  10.         // 受信が完了したら「r_sci_usrt.c」ファイルの
  11.         // 「sci_uart_rxi_isr()」割り込みハンドラからここに来る
  12.         recieved = true;
  13.     }
  14.     if (p_args->event == UART_EVENT_TX_COMPLETE) {
  15.         // 送信が完了したら「r_sci_usrt.c」ファイルの
  16.         // 「sci_uart_tei_isr()」割り込みハンドラからここに来る
  17.     }
  18.     if (p_args->event == UART_EVENT_RX_CHAR) {
  19.         // 一文字受信したら「r_sci_usrt.c」ファイルの
  20.         // 「sci_uart_rxi_isr()」割り込みハンドラからここに来る
  21.     }
  22.     if (p_args->event == UART_EVENT_ERR_FRAMING) {
  23.         // フレーミングエラーを検出した時に「r_sci_usrt.c」ファイルの
  24.         // 「sci_uart_eri_isr()」割り込みハンドラからここに来る
  25.     }
  26.     if (p_args->event == UART_EVENT_BREAK_DETECT) {
  27.         // ブレークを検出した時に「r_sci_usrt.c」ファイルの
  28.         // 「sci_uart_eri_isr()」割り込みハンドラからここに来る
  29.     }
  30.     if (p_args->event == UART_EVENT_TX_DATA_EMPTY) {
  31.         // 送信バッファが空になった時に「r_sci_usrt.c」ファイルの
  32.         // 「sci_uart_txi_isr()」割り込みハンドラからここに来る
  33.     }
  34. }
  35. // ここまで ----------------------------------------------------------------
  36. /*******************************************************************************************************************//**
  37.  * main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used. This function
  38.  * is called by main() when no RTOS is used.
  39.  **********************************************************************************************************************/
  40. void hal_entry(void)
  41. ...


お次は、その下の「hal_entry()」内に以下のコードを加えます。

  1. /*******************************************************************************************************************//**
  2.  * main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used. This function
  3.  * is called by main() when no RTOS is used.
  4.  **********************************************************************************************************************/
  5. void hal_entry(void)
  6. {
  7.     /* TODO: add your own code here */
  8.     // ここから ----------------------------------------------------------------
  9.     uint8_t c;
  10.     // シリアル通信を開く
  11.     R_SCI_UART_Open(&g_uart2_ctrl, &g_uart2_cfg);
  12.     while (1) {
  13.         // 受信する
  14.         R_SCI_UART_Read(&g_uart2_ctrl, &c, 1);
  15.         // 受信するまで待つ
  16.         while (!recieved);
  17.         // 受信完了フラグをリセットする
  18.         recieved = false;
  19.         // 受信した一文字を送信する
  20.         R_SCI_UART_Write(&g_uart2_ctrl, &c, 1);
  21.     }
  22.     // ここまで ----------------------------------------------------------------
  23. #if BSP_TZ_SECURE_BUILD
  24.     /* Enter non-secure code */
  25.     R_BSP_NonSecureEnter();
  26. #endif
  27. }


なにをやっているのか?…というのは、まあ、コメントに書いた通りなのですが。

まず、追加した「uart2_callback()」というのは、コールバック関数というもので、チャンネル2のシリアルポートに割り込みが発生した場合に処理が飛んでくる関数です。

「uart2_callback」という名称については、UARTスタックを追加した時「callback」プロパティで設定しましたよね?

で、割り込みが発生して「uart2_callback()」関数が飛んできて、その中で「p_args->event」変数の内容を調べて、それが受信完了割り込み…すなわち「UART_EVENT_RX_COMPLETE」というフラグを含んでいた場合は、冒頭で定義した「received」変数を「true」にする…という処理をしています。

割り込みには受信完了のほか、送信完了やエラー発生など、色々なものがあります。

上記の例では、ご丁寧に受信完了以外の条件分岐も記述されていますが、何も実装していないし、今回は受信完了割り込みしか使わないので、面倒だったらそれ以外のif文は省いてオッケーです。


さて、次に「hal_entry()」です。

これは、最初から実装されている関数です。

C言語のプログラムは、一般的に「main()」という関数から処理が始まります。

しかしながらFSPでは、その役割は「hal_entry()」が担っています。

とはいえ、これはFSPのお作法というか…実際「Hinagata」プロジェクトのソースツリーの中にも「main.c」ファイルが存在し、その中に「main()」関数も記述されています。

この「main()」は開始早々この「hal_entry()」を呼んでいるので「hal_entry()」は、実質「main()」と同じですね。

(こんな回りくどい実装をしているのは「main()」を直接いじれなくなるRTOSを使う場合を想定している旨が、関数の上のコメントに書いてありますね。)

追記した部分ですが「c」という変数を定義した後に「R_SCI_UART_Open()」という関数を呼んでいます。

これでUARTスタック、すなわちシリアルポートを使用する準備をします。

その後、whileループに入ります。

ループの冒頭で「R_SCI_UART_Read()」という関数を呼んでいます。

これは、シリアル通信の受信を行う関数です。

この関数は、受信が終わるまでロックする…ということもなく、素通りします。

受信ができたらその時点で、受信データは関数に渡した「c」のポインタに格納される仕組みとなっています。

さっきから頭に「R_SCI_UART_うんちゃら」という名前の関数が出てきていますが、これこそが今回追加したUARTスタックで提供されている関数です。

これらの関数は、今回使用するものの他にも多く用意されています。

詳しくは、こちらを御覧ください。

さて「R_SCI_UART_Read()」で受信を指示した後は、更にループに入ります。

このループは「received」が「true」にならない限り、ここでずっとグルグル回っているという処理、すなわち無限ループになります。

「received」の初期値は「false」ですから、最初は必ずグルグル回ります。

その間に受信完了割り込みが起きて、前述の「uart2_callback()」の中で「received」が「true」に変化したら、ようやくこの無限ループから次に進むことができます。

このループ、要はシリアル通信の受信待ちです。

ループを抜けると、次の受信に備えて「received」を「false」に戻しておきます。

この時には既に「c」の中には受信した1文字のデータが格納されているはずです。

次の「R_SCI_UART_Write()」関数で、その受信した「c」の中の1文字のデータを今度はそのまま送信します。

送信後は、また受信処理に戻り、それを永遠に繰り返します。

すなわち今回のアプリケーションは、シリアル通信で1文字受信すると、それと同じ1文字を送信する…というテストプログラムになります。


さて、思惑通りに動くかな?

物理的な接続です!

まずは、Arduinoとデバッガーとパソコンを接続します。

このページを参考にして欲しいのですが、唯一違うのがシリアル通信部分。

今回は変換ケーブルから出ている信号線ではなくて、Arduinoのピンソケットから取りましょう。

ピンソケットの位置は、今一度、このページの冒頭の図を参考に。

GNDSCI_TXD2およびSCI_RXD2の3本ですね。

こんな感じ。

物理的接続


USB/シリアル通信変換ケーブル側の配線は、上からTXDRXDGNDの順番でこんな感じです。

USB/シリアル通信変換ケーブル側の配線


これで「Hinagata」プロジェクトを走らせましょう。

まずは「プロジェクト・エクスプローラー」で「Hinagata」プロジェクトを右クリック、出てきたメニューから「プロジェクトのビルド」を左クリックしてください。

「e2 studio」 - 12

ビルドが終わったら、再び「プロジェクト・エクスプローラー」で「Hinagata」プロジェクトを右クリック、出てきたメニューから、今度は「デバッグ」を左クリック、更に表示されたメニューから「Renesas GDB Hardware Debugging」を左クリックします。

「e2 studio」 - 13


以下のダイアログが表示されたら「E2 Lite (ARM)」を選択状態にして、ダイアログ下部の「OK」ボタンをクリック。

「Renesas Hardware Debugging」ダイアログ - 1


再度、以下のダイアログが表示されたら「R7FA4M1AB」を選択状態にして、ダイアログ下部の「OK」ボタンをクリックです。

「Renesas Hardware Debugging」ダイアログ - 2


これでオッk…ああ!?

まあ、とりあえず「OK」ボタンをクリックで。

致命的なエラー


これを修正するには、このページの中盤くらいにある「エミュレーターから電源供給」の項目を「いいえ」に設定する必要があるようです。

デバッグの構成は「Hinagata.elf」という名前で、この時点で作成されています。

設定できたら「デバッグ」ボタンをクリックして、デバッグ開始です!

デバッグの構成


デバッグが開始されると、いくつかのポイントで自動的にブレークがかかります(2回くらいだっけか?)。

ブレークがかからなくなるまで「」ボタンをクリックしてプログラムを続行させましょう。

次に「TeraTerm」の起動です。

今回は、UARTスタックのプロパティで115200に設定したのですよね?

TeraTerm - 1


接続ができたら「TeraTerm」の画面でキーボードから何か文字を入力してみてください。

このように、入力した文字が「TeraTerm」にそのまま表示されれば動作確認は完了です!

TeraTerm - 2


「Hinagata」プロジェクトに無事UARTスタックが追加され、それが正常に動いていることが確認できました。

今回はUARTスタック、すなわちシリアル通信のライブラリを追加しましたが、I2CやSPIなどの他の通信の場合でも、FSPの使い勝手は概ね一緒です。

更には、AD変換やタイマーなど、FSPを使えばデバイスドライバを作成することなく簡単にこれらの機能をファームウェアで使用することが可能です。

便利な時代になりましたね~。


さて、今回はTOPPERS/ASPとは関係なく、フツーにFSPを使うだけの記事となってしまいました。

次回は、今回と同じプログラムを「TOPPERS/ASP Arduino UNO R4版」で動かす例を紹介します。

便利なFSPとTOPPERS/ASPを組み合わせて使うことができますよ!

あとは、今回のプログラムは受信待ちに「received」をフラグに無限ループを使うという、ある意味でダサい実装となってしまいました。

それがTOPPERS/ASPなどのRTOSを使用することで、どれだけスマートな実装になるのかも説明させていただきます。


…なにげに前回の投稿から2ヶ月も経っていたのですね。

ブログを書くのも趣味の一つなのですが、本業が忙しくてそれができないようだと、ライフワークバランスが崩壊しているなぁ~…と思います。

ライフのためのワークであって、ワークのためのライフでは本末転倒だと思う今日このごろです。


<続く>

TOPPERS/ASP - Arduino UNO R4版 その10

前回からの続き です。 このテーマを最初からご覧になる場合は こちら からどうぞ。 TOPPERS/ASPとFSPを組み合わせて使う~その2 前回ビルドまで通した「OBJ_template」に対し、実際にシリアル通信プログラムを肉付けしていきましょう。 「 TOPPERS/ASP...