「RX」カテゴリーアーカイブ

C++によるRXマイコンCANドライバー(その1)

RX マイコン内蔵 CAN ドライバーの実装

RXマイコンには、CAN を内蔵しているデバイスがある。

RX マイコンの CAN ペリフェラルは機能が豊富で、CAN 全体の構成も複雑なので、柔軟な「管理」を考えるとインターフェースが難しい・・

「送信」も、「受信」も、色々なバリエーションが考えられるので、どのような構成が「正解」なのか色々考える必要がある。
※機能が豊富過ぎると、どのような構成にすると、メリットが大きいのか考える必要があり、実験や調査に時間がかかりそうだ・・

とりあえず、どのような構成にするのがベストなのかイマイチ判らないのと、CAN についての知識が不足しているので、ハードウェアー機能を網羅するような対話形式のインターフェースを作り、実験しながら考えていく事にする。
※もう十年以上前に、仕事で、Autosar 準拠の機器で Linux ドライバーを実装した事があるが、その頃とは状況が異なっている・・
Autosar は上位の層で、自分が担当したのは、下位の層、ハードウェアーを直接操作して、CAN デバイスを操作するドライバーだった、Linux ドライバーは初めてで、それなりに苦労した事を覚えている・・・

  • CAN の通信 IC は Philips 製で、それが、PCI を介して接続されていたと思う。
  • この時は、単純な機能しか無かったので、ソフトで色々な機能をサポートしていた、ドライバーはシンプルなものだった。

RX24T は、初期のバージョンでは、CAN が無かったが、新しいバージョンでは、CAN が追加された、ただ、今までの CAN とは異なる構成になっている。
その為、同じドライバーでは、面倒を見れないと思える、これもどうするのが良いのか悩む要因の一つだ・・・
※RSCAN と呼ばれるペリフェラルに置き換わっている。
※RX66Tは、従来通りの CAN となっている。


別の問題として、CAN 関連レジスタにアクセスするテンプレートが一筋縄ではいかない構成なので、それもスマートに解決する必要がある。
※メールボックスレジスタ(32個ある)などにアクセスするテンプレートが思うように作れなかったので、中途な状態になっていた、最初は、それの改善方法を考える事から始める。

レジスタクラスを改造する

CAN には、「メールボックス」が32個あり、同等なアクセスを提供している。

しかし、レジスタ定義テンプレートクラスは、単独、1個のレジスタを想定して設計されている為、どのようにアクセスするか、悩んでいた。

テンプレートクラスの場合、アクセスするアドレスは固定となっている為、さらにインデックスを加えてアクセスするようには出来ていない。
そこで、「インデックス」付きアクセスを実現する為、最終的にレジスタメモリにアクセスするクラス「rw8_t」とは別クラスを追加した。

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  Read/Write 8 bits アクセス・テンプレート、インデックス付
        @param[in]  adr アドレス
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <address_type adr>
    struct rw8_index_t {
        typedef uint8_t value_type;

        static uint32_t index;

        //-----------------------------------------------------------------//
        /*!
            @brief アドレスの取得
            @return アドレス
        */
        //-----------------------------------------------------------------//
        static address_type address() noexcept { return adr + index; }

        //-----------------------------------------------------------------//
        /*!
            @brief  書き込み
            @param[in]  data    書き込み値
        */
        //-----------------------------------------------------------------//
        static void write(value_type data) noexcept {
            wr8_(adr + index, data);
        }

        //-----------------------------------------------------------------//
        /*!
            @brief 読み出し
            @return 読み出し値
        */
        //-----------------------------------------------------------------//
        static value_type read() noexcept {
            return rd8_(adr + index);
        }

...

    };

改造は簡単な物で、変数として「index」を追加して、事前に「index」を設定してからアクセスする事で、基底アドレス+index にアクセス出来る。
注意しなければならないのは、「adr」が同じテンプレートの場合、static で宣言されている「index」は共有されてしまうが、都合の良い事に、そのような場合が起こるのは「稀」だと思える。

メールボックス制御レジスタクラスは、元々、MCTL0 ~ MCTL31 が定義されている。
しかし、そのインスタンスでアクセスするのでは、冗長になる。
そこで、上記の「rw8_index_t」クラスを使い、index 分のアクセスを行えるように改修した。

さらに、「operator []」でアクセスできるように、「[]」をオーバーロードした。
operator の「戻り」は参照を使って、自分を戻しているので、インデックス付きアクセスが出来る。

    //-----------------------------------------------------------------//
    /*!
        @brief  メッセージ制御レジスタ j( MCTLj )( j = 0 ~ 31 )
        @param[in]  ofs オフセット
    */
    //-----------------------------------------------------------------//
    template <uint32_t ofs>
    struct mctl_t : public rw8_index_t<ofs> {
        typedef rw8_index_t<ofs> io_;
        using io_::operator =;
        using io_::operator ();
        using io_::operator |=;
        using io_::operator &=;

        void set_index(uint32_t j) { if(j < 32) { io_::index = j; } }

        bit_rw_t <io_, bitpos::B0>  SENTDATA;  // Send mode
        bit_rw_t <io_, bitpos::B0>  NEWDATA;   // Recv mode
        bit_rw_t <io_, bitpos::B1>  TRMACTIVE; // Send mode
        bit_rw_t <io_, bitpos::B1>  INVALDATA; // Recv mode
        bit_rw_t <io_, bitpos::B2>  TRMABT;    // Send mode
        bit_rw_t <io_, bitpos::B2>  TMSGLOST;  // Recv mode

        bit_rw_t <io_, bitpos::B4>  ONESHOT;

        bit_rw_t <io_, bitpos::B6>  RECREQ;
        bit_rw_t <io_, bitpos::B7>  TRMREQ;

        mctl_t& operator [] (uint32_t idx)
        {
            set_index(idx);
            return *this;
        }
    };
    typedef mctl_t<base + 0x0620> MCTL_;
    static  MCTL_ MCTL;

この改修で、以下のように自然にアクセスする事が出来るようになった。

    CAN::MCTL[idx] = CAN::MCTL.TRMREQ.b(1);

    CAN::MCTL[idx].TRMREQ = 1;

RX64M で送信、受信実験

以前に CAN バス・トランシーバー VP230 が乗った基板を購入した(中華製)このトランシーバーは 3.3V で動作するので、RX64M で動かすのには都合が良い、ターミネーター抵抗(120 オーム)も付いている。

ドライバーを作りこむのに、実験環境を作らなければならないが、送信側、受信側を別々にすると、二台のボードで実験する事になり、デバッグの手順が複雑になる。

RX64M は、CAN を3チャネル持っているので、CAN0、CAN1 を使って、相互に接続し、1台で、送信と受信が出来るような構成にした。

ボーレートの設定

RX マイコンの CAN はボーレートの設定が多少複雑だ、ドライバークラスは、複雑な設定を隠蔽して、簡単な設定を自動で行えるようにしなければメリットが薄い。
また、間違った設定を構造的に出来なくする事も重要だ。

CAN は、1ビットを送受信する過程で、色々な処理をするので、このような複雑なものになっているようだ。

RX マイコンの CAN では、1 ビットを「TQ」と呼ばれる値(ビットタイム)で分割し、これが、8~25になるようにボーレートを設定する。

TQ は、SS、TSEG1、TSEG2、を合計した物で、以下のような関係になっている。

SS = 1 Tq
TSEG1 = 4 Tq ~ 16 Tq
TSEG2 = 2 Tq ~ 8 Tq
SJW = 1 Tq ~ 4 Tq
TSEG1 > TSEG2 ≧ SJW

まず、「余り」が0になるような TQ 値を求める。
※そうしないと、割り切れない場合、微妙な通信速度になり、標準的な機器と通信出来ない。

    // 通信速度に対する、TQ 値 8 to 25 で適切な値を選ぶ
    // より大きい値で適合した値を選択
    uint32_t tq = 25;
    while(1) {
        if((get_can_clock_() % (static_cast<uint32_t>(speed) * tq)) == 0) {
            break;
        }
        tq--;
        if(tq < 8) {  // 割り切れない場合エラーとする
            format("(1)BRP divider indivisible...\n");
            return false;
        }
    }

-「get_canclock()」は、PCLKB(60MHz)となる。

  • 「speed」は、enum class で定義してあり、キャストして整数としている。
    enum class SPEED {
        _50K  =    50'000,  ///<  50 Kbps(標準ではない)
        _100K =   100'000,  ///< 100 Kbps(標準ではない)
        _125K =   125'000,  ///< 125 Kbps
        _250K =   250'000,  ///< 250 Kbps
        _500K =   500'000,  ///< 500 Kbps
        _750K =   750'000,  ///< 750 Kbps(標準ではない)
        _1M   = 1'000'000   ///< 1 Mbps
    };

次に、TSEG1、TSEG2 の配分を決める。

    uint32_t tseg1 = 16;
    uint32_t tseg2 = 8;
    uint32_t sjw = 1;  // とりあえず固定

    while(1) {
        if(tq == (1 + tseg1 + tseg2)) break;

        tseg1--;
        if(tq == (1 + tseg1 + tseg2)) break;
        else if(tseg1 < 4) {
            format("(3)TSEG1 value indivisible...\n");
            return false;
        }

        tseg1--;
        if(tq == (1 + tseg1 + tseg2)) break;
        else if(tseg1 < 4) {
            format("(4)TSEG1 value indivisible...\n");
            return false;
        }

        tseg2--;
        if(tq == (1 + tseg1 + tseg2)) break;
        else if(tseg2 < 2) {
            format("(5)TSEG2 value indivisible...\n");
            return false;
        }
    }

RX64M PCLKB=60MHz の場合、1Mbps に設定すると、以下のような値となる。

CAN0: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7

※「SJW」はこの変数に関する説明が無かったので、とりあえず「1」にしているが、出来るだけ大きい方が良いのかもしれない・・

メールボックスをどのように割り振るか?

RX マイコンの CAN は、メールボックスを中心とした構成になっている。
メールボックスは32個あり、どのように割り振るのか、ケースバイケースとなると思うが、「受信」動作はかなり厄介だ・・

  • 特定の ID だけ受信する(オペレーションモードにする前に決めなければならない)
  • 全ての ID を受信して、ソフトで振り分ける。
  • 上記二つの混合

「特定 ID」の受信では、初期化時に ID が決まっていなければならないが、ハードウェアーが自動で行うので、ソフトの負荷が小さくなる。
※その ID の数分、メールボックスを消費する。
「ソフト処理」では、どんな構成でも可能だが、その分、CPU 負荷が大きくなる。
CAN の場合、1つのパケットは 100 マイクロ秒くらいなので、120MHz の RX64M でも、流れるパケットによっては、処理がオーバーフローするかもしれない。

対話式インターフェース

CAN レジスタの説明を読んだ理解と、実際の動作が異なる場合があると思うので(CAN の理解不足などから)対話式インターフェースを実装した。

最初のステップとして、メールボックス「ありき」で対話式インターフェースを用意した。
※最終的には、メールボックスをアプリケーションユーザーに意識させるのは良くないと思うので実験用だけ。

Start CAN sample for 'RX64M' 120[MHz]
CAN0: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7
CAN1: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7
# help
    ch CH-no               set current CAN channel (CH-no: 0, 1)
    id number              set current ID number
    ext                    set ext-id mode
    std                    set std-id mode
    data MB-no [byte ...]  set frame data (MB-no: 0 to 31)
    list MB-no             list frame (MB-no: 0 to 31)
    reset MB-no            reset MB (MB-no: 0 to 31)
    send MB-no             send frame (MB-no: 0 to 31)
    recv MB-no             recv frame (MB-no: 0 to 31)
    stat MB-no             stat frame (MB-no: 0 to 31)
    state                  report CAN state
    help                   command list (this)

簡単な通信を行い、送信、受信動作を確認。

# ch 1
# id 0x754
# data 0 0
# recv 0
# stat 0
MCTL: 0x40 (0b01000000)
# ch 0
# data 1 0x75 0x46 0xac 0xf8 0x40
# send 1
# ch 1
# stat 0
MCTL: 0x41 (0b01000001)
# list 0
ID: std 0x754 (1876)
DATA(5): 75 46 AC F8 40
TS: 31995
#

メールボックスの扱い方がある程度分かったと思う・・・

今回はここまで、次は、マネージメントを内部で自動で行い、扱いやすくする方法や構造を考える。
また、割り込みも使う必要があるので、その辺りを中心に実装する。

RX マイコン用 FatFS のバージョンを最新版へ(ff14)

exFAT を有効にする

最近 64GB(SDXC) の SD カードを購入したので、RX72N Envision Kit でも試してみた。

FAT32 では、1つのファイルは最大 4GB までしか対応出来ず、SD カードの場合、32GB を超えるサイズは、標準で exFAT になっている。
exFAT は、もはや FAT32 とは、根本的に異なる構造のようで、新たに設計からやり直したファイルシステムのようだ。

FatFS では、exFAT をかなり前からサポートしており、ffconf.h の「FF_FS_EXFAT」を「1」にするだけだ。
リソースの少ないマイコンでも扱えるように、細かい工夫が色々してある、素晴らしいとしか言いようが無い。
何の苦労もなく利用できる事に、ChaN さんには感謝しかない。

ファイルが 4GB を超えるサイズを扱える事から、seek などの位置指定は 64 ビット仕様になるので、その修正も行った。
※FatFS では、「FSIZE_t」が 64 ビットで定義されている、exFATを使わない場合は、32ビットとなる。

ファイルリストは普通に取れたので、他も動作すると思えたが、ディレクトリの移動が出来ない・・・

調べると、exFAT の場合「f_getcwd」がカレントディレクトリパスを返さない・・・

これは、バグだろうと思い、良い機会でもあるので、ff13c から ff14 へアップグレードした。

しかし・・・

状況は変わらない・・・
何で?
と思い、f_getcwd の説明を読んでみたー

Note: In this revision, this function cannot retrieve the current directory path on the exFAT volume. It always returns the root directory path.

これは、仕様のようだ・・・

さて、どうするか・・・


カレントパスを自分で管理するしか無い

以前の実装では、自分で管理していたが、FatFS にその機能がある事が判り、FatFS の API を使うように変更したのに・・・

先祖返りとは・・・

そもそも、カレントパスの管理が一つのコンテキストなのは多少問題でもある。
※ FAT ではファイル名の文字コードが拡張2バイトで、ロケールに左右される問題点があった。(たとえば、アラビア語とに日本語を同時に利用出来ない)

FreeRTOSなどを使うようになって、複数のタスクから、ファイルシステムにアクセスする場合、カレントパスを共有するのは「少しマズイ」。

そこで、それを見越して、ファイルのアクセス関係を少し整理してみた。

ただ、複雑な相対パスなどを認識する事は出来ない簡易的な解析機能しか無い。


Micron 64GB SDXC カードの速度

※参考速度

Write: 'test.bin'
Write Open:  23 [ms]
Write: 684 KBytes/Sec
Write Close: 4 [ms]
Read: 'test.bin'
Read Open:  1 [ms]
Read: 1622 KBytes/Sec
Read Close: 0 [ms]

読み込み速度は、1.6M バイトもあるー

FreeRTOS 対応

FreeRTOS などで、マルチスレッドでファイルシステムを動かす場合、排他制御を有効にする必要がある。

ffconf.h の主要部分を書き換える。

#include <FreeRTOS.h>
#include <semphr.h>
#define FF_FS_REENTRANT 1
#define FF_FS_TIMEOUT   1000
/// #define FF_SYNC_t       HANDLE
#define FF_SYNC_t       xSemaphoreHandle

※「FF_FS_LOCK」は、「FF_FS_REENTRANT」の制御を行う場合、「0」にしておく必要がある。

ffsystem.c の修正。
このソースには、排他制御の呼び出しが集約されている、標準では、WIN32 関係 API の呼び出しになっているので、それをコメントアウトする。
FreeRTOS 用は、既に実装されており、コメントアウトされているので、そのコメントアウトを外して有効にする。

int ff_cre_syncobj (    /* 1:Function succeeded, 0:Could not create the sync object */
    BYTE vol,           /* Corresponding volume (logical drive number) */
    FF_SYNC_t* sobj     /* Pointer to return the created sync object */
)
{
    /* Win32 */
//  *sobj = CreateMutex(NULL, FALSE, NULL);
//  return (int)(*sobj != INVALID_HANDLE_VALUE);

    /* FreeRTOS */
    *sobj = xSemaphoreCreateMutex();
    return (int)(*sobj != NULL);
}

int ff_del_syncobj (    /* 1:Function succeeded, 0:Could not delete due to an error */
    FF_SYNC_t sobj      /* Sync object tied to the logical drive to be deleted */
)
{
    /* Win32 */
//  return (int)CloseHandle(sobj);

    /* FreeRTOS */
    vSemaphoreDelete(sobj);
    return 1;
}

int ff_req_grant (  /* 1:Got a grant to access the volume, 0:Could not get a grant */
    FF_SYNC_t sobj  /* Sync object to wait */
)
{
    /* Win32 */
//  return (int)(WaitForSingleObject(sobj, FF_FS_TIMEOUT) == WAIT_OBJECT_0);

    /* FreeRTOS */
    return (int)(xSemaphoreTake(sobj, FF_FS_TIMEOUT) == pdTRUE);
}

void ff_rel_grant (
    FF_SYNC_t sobj  /* Sync object to be signaled */
)
{
    /* Win32 */
//  ReleaseMutex(sobj);

    /* FreeRTOS */
    xSemaphoreGive(sobj);
}

標準では、exFAT は無効にしておく・・

一応、動作するようだが、もう少し、全体をチェックしたいので、標準では「無効」にしてコミットしておく事にする。

gcc-8 系での、strncpy などの警告

gcc-7 系から、strcpy、strncpy などを使う場合に、コピー先の配列サイズが、コピー元サイズより少ない場合、「警告」が出るようになった。
※サイズ不足でコピーが出来ない場合、終端「0」が抜けてしまい、問題が起こる、これを抑止するものだ。
※どのように判断しているのか不明だが、警告が出る場合と出ない場合があり、対処するのが難しい場合がある。
std::string などを使って、動的に配列を作れば良いのだが、組み込み系ではそうもいかない。

警告を完全に抑止するようなオプションを付ける事も方法の一つではあるのだが、それも、少し違う。

「ここは、まず、大丈夫なハズ」でも警告が出る場合は、以下のように抑止するようにした。

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-truncation"
                strncpy(&dst[l], base, len - l - 1);
#pragma GCC diagnostic pop

また、strncpy の亜種を実装してある、名前空間「str」に置いてある。
「string_utils.hpp」
あらかじめ、コピーする最大サイズを -1 して、最後は「0」で埋める。

        static char* strncpy_(char* dst, const char* src, uint32_t len) noexcept
        {
            if(dst == nullptr || src == nullptr || len == 0) return dst;

            auto tmp = dst;
            --len;
            while(tmp < (dst + len)) {
                auto ch = *src++;
                *tmp++ = ch;
                if(ch == 0) {
                    return dst;
                }
            }
            *tmp = 0;
            return dst;
        }

文字列の扱いは、メモリが少ない場合システムの場合でも、少し考える必要がある。

std::string 専用のアロケーターを実装して、簡単なメモリ管理を実装するのも良さそうだが、RX24T 位のデバイスを考えると、有限のメモリで凌ぐのでも良いかと考えてしまう・・・

GNU-RX 8.3.0 TFU の利用方法

GNU-RX 8.3.0 で、TFU(三角関数演算器)利用方法

多分、これで、使えるようになると思う。

関数プロトタイプは以下のようになっているようだ、この定義はincludeファイルには含まれないようで、-mtfu オプションを有効にすると、内部に組み込まれるようだ。

    // -mtfu=intrinsic
    void __init_tfu(void);
    void __builtin_rx_sincosf(float, float*, float*);
    void __builtin_rx_atan2hypotf(float, float, float*, float*);
    // -mtfu=intrinsic,mathlib
    float __builtin_rx_sinf(float);
    float __builtin_rx_cosf(float);
    float __builtin_rx_atan2f(float, float);
    float __builtin_rx_hypotf(float, float);

また、-mtfu オプションで TFU を有効にすると、ビルトイン変数

  __TFU

が有効になる。

これら、ビルトイン関数を利用する場合、多分、事前に初期化関数を呼んでおく必要がある。

    __init_tfu();

後は、普通に関数を呼べば良いものと思う。

                float a = vtx::get_pi<float>() * 0.25f;
                float si, co;
                __builtin_rx_sincosf(a, &si, &co);
                utils::format("%8.7f: %8.7f, %8.7f\n") % a % si % co;
                a = vtx::get_pi<float>() * 1.75f;
                si = __builtin_rx_sinf(a);
                co = __builtin_rx_cosf(a);
                utils::format("%8.7f: %8.7f, %8.7f\n") % a % si % co;
Start test for 'RX72N' 240[MHz]
SCI Baud rate (set):  115200
SCI Baud rate (real): 115355 (0.13 [%])
CMT rate (set):  850 [Hz]
CMT rate (real): 849 [Hz] (-0.12 [%])
0.7853982: 0.7071068, 0.7071067
5.4977875: -0.7071068, 0.7071067

GNU-RX 8.3.0 で RX72N の機能を使える事が判った。

これで、RX72N に備わっている機能で、気になっていた部分の疑問は大体解決した。

ベンチマークについては、以下のような資料がある。

RXv3 CPU搭載RXファミリ 数値計算用ライブラリ関数ベンチマーク

コンパイラーオプションの自動判別

通常の gcc ソースコードを使ってビルドした、プレーンな gcc と、ルネサス版の魔改造 gcc で、オプションを自動設定する Makefile の制御文を考えてみた。

# rx-elf-gcc compiler version check
TARGET_ISA_TEXT := $(shell rx-elf-gcc --target-help | grep ISA)

# AS_DEFS       =   --defsym NOT_USER=1
ifeq ($(TARGET_ISA_TEXT), )
# gcc-7.5.0 current gcc source build
CC_DEFS     =   -mcpu=rx600 -Wa,-mcpu=rxv2
CP_DEFS     =   -mcpu=rx600
else # Renesas GNU-RX gcc
CC_DEFS     =   -misa=v3
# CP_DEFS       =   -misa=v3 -mdfpu -mtfu=intrinsic,mathlib
CP_DEFS     =   -misa=v3 -mtfu=intrinsic,mathlib
# CP_DEFS       =   -misa=v3 -mdfpu
endif

これは、gcc のオプションで「--target-help」を実行して「ISA」の文字列があれば「ルネサス版」と判断するもの。

GNU-RX 8.3.0 の最適化と倍精度浮動小数点オプション

Renesas GNU-RX 8.3.0 の最適化

Renesas GNU-RX の最適化は、通常の gcc (7.5.0) とは異なるようだー
より、深い最適化を行っている。

gcc-7.5.0 で、以下のプログラムをコンパイルすると・・

    typedef device::PORT<device::PORT0, device::bitpos::B1> LED;

    while(1) {
        utils::delay::milli_second(250);
        LED::P = 0;
        utils::delay::milli_second(250);
        LED::P = 1;
    }

論理演算を行ったコードが生成される。

ffc0029c:       cc 3e                           mov.b   [r3], r14
ffc0029e:       75 42 fa                        mov.l   #250, r2
ffc002a1:       75 2e fe                        and     #-2, r14
ffc002a4:       c3 3e                           mov.b   r14, [r3]

ffc002bc:       cc 3e                           mov.b   [r3], r14
ffc002be:       65 1e                           or      #1, r14
ffc002c0:       c3 3e                           mov.b   r14, [r3]

※中間に、「mov.l #250, r2」が挟まれてるのが多少痛いものの、まぁ普通だ。

Renesas GNU-RX では・・・

ffc00278:       f0 38                           bclr    #0, [r3].b

ffc00294:       f0 30                           bset    #0, [r3].b

ビット操作命令になっている。

RAYTRACE_sample が、361ms から 351ms に時間短縮したのも、他、細かい最適化によるものと思われる。
ルネサス社が魔改造したコンパイラは、高性能なのだと思われる。
※何故、gcc のオリジナルコードにマージしないのか?、疑問は残る・・・

Renesas GNU-RX 8.3.0 のオプション

倍精度浮動小数点命令を生成するには、コアが「RXv3」でなおかつ、「dfpu」を有効にすれば良いらしい。
※gcc のソースコードで、RX マイコンに関係する部分を少し読んでみた。

     -misa=v3 -mdfpu

なので、上記オプションを使って、簡単なコードで調べてみた。

    double a = 0.0;
    while(1) {
        utils::delay::milli_second(250);
        LED::P = 0;
        utils::delay::milli_second(250);
        LED::P = 1;
        a += 0.1;
        utils::format("%5.4f\n") % static_cast<float>(a);
    }

アセンブルリストを見てみると、確かに「倍精度浮動小数点命令」が生成されている、「a + 0.1;」の部分

ffc003d2:       fc c9 08 12 10                  dmov.D 72[r0], dr1
ffc003d7:       f9 03 00 9a 99 99 99            dmov.L #0x9999999a, drl0
ffc003de:       f9 03 02 99 99 b9 3f            dmov.L #0x3fb99999, drh0
ffc003e5:       76 90 00 11                     dadd  dr1, dr0, dr1
ffc003e9:       fc 79 08 12 10                  dmov.D dr1, 72[r0]

※ 64 ビットの IEEE-754 で定数 0.1 の表現は、0x3fb9'9999'9999'999a となる。
※ 64 ビットの定数をレジスターにロードするコストは大きい・・・

ちゃんと、倍精度の命令を生成しているので、RAYTRACE_sample を走らせてみた。

・・・逆に遅くなる。

調べると、dfpu を指定しない場合、常に32ビットで演算されていたものが、dfpu の指定で、部分的に64ビットの演算が行われる為のようだ。

実際にプログラムで利用する際は、細心の注意が必要だと思える・・・
※インテル CPU の場合、double を float にして計算すると、逆に遅くなるが、それとは対照的だ・・
でも、原理は理解は出来る。

それでも、64ビットの計算を、専用命令で行えるのは、ソフトエミュレーションに比べて格段に速いと思われるので、倍精度命令が生成される事実は心強い。

TFU についての調査

TFU については、仕様が公開されていないので、どのような物か、良く判らない・・・

gcc のソースコード「gcc/config/rx/rx.opt」を見ると・・・

EnumValue
Enum(rx_tfu_types) String(intrinsic) Value(RX_INTRINSIC)

EnumValue
Enum(rx_tfu_types) String(intrinsic,mathlib) Value(RX_MATHLIB)

とあるので、

    -mtfu=intrinsic

    -mtfu=intrinsic,mathlib

のどちらかを設定すれば良さそうだと判る。

「gcc/config/rx/rx.c」で、

    if (TARGET_TFU)
    {
      ADD_RX_TFU_BUILTIN (INIT, "__init_tfu", void);
      ADD_RX_BUILTIN3 (SINCOSF, "sincosf", void, float, float_ptr, float_ptr);
      ADD_RX_BUILTIN4 (ATAN2HYPOTF, "atan2hypotf", void, float, float, float_ptr, float_ptr);
      if (rx_tfu_type == RX_MATHLIB)
      {
        ADD_RX_BUILTIN1 (SINF, "sinf", float, float);
        ADD_RX_BUILTIN1 (COSF, "cosf", float, float);
        ADD_RX_BUILTIN2 (ATAN2F, "atan2f", float, float, float);
        ADD_RX_BUILTIN2 (HYPOTF, "hypotf", float, float, float);
      }
    }

となっている、
初期化の関数と思われる「__init_tfu」は、勝手に呼ぶコードが埋め込まれるのか不明だ。
※自動的に ctor に積まれるのかもしれない・・・

上記関数が演算器で演算されるものと思われるが、どのくらい効果があるのか不明・・・
また、現段階では、演算器が使われているかも不明で、何か、他に設定する事があるのかもとも思う。
とりあえず、TFU に関しては、調査を続ける。

Renesas GNU-RX 8.3.0 を使ってみる

はじめに

前の、投稿で、Renesas は GNU-RX 4.8.x ベースなので、サポートの可能性が低いと言ったが、あれは誤りだったーー

調べたら、「Open Source Tools for Renesas」 とゆー HP があり、ここに登録する事で、gcc-8.3.0 ベースの RX マイコン用ツールチェイン一式をダウンロード出来るようだ。
※ソースコードもダウンロード出来る。

登録が少し面倒で、登録したメールアドレスに起因するアクティベーションコードが発行され、そのコードが無いと、ツールをインストール出来ないようだが、コンパイラは制限無く使えるようだ。

ただ、gcc のソースツリーとは異なる実装を行っており、オプションが異なる。

「GNU Tools」は、「CyberTHOR Studios Limited」が保守管理しているようだが、ナゾの組織だ・・
※以前のKPITに類する物なのかもしれない。

それにしても、「知らない」とは恐ろしい・・・
このツールベースで、ルネサスの IDE から動かせば、E1エミュレータなどを使って、ステップ実行も出来そうに思う。

構成

このツールチェインは、以下のツールをまとめた物のようだ。

  • binutils_rx_2.24_2020q2
  • gcc_rx_8.3.0_2020q2
  • newlib_rx_3.1.0_2020q2
  • gdb_rx_7.8.2_2020q2

ターゲットヘルプの出力:

% rx-elf-gcc --target-help
The following options are target specific:
  -fpu                        Enable the use of RX FPU instructions.  This is
                              the default.
  -m32bit-doubles             Stores doubles in 32 bits.  This is the default.
  -m64bit-doubles             Store doubles in 64 bits.
  -mallow-string-insns        Enables or disables the use of the SMOVF, SMOVB,
                              SMOVU, SUNTIL, SWHILE and RMPA instructions.
                              Enabled by default.
  -mas100-syntax              Generate assembler output that is compatible with
                              the Renesas AS100 assembler.  This may restrict
                              some of the compiler's capabilities.  The default
                              is to generate GAS compatible syntax.
  -mbig-endian-data           Data is stored in big-endian format.
  -mcpu=                      Specify the target RX cpu type.
  -mdfpu                      Enable the use of RX DFPU instructions.
  -mgcc-abi                   Enable the use of the old, broken, ABI where all
                              stacked function arguments are aligned to 32-bits.
  -mint-register=             Specifies the number of registers to reserve for
                              interrupt handlers.
  -misa=                      Specify RX ISA version.
  -mjsr                       Always use JSR, never BSR, for calls.
  -mlarge-function-growth=    Permited value of the limit growth of large
                              function (in percent).
  -mlittle-endian-data        Data is stored in little-endian format.
                              (Default).
  -mlra                       Enable the use of the LRA register allocator.
  -mmax-constant-size=        Maximum size in bytes of constant values allowed
                              as operands.
  -mno-balign                 Do not use .balign
  -mpid                       Enables Position-Independent-Data (PID) mode.
  -mrelax                     Enable linker relaxation.
  -mrx-abi                    Enable the use the standard RX ABI where all
                              stacked function arguments are naturally aligned.
                              This is the default.
  -mrxpeephole                Coremark improvement.
  -mrxv2-fsqrt                Enable to use of FSQRT hardware instruction for
                              RXv2 instruction set
  -msave-acc-in-interrupts    Specifies whether interrupt functions should save
                              and restore the accumulator register.
  -msim                       Use the simulator runtime.
  -msmall-data-limit=         Maximum size of global and static variables which
                              can be placed into the small data area.
  -mtfu=                      Enable the use of RX TFU instructions.
  -mwarn-multiple-fast-interrupts Warn when multiple, different, fast interrupt
                              handlers are in the compilation unit.
  -nofpu                      Disable the use of RX FPU instructions.

Assembler options
=================

Use "-Wa,OPTION" to pass "OPTION" to the assembler.

 RX specific command line options:
  --mbig-endian-data
  --mlittle-endian-data [default]
  --m32bit-doubles [default]
  --m64bit-doubles
  --muse-conventional-section-names
  --muse-renesas-section-names [default]
  --msmall-data-limit
  --mrelax
  --mpid
  --mint-register=<value>
  --mcpu=<rx100|rx200|rx230|rx600|rx610|rx64m|rx66T|rx71m|rx72T>
  --misa=<v1|v2|v3>
  --mno-allow-string-insns  --mgcc-abi
  --mrx-abi [default]

Linker options
==============

Use "-Wl,OPTION" to pass "OPTION" to the linker.

elf32rx:
  --build-id[=STYLE]          Generate build ID note
  -z common-page-size=SIZE    Set common page size to SIZE
  -z defs                     Report unresolved symbols in object files.
  -z execstack                Mark executable as requiring executable stack
  -z max-page-size=SIZE       Set maximum page size to SIZE
  -z muldefs                  Allow multiple definitions
  -z noexecstack              Mark executable as not requiring executable stack
  --no-flag-mismatch-warnings Don't warn about objects with incompatible
                                endian or dsp settings
  --flag-mismatch-warnings    Warn about objects with incompatible
                                endian, dsp or ABI settings
  --ignore-lma                Ignore segment LMAs [default]
                                (for Renesas Tools compatibility)
  --no-ignore-lma             Don't ignore segment LMAs

注目するのは・・

--mcpu=<rx100|rx200|rx230|rx600|rx610|rx64m|rx66T|rx71m|rx72T>

RX72M、RX72N、RX66N などの CPU タイプは無いが、「RX72T」がある。

そして、

-mdfpu                      Enable the use of RX DFPU instructions.
-mtfu=                      Enable the use of RX TFU instructions.

多分、「倍精度浮動小数点命令」、「三角関数演算器」のサポートがある!

mcpu タイプに RX72N が無いのが、良く判らないが、gcc でも、サポートがされる事が判ったのがうれしい。
※まだ、有効にした場合の検証をしていない・・

MSYS2 からも、ツールのパスを設定すれば、使えるようだ・・

PATH=$PATH:/C/'Program Files (x86)'/'GCC for Renesas RX 8.3.0.202002-GNURX-ELF'/rx-elf/rx-elf/bin

ただ、

Assembler messages:
Warning: cannot compress debug sections (zlib not installed)

上記警告が出る・・・

お馴染み、レイトレースを走らせてみる

いつもの、ベンチマーク「レイトレース」を RX72N Envision Kit で走らせてみた。

何と、「最速」をマークした・・・
※gcc-7.5.0 では、361ms くらいだった・・

-mcpu=rx71m

※RX64M、とRX71M でコアの違いは無いと思うが・・・、とりあえず、RX71M でコンパイルした。
※RX64M でコンパイルしたものと同じバイナリーが出る。

今後の開発環境

今後、アップデートも行われるようなので、このコンパイラを使っていきたいと思う。

Makefile のオプションが変わるので、自分でビルドした gcc とは異なる設定が必要。

# CC_DEFS       =   -mcpu=rx600 -Wa,-mcpu=rxv2
CC_DEFS     =   -mcpu=rx71m -dfpu
# CP_DEFS       =   -mcpu=rx600
CP_DEFS     =   -mcpu=rx71m

上記のように、オプションを変更する。

もう少し、評価したら、Makefile を修正後、コミットする予定なので、自分で gcc をビルドしている人は、GNU-RX 8.3.0 をダウンロードして準備する必要がある。

RXマイコンの開発環境をバージョンアップ

RXマイコンの開発環境をアップグレード

気がつくと、gcc は 10.1.0 がリリースされており、RX マイコンも RXv3 コアがリリースされた事などから、そろそろ、開発環境を見直す時期なのかもしれない。

RX72N の RXv3 コアで大きく変わった事と言えば、倍精度の浮動小数点演算命令がサポートされた事だと思う。
※RXv3 コアでも、倍精度浮動小数点演算をサポートしない、RX66T、RX72T、などがあり多少複雑だ。
※他に、三角関数演算器が追加された。(ただ、この演算器については、使い方が隠蔽されており、実際に使える状態になるのは、メーカーのサポートが必要になると思われる。)

CC-RX では、当然サポートされるだろうけど、GNU-RX でサポートされるかは、今の処不明となっている。
※GNU-RX は gcc-4.8.x をベースにしており、本流のツリーにマージしていない事から、gcc でサポートされる可能性は非常に低いと考えられる。

それもあって、倍精度の命令を生成する仕組みを gcc に実装するにはどうしたら良いかを考え始め、情報を集めている。

binutils-2.34(アセンブラ)は、RXv3 の命令をアセンブル出来るようになっており、問題は無いものと思う。

まずは、binutils をアップグレード

今まで、binutils-2.30 を利用してきたが、このバージョンでは、RX マイコンの RXv3 で新設された命令をアセンブルする事が出来ない。

GNU assembler (GNU Binutils) 2.30

 RX specific command line options:
  --mbig-endian-data
  --mlittle-endian-data [default]
  --m32bit-doubles [default]
  --m64bit-doubles
  --muse-conventional-section-names
  --muse-renesas-section-names [default]
  --msmall-data-limit
  --mrelax
  --mpid
  --mint-register=<value>
  --mcpu=<rx100|rx200|rx600|rx610|rxv2>
  --mno-allow-string-insns

そこで、binutils-2.34 を使う

GNU assembler (GNU Binutils) 2.34

 RX specific command line options:
  --mbig-endian-data
  --mlittle-endian-data [default]
  --m32bit-doubles [default]
  --m64bit-doubles
  --muse-conventional-section-names
  --muse-renesas-section-names [default]
  --msmall-data-limit
  --mrelax
  --mpid
  --mint-register=<value>
  --mcpu=<rx100|rx200|rx600|rx610|rxv2|rxv3|rxv3-dfpu>
  --mno-allow-string-insns

rxv3、rxv3-dfpu が追加されている。

C/C++ コンパイラは、gcc-7.5.0 を使う

現在、gcc-10.1.0 がリリースされているので、色々試してみたが・・・

gcc-9.x 系 ---> デバッグビルド(最適化 -O0 で、割り込みベクターを伴った関数で、gcc がストールする)
gcc-8.x 系 ---> デバッグビルド(最適化 -O0 で、割り込みベクターを伴った関数で、gcc がストールする)
gcc-7.5.0 ---> 問題無し

本来、gcc のストールは、フルバグレポートを送る必要があると思うが、レポートの作り方や送り方を調べるのは時間がかかりそうなので、それは追々対応してみたいと思う。

RX マイコンの専用コードは、6.4.0 とあまり変わらないが、とりあえず、7.5.0 をビルドして実行バイナリーの動作をテストしてみた。
※色々、テストしたが、7.5.0 が安定しているようだー

gcc version 7.5.0 (GCC)
The following options are target specific:
  -fpu                        Enable the use of RX FPU instructions.  This is
                              the default.
  -m32bit-doubles             Stores doubles in 32 bits.  This is the default.
  -m64bit-doubles             Store doubles in 64 bits.
  -mallow-string-insns        Enables or disables the use of the SMOVF, SMOVB,
                              SMOVU, SUNTIL, SWHILE and RMPA instructions.
                              Enabled by default.
  -mas100-syntax              Generate assembler output that is compatible with
                              the Renesas AS100 assembler.  This may restrict
                              some of the compiler's capabilities.  The default
                              is to generate GAS compatible syntax.
  -mbig-endian-data           Data is stored in big-endian format.
  -mcpu=                      Specify the target RX cpu type.
  -mgcc-abi                   Enable the use of the old, broken, ABI where all
                              stacked function arguments are aligned to 32-bits.
  -mint-register=             Specifies the number of registers to reserve for
                              interrupt handlers.
  -mjsr                       Always use JSR, never BSR, for calls.
  -mlittle-endian-data        Data is stored in little-endian format.
                              (Default).
  -mlra                       Enable the use of the LRA register allocator.
  -mmax-constant-size=        Maximum size in bytes of constant values allowed
                              as operands.
  -mpid                       Enables Position-Independent-Data (PID) mode.
  -mrelax                     Enable linker relaxation.
  -mrx-abi                    Enable the use the standard RX ABI where all
                              stacked function arguments are naturally aligned.
                              This is the default.
  -msave-acc-in-interrupts    Specifies whether interrupt functions should save
                              and restore the accumulator register.
  -msim                       Use the simulator runtime.
  -msmall-data-limit=         Maximum size of global and static variables which
                              can be placed into the small data area.
  -mwarn-multiple-fast-interrupts Warn when multiple, different, fast interrupt
                              handlers are in the compilation unit.
  -nofpu                      Disable the use of RX FPU instructions.

newlib は、2.4.0 のままとした。

newlib は、バージョンを上げる場合に注意を要するので、無難な選択として、2.4.0(変更しない)とした。

RX フレームワークをビルドする。

コンパイラが出来たら、全体のプロジェクトをビルドする、まず「debug」ビルドからー

sh all_project_build.sh -debug clean

...

sh all_project_build.sh -debug

問題無い!

次にリリースビルド・・・

sh all_project_build.sh clean

...

sh all_project_build.sh

これも問題無い!、いくつかのバイナリーを実際に動かしてみたが、特に問題になるような動作は認められなかった。

gcc に倍精度命令を出力させる件は時間がかかりそうなので、調査を続ける事として、当面、開発環境は、これで行く事にする。

binutils-2.34
gcc-7.5.0
newlib-2.4.0

MSYS2 で作成した gcc のバイナリーをリンクにアップロードしてある。

CP2102Nを使ったUSBシリアル基板

PCBGOGO で作成した基板

以前に、別件で、PCBGOGO で基板を作る際、同時に注文したもので、部品も揃っていたのだが、ハンダペーストが無かったのもあって、ほったらかしにしていた・・・
※10枚作って、$5だった、安!、送料は$17とかだった・・

それをようやく組み立てた。

ハンダ付けに苦労はしたが、何とか組み立て、接触不良無く、一発で動作した。
※パッドの形状などから、QFN20 のハンダ付けが一番不安だった・・・

KiCAD プロジェクト一式

Silicon Labs CP2102N について

中華の格安モジュール基板で「CP2102」は大量に出回っているのだけど、「CP2102N」は少ない。
仕様的には同じ部分も多いが、マイナーチェンジ版の「N」は、もう一息だった部分が改良されていて、申し分のないものになっている。

  • 一台の PC に複数のデバイスを繋いだ場合の挙動が改善されている。
  • FTDI のメジャーチップより高性能で、同じボーレートでも、送信も受信も速い。
  • カスタムするツールの導入が簡単で、専用の ID や文字列を埋め込める。
  • 安い!(QFN24 で @160 くらい)

QFN パッケージの憂鬱

CP2102N は、パッケージとして、QFN20、QFN24、QFN28 などがあるのだが、KiCADでトラックを引く前に、部品を発注してしまった・・

QFN パッケージでも、手ハンダで取り付け出来るのだが、QFN20 は、角のピンが隠れているし、裏面にGNDパターンがあり、手ハンダでは難しい事が判って、大いに後悔した・・・

QFN24 にしとけば良かった・・・

ハンダペーストを楊枝で、適当に塗って、コテライザーを使って、溶かして付ける。

念の為、フラックスを塗って、サイド側からハンダコテを当ててパッドにハンダを盛る。

これで、何とかなったようだ・・・

接触不良も無く、動いているようだー。

タカチのケースに入れる事を考えた形状

初めから、タカチのケースに入れる事を考えて作ったのだが、急いでいた事もあって、イマイチだった・・・

基板をリューターで削って、何とかなった。
※一応、隅に配線が通らないようにはしている。

3.3V のレギュレータ

RXマイコンでは、3.3Vで動かす事が多いので、3.3Vの三端子を載せてあり、電源電圧として、5V、3.3Vを切り替え出来る。

チップ部品は結構大変

あまり考えずに、1608サイズのチップ部品にしたが、手ハンダはキツイ!
何か、治具を作らないと綺麗に配置するのは難しいかもしれない、視力が衰えている事もあるが、ルーペで拡大しないとならないし、ハンダ付けする時部品を押さえておかないとならない。

rx_prog の不具合?

FTDI より 高速に送信、受信が出来るものの、大きなファイルを書き込むと、エラーで止まる現象が起こる。
※FTDIでも発生していたのだが・・

原因は調査中だが、CP2102Nの問題では無さそうで、MSYS2のPOSIX系シリアルドライバーのハンドリングにありそうだ、こちらは、簡単に原因を見つけられなかったので、解析中。

2020-06-08 01:10:15 Monday
問題は解決したものと思う:

  • erase-page、write-page のループで、間隔を空けないで、次のコマンドを送ると、RXマイコン側がストールするようだ。
  • erase-page の場合、ページ(256バイト)毎に2ミリ秒の待ちを入れた。
  • write-page の場合、ページ(256バイト)毎に5ミリ秒の待ちを入れた。
  • 上記待ちを入れた状態で、ストールせず、正常に書き込めた。
  • 待ちを入れない場合、RXマイコン側でバッファオーバーランなどが発生するのかも・・
  • この修正は、コミットした。(rx_prog)

2020-06-09 04:34:32 Tuesday

  • rx_prog を拡張して、erase-page-wait、write-page-wait パラメーターを追加。
  • rx_prog.conf にも変数を追加。
  • 設定は、マイクロ秒単位、標準で、それぞれ、2000、5000を設定してある。

FTDIとシリコンラボで、ボーレートが同じでも全体のパフォーマンスが異なるのは・・
以下のような理由によるものと思う:

  • 基本的にドライバーの違いのようだ。
  • FTDIでは、小さいパケットを送ると、「都度送る」
  • シリコンラボの場合は、「貯めて送る」と、「バッファにあったら送る」
  • USBサービスの関係から、「都度送る」と間隔が空いてしまうようだ。
  • ハンドシェークを行わない場合、受け手の仕様によっては、相性問題が発生するかもしれない。
  • 受け手の作りが「甘い」と、FTDIの方が相性が良い事になる感じか・・

FTDI と シリコンラボ速度比較

AUDIO_sample/RX64M/audio_sample.mot (628460 バイト)

rx-elf-size audio_sample.elf
   text    data     bss     dec     hex filename
 541476      48   86936  628460   996ec audio_sample.elf
-rw-r--r-- 1 hira hira 1353908  6月  8 03:13 audio_sample.mot

FTDI(FT232XS) @230 秋月電子:

time rx_prog -P COM5 -d RX64M --progress --erase --write --verify audio_sample.mot
Erase:  #################################################
Write:  #################################################
Verify: #################################################
0.39user 1.90system 5:57.08elapsed 0%CPU (0avgtext+0avgdata 9304maxresident)k
0inputs+0outputs (2442major+0minor)pagefaults 0swaps

※FTDIのメジャーデバイス、FT232RL(@400) でも結果は同じ。


SiliconLabs(CP2102N) @160 DigiKey:

time rx_prog -d RX64M --progress --erase --write --verify audio_sample.mot
Erase:  #################################################
Write:  #################################################
Verify: #################################################
0.28user 1.09system 1:51.59elapsed 1%CPU (0avgtext+0avgdata 9372maxresident)k
0inputs+0outputs (2460major+0minor)pagefaults 0swaps

上記のように3倍以上違い、値段も安い!

なので、CP2102N を使うべきー

RX72N Envision Kit での開発(その5)GUI編

GUI_sample (RX65N/RX72N Envision Kit)

C++ GUI フレームワーク

RX65N Envision Kit から採用された GUI ライブラリとして、emWin が既にあります。

ですが、これは C で実装されており、アプリケーションを作るには、ハードルが高いと思えます。

以前に PC 向けに、OpenGL を描画エンジンとして使った、GUI フレームワーク glfw3_app を実装した経緯があり、GUI 操作に必要な構成は研究して判っているつもりなので、それらの知見を使い、組み込みマイコンでも扱いやすいようにダイエットした GUI Widget のフレームワークを実装しました。

「漢字」が標準で使えるのも特徴です。
※現在は16x16ピクセルのフォント「東雲16ドット漢字フォント」を利用させてもらっています。
※メモリに余裕があるので、ROM 領域にビットマップとして持っています。(260キロバイト程度消費する)
※漢字が必要無い場合は、含めない事も出来ますし、又はSDカードから読み込んでキャッシュする事も出来ます。

描画に関しては、DRW2D エンジンが無くても利用可能なように、現在はソフトウェアーで処理しています。
※RX64M/RX71M などに LCD をバス接続した場合や、フレームバッファ内蔵の LCD を接続する場合を考慮しています。
※DRW2D でアクセレートする事も可能な構造にしてあります。(DRW2D 版は開発中)

このフレームワークでは、記憶割り当てを使わない事を念頭に設計してあり、比較的小規模なアプリ向けとして機能を絞ってあります。
もっと「リッチ」な物が必要なら、新たに実装して追加出来る余地も残してあります。

現状で、用意してあるのは以下の Widget です。
※ソースコードは、「RX/graphics」以下にあります。

Widget 機能 ソース 完成度
frame フレーム frame.hpp
button ボタン button.hpp
check チェックボックス check.hpp
radio ラジオボタン radio.hpp
slider スライダー slider.hpp
menu メニュー menu.hpp
spinbox スピンボックス spinbox.hpp ×
group グループ管理 group.hpp

「見た目」は、シンプルなものにしてあり、ピクセルデータを用意する事無く、プリミティブの組み合わせで描画しています。

今後、必要な widget を拡充して行く予定です。

全体の構成

GUI は一般的には、メッセージ通信により、機能を提供する事が一般的だと思います。
※代表的なのは Windows でしょうか・・

ただ、この方式は、冗長なコードになりやすいし、機能が複雑になるとメッセージの順番や、メッセージのマスクなど、トリッキーなコードになりやすいと思います。

このフレームワークでは、「同期式」と呼ばれる、リアルタイムなゲームで使われるようなシステムを使っています。

画面の更新は 60Hz 程度なので、それに合わせて、タッチパネルの情報を取得して順次処理を行っています。

又、C++ の機能を積極的に利用する事で、シンプルな構成に出来、アプリケーションを実装しやすくします。

GUI 部品管理

この GUI フレームワークでは、管理する Widget の数をテンプレートで定義しています(有限個)。

    // 最大32個の Widget 管理
    typedef gui::widget_director<RENDER, TOUCH, 32> WIDD;
    WIDD        widd_(render_, touch_);

new などを使い、動的に増やす事も出来ますが、メモリが足りなくなった場合の対応を考えると、「有限数」の方が管理し易いように思います。

LCD は 4.3 インチで、解像度も 480 x 272 程度と小さいので、PC のディスクトップのように、複雑でリッチな GUI は、当面必要無いと思える為です。


C++ での実装で、少し問題な点があります、現状の実装では、widget の追加と削除は、グローバル関数としています。

extern bool insert_widget(gui::widget* w);
extern void remove_widget(gui::widget* w);

この関数は、「widget_director」テンプレートクラス内の API「insert、remove」を呼ぶようにしなければなりません。
そこで、サンプルでは、以下のように、widget_director のインスタンスを置いてあるソースで定義してあります。

/// widget の登録・グローバル関数
bool insert_widget(gui::widget* w)
{
    return widd_.insert(w);
}

/// widget の解除・グローバル関数
void remove_widget(gui::widget* w)
{
    widd_.remove(w);
}

※他に良い方法を思い付かなかったので、このように、あまりスマートとは言えない方法になっています。
※こうしておけば、widget_director を複数持って、場合により、切り替える事も出来そうです。

widget_director は、描画ループの中から「update」を呼び出せば、全ての管理が行われます。

    while(1) {
        render_.sync_frame();
        touch_.update();

        widd_.update();

...

    }

※「touch_.update();」は、タッチパネルインターフェース(FT5206)のサービスです。
※widget_director テンプレートでは、レンダリングクラスと、タッチパネルクラスの型を必要とし、コンストラクター時、参照で与えます。

    // GLCDC 関係リソース
    typedef device::glcdc_mgr<device::GLCDC, LCD_X, LCD_Y, PIX> GLCDC;

    // フォントの定義
    typedef graphics::font8x16 AFONT;
//  for cash into SD card /kfont16.bin
//  typedef graphics::kfont<16, 16, 64> KFONT;
    typedef graphics::kfont<16, 16> KFON
    typedef graphics::font<AFONT, KFONT> FONT;

    // DRW2D レンダラー
//  typedef device::drw2d_mgr<GLCDC, FONT> RENDER;
    // ソフトウェアーレンダラー
    typedef graphics::render<GLCDC, FONT> RENDER;

    GLCDC       glcdc_(nullptr, reinterpret_cast<void*>(LCD_ORG));
    AFONT       afont_;
    KFONT       kfont_;
    FONT        font_(afont_, kfont_);
    RENDER      render_(glcdc_, font_);

    FT5206_I2C  ft5206_i2c_;
    typedef chip::FT5206<FT5206_I2C> TOUCH;
    TOUCH       touch_(ft5206_i2c_);

    // 最大32個の Widget 管理
    typedef gui::widget_director<RENDER, TOUCH, 32> WIDD;
    WIDD        widd_(render_, touch_);

widget 親子関係とグループ化

widget は、階層構造が可能なようにしてあり、座標管理も差分で行えるようにしてあります。
※その為、親、子、関係があります。

また、ラジオボタンのように、自分の変化を受けて、他も変化が必要な場合があり、この場合、グループ化が役立ちます。

以下のように、3つのラジオボタンをグループ化しておけば、チェック、アンチェックの管理は自動で行えます。
※ラジオボタンの実装では、自分の状態が変化した時、「自分の親」に登録されている、「子」で、ラジオボタンを調べて、その状態を変更しています。

    typedef gui::group<3> GROUP3;
    GROUP3      group_(vtx::srect(   10, 10+50*2, 0, 0));
    typedef gui::radio RADIO;
    RADIO       radioR_(vtx::srect(   0, 50*0, 0, 0), "Red");
    RADIO       radioG_(vtx::srect(   0, 50*1, 0, 0), "Green");
    RADIO       radioB_(vtx::srect(   0, 50*2, 0, 0), "Blue");

※ラジオボタンは、グループ化する為、グループ座標の差分となっている。
※各 widget では、サイズ指定で「0」を指定すると、標準的なサイズがロードされます、この定義は、各 widget 内で定義されています。

C++ では、オペレータを使って、特別な機能を割り当て出来ます。
この場合、「+」は、group への、radio ボタン登録として機能します。

    // グループにラジオボタンを登録
    group_ + radioR_ + radioG_ + radioB_;

コールバックとラムダ式

C++ では、C++11 からラムダ式が使えるようになりました。

GUI の操作では、何か「変化」が発生した場合に、コールバック関数が呼ばれます。

C++ では、std::function テンプレートを使っています。

たとえば、button widget では、以下のように定義してあります。

    typedef std::function<void(uint32_t)> SELECT_FUNC_TYPE;

ボタンが押された(ボタンをタッチして、離れた瞬間)時、押された回数をパラメータに、コールバック関数が呼ばれます。

C++ では、ラムダ式が使えるので、コールバック関数を登録しないで、ラムダ式により、直接動作を実装出来ます。

    button_.at_select_func() = [=](uint32_t id) {
        utils::format("Select Button: %d\n") % id;
    };

これは、非常に便利で、アプリケーションを作成する時は大いに役立ち、シンプルに実装出来ます。
※クラス内の場合は、キャプチャーを「[this]」とする事で、クラス内のメソッドを呼べるようになります。

GUI サンプルのメイン

サンプルでは、一通りの GUI を定義、登録して、各 widget にラムダ式を使って、挙動を表示(シリアル出力)するようにしています。

widget の定義と登録:
※各 widget のコンストラクターで、widget_director へ登録される。

    typedef gui::button BUTTON;
    BUTTON      button_  (vtx::srect(   10, 10+50*0, 80, 32), "Button");
    typedef gui::check CHECK;
    CHECK       check_(vtx::srect(   10, 10+50*1, 0, 0), "Check");  // サイズ0指定で標準サイズ
    typedef gui::group<3> GROUP3;
    GROUP3      group_(vtx::srect(   10, 10+50*2, 0, 0));
    typedef gui::radio RADIO;
    RADIO       radioR_(vtx::srect(   0, 50*0, 0, 0), "Red");
    RADIO       radioG_(vtx::srect(   0, 50*1, 0, 0), "Green");
    RADIO       radioB_(vtx::srect(   0, 50*2, 0, 0), "Blue");
    typedef gui::slider SLIDER;
    SLIDER      sliderh_(vtx::srect(200, 20, 200, 0), 0.5f);
    SLIDER      sliderv_(vtx::srect(440, 20, 0, 200), 0.0f);
    typedef gui::menu MENU;
    MENU        menu_(vtx::srect(120, 70, 100, 0), "ItemA,ItemB,ItemC,ItemD");

登録された GUI を有効にして、コールバック関数に、ラムダ式で挙動を実装する。

    void setup_gui_()
    {
        button_.enable();
        button_.at_select_func() = [=](uint32_t id) {
            utils::format("Select Button: %d\n") % id;
        };

        check_.enable();
        check_.at_select_func() = [=](bool ena) {
            utils::format("Select Check: %s\n") % (ena ? "On" : "Off");
        };

        // グループにラジオボタンを登録
        group_ + radioR_ + radioG_ + radioB_;
        group_.enable();  // グループ登録された物が全て有効になる。
        radioR_.at_select_func() = [=](bool ena) {
            utils::format("Select Red: %s\n") % (ena ? "On" : "Off");
        };
        radioG_.at_select_func() = [=](bool ena) {
            utils::format("Select Green: %s\n") % (ena ? "On" : "Off");
        };
        radioB_.at_select_func() = [=](bool ena) {
            utils::format("Select Blue: %s\n") % (ena ? "On" : "Off");
        };
        radioG_.exec_select();  // 最初に選択されるラジオボタン

        sliderh_.enable();
        sliderh_.at_select_func() = [=](float val) {
            utils::format("Slider H: %3.2f\n") % val;
        };
        sliderv_.enable();
        sliderv_.at_select_func() = [=](float val) {
            utils::format("Slider V: %3.2f\n") % val;
        };

        menu_.enable();
        menu_.at_select_func() = [=](uint32_t pos, uint32_t num) {
            char tmp[32];
            menu_.get_select_text(tmp, sizeof(tmp));
            utils::format("Menu: '%s', %u/%u\n") % tmp % pos % num;
        };

まとめ

やはり、GUI のような構造的な体系には、C++ が必要だと痛感します。

今回の GUI フレームワークで、widget は「継承」を使っていますが、GUI の部品はスタティックに定義してあり、「new」や「delete」もしません。
※実際は、偶然そうなっているのでは無く、「しなくて済むよう」に工夫しています。

複雑な構成のアプリケーションを実装したい場合、シーン管理を使って、各シーンで登場する GUI を定義、実装すれば、シーン毎に GUI の定義を別ける事が出来ます。
※「LOGGER_sample」を参照。(未完成で実装中です)

シーンの定義については、「common/scene.hpp」テンプレートクラスを参照して下さい。

RX72N Envision Kit での開発(その4)SDCARD 編

RX72N Envision Kit の再販が始まったようだ・・・

チップワンストップ:
2020-06-04 17:31:31 Thursday:
※在庫8個だったけど、1日たったら、完売してた・・・、まだまだ潤沢に流通していないようだ・・

SD-CARD 操作と、関係ユーティリティの使い方

組み込みマイコンでは、SDカードのアクセスは必須な機能となっています。

ChaN さんによる FatFs ライブラリがオープンソースとして公開されており、非常に簡単に利用する事が出来ます。
※FatFs ライブラリは、常に進化しており、なるべく最新版を使う事が望まれます、自分のフレームワークでは、ff13c を利用しています。

SDカードにアクセスするには、簡易的な SPI モードと、SD モードがあります。

このサンプルでは、以下のように、各デバイスで、SDカードのアクセスを行います。

RX24T RX64M DIY RX64M GR-KAEDE RX65N RX72N
RSPI0 Soft SPI RSPI SDHI SDHI

RX64M から搭載(RX64M/Rx71M はオプション)された SDHI インターフェースを使った、SD モードのネィティブモードドライバーも利用可能です。
SD モードでは、4ビットバスを使った高速な転送が可能となっています。
※RX64M は SDHI はオプションになっており、自分が実験したデバイスは、「SDHI なし」なので、Soft-SPI で利用しています。

SDカードのアクセスでは、なるべく、簡単で柔軟性のある宣言で、実装が出来るように工夫してあり、簡単に扱う事が出来ます。

SDCARD_sample プロジェクト

インターフェースの宣言

RX24T

    typedef device::rspi_io<device::RSPI0> SDC_SPI;
    typedef device::PORT<device::PORT6, device::bitpos::B5> SDC_SELECT; ///< カード選択信号
    typedef device::PORT<device::PORT6, device::bitpos::B4, 0> SDC_POWER;   ///< カード電源制御
    typedef device::PORT<device::PORT6, device::bitpos::B3> SDC_DETECT; ///< カード検出
    typedef device::NULL_PORT SDC_WPRT;  ///< カード書き込み禁止ポート設定
    typedef fatfs::mmc_io<SDC_SPI, SDC_SELECT, SDC_POWER, SDC_DETECT, SDC_WPRT> SDC;
    SDC_SPI sdc_spi_;
    SDC     sdc_(sdc_spi_, 20'000'000);
  • RX24T では、RSPI0 を使います。
  • RSPI では、MISO、MOSI、SCLK の3つの信号を利用します。
  • 「カード選択信号」は、PORT6、B5 を使います。
  • 「カード電源制御」は、PORT6、B4 で、アクティブ Low の信号としています。
  • 「カード検出信号」は、PORT6、B3 を使います。
  • 「書き込み禁止信号」は、使わない為「NULL_PORT」を指定します。
  • 「fatfs::mmc_io」クラスは、SPI 制御の為のインターフェースクラスです。
  • 上記のように、RSPI0 の定義を、mmc_io にパラメータとして定義し、インスタンスをコンストラクターに渡します。
  • RSPI の上限のクロック速度を指定します。

RX64M / GR-KAEDE

  #ifdef GR_KAEDE
    typedef device::rspi_io<device::RSPI> SDC_SPI;
    typedef device::PORT<device::PORTC, device::bitpos::B4> SDC_SELECT; ///< カード選択信号
    typedef device::NULL_PORT  SDC_POWER;   ///< カード電源制御(常に電源ON)
    typedef device::PORT<device::PORTB, device::bitpos::B7> SDC_DETECT; ///< カード検出
    typedef device::NULL_PORT SDC_WPRT;  ///< カード書き込み禁止
  #else
    typedef device::PORT<device::PORTC, device::bitpos::B3> MISO;
    typedef device::PORT<device::PORT7, device::bitpos::B6> MOSI;
    typedef device::PORT<device::PORT7, device::bitpos::B7> SPCK;
    typedef device::spi_io2<MISO, MOSI, SPCK> SDC_SPI;  ///< Soft SPI 定義
    typedef device::PORT<device::PORTC, device::bitpos::B2> SDC_SELECT; ///< カード選択信号
    typedef device::PORT<device::PORT8, device::bitpos::B2, 0> SDC_POWER;   ///< カード電源制御
    typedef device::PORT<device::PORT8, device::bitpos::B1> SDC_DETECT; ///< カード検出
    typedef device::NULL_PORT SDC_WPRT;  ///< カード書き込み禁止
  #endif
    SDC_SPI sdc_spi_;
    typedef fatfs::mmc_io<SDC_SPI, SDC_SELECT, SDC_POWER, SDC_DETECT, SDC_WPRT> SDC;
    SDC     sdc_(sdc_spi_, 25'000'000);
  • DIY ボードでは、ソフト SPI の定義をしています。
  • GR-KAEDE では RSPI の定義をしています。
  • GR-KAEDE の RSPI/SD カードインターフェースは、E1 デバッガー端子と共有になっておりプルダウン抵抗があります。
  • SD カードの初期化時、プルアップが必要なので、対策する必要があります。
  • 対策方法は、main.cpp の説明を参照して下さい。
  • ソフト SPI では、クロックの生成など、全てソフトウェアーで行う為、自由度がありますが、速度は RSPI に比べてかなり遅いです。
  • MISO、MOSI、SPCK のポートを定義して、ソフト SPI クラス (spi_io2) として宣言します。
  • spi_io2 クラスは、SD カードの SPI 制御向けに特化したクラスとなっています。
  • 「カード選択信号」は、PORTC、B2 を使います。
  • 「カード電源制御」は、PORT8、B2 で、アクティブ Low の信号としています。
  • 「カード検出信号」は、PORT8、B1 を使います。
  • 「書き込み禁止信号」は、使わない為「NULL_PORT」を指定します。
  • 「fatfs::mmc_io」クラスは、SPI 制御の為のインターフェースクラスです。
  • 上記のように、Soft-SPI の定義を、mmc_io にパラメータとして定義し、インスタンスをコンストラクターに渡します。
  • ソフト SPI の上限のクロック速度を指定していますが、目安程度でしかありません。

RX65N Envision Kit

    typedef device::PORT<device::PORT6, device::bitpos::B4, 0> SDC_POWER;  ///< 「0」でON
    typedef device::NULL_PORT SDC_WPRT;  ///< カード書き込み禁止ポート設定
    typedef fatfs::sdhi_io<device::SDHI, SDC_POWER, SDC_WPRT, device::port_map::option::THIRD> SDC;
    SDC     sdc_;
  • SDHI を使う場合、ポートは決められた組み合わせの中から選択する必要があります。
  • SDHI が使うポートの詳細は、RX65x/port_map.hpp を参照して下さい、「候補3」です。
  • 「カード電源制御」は、PORT6、B4 で、アクティブ「Low」です。
  • 「書き込み禁止信号」は、使わない為「NULL_PORT」を指定します。
  • RX65N Envision Kit では、SDカードインターフェースは未実装となっています。
  • 電源制御は、専用 IC を使わず、Pチャネルの MOS-FET をスイッチとして流用している為、アクティブ「Low」となっています。
  • クロック速度は、現在の実装では、30MHz となっており、ハードコードになっています。

RX72N Envision Kit

    typedef device::PORT<device::PORT4, device::bitpos::B2> SDC_POWER;
    typedef device::NULL_PORT SDC_WPRT;  ///< カード書き込み禁止ポート設定
    typedef fatfs::sdhi_io<device::SDHI, SDC_POWER, SDC_WPRT, device::port_map::option::THIRD> SDC;
    SDC     sdc_;
  • SDHI を使う場合、ポートは決められた組み合わせの中から選択する必要があります。
  • SDHI が使うポートの詳細は、RX72N/port_map.hpp を参照して下さい、「候補3」です。
  • 「カード電源制御」は、PORT4、B2 で、アクティブ「High」です。
  • 「書き込み禁止信号」は、使わない為「NULL_PORT」を指定します。
  • クロック速度は、現在の実装では、30MHz となっており、ハードコードになっています。
  • 実験では、60MHz での動作も確認しましたが、RX65N Envision Kit では、不安定だったので、余裕を持って 30MHz としています。
  • クロック信号における、インピーダンスのマッチングを行えば、60MHz でも安定して利用可能と思います。

FatFs とのインターフェース定義

  • FatFs は、初期化(マウント)、セクター単位の読出し、書き込みなどの API を用意するだけで良く、以下のように、API を宣言します。
  • FatFs は C のプログラムなので、「extern "C"」で、C から呼べるようにしておきます。
  • ファイル書き込み時、タイムスタンプで使う「時間」が必要です、このフレームワークでは、GMT を使っています。
extern "C" {

    DSTATUS disk_initialize(BYTE drv) {
        return sdc_.disk_initialize(drv);
    }

    DSTATUS disk_status(BYTE drv) {
        return sdc_.disk_status(drv);
    }

    DRESULT disk_read(BYTE drv, BYTE* buff, DWORD sector, UINT count) {
        return sdc_.disk_read(drv, buff, sector, count);
    }

    DRESULT disk_write(BYTE drv, const BYTE* buff, DWORD sector, UINT count) {
        return sdc_.disk_write(drv, buff, sector, count);
    }

    DRESULT disk_ioctl(BYTE drv, BYTE ctrl, void* buff) {
        return sdc_.disk_ioctl(drv, ctrl, buff);
    }

    DWORD get_fattime(void) {
        time_t t = 0;
#if defined( ENABLE_RTC) || defined(ENABLE_I2C_RTC)
        rtc_.get_time(t);
#else
        t = utils::str::make_time(nullptr, nullptr);
#endif
        return utils::str::get_fattime(t);
    }
}

初期化、及び、SDカード挿入の監視

  • このサンプルでは、1/100 秒のタイマーを使って、ループを行い、その中で、SD カードの監視を行います。
  • ループ間隔は、スイッチの状態をサンプリングしており、チャタリングを除去するフィルターとして働きます。
    while(1) {
        cmt_.at_task().sync_100hz();

        sdc_.service();

        command_();
    }

簡単なシェル(SDカード操作)

  • 1/100 のループ中、シリアル入力も監視しており、バッファリングしています。
  • これを利用して簡単なコマンド操作として機能するようにしています。
    -「help」と打つと簡単なヘルプが表示されます。
  • 簡易的な実装で、厳密な入力のチェックを行っていません。
コマンド オプション 機能
ls -l ディレクトリのリスティング
cd カレントディレクトリの移動
pwd カレントディレクトリパスの表示
free SDカードの全容量、空き容量の表示
Start SD-CARD Access sample for 'RX72N' 240[MHz]
# ls
SoftShut.wad    TFALL.WAD       TIMER.WAD       TOFF.WAD        TON.WAD
VSOFF.WAD       VSON.WAD        TEST.BIN        System Volume Information/
inv_roms/       inv_wavs/       Dragon_Quest2_fix.nes
DragonQuest_J_fix.nes           Galaga_J.nes    GALAXIAN.NES    GRADIUS.NES
kfont16.bin     NoImage.jpg     Pac-Man.nes     Solstice_J.nes
Super_mario_brothers.nes        XEVIOUS.NES     HRC_logo_s.bmp
Super Mario USA (Japan).nes     Audios/
/ # cd Audios
/Audios # ls
AlbumArtSmall.jpg               Folder.jpg      Thumbs.db       AlfredBrendel/
CarpentersBest/ Chopin_Recital/ Denim/          DRACULA/        GeorgeMichael/
Glenn Gould/    JAnime/         KNOCK ON MY DOOR/               Make It Big/
Michael Jackson/                Mornin' After/  NarcisoYepes/
Over The Top_ Original Motion Picture Soundtrack/               PRIMO/
Rachmaninov Symphonies No_1-3 L_Maazel&Berliner Philharmoniker/
Rachmaninov_ Piano Concertos #2 & 3/            SoundTrack/
The Best Of Mission_ Impossible/
THE ORIGINAL~Songs for Scheherazade~/           Vienna Recital/
うる星やつら ジュークボックス/  のだめカンタービレ Best 100/
もってけ! セーラーふく/         イーハトーヴ交響曲/
コンピレーション/               スペシャル/     ドラゴンクエスト/
ボーカロイド/   ラフマニノフ_ ピアノ協奏曲 #1 & #2/
ラフマニノフ_ ピアノ協奏曲 #3 & #4/
ラフマニノフ_ ピアノ協奏曲第1番 & 第2番/        リシッツァ/
ルパン三世’71 ME TRACKS/           今井美樹/       仲道郁代/
倉木麻衣/       國府田マリ子/   坂本真綾/       堀江由衣/       太田貴子/
奥慶一/         宇多田ヒカル/   宮里久美/       工藤静香/       庄司紗矢香/
林原めぐみ/     椎名へきる/     相川七瀬/       西山ギター課題/ 金月真美/
/Audios # cd ..
/ # ls -l
     16384 May  4 2018 06:08  SoftShut.wad
     16384 May  4 2018 06:08  TFALL.WAD
     16384 May  4 2018 06:08  TIMER.WAD
     16384 May  4 2018 06:08  TOFF.WAD
     16384 May  4 2018 06:08  TON.WAD
     16384 May  4 2018 06:08  VSOFF.WAD
     16384 May  4 2018 06:08  VSON.WAD
   1048576 Jan  1 1980 09:00  TEST.BIN
           Nov  1 2017 07:25 /System Volume Information
           Mar 10 2019 07:37 /inv_roms
           Mar 10 2019 07:37 /inv_wavs
    131088 Feb 16 2017 03:00  Dragon_Quest2_fix.nes
     65552 Feb 16 2017 02:56  DragonQuest_J_fix.nes
     24592 Jun 21 2000 07:56  Galaga_J.nes
     40976 Feb 28 1997 17:26  GALAXIAN.NES
     65680 Apr 26 1997 01:53  GRADIUS.NES
    249824 Jun 25 2018 18:14  kfont16.bin
     49785 Nov 14 2018 06:58  NoImage.jpg
     24592 Dec 24 1996 20:32  Pac-Man.nes
    131088 Sep 20 1999 10:59  Solstice_J.nes
     40976 Jul  8 2018 23:59  Super_mario_brothers.nes
     40976 Jul 17 1997 23:31  XEVIOUS.NES
     86072 Sep  5 2018 19:57  HRC_logo_s.bmp
    262160 Jul  1 2018 12:29  Super Mario USA (Japan).nes
           Nov 18 2019 01:56 /Audios
Total 25 files
/ # free
1335360/15629312 [KB] (8.5%)
/ # write test.bin
Write: 'test.bin'
Write Open:  280 [ms]
Write: 395 KBytes/Sec
Write Close: 4 [ms]
/ # read test.bin
Read: 'test.bin'
Read Open:  1 [ms]
Read: 1171 KBytes/Sec
Read Close: 0 [ms]
/ # help
    ls [-l] [file]      list current directory (-l: long)
    pwd                 current directory path
    cd [file]           change current directory
    free                list disk space
    write filename      test for write
    read filename       test for read
    time [yyyy/mm/dd hh:mm[:ss]]   set date/time
  • write コマンドは、書き込み速度を計測します。
  • read コマンドは、読み込み速度を計測します。
  • time コマンドは、RTC への時間設定を行います。
  • RX65N Envision Kit/RX72N Envision Kit では、RTC は無効になっており利用出来ません。
  • サンプルには、I2C 接続の RTC を扱う場合を実装してあります。

Makefile での FatFs 設定

  • Makefile で、「-DFAT_FS -DFAT_FS_NUM=2」を設定し、コンパイル時に渡す必要があります。
    -「FAT_FS_NUM」は、POSIX の open 関数で同時にオープン出来るファイル数です。
  • 大きくすると、管理領域としてメモリを消費します。

まとめ

  • C++ のテンプレートクラスは、強力で、C 言語では実現できないような柔軟性を提供しています。
  • SDカードのインターフェースは、それなりに複雑ですが、サンプルでは、RSPI、ソフトSPI、SDHI に対応する方法を提示しています。
  • 異なるハードウェアーでも、同じように扱う工夫は、まさにオブジェクト指向的な概念によるものです。
  • この例からも、C++ が組み込みマイコンのプログラムに向いている事が判ると思います。
  • 問題なのは、C++ を学ぶのは、実際にはそれ程簡単では無く、ある程度の修練が必要です。
  • C++ は C 言語から派生したので、C 言語を自由に扱えるレベルの人でも、判ったつもりになっている人が多いようです。
  • 「C++ は C 言語とは全く違うプログラム言語である」ここから始める必要があると思います。
  • ただ、学ぶハードルは、それなりに低くなっており、色々な勉強会や、講習会に参加する事もできます。
  • 学ぶには、ある程度の時間や、修練は必要でしょうが、そのメリットは十分にあるものと思います。

RX72N Envision Kit での開発(その3)CMT 編

コンペアマッチタイマ・テンプレートの使い方(cmt_mgr.hpp)

前回の SCI 編で既に使っていますが、CMT を使う場合を、詳しく説明したいと思います。

CMT は一定間隔でイベントを発生するタイマーで、割り込みを発生させる事が出来ます。
通常、マスタークロックは、PCLKB が使われます。
PCLKB が 60MHz の場合、1.7Hz~7.5MHz 程度の時間間隔をプログラム出来ます。
※分周比は制限があり、自由な周波数を設定出来るわけではありません。

このテンプレートクラス(common/cmt_mgr.hpp)も、RX マイコンの C++ フレームワークの中では初期の段階から改修されてきているクラスです。
通常行いたい事を、最低限の手順で出来るように工夫してあります。

FreeRTOS のようなマルチタスクOSを使わない場合で、平行処理的な制御を扱う場合には欠かせない物です。
比較的簡単に平行処理的な概念を利用する事が出来るので、シンプルなアプリケーションには、逆に使いやすいものです。

RX マイコンでは、CMT は標準装備なので、どんな RX マイコンでも利用可能です。

通常 CMT0 から CMT3 までの4チャネルを使う事が出来ます。
※ICU の解説では、CMT0 は、OS 用で予約してあるのですが、OS(FreeRTOS と思われる)を使わない場合、どれを使っても問題無いと思えます。

FreeRTOS では、以下のように初期化しています。

    extern void vTickISR(void);
    extern void vSoftwareInterruptISR(void);

    void vApplicationSetupTimerInterrupt(void)
    {
        uint8_t intr = configKERNEL_INTERRUPT_PRIORITY;
        cmt_.start(configTICK_RATE_HZ, intr, vTickISR);

        device::icu_mgr::set_task(device::ICU::VECTOR::SWINT, vSoftwareInterruptISR);
        device::icu_mgr::set_level(device::ICU::VECTOR::SWINT, configKERNEL_INTERRUPT_PRIORITY);
    }

通常、タイマーの割り込み処理から呼ばれる関数をアタッチ出来ます。
C++ では、「ファンクタ」と呼ばれる手法を良く使います。
cmt_mgr も、割り込み内から呼ぶ関数を、ファンクタとして扱うようにしています。
cmt_mgr のプロトタイプは以下のようになっています。

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  CMT マネージャー・クラス
        @param[in]  CMT チャネルクラス
        @param[in]  TASK    タイマー動作クラス
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <class CMT, class TASK = utils::null_task>
    class cmt_mgr {

ここで、「TASK」はファンクタとして機能し、以下のように、() オペレータを使って動かす関数を定義しておきます。

    class my_cmt_task {
    public:
        void operator() () {
            // 処理
        }
    };

CMT1 を使い、cmt_mgr の定義は、上記クラスを使い、以下のようにします。

    typedef utils::cmt_mgr<device::CMT1, my_cmt_task> CMT;
    CMT     cmt_;

これで、割り込み処理で、この部分が処理されます。
※デフォルトでは、「utils::null_task」クラスが指定されます、これは、何もしないファンクタです。
※何もしないファンクタは、最適化で、完全に無くなります。
C++ のテンプレートでは、このような仕組みにする事で、処理する関数を呼び出すオーバーヘッドを節約出来ます。


CMT の起動

CMT は、間隔(周波数)と、割り込みレベルだけです。
※割り込みレベルが「0」の場合、割り込みを使いません。

    {
        uint8_t intr_lvl = 3;
        uint32_t freq = 1000;  // 1000Hz
        cmt_.start(freq, intr_lvl);
    }

割り込みとの同期

割り込み処理と、メイン側で同期を取りたい場合は、「sync()」を使います。

    while(1) {
        cmt_.sync();

    }

上記のループは、1000Hzの間隔でループする事になります。

割り込み回数の取得

また、割り込み回数が欲しいなら、「get_counter()」を使います。
※このカウンターは、外部割込み関数を登録した場合には無効になります。

    auto n = cmt_.get_counter();

※カウンターは32ビットで1周します。

設定周波数の取得(リアルな値)

「cmt_mgr」クラスでは、設定した周波数になるべく近い周期を生成しようと努力します。
ですが、マスタークロックに対して、割り切れない場合、実際の周期には誤差があります。

以下の関数で、設定した周波数と、実際に運用されている周波数を表示します。

    uint32_t freq = 850;
    cmt_.start(freq, 4);

    utils::format("Freq  (set): %u Hz\n") % cmt_.get_rate();
    utils::format("Freq (real): %u Hz\n") % cmt_.get_rate(true);
Freq  (set): 850 Hz
Freq (real): 849 Hz

C++ でのインスタンスの考え方

よく、例題などで、クラスのインスタンスを「new」を使って作成して、「delete」で廃棄する例を見ます。
また、C++ をあまり良く判っていない人は、インスタンスを作るコストが大きいので、C++ は小規模システム向きでは無いと思っている人が多いようです。
でも実際は、動的に作らなければ良いだけの事で、テンプレートなどを利用する事で、柔軟性があり、「動的な割り当て」をほぼ「0」に出来ます。

このような勘違いや思い込みは、C++ の場合非常に沢山あり、それが、C++ を敬遠する理由にもなっているようです。
※公開している RX マイコンフレームワークには「new、delete、malloc、free」は一切ありません。
※外部ライブラリで使われている場合があり、その対応としては多少あります。

    MyClass* my_class = new MyClass();

    my_class->xxx();

    delete my_class;

「new」すれば、必ず「delete」しなければならず、管理が面倒です、常に使う物は、単に宣言すれば良く、コンパイラが、メモリを自動で割り当てます。
※シェアードポインターで包む事で、廃棄忘れを防止出来ますが、「動的」に確保する必要が無い場合は、実態を書けば良いだけです。
※関数内の場合は、割り当てはスタック上ですが、関数外に置けば、ワークメモリに割り当ててくれます。

    {
        MyClass my_class;

        my_class.xxx();

    }

※上記の場合、MyClass の実態「my_class」は、スコープを抜ければ、自動で廃棄されます。

このように、C++ では、「割り当て」は極力減らし、それに伴うリスクを避ける事が出来ます。