RXマイコン、RX621/RX62N のサポート

※昔に購入した RX62N が載った格安ボード、確か2000円くらいで買った覚えがあるが、現在は5000円くらいする。


古い、RX621/RX62N のサポート

RX621/RX62N は、そこそこ古いRXマイコンのグループで、C++ フレームワークの出発点となった記念すべきCPUでもある。

しかし、新しいグループがリリースされ、使わなくなり、機能的に見劣りするのと、新規に購入するのもコスト的に見合わないので、ある時期から「レガシー」として、サポートをやめてしまった。

ところが、現在、政治的な動きに係る半導体不足から、最近リリースされた新しいデバイスの購入が殆ど出来なくなっており、いつ解消するかも不透明となっている。
RX621/RX62N は、かなり昔のデバイスで、そこそこ流通しており、かなり昔に、本の付録として流通した事もあるし、秋月では、未だに販売もされている。
なので、持っている人で肥やしになってる人も少なからずいるのではないか?

最終的には、RXマイコン全制覇を目論んでいるので、そろそろ、仕組みを考えておくのも必要だと考えていたので今回のサポートは丁度良いタイミングとなっている。

速度的には、100MHz 動作で、浮動小数点演算器も持っているデバイスなので、パフォーマンス的には、現在でもそんなに悪くなく、機能もそこそこあるので、何か作る場合でも、そんなに困らないのではと思う。

それでも、新規に購入しようとするとそこそこの価格だが、秋月で購入も可能なので、入手性など考えると、サポートしない理由はない。


構成を部分的に考え直して修正

この頃のRXマイコンは、現在の豊富な機能と比べると、かなり見劣りする。

C++ フレームワークでは、機能的に異なる部分を隠蔽するような仕組みを多数使っているので、「#if」などコンパイルの制御文を駆使しなくても、かなり綺麗に実装が可能となっている。
それでいて、最適化後のコードには、余分な部分が殆ど残らないので、速度低下もなく、上位の層(アプリケーション部分)は、ほぼそのまま再利用が可能となっている。

「#if」を多用して、細かい動作を切り替えているソースを読んだ事がある人は、判ると思うが、非常に読みにくく、デバッグも困難になる。
C++ では、そんな実装方法をとらなくても、最小限の分岐だけで、かなり複雑で柔軟な構成にできる。


FIRST_sample

まずは、LED 点滅から・・・

RX62x は、クロック生成部分が、非常にシンプルで、構成が大きく異なるので、専用のクラスとし、新規に書き直した。

ポートの構成も、大きく異なるので、専用のクラスとした。

各ファイルのインクルード制御は、common/device.hpp、common/renesas.hpp で行っている。

  • なので、アプリケーション側は、「common/renesas.hpp」をインクルードするだけで良い。

また、オシロスコープを使って波形を計測、ソフト遅延のパラメーターを適正な値に調整した。

この部分、「#if」で別けているので、定数を clock_profile に定義して参照するようにしないと駄目だな・・・
※今後改修する予定。


SCI_sample

  • SCI を動かすには、割り込みが必要なので、まずは、割り込み関係をサポート
  • 構成は、RX24T に近く、部分的に流用した
  • ついでに、他マイコンで、割り込み関係に係る部分を修正した

SCI 関係のペリフェラルは、現行のRXマイコンと、初期のRXマイコンでは、かなり異なる。

大きく異なるのは、ボーレートクロック生成の仕組みとなっている:

  • 倍速モードの追加
  • ボーレート変調レジスタの追加(比較的高いボーレートの場合に、クロックを間引いて、なるべく正確なレートになるように調整)
        //-----------------------------------------------------------------//
        /*!
            @brief  シリアル拡張レジスタ (SEMR)
            @param[in]  ofs     レジスタ・オフセット
        */
        //-----------------------------------------------------------------//
        template <uint32_t ofs>
        struct semr_t : public rw8_t<ofs> {
            typedef rw8_t<ofs> io_;
            using io_::operator =;
            using io_::operator ();
            using io_::operator |=;
            using io_::operator &=;

            bit_rw_t<io_, bitpos::B0> ACS0;
            bit_rw_t<io_, bitpos::B2> BRME;       // sci[g]

            bit_rw_t<io_, bitpos::B4> ABCS;
            bit_rw_t<io_, bitpos::B5> NFEN;       // sci[g]
            bit_rw_t<io_, bitpos::B6> BGDM;       // sci[g]
            bit_rw_t<io_, bitpos::B7> RXDESEL;    // sci[g]
        };

上記は、SEMR レジスタの定義だが、「sci[g]」は、新しいRXマイコンにはあるが、RX62x には存在しない。
現行の SCI 制御クラス(sci_io.hpp)では、全てを使ってボーレートを生成しているので、そのままでは、機能の無いマイコンには使えない。

RX62x に無いフラグやレジスタを定義から外す事も可能だが、それを行うと、sci_io.hpp のコンパイルが出来なくなってしまう。

  • なので、利用しているフラグビットは、常に存在するようにしないとならない(使う、使わないだけで別ける)
  • より厳密に行うには、そのビットにアクセスしても、無意味になるように出来るけど、それはやり過ぎと思ったので・・

そこで、SCI の定義クラスに、どのビットが有効なのかを宣言しておく。

  • sci_io クラスは、それを参照して利用するか、しないかを決定する。
  • 無効なビットに書き込む場合は、「0」を必ず書くようにする。
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  SCIa 定義クラス
        @param[in]  base    ベース・アドレス
        @param[in]  per     ペリフェラル型
        @param[in]  txv     送信ベクター
        @param[in]  rxv     受信ベクター
        @param[in]  tev     送信終了ベクター
        @param[in]  pclk    PCLK 周波数
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <uint32_t base, peripheral per, ICU::VECTOR txv, ICU::VECTOR rxv,
        typename INT, INT tev, uint32_t pclk>
    struct scia_t : public sci_core_t<base> {

        static constexpr auto PERIPHERAL = per; ///< ペリフェラル型
        static constexpr auto TX_VEC = txv;     ///< 受信割り込みベクター
        static constexpr auto RX_VEC = rxv;     ///< 送信割り込みベクター
        static constexpr auto TE_VEC = tev;     ///< 送信終了割り込みベクター
        static constexpr auto PCLK = pclk;  ///< PCLK 周波数

        static constexpr bool SEMR_BRME = false;    ///< BRME(ボーレート微調整)
        static constexpr bool SEMR_BGDM = false;    ///< BGDM(ボーレート倍速)

最近のRXマイコンでは:

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  SCIg クラス
        @param[in]  base    ベース・アドレス
        @param[in]  per     ペリフェラル型
        @param[in]  txv     送信ベクター
        @param[in]  rxv     受信ベクター
        @param[in]  tev     送信終了ベクター
        @param[in]  pclk    PCLK 周波数
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <uint32_t base, peripheral per, ICU::VECTOR txv, ICU::VECTOR rxv,
        typename INT, INT tev, uint32_t pclk>
    struct scig_t : public sci_core_t<base>, sci_core2_t<base> {

        static constexpr auto PERIPHERAL = per; ///< ペリフェラル型
        static constexpr auto TX_VEC = txv;     ///< 受信割り込みベクター
        static constexpr auto RX_VEC = rxv;     ///< 送信割り込みベクター
        static constexpr auto TE_VEC = tev;     ///< 送信終了割り込みベクター
        static constexpr uint32_t PCLK = pclk;  ///< PCLK 周波数

        static constexpr bool SEMR_BRME = true; ///< BRME(ボーレート微調整)
        static constexpr bool SEMR_BGDM = true; ///< BGDM(ボーレート倍速)
    };

RXマイコングループの違いは、以下のようになっている(#if が必要な部分)

  • SCI の基準クロックは、clock_profile に定義してあり、それを参照している。
  • RX62x の場合は「clock_profile::PCLK」、RX24T の場合は「colock_profile::PCLKB」となる。
  • SCI ハードウェアーの開始アドレスも大きく異なる。
  • RX72N などでは、割り込みの「型」も異なる。
#if defined(SIG_RX621) || defined(SIG_RX62N)
    typedef scia_t<0x00088240, peripheral::SCI0, ICU::VECTOR::TXI0, ICU::VECTOR::RXI0,
        ICU::VECTOR, ICU::VECTOR::TEI0, clock_profile::PCLK> SCI0;
    typedef scia_t<0x00088248, peripheral::SCI1, ICU::VECTOR::TXI1, ICU::VECTOR::RXI1,
        ICU::VECTOR, ICU::VECTOR::TEI1, clock_profile::PCLK> SCI1;
    typedef scia_t<0x00088250, peripheral::SCI2, ICU::VECTOR::TXI2, ICU::VECTOR::RXI2,
        ICU::VECTOR, ICU::VECTOR::TEI2, clock_profile::PCLK> SCI2;
    typedef scia_t<0x00088258, peripheral::SCI3, ICU::VECTOR::TXI3, ICU::VECTOR::RXI3,
        ICU::VECTOR, ICU::VECTOR::TEI3, clock_profile::PCLK> SCI3;
    typedef scia_t<0x00088268, peripheral::SCI5, ICU::VECTOR::TXI5, ICU::VECTOR::RXI5,
        ICU::VECTOR, ICU::VECTOR::TEI5, clock_profile::PCLK> SCI5;
    typedef scia_t<0x00088270, peripheral::SCI6, ICU::VECTOR::TXI6, ICU::VECTOR::RXI6,
        ICU::VECTOR, ICU::VECTOR::TEI6, clock_profile::PCLK> SCI6;
#elif defined(SIG_RX24T)
    typedef scig_t<0x0008A020, peripheral::SCI1, ICU::VECTOR::TXI1, ICU::VECTOR::RXI1,
        ICU::VECTOR, ICU::VECTOR::TEI1, clock_profile::PCLKB> SCI1;
    typedef scig_t<0x0008A020, peripheral::SCI1C, ICU::VECTOR::TXI1, ICU::VECTOR::RXI1,
        ICU::VECTOR, ICU::VECTOR::TEI1, clock_profile::PCLKB> SCI1C;
    typedef scig_t<0x0008A0A0, peripheral::SCI5, ICU::VECTOR::TXI5, ICU::VECTOR::RXI5,
        ICU::VECTOR, ICU::VECTOR::TEI5, clock_profile::PCLKB> SCI5;
    typedef scig_t<0x0008A0A0, peripheral::SCI5C, ICU::VECTOR::TXI5, ICU::VECTOR::RXI5,
        ICU::VECTOR, ICU::VECTOR::TEI5, clock_profile::PCLKB> SCI5C;
    typedef scig_t<0x0008A0C0, peripheral::SCI6, ICU::VECTOR::TXI6, ICU::VECTOR::RXI6,
        ICU::VECTOR, ICU::VECTOR::TEI6, clock_profile::PCLKB> SCI6;
    typedef scig_t<0x0008A0C0, peripheral::SCI6C, ICU::VECTOR::TXI6, ICU::VECTOR::RXI6,
        ICU::VECTOR, ICU::VECTOR::TEI6, clock_profile::PCLKB> SCI6C;

sci_io クラスでは、SCI に定義してある、値を参照して処理を切り替えている。

            // BGDM が使える場合、1/8 スタート
            uint32_t mtx = 8;
            uint32_t limit = 1024;
            if(!SCI::SEMR_BGDM) {  // BGDM が使えない場合 1/16 スタート
                mtx = 16;
                limit = 512;
            }
            uint32_t brr = SCI::PCLK / mtx / baud;
            uint8_t cks = 0;
            while(brr > limit) {
                brr >>= 2;
                ++cks;
                if(cks >= 4) {  // 範囲外の速度
                    port_map::turn(SCI::PERIPHERAL, false, PSEL);
                    power_mgr::turn(SCI::PERIPHERAL, false);
                    return false;
                }
            }

            // BGDM フラグの設定
            bool bgdm = true;
            if(SCI::SEMR_BGDM) {
                if(brr > 512) { brr >>= 1; bgdm = false; mtx <<= 1; }
            } else {
                bgdm = false;
            }
            bool abcs = true;
            if(brr > 256) { brr >>= 1; abcs = false; mtx <<= 1; }

C++ を知らない人は、最適化されても、余分なコードが含まれると思うかもしれないが、C++ の最適化では、参照先の状態に応じて、必要無い部分は、全て消してくれる。
テンプレートクラスでは、それが、究極的に行われる。
そして、必要な部分だけが、残る、コンパイル時に計算出来る部分は、計算され、定数が直接使われる。
※なので、constexpr などが重要な要素となる。


GNU-RX 8.3.0 の対応

今まで、RXv2 コアは、コンパイルオプション、「-misa=v2」を使っていたのだが、RXv2 コアは、昔と今では、多少異なるようだ・・・

RX62x で、「-misa=v2」でコンパイルすると、浮動小数点の計算で例外が出るようだ。
RX62x は、RXv1 コア。
※RX24T、RX65x などでは問題無い。
結局、RX62x では、「-mcpu=RX600」とする事で、適合するコードが生成される事が判った。

以下、makefile の共通部分:

ifeq ($(RX_DEF),SIG_RX62N)
  RX_CPU = RX62N
  RX_CHOOSE = -mcpu=RX600
  LDSCRIPT  =   ../../RX62x/$(DEVICE).ld
endif

ifeq ($(RX_DEF),SIG_RX24T)
  RX_CPU = RX24T
  RX_CHOOSE = -misa=v2
  LDSCRIPT  =   ../../RX24T/$(DEVICE).ld
  PROG_VERIFY =
endif

RAYTRACER_sample

少し大きいアプリを試しておく必要があるので、浮動小数点演算などが多く含まれたプログラムを走らせてみた。

RX62x は、RXv2 コアだが、fsqrt などの命令をサポートしないようだ・・
RX62x は、RXv1 コア
これが利いて、96MHz ではあるが、一番遅い・・

マイコン core fsqrt 命令 周波数 [MHz] 描画方式 時間 [ms]
RX62N RX600 X 96 8 bits, port-bus 1860
RX24T RXv2 O 80 8 bits, port-bus 1224
RX65N RXv2 O 120 Frame Memory 784
RX64M RXv2 O 120 16 bits, port-bus 751
RX66T RXv3 O 160 8 bits, port-bus 602
RX72T RXv3 O 192 8 bits, port-bus 464
RX71M RXv2 O 240 16 bits, port-bus 439
RX72N RXv3 O 240 Frame Memory 361
  • 単に動かしただけで、生成した画像を観ていないが、ストールせずに走るので、ちゃんと計算しているものと思う。
  • 余裕が出来たら LCD を接続して確認したい。

まとめ

RX62x のサポートがスムーズに出来たので、頃合いをみて、次は手持ちの RX63T をサポートしようと思う。
その前に、各サンプルで、RX62x のプロジェクトを追加する必要があるけど・・

それと、rx_prog で RX62x のフラッシュを書けるようにしたいが、これは、かなり昔のプロトコルなので時間がかかりそうだ・・・


雑誌付録の RX62N 基板を持っていて、譲っても良い人は連絡をくれると助かります。