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

RX マイコン GUI フレームワークのエミュレーション

フラッシュROMの書き換えに時間がかかる・・

RX65N、RX72N Envision Kit で、GUI アプリケーションを作成する場合、オブジェクトがそこそこ大きくなる。
それを、マイコンにロードして動作を確認していると、一度のターンで、それなりに時間がかかり、開発効率が悪い。
GUI 系のアプリでは、現状、細かい調整や見た目重視になるので、さらにターン数は多くなり、完成度を上げるハードルが高くなる。
そこで、Windows のアプリ上で、動作を確認出来るように、エミュレーター的なアプリを作成した。

RX72N Envision Kit では、マイコン内蔵のフラッシュメモリ書き込みは、1本の端子で J-TAG をシリアライズして接続している。
※FINED 端子
その制約で、書き込み速度が RX65N Envision Kit に比べてさらに遅くなっている。(RX65N Envision Kit は J-TAG 接続)


エミュレーターと言っても、RXマイコンのバイナリーレベルで動かすのではなく、単純なAPIレベルでの動作に限定している。

俺俺フレームワークの「glfw_app」を利用し、RXマイコンから必要なソースコードを持っていき、
関係する部分をコンパイル出来るようにする。
そして、描画したフレームバッファを表示するだけとなっている。
また、マウスでクリックした状態を、タッチパネルでの操作に似せて、情報をフレームワークに送る。
※残念ながら、マルチタッチはエミュレーション出来ない。

RX マイコンの GUI フレームワークは、テンプレートで実装している部分が多く、描画部と、タッチ入力部は分離している。
なので、非常に簡単なパスを作るだけで実現できる。

実際のパス接続

GUIフレームワークで要求されるクラスは主に二つとなっている。

  • レンダリングクラス
  • タッチ入力クラス

上記二つのクラスをテンプレートパラメーターとして widget_director に参照で渡す構造となっている。

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  Widget ディレクター
        @param[in]  RDR     レンダークラス
        @param[in]  TOUCH   タッチクラス
        @param[in]  WNUM    widget の最大管理数
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <class RDR, class TOUCH, uint32_t WNUM>
    struct widget_director {

また、ソフトウェアーレンダリングクラスは、描画ハードウェアーのインスタンスを参照で渡している。
描画クラスには、フォントのインスタンスが含まれる。

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  レンダリング
        @param[in]  GLC     グラフィックス・コントローラー・クラス
        @param[in]  AFONT   ASCII フォント・クラス
        @param[in]  KFONT   漢字フォントクラス
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <class GLC, class FONT = font_null>
    class render {

描画ハードウェアーは、フレームバッファを管理している。
中身は、描画スクリーンのサイズと、先頭アドレスだけとなっている。

そこで、以下のようなクラスを用意した。

    static const int16_t LCD_X = 480;
    static const int16_t LCD_Y = 272;

    template <uint32_t LCDX, uint32_t LCDY>
    class glcdc_emu {
    public:
        static const uint32_t   width  = LCDX;
        static const uint32_t   height = LCDY;

    private:
        uint16_t fb_[LCDX * LCDY];

    public:

        void sync_vpos() { }

        void* get_fbp() { return fb_; }
    };
    typedef glcdc_emu<LCD_X, LCD_Y> GLCDC;

また、タッチクラスは、マウス入力をそのまま使うので、以下のようなクラスとした。

    class touch_emu {
    public:

        struct touch_t {
            vtx::spos   pos;
        };

    private:
        touch_t touch_[4];
        uint32_t    num_;

    public:
        touch_emu() : num_(0) { }

        uint32_t get_touch_num() const { return num_; }

        const auto& get_touch_pos(uint32_t idx) const {
            if(idx >= 4) idx = 0;
            return touch_[idx];
        }

        void update()
        {
        }

        void set_pos(const vtx::spos& pos)
        {
            touch_[0].pos = pos;
            num_ = 1;
        }

        void reset() { num_ = 0; }
    };

RX マイコンのサンプル「GUI_sample」を使って実験

※アプリケーションが出力する文字列を受け取り、ターミナルフレームに出力している。

これで、アプリの動作を細かく調整出来る。

GUI ビルダーも作る事が出来ると思うが、それはそれで、別の問題もある。
自分のフレームワークでは、widget の部品は、全てソースコードレベルで行っているので、ビルダーでソースコードを生成するのは簡単だが、その逆は、結構大変そうだ・・・
※一方通行で「ヨシ」とするなら簡単だが、ソースコードの管理が、それはそれで面倒・・
とりあえず、大がかかりになりそうなので、後で考える事にする・・・

何か作ってみる

とりあえず、簡単なところで、「電卓」を作ってみた、実用性もあるし、そこそこの難易度なので、それなりに有益かと思う。
もう少し、リッチなボタンが欲しいとこだが、それは、次の課題とする。
※現在の実装では、ボタンのコーナーのラウンドは、アンチエイリアスしていない。
また、プログラム電卓的な機能や、グラフ表示、など、拡張性がかなりあると思うので、今後機能を追加していこうと思う。

  • グラフ表示が出来る電卓は、購入するとなると、それなりの値段がするので、自分で作るには面白いガジェットかもしれない。
  • スマホで動く高機能な電卓が色々あるのだけど自分で作るのは、それだけで楽しい。

RX72N Envision Kit で動かした場合。


右の空きスペースは、関数電卓用のボタンを配置する予定。

とりあえず、PC で動作確認を行い、実際にターゲットでも同じように動作する事を確認できた。
今まで複雑な描画を含むアプリ作成は、効率が悪かったが、これからは、かなり効率良く開発出来ると思う。

CALC_sample

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

不具合修正

不具合を修正してブラッシュアップした。

  • リモートフレームを受信しない不具合を修正
  • 複数メールボックス割り込みの修正
  • 拡張 ID 受信の不具合修正

通常モードでは、メールボックスのフラグ設定により、受信するメールボックスが決定する仕様のようだ・・・

  • メールボックスの「RTR」が「1」なら、リモートフレームを受信
  • メールボックスの「RTR」が「0」なら、データフレームを受信
  • メールボックスの「IDE」が「1」なら、拡張 ID フレームを受信
  • メールボックスの「IDE」が「0」なら、標準 ID フレームを受信

最低でも4つのメールボックスで「待ち」状態にしないと、全てを受信できない・・
また、先のバージョンでは、メールボックスに対する割り込みフラグが正しく設定されていないようだったので、それも修正した。
※割り込み受信処理中にも受信が可能なように、8つのメールボックスをアクティブにしている。

Start CAN sample for 'RX64M' 120[MHz]
CAN command version: 0.86
CAN0: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7, SJW: 4
    RX Interrupt level: 1, TX Interrupt level: 1
CAN1: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7, SJW: 4
    RX Interrupt level: 1, TX Interrupt level: 1
#
# ch 1
# send 0x123
# map
R      1 S:x0000123 (291)
ID = 1 / Total = 1, Records = 0, Df = 0, Rf = 1
# send_loop 50
# map
D      1 S:x0000079 (121)
D      1 S:x00002B2 (690)
D      1 S:x00001C8 (456)

...

D      1 S:x000014E (334)
D      1 S:x000028C (652)
R      1 S:x0000123 (291)
D      1 S:x00002CB (715)
D      1 S:x00002BD (701)
D      1 S:x000000C (12)

...

D      1 S:x00000DB (219)
ID = 49 / Total = 51, Records = 204, Df = 48, Rf = 1
#
# dump 0x123
R      1 S:x0000123 (291)
R: ID: std 0x123 (291)
(0):
TS: 47248
# dump 219
D      1 S:x00000DB (219)
D: ID: std 0x0DB (219)
(2): D8 68
TS: 22193

リモートフレーム、データフレームの扱いについて、理解したので、表示も修正した。
※send 時、ID だけ設定するとリモートフレームを送信するようになっている。

RX66T の CAN ポート設定

RX66T では、CAN は1チャネルのみだが、候補が7番まであるので、それに伴って、port_map クラスを修正した。

        static bool sub_can_(option opt, bool enable)
        {
            uint8_t sel = enable ? 0b10000 : 0;
            switch(opt) {
            case option::FIRST:
                // PE0/CRX0 (22/144) 1ST
                // PD7/CTX0 (23/144)
                PORTE::PMR.B0 = 0;
                PORTD::PMR.B7 = 0;
                MPC::PE0PFS.PSEL = sel;
                MPC::PD7PFS.PSEL = sel;
                PORTE::PMR.B0 = enable;
                PORTD::PMR.B7 = enable;
                break;
            case option::SECOND:
                // PF3/CRX0 (31/144) 2ND
                // PF2/CTX0 (32/144)
                PORTF::PMR.B3 = 0;
                PORTF::PMR.B2 = 0;
                MPC::PF3PFS.PSEL = sel;
                MPC::PF3PFS.PSEL = sel;
                PORTF::PMR.B3 = enable;
                PORTF::PMR.B2 = enable;
                break;
            case option::THIRD:
                // PB6/CRX0 (40/144) 3RD
                // PB5/CTX0 (41/144)
                PORTB::PMR.B6 = 0;
                PORTB::PMR.B5 = 0;
                MPC::PB6PFS.PSEL = sel;
                MPC::PB5PFS.PSEL = sel;
                PORTB::PMR.B6 = enable;
                PORTB::PMR.B5 = enable;
                break;
            case option::FOURTH:
                // PA7/CRX0 (52/144) 4TH
                // PA6/CTX0 (53/144)
                PORTA::PMR.B7 = 0;
                PORTA::PMR.B6 = 0;
                MPC::PA7PFS.PSEL = sel;
                MPC::PA6PFS.PSEL = sel;
                PORTA::PMR.B7 = enable;
                PORTA::PMR.B6 = enable;
                break;
            case option::FIFTH:
                // PA1/CRX0 (58/144) 5TH
                // PA0/CTX0 (59/144)
                PORTA::PMR.B1 = 0;
                PORTA::PMR.B0 = 0;
                MPC::PA1PFS.PSEL = sel;
                MPC::PA0PFS.PSEL = sel;
                PORTA::PMR.B1 = enable;
                PORTA::PMR.B0 = enable;
                break;
            case option::_6TH:
                // PC6/CRX0 (62/144) 6TH
                // PC5/CTX0 (63/144)
                PORTC::PMR.B6 = 0;
                PORTC::PMR.B5 = 0;
                MPC::PC6PFS.PSEL = sel;
                MPC::PC5PFS.PSEL = sel;
                PORTC::PMR.B6 = enable;
                PORTC::PMR.B5 = enable;
                break;
            case option::_7TH:
                // P23/CTX0 (96/144) 7TH
                // P22/CRX0 (97/144)
                PORT2::PMR.B3 = 0;
                PORT2::PMR.B2 = 0;
                MPC::P23PFS.PSEL = sel;
                MPC::P22PFS.PSEL = sel;
                PORT2::PMR.B3 = enable;
                PORT2::PMR.B2 = enable;
                break;
            default:
                return false;
            }
            return true;
        }

有効な ID だけ通すフィルター

can_io クラスでは、フィルター機能を使っていない。
RX マイコンの CAN では、フィルターの設定が面倒で、数に制限がある為、実際のアプリでは、適応するのが難しいかもしれない。

ハードウェアーの機能を使えば、処理負荷を軽減できるものの、実用性が怪しい、また、異なるハードウェアー(RSCAN)などで、コードの再利用が難しくなる。

C++ では、特定の ID だけ通す(通さない)フィルターを簡単に実装出来る。
今回「boost::unordered_set」を使った。
※「std::unorderd_set」よりバイナリーが小さくなる。

まず、通すリストを構築する。

    // 有効な ID だけ通すフィルター
    typedef boost::unordered_set<uint32_t> VALID;
    VALID   valid_{ 0x123, 0x200, 0x300, 0xaaa, 15, 21, 33 };

非常に簡単だw

次に、メインループで、受信したフレームをディスパッチする際に、上記フィルターがヒットするか確認する。

        while(can1_.get_recv_num() > 0) {
            auto frm = can1_.get_recv_frame();
            if(valid_.find(frm.get_id()) != valid_.end()) {
                utils::format("\nCAN1:\n");
                CAN::list(frm, "  ");
            }
        }

この場合、CAN1 に受信したフレームで、「有効」な ID だけ表示する。

# send 0x123 0
CAN1:
  D: ID: std 0x123 (291)
  (1): 00
  TS: 29604
#
# send 0x1 1 2 3
# send 200 5 6 7
# send 0x200 5 6 7
#
CAN1:
  D: ID: std 0x200 (512)
  (3): 05 06 07
  TS: 41568
#

「unorderd_set」は、ハッシュを使って効率よく探すので、探す ID が多くても、検索にかかる負荷は小さくて済むものと思う。
また、「通す ID」を追加したり、削除したりも簡単に出来る。

今回はここまで。

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

複数メールボックスの利用

以前の実装では、送信も受信も1つのメールボックスを使っていたが、少なくとも「受信」では、1つの受信時、割り込み処理で、メールボックスから、フレームを取り出してバッファに積む間に、別の ID を受信していると、そのフレームをロストしてしまう。
そこで、複数のメールボックスを利用するように修正した。

送信側も複数のメールボックスを利用するように修正。

can_io テンプレートのプロトタイプは以下のようになった。

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  CAN 制御クラス
        @param[in]  CAN     CAN 定義クラス
        @param[in]  RBF     受信バッファクラス (utils::fixed_fifo<can_frame, N>)
        @param[in]  TBF     送信バッファクラス (utils::fixed_fifo<can_frame, N>)
        @param[in]  PSEL    ポート候補
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <class CAN, class RBF, class TBF, port_map::option PSEL = port_map::option::FIRST>
    class can_io : public can_io_def {

アプリケーションからは、以下のように「typedef」して使う。

    // CAN 受信バッファの定義
    typedef utils::fixed_fifo<device::can_frame, 256> CAN_RXB;
    // CAN 送信バッファの定義
    typedef utils::fixed_fifo<device::can_frame, 128> CAN_TXB;

    typedef device::can_io<device::CAN0, CAN_RXB, CAN_TXB, CAN0_PORT> CAN0;
    CAN0    can0_;

CAN/ID の収集と解析

とりあえず、「can_io」クラスは、送信、受信が行えるようになったので、CAN バスの解析機能を実装してみた。

新規に「can_analize」テンプレートクラスを追加して、内部で、CAN/ID を動的に収集して、表示する。

ID の収集には、「boost/unordered_map.hpp」を利用したが、標準で用意されている「std::unordered_map」は、関連クラスの関係でリンクが難しい事が判った為だ。
boost の方が小回りが利く。
※この利用では、何故か、浮動小数点ライブラリをリンクする必要があるようだ・・・

ID を動的にソートして収集するには「std::map」が良いが、「std::map」は、ツリー式で、件数が増えるとメモリの肥大化が問題になると思われる。
※計測はしていないが、経験的にハッシュを使う「unordered_map」の方が高速で省メモリなのではと思う。
※使用メモリも、件数によると思う、これも調査していないので何とも言えない。
※また、たとえば、車の CAN、工作機械などの CAN などで、標準的に利用している ID の数も、今はまだ理解していない。
※コード的には、コンテナの扱いが同等なので、どちらでも利用出来る。

        typedef boost::unordered_map<uint32_t, info_t> MAP;
//      typedef std::map<uint32_t, info_t> MAP;
  • どちらかを有効にする。
  • 実行ファイルは、「std::map」の方が若干大きくなる。

解析機能は、CAN0 側にのみ付けているので(両方付ける事も出来る)、ループ接続で実験する際は、CAN1 側から送信する必要がある。


これらの機能実装は、C++ ならではで、組み込みマイコンでも、boost がまともに動くのは、便利としか言いようが無いw

送信と受信の様子:

Start CAN sample for 'RX64M' 120[MHz]
CAN0: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7, SJW: 4
    RX Interrupt level: 1, TX Interrupt level: 1
CAN1: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7, SJW: 4
    RX Interrupt level: 1, TX Interrupt level: 1
# ch 1
# send 0x100 1 2 3
# map
     1 S:x0000100 (256)
Total = 1
# dump 256
     1 S:x0000100 (256)
ID: std 0x100 (256)
DATA(3): 01 02 03
TS: 11269
# send 123 5 6 7
# send 500 67 90 200
# send 300 100 99 100
# map
     1 S:x000012C (300)
     1 S:x00001F4 (500)
     1 S:x000007B (123)
     1 S:x0000100 (256)
Total = 4
# dump 500
     1 S:x00001F4 (500)
ID: std 0x1F4 (500)
DATA(3): 43 5A C8
TS: 14498
# send 500 0x8A 0xcb 0xfe 0x24
# map
     1 S:x000012C (300)
     2 S:x00001F4 (500)
     1 S:x000007B (123)
     1 S:x0000100 (256)
Total = 4
# dump 500
     2 S:x00001F4 (500)
ID: std 0x1F4 (500)
DATA(4): 8A CB FE 24
TS: 9925
#

「send_loop」コマンドを追加。
ランダムなデータを送信するテストを行った:

# ch 1
# send_loop 100
# map
     1 S:x00002A4 (676)
     1 S:x0000301 (769)

...

     1 S:x000013D (317)
     2 S:x0000260 (608)
     1 S:x0000015 (21)
     1 S:x000031D (797)
     1 S:x00000D6 (214)
     1 S:x0000228 (552)
Total ID = 92 / Total count = 100, Total Record = 367
# send_loop 100
# map
     1 S:x000008A (138)
     1 S:x000026E (622)
     1 S:x0000028 (40)
     1 S:x0000116 (278)
     1 S:x00001BD (445)
     1 S:x00001AC (428)

...

     2 S:x00000F7 (247)
     1 S:x00000DB (219)
     1 S:x000013D (317)
     1 S:x00002E9 (745)
     1 S:x00000A6 (166)
     1 S:x0000228 (552)
Total ID = 170 / Total count = 200, Total Record = 678
# send_loop 50
# map
     1 S:x000012E (302)
     1 S:x00000C8 (200)
     1 S:x0000070 (112)
     1 S:x0000077 (119)
     1 S:x00000DA (218)

...

     2 S:x00000F7 (247)
     1 S:x000013D (317)
     1 S:x00002E9 (745)
     1 S:x0000228 (552)
Total ID = 210 / Total count = 250, Total Record = 853
#

とりあえず、ちゃんと動いているようだ。

    ch CH-no               set current CAN channel (CH-no: 0, 1)
    send CAN-ID [data...]  send data frame
    stat MB-no             stat mail-box (MB-no: 0 to 31)
    list MB-no             list mail-box (MB-no: 0 to 31)
    map [CAN-ID]           Display all collected IDs
    clear                  clear map
    dump CAN-ID            dump frame data
    send_loop NUM          random ID, random DATA, send loop
    help                   command list (this)

新しいコマンドを追加したサンプルはプッシュ済み

今回はここまで。

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

MB (メールボックス)レジスタクラスを一応改修

CAN で、厄介なのは、MB レジスタ。
ここだけは、概念がかなり他と違う、一つのパーテーションが 16 バイトなので、通常のレジスタアクセスクラスではサポート出来ない。

そこで、専用の実装を考えた。
※本来は、直接アクセスしなくても、サポートクラスを定義して、丸ごとコピーすれば良い。
※技術的な興味から実装

  • 「DLC」はバイトアクセス
  • 「DATA」は 8 バイト分のアクセスを行うクラスを追加
  • 「TS」は 16 ビットアクセス
  • それ以外は 32 ビットアクセス
  • SID、EID をまとめて設定、読出し出来るように関数を追加「get_id()、set_id()」
  • 全体を初期化する関数を追加「clear()」
  • 全体をコピーする関数を追加「copy()」(他のメールボックスから丸ごとコピーする場合)
  • 「=」オペレーターを「private」に(代入禁止)
        //-----------------------------------------------------------------//
        /*!
            @brief  メールボックスレジスタ j ( MB[j] )( j = 0 ~ 31 )
        */
        //-----------------------------------------------------------------//
        struct mb_t {
            typedef rw32_index_t<base +  0> io0_;
            typedef rw8_index_t <base +  4 + 1> io1_;
            typedef rw16_index_t<base + 12 + 2> io3_;

            bits_rw_t<io0_, bitpos::B0,  18>  EID;
            bits_rw_t<io0_, bitpos::B18, 11>  SID;
            bit_rw_t <io0_, bitpos::B30>      RTR;
            bit_rw_t <io0_, bitpos::B31>      IDE;

            bits_rw_t<io1_, bitpos::B0, 4>    DLC;

            struct data_t {
                volatile uint8_t& operator [] (uint32_t n) {
                    return *reinterpret_cast<volatile uint8_t*>(io0_::address() + 6 + n);
                }
            };
            data_t  DATA;

            io3_    TS;

            void set_index(uint32_t j) {
                if(j < 32) {
                    io0_::index = j * 16;
                    io1_::index = j * 16;
                    io3_::index = j * 16;
                }
            }

            void clear(uint32_t d = 0) {
                auto a = io0_::address();
                wr32_(a,      d);
                wr32_(a +  4, d);
                wr32_(a +  8, d);
                wr32_(a + 12, d);
            }

            void copy(uint32_t idx) {
                wr32_(io0_::address() +  0, rd32_(base + idx * 16 +  0));
                wr32_(io0_::address() +  4, rd32_(base + idx * 16 +  4));
                wr32_(io0_::address() +  8, rd32_(base + idx * 16 +  8));
                wr32_(io0_::address() + 12, rd32_(base + idx * 16 + 12));
            }

            uint32_t get_id() {
                return SID() | (EID() << 11);
            }

            void set_id(uint32_t id) {
                SID = id & 0x7ff;
                EID = id >> 11;
            }

            mb_t& operator [] (uint32_t idx) {
                set_index(idx);
                return *this;
            }
        private:
            void operator = (const mb_t& t) {
            };
        };
        typedef mb_t MB_;
        static  MB_ MB;

これで、CAN 関係で、気になっていた部分は、納得いく範囲で、抽象的にアクセス出来るようになったと思う。

割り込み関係の整備と設計

本格的な上位層を実装する前に、割り込み関係の動作を確認しようと思う。

また、FIFO の機能をどうするか?
ハードウェアーで持ってる FIFO も、中途半端で微妙だよなぁー・・・
FIFO が4つくらいあっても、1パケット(フレーム)が100マイクロ秒前後なので、激しい通信がある場合は足りないと思う。
結局、ソフトで FIFO を作って、突発的に増えた場合の通信量を吸収するしかない。

経験的に、割り込み関数内でガッツリ実装するのは良くないと判っている。(それしか方法が無い場合もあるけど・・)
大抵、そんな事をしなくても、何とかなるし、割り込み内からコールバックを呼んでも、そこから出来る事は限られる。
※本当はコールバックも呼びたくない、もし、重い処理を実装されたら、終わってしまうから・・


RX64M の CAN では、割り込みは、SELB 選択型割り込みになっていて、4つの割り込みがある。
エラー割り込みは、BE0 グループ割り込みになっている。

    typedef can_seli_t<0x00090200, peripheral::CAN0,
        ICU::VECTOR_SELB::RXF0, ICU::VECTOR_SELB::TXF0,
        ICU::VECTOR_SELB::RXM0, ICU::VECTOR_SELB::TXM0, ICU::VECTOR_BE0::ERS0> CAN0;

RX66T では、エラー割り込みの「型」は同一だが、他は通常割り込みになっている。

    typedef can_norm_t<0x00090200, peripheral::CAN0,
        ICU::VECTOR::RXF0, ICU::VECTOR::TXF0,
        ICU::VECTOR::RXM0, ICU::VECTOR::TXM0, ICU::VECTOR_BE0::ERS0> CAN0;

icu_mgr クラスは、「割り込み型」により異なる関数が定義してあるので、C++ コンパイラが自動的に適切な関数を選ぶ、ドライバー側は「型」を意識する必要が無く、プログラムがシンプルになる。

  • #ifdef などで、CPU の違い毎に動作を別ける実装をしなくて済む。
  • 実際には、can のレジスタ定義では、CPU 毎の定義で「別けて」いるけど、その上位層では、感知しなくて済む。
  • つまり、この仕組みは、新規に CPU が追加されても、下位層でそのサポートを行えば、上位層のコードをケアする必要が無い。
 static ICU::VECTOR set_interrupt(ICU::VECTOR vec, utils::TASK task, uint8_t lvl) noexcept;
 static ICU::VECTOR set_interrupt(ICU::VECTOR_SELB sel, utils::TASK task, uint8_t lvl) noexcept;

※ちょっと不思議なのは、MIER メールボックス割り込み許可レジスタが、RESET 時不定になっている。
他にも不定になっているレジスタがいくつかある・・・


割り込み関係では、厄介な事がある、割り込みが起動した時、どのメールボックスによる要因なのかが判らない。
一応、調べる機構はあるが、送信と受信がシェアされているので、受信にしか使えない・・

送信は、同時に複数のフレームを送信しないので、それでも良いかもしれない・・

とりあえず、メールボックス 4 を送信専用にして、それを使って送信を行った。
問題無く、送信出来ていて、送信割り込みも機能する事を確認した。
※割り込みを使わない場合は物理的に難しいので、割り込みを使う事が前提となる。

CAN での受信は、色々と考える事が多い:

  • 固定 ID で来るデータを周期的に観るだけの場合。
  • 固定 ID で来るデータ全てに応答する場合。
  • どんな ID が来ているかを集計したい場合。(アナライザ的な機能)
  • ID 集計後に、無視する ID、観測する ID、応答する ID を決定する場合。
  • ID に個別の処理(コールバック)を設定する場合。

アプリケーションを作る場合、柔軟な構成にしたいので、上記の事を考えた設計をしたいが、RX マイコンの CAN 機能だけでは、難しい・・・

ID の範囲が「標準」のみなら、2048個なので、ルックアップするのは現実的だが、「拡張 ID」だと29ビットあるので、少し難しい。
STL の場合、「std::set]、「std::unorderd_set」を使えば、簡単に出来るが、内部で記憶割り当てを使うので、メモリの少ないマイコンの場合は使いたくないのも事実・・・
さてどうするか?

CAN 動作モードの切り替え(注意)

            // CAN オペレーションモードに移行
            uint8_t idfm = 0b10;  // 標準 ID モード、拡張 ID モード、ミックス
            uint8_t bom  = 0b00;  // ISO 11898-1 規格、バスオフ復帰モード
            CAN::CTLR = CAN::CTLR.CANM.b(0b00) | CAN::CTLR.SLPM.b(0) | CAN::CTLR.IDFM.b(idfm)
                | CAN::CTLR.BOM.b(bom);

            // CAN オペレーションモードに移行するまで待機
            while(CAN::STR.RSTST() != 0 || CAN::STR.HLTST() != 0) {
                sleep_();
            }

上記のように、CAN の動作モードを切り替えると、実際にモードが切り替わるまで遅延があるようだ。
オペレーションモードに移行した事を確認してから、次の動作を行う必要がある。
※そうしないと、制御レジスターの書き換えが失敗するなどの問題がある。
※一応、それらしい事が書いてあるが、レジスタの説明に書くべきだろうと思う。

MCTL レジスタの書き換えには注意

ハードウェアーマニュアルに書いてあるが、ビットを OFF にするには、色々と制約と順番などがある。
これは、注意を要する。

RX マイコンの CAN 関係の実装は、落とし穴が沢山あって、簡単では無いなぁーと感じる・・・
要は、あまり完成度が高く無いと感じる、だから RX24T で新しい CAN (RSCAN) になったのかもしれない。
そんな事を言っても仕方無いので、少しづつ機能を確認しつつ追加して、何とか完成させるしかない。

受信割り込みの確認

結局、SCI と同じように FIFO を使って受信動作を実装した。
なので、現状では、ID をマスクしたり、監視する機能は、全てソフトで行う必要がある。
パフォーマンスは犠牲になるが、当面はそれで良い、柔軟性が重要だ、パフォーマンスに問題が起こったら、マスクフィルタの設定などを追加すれば良い。

Start CAN sample for 'RX64M' 120[MHz]
CAN0: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7, SJW: 4
    RX Interrupt level: 1, TX Interrupt level: 1
CAN1: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7, SJW: 4
    RX Interrupt level: 1, TX Interrupt level: 1
# send 0x100 0 1 2 3 4 5
#
CAN1:
  ID: std 0x100 (256)
  DATA(6): 00 01 02 03 04 05
  TS: 51713

# ch 1
# send 0x200 10 20 30 99 0x7f
#
CAN0:
  ID: std 0x200 (512)
  DATA(5): 0A 14 1E 63 7F
  TS: 43785

# ch 0
# send 0x450 0
#
CAN1:
  ID: std 0x450 (1104)
  DATA(1): 00
  TS: 12821

#

※ CAN0、CAN1 の送信、受信の確認


今回はここまで。

ソースコードは、github RX/CAN_sample にある。

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 のバイナリーをリンクにアップロードしてある。