「電子工作な日々」カテゴリーアーカイブ

電子工作に関連するお話など・・

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 基板を持っていて、譲っても良い人は連絡をくれると助かります。

RXマイコン、デジタルストレージオシロスコープ(その4)

かなり間が空いたが、再び、デジタルストレージの作業を行った。

ソフトの作業も平行して行っており、かなり充実してきている。


プローブを買い直す。

60MHz のプローブを買い直した、まぁ現代は、それなりに安く購入できるので助かる。

以前に買った 60MHz のプローブはトリマを回すのに特殊なドライバーが必要で(ドライバー無くした)、もっと安いプローブがあったので、別件で部品を買うついでに買ってみた。

60MHz プローブ(TEXAS60)


昔会社勤めの時、プローブの扱いに注意するよう散々言われた、テクトロニクスの 350MHz オシロ用プローブは1本5万くらいしていた。
※実験で色々作業している過程で、プローブを壊す(先のピンを折ってしまう)事が多かったようだ。
※テクトロのプローブは、沢山の知恵が詰っている優れもので、感動した事を覚えている。

プリアンプを考える

実験的に、初段のプリアンプとして、OPA2134Aを使っていたが、これは、オーディオ用なので、帯域が足りない。
※高い周波数でゲインが低下する。
※それに、そんなに安いオペアンプではない。

そこで、色々なオペアンプを探すものの、高性能な物は値段が高く、オーバースペックでもある。
サンプリングが 2MHz だと、高い周波数は測れないのは当然なのだが、
低い周波数でも矩形波を観測した場合などに、立ち上がりや立下りが実際と異なり鈍ると測定器として成立しない。
※正弦波でも、周波数によってゲインが下がって、正確な電圧を計測出来ない。
入力インピーダンスは通常1Mオームと高いので、高速でも、バイアス電流がある程度必要なオペアンプは使えない。

意外と丁度良い物が無い・・

基本的な特性として、以下の点が挙げられる。(これは、今回のプロジェクトに合致する)
※他にも、色々な項目があり、以下の条件をクリアしていても使えない場合もある。

  • スルーレートが高く、GB積が大きい
  • 入力バイアス電流が低い
  • 雑音が少ない
  • +-5V で使える
  • コストがそれなり
  • 二個入り

探す目安でしかないが、非常に沢山の品種から探すのは、何かトピックが無いと絞るのが難しいので、これらの項目で絞って選択する事になる。
※「A/D 変換の入力プリアンプ用」も選択の目安となると思う。
※1個単位で買えて、それなりに安い事も重要。

入力バイアス電流、雑音、速度は、一般的にトレードオフの関係があり、昔は、そのようなオペアンプは無かったと思うが、現在は、技術革新があり、そのような品種がリリースされている。

それで見つけたのが「アナログデバイセズの AD8066」、チップワンストップで10個程購入した(1個400円くらいだった)
※現在は、廃盤になったのか、在庫分くらいしか無いようで、倍の値段になっている・・・

主な特徴は:

  • 入力バイアス電流:1pA
  • 低価格
  • 145MHzで-3dBの帯域幅(G = +1)
  • 180V/µsのスルーレート(G = +2)
  • 低雑音
  • 5~24V
  • 単一電源およびレール・ツー・レール出力
  • 低いオフセット電圧:最大1.5mV

部品が来て、実験すると、まさに思った通りの性能で、感動した!
素晴らしいとしか言いようが無い!

アナログデバイセズ AD8065/AD8066

DC カップリング

RX72N Envision Kit の外部ポートには、200オームの保護抵抗があり、直でドライブするのは不安な為、FETスイッチを入れてある。

DC を除去する「AC」モードと、DC を観測する機能の切り替えとして、回路を簡単にする為、簡易な回路に変更した。
単純に、カップリングのコンデンサを、フォトモスリレーでショートするだけにした、実験では問題無いようだ。
※ACからDCに切り替える時、コンデンサに溜まった電荷をショートするが、ON 抵抗があるので問題無いだろうと思う。

※ただ、この時使った「PS7200K」は、現在では入手が難しいようなので、他の部品を探す必要がある。

一応入力保護

入力に過大な電圧が入った場合に回路を保護するサージ対策について調べた。
完全に保護する事は出来ないが、ある程度のバリアにはなると思う。
すると、非常に高性能でありながら、回路に与える影響が非常に少ない部品(ガス放電管アレスタ)がある事に気がついた。
値段もそんなに高く無い。
今回は、海外の部品で「Littelfuse SG90」を使う事にした(SMD 部品)。
※日本製は数がまとまらないと入手が難しい。(日本製を使いたいのに、このような制約で使う事が出来ない・・)
※最低発注数1000個とか無理!

Littlefuse GDT-SG90

レンジ切り替え

基本、RXマイコンのA/D入力(0~3.3V)なので、レンジ切り替えが必要で、以下の回路を考えてみたが、実験すると、これでは駄目な事が判った・・・

74HC4051 の入力部分には、寄生容量があり、波形が鈍る・・・
※最初は、2MHz のサンプリングなので十分と思っていたが、オシロスコープの入力インピーダンスは1Mオームと高いので、微小な容量でも効いてくる。

アナログSWを通した場合:

本来の波形:

結局、メカリレーにするしか無いかもしれない・・・
※一応、そんな事もあろうかと、部品は買ってあるが、回路を作るのが面倒だし、リレーをドライブする回路を追加する必要がある。

その前に、フォトMOSリレーをスイッチ替わりにして実験してみようと思う。

まとめ

オペアンプは、価格が高いので、他を探す必要がある。(一応候補は見つけたが、実験しておく必要がある。)
ガス放電管アレスタは、Digikey から購入する事になると思うので、キリが良いところで、まとめて発注する。

本当は、PCB のトラックを引くところまで済ます予定だったが、レンジ切り替えの問題で、先延ばしになった。

今回はここまで、別件もあり、中々集中する事が出来ない・・

RXマイコンで、TinyUSB を使ってみる

TinyUSB について

最近、USB のスタックで評判の TinyUSB を実験してみた。

現状の実装では、主に以下のような感じとなっている。

  • オープンソースである。
  • 非常に多くのマイコンに対応している。
  • RX マイコンも、デバイス(クライアント)、ホストのドライバーが用意されている。
  • 主にデバイスの機能が充実している。(PC に繋ぐ、USB 機器を作る場合)
  • USB ホストは実装が始まったばかりで、あまり枯れていないよう。
  • 全体の構成が判りやすく、サンプルやドキュメントが充実している。
  • 機能実装、バグ修正などが GitHub で行われており、将来性が有望。
  • 「Tiny」となっているが、プロジェクトはかなり大きい。
  • API の仕様が明確で適切なので、マイコンの種別に関係無く、ソースコードの再利用が出来る。(ここが大きい!)

など、かなり魅力的なプロジェクトとなっている。

RX マイコン用のドライバーもあるので、自分の環境で実験してみた。
※当初、ホストのドライバーが無かったが、現在は、コミットされている。

RX マイコン用ドライバーは、Koji KITAYAMA さんが実装しているようで、御礼申し上げます。


自分のシステムに取り込む場合

自分の C++ フレームワークに取り込むには、多少の改造が必要なので、その過程を記す。

  • TinyUSB には、サンプルプログラムが沢山あるが、RX マイコンで使う場合、主に CC-RX 環境に依存しているので、gcc で使うには問題がある。
  • そこで、最小限の改造で、使うようにする為、自分のシステムに必要な部分だけ取り込んで実験した。
  • とりあえず、ホスト機能を使い、ゲームパッドやキーボードを利用する事が目的となっている。
  • 非常に機能が豊富なので、他も試していきたい。

RX マイコンのオフィシャルな対応としては、RX63N、RX65N、RX72N となっているが、基本、USB ペリフェラルは、ほぼ同じ構成なので、RX64M、RX71M、RX66T、RX72T など多くのデバイスにも使えると思われる。
※基本 USB2.0 系で、LowSpeed(1.5Mbps)、FullSpeed(12Mbps)に対応していれば良いようである。
※ RX631/RX63N はマイコン側が LowSpeed に対応しない。
※ RX64M/RX71M の HighSpeed(480Mbps)には対応していない。

USB0 のクロック設定

USB0 ペリフェラルのクロック源には 48MHz が必要なので、クロックジェネレーターの設定を調整する。
RX72N では、内部 PLL を 240MHz にすれば、48MHz は 1/5 にすれば良い。

自分のフレームワークでは、clock_profile.hpp で、以下の設定をして、boost_master_clock() を呼べば、あとは自動で設定してくれる。

    class clock_profile {
    public:
        static constexpr bool       TURN_USB    = true;             ///< USB を使う場合「true」
        static constexpr uint32_t   BASE        =  16'000'000;        ///< 外部接続クリスタル
        static constexpr uint32_t   PLL_BASE    = 240'000'000;        ///< PLL ベースクロック(最大240MHz)

        static constexpr uint32_t   ICLK        = 240'000'000;        ///< ICLK 周波数(最大240MHz)
        static constexpr uint32_t   PCLKA       = 120'000'000;        ///< PCLKA 周波数(最大120MHz)
        static constexpr uint32_t   PCLKB       =  60'000'000;        ///< PCLKB 周波数(最大60MHz)
        static constexpr uint32_t   PCLKC       =  60'000'000;        ///< PCLKC 周波数(最大60MHz)
        static constexpr uint32_t   PCLKD       =  60'000'000;        ///< PCLKD 周波数(最大60MHz)
        static constexpr uint32_t   FCLK        =  60'000'000;        ///< FCLK 周波数(最大60MHz)
        static constexpr uint32_t   BCLK        = 120'000'000;        ///< BCLK 周波数(最大120MHz)
    };

※48MHz が作れない設定を行うと、コンパイルを失敗する。

        static constexpr bool       TURN_USB    = true;             ///< USB を使う場合「true」
        static constexpr uint32_t   BASE        =  16'000'000;        ///< 外部接続クリスタル
        static constexpr uint32_t   PLL_BASE    = 200'000'000;        ///< PLL ベースクロック(最大240MHz)

        static constexpr uint32_t   ICLK        = 200'000'000;        ///< ICLK 周波数(最大240MHz)
        static constexpr uint32_t   PCLKA       = 100'000'000;        ///< PCLKA 周波数(最大120MHz)
        static constexpr uint32_t   PCLKB       =  50'000'000;        ///< PCLKB 周波数(最大60MHz)
        static constexpr uint32_t   PCLKC       =  50'000'000;        ///< PCLKC 周波数(最大60MHz)
        static constexpr uint32_t   PCLKD       =  50'000'000;        ///< PCLKD 周波数(最大60MHz)
        static constexpr uint32_t   FCLK        =  50'000'000;        ///< FCLK 周波数(最大60MHz)
        static constexpr uint32_t   BCLK        = 100'000'000;        ///< BCLK 周波数(最大120MHz)

---

../RX600/system_io.hpp:228:34: error: static assertion failed: USB Clock can't divided.
    static_assert(usb_div_() >= 2 && usb_div_() <= 5, "USB Clock can't divided.");
                  ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
../RX600/system_io.hpp:230:44: error: conversion from 'long unsigned int' to 'device::rw16_t<524324>::value_type' {aka 'short unsigned int'} changes value from '4294967295' to '65535' [-Werror=overflow]
    device::SYSTEM::SCKCR2.UCK = usb_div_() - 1;
                                 ~~~~~~~~~~~^~~
cc1plus.exe: all warnings being treated as errors
make: *** [Makefile:193: release/main.o] エラー 1

USB0 のポート設定

RX72N Envision Kit では、USB の関係ポートとして、以下の2ポートを利用している。

  • USB0_VBUSEN(P16) USB 機器の電源制御ポート
  • USB0_OVRCURB(P14) 外部接続機器の電流制限がオーバーした場合を通知するポート

現在の実装では「オーバーカレント」は観ていないようなので、USB0_VBUSEN のみ設定を行う。

ポート設定は、現在は、ポートマップのオーダーを「SECOND」にする事で、RX72N Envision Kit に対応したもになるようにしてある。

とりあえず、tinyusb_mng クラスを用意して、対応している。

        //-----------------------------------------------------------------//
        /*!
            @brief  開始
            @param[in]  ilvl    割り込みレベル
            @return 成功なら「true」
        */
        //-----------------------------------------------------------------//
        bool start(uint8_t ilvl) noexcept
        {
            if(ilvl == 0) {  // 割り込み無しはエラー
                return false;
            }

            power_mgr::turn(USB_CH::PERIPHERAL);

            // VBUSEN, OVERCURA ピンの設定
            if(!port_map::turn(USB_CH::PERIPHERAL, true, PSEL)) {
                return false;
            }

            ivec_ = icu_mgr::set_interrupt(USB_CH::I_VEC, i_task_, ilvl);

//          utils::format("USB clock divider: 0b%04b\n") % static_cast<uint16_t>(device::SYSTEM::SCKCR2.UCK());
//          utils::format("USB0 interrupt vector: %u\n") % static_cast<uint16_t>(ivec_);

            tuh_init(BOARD_TUH_RHPORT);

            return true;
        }
---

    // RX72N Envision Kit
    typedef device::tinyusb_mng<device::USB0, device::port_map::ORDER::SECOND> TINYUSB;
    TINYUSB tinyusb_;

USB0 の省電力設定

USB0 の省電力設定解除は、ドライバーでされているが、自分のシステムで行いたいので、コメントアウトした。

tinyusb/src/portable/renesas/usba/hcd_usba.c

bool hcd_init(uint8_t rhport)
{
  (void)rhport;
  /* Enable USB0 */

//  uint32_t pswi = disable_interrupt();
//  SYSTEM.PRCR.WORD = SYSTEM_PRCR_PRKEY | SYSTEM_PRCR_PRC1;
//  MSTP(USB0) = 0;
//  SYSTEM.PRCR.WORD = SYSTEM_PRCR_PRKEY;
//  enable_interrupt(pswi);

USB0 の割り込み設定

RX72N では、USB0/USBI は選択型割り込みとなっているので、割り込みベクターを独自に設定して、TinyUSB のハンドラを呼ぶようにしている。

        static INTERRUPT_FUNC void i_task_()
        {
#if CFG_TUH_ENABLED
            tuh_int_handler(0);
#endif
#if CFG_TUD_ENABLED
            tud_int_handler(0);
#endif
        }

...

            ivec_ = icu_mgr::set_interrupt(USB_CH::I_VEC, i_task_, ilvl);

TinyUSB の RX マイコンドライバーでは、マイコンとして、RX72N を選ぶと、選択型割り込みBは、185番がハードコートされている。

自分のシステムでは、選択型割り込みBの割り込みベクターは、内部で管理されていて、設定順により優先度が決定される。
USB0 の初期化は最初に呼ぶので、128番が設定される。

そこで、ドライバーのソースを修正して、128番を使うように修正を行った。

tinyusb/src/portable/renesas/usba/hcd_usba.c

// hcd_init 内
  IR(PERIB, INTB128) = 0;  // IR(PERIB, INTB185) = 0;

void hcd_int_enable(uint8_t rhport)
{
  (void)rhport;
#if ( CFG_TUSB_MCU == OPT_MCU_RX72N )
  IEN(PERIB, INTB128) = 1;  // IEN(PERIB, INTB185) = 1;
#else
  IEN(USB0, USBI0) = 1;
#endif
}

void hcd_int_disable(uint8_t rhport)
{
  (void)rhport;
#if ( CFG_TUSB_MCU == OPT_MCU_RX72N )
  IEN(PERIB, INTB128) = 0;  // IEN(PERIB, INTB185) = 0;
#else
  IEN(USB0, USBI0) = 0;
#endif
}

これで、TinyUSB を自分のシステムで利用する準備が整った。


TinyUSB のコア部分

TinyUSB のコアでは、「tuh_task()」をサービスすれば良いようで、通常無限ループにする。
しかし、それだと、他に何も出来ないので、とりあえず、CMT で 1000Hz(1ms) のタイミングを作り、呼び出すようにした。

    uint16_t cnt = 0;
    uint16_t cnt_max = 50;  // by 100Hz
    while(1) {
        cnt_max = 500;
        cmt_.sync();

        tuh_task();
#if CFG_TUH_CDC
        cdc_task();
#endif

#if CFG_TUH_HID
        hid_app_task();
#endif

        ++cnt;
        if(cnt >= cnt_max) {
            cnt = 0;
        }
        if(cnt < (cnt_max / 2)) {
            LED::P = 0;
        } else {
            LED::P = 1;
        }
    }

TinyUSB のソースをコンパイルする。

iodefine.h は、e2studio/gcc で生成した物をアプリケーションのルートに置いて利用した。

TinyUSB は、tusb_config.h をアプリケーションに合わせて設定する事で、必要なソースをコンパイルするようになっている。
※アプリケーションのルートに置いてある。

ホスト機能を提供する場合、基本的な設定は以下のようになっている。

#define CFG_TUH_HUB                 0
#define CFG_TUH_CDC                 0
#define CFG_TUH_HID                 4 // typical keyboard + mouse device can have 3-4 HID interfaces
#define CFG_TUH_MSC                 0 // Mass Storage Device
#define CFG_TUH_VENDOR              0

とりあえず、HUB は「0」にしてある。

必要なソースを Makefile に追加して、パス、外部変数などを設定する。

CSOURCES    =   common/init.c \
                common/vect.c \
                common/syscalls.c \
                tinyusb/src/tusb.c \
                tinyusb/src/common/tusb_fifo.c \
                tinyusb/src/device/usbd.c \
                tinyusb/src/device/usbd_control.c \
                tinyusb/src/class/audio/audio_device.c \
                tinyusb/src/class/cdc/cdc_device.c \
                tinyusb/src/class/dfu/dfu_device.c \
                tinyusb/src/class/dfu/dfu_rt_device.c \
                tinyusb/src/class/hid/hid_device.c \
                tinyusb/src/class/midi/midi_device.c \
                tinyusb/src/class/msc/msc_device.c \
                tinyusb/src/class/net/ecm_rndis_device.c \
                tinyusb/src/class/net/ncm_device.c \
                tinyusb/src/class/usbtmc/usbtmc_device.c \
                tinyusb/src/class/video/video_device.c \
                tinyusb/src/class/vendor/vendor_device.c \
                tinyusb/src/class/cdc/cdc_host.c \
                tinyusb/src/class/hid/hid_host.c \
                tinyusb/src/class/msc/msc_host.c \
                tinyusb/src/host/hub.c \
                tinyusb/src/host/usbh.c \
                tinyusb/src/portable/ohci/ohci.c \
                tinyusb/src/portable/renesas/usba/hcd_usba.c

...

USER_DEFS   =   SIG_RX72N CFG_TUSB_MCU=OPT_MCU_RX72N

...

# インクルードパス
INC_APP     =   . ../ \
                ../RX600/drw2d/inc/tes \
                ../tinyusb/src

# C コンパイル時の警告設定
CP_OPT      =   -Wall -Werror \
                -Wno-unused-variable \
                -Wno-unused-function \
                -Wno-stringop-truncation \
                -fno-exceptions

これで、TinyUSB 関係ソースをコンパイル出来た。


HID 関係のサービスを実装する

HID 関係の実装は、サンプルがあるので、それを参考にした。

基本、接続時、解放時、データ転送などでコールバックが呼ばれるので、それに対応するコードを実装する。

extern "C" {

    void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len)
    {
        device::tinyusb_base::hid_mount_cb(dev_addr, instance, desc_report, desc_len);
    }

    void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance)
    {
        device::tinyusb_base::hid_umount_cb(dev_addr, instance);
    }

    void hid_app_task(void)
    {
        device::tinyusb_base::hid_app_task();
    }

    void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
    {
        device::tinyusb_base::hid_report_received_cb(dev_addr, instance, report, len);
    }
#if 0
    void tuh_cdc_xfer_isr(uint8_t dev_addr, xfer_result_t event, cdc_pipeid_t pipe_id, uint32_t xferred_bytes)
    {
    }
#endif
    void cdc_task(void)
    {
    }

}

とりあえず、C++ で実装して、TinyUSB に繋いだ・・

    struct tinyusb_base {

        static uint8_t dev_addr_;
        static uint8_t instance_;
        static bool send_;
        static uint8_t leds_;

        static void hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len)
        {
            uint16_t vid, pid;
            tuh_vid_pid_get(dev_addr, &vid, &pid);

            utils::format("HID device address = %d, instance = %d is mounted\r\n")
                % static_cast<uint16_t>(dev_addr) % static_cast<uint16_t>(instance);
            utils::format("VID = %04x, PID = %04x\r\n") % vid % pid;

            auto detect = false;
            auto itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
            switch (itf_protocol) {
            case HID_ITF_PROTOCOL_KEYBOARD:
//              process_kbd_report( (hid_keyboard_report_t const*) report );
                utils::format("Detected KEYBOARD\n");
                detect = true;
                break;

            case HID_ITF_PROTOCOL_MOUSE:
//              process_mouse_report( (hid_mouse_report_t const*) report );
                utils::format("Detected MOUSE\n");
                detect = true;
                break;

            default:
                // Generic report requires matching ReportID and contents with previous parsed report info
//              process_generic_report(dev_addr, instance, report, len);
                utils::format("Detected GENERIC\n");
                detect = true;
                break;
            }

            if (detect) {
                if ( !tuh_hid_receive_report(dev_addr, instance) ) {
                    utils::format("Error: cannot request to receive report\r\n");
                }
            }
        }

        static void hid_umount_cb(uint8_t dev_addr, uint8_t instance)
        {
            utils::format("HID device address = %d, instance = %d is unmounted\r\n")
                % static_cast<uint16_t>(dev_addr) % static_cast<uint16_t>(instance);
        }

        static void hid_app_task()
        {
            if(send_) {
                if(!tuh_hid_set_report(dev_addr_, instance_, 0, HID_REPORT_TYPE_OUTPUT, &leds_, sizeof(leds_))) {
                    utils::format("set report fail...\n");
                }
                send_ = false;
            }
        }

        static void hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len)
        {
            static uint8_t cnt = 0;

            auto itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
            switch (itf_protocol) {
            case HID_ITF_PROTOCOL_KEYBOARD:
                {
                    uint8_t tmp[8];
                    memcpy(tmp, report, len);
                    utils::format("%d, %d, %d, %d\n") % static_cast<uint16_t>(tmp[0]) % static_cast<uint16_t>(tmp[1]) % static_cast<uint16_t>(tmp[2]) % static_cast<uint16_t>(tmp[3]);
                    if(tmp[2] == 83 || tmp[2] == 57) {
                        ++cnt;
                        if(cnt & 1) {
                            leds_ |= KEYBOARD_LED_NUMLOCK | KEYBOARD_LED_CAPSLOCK;
                        } else {
                            leds_ = 0;
                        }
                        dev_addr_ = dev_addr;
                        instance_ = instance;
                        send_ = true;
                    }
                }
                break;

            case HID_ITF_PROTOCOL_MOUSE:

                break;

            default:

                break;
            }

            // continue to request to receive report
            if ( !tuh_hid_receive_report(dev_addr, instance) ) {
                utils::format("Error: cannot request to receive report\r\n");
            }
        }
    };

実験してみるが・・・

とりあえず、コンパイルが通ったので、RX72N Envision Kit にキーボードを繋いで実験してみた。

Start 'USB0' test for 'RX72N' 240[MHz]
SCI Baud rate (set):  115200
SCI Baud rate (real): 115384 (0.16 [%])
CMT rate (set):  1000 [Hz]
CMT rate (real): 1000 [Hz] (0.00 [%])
Start USB: OK!
HID device address = 1, instance = 0 is mounted
VID = 1c4f, PID = 0027
Detected KEYBOARD
0, 0, 25, 0
0, 0, 0, 0
0, 0, 10, 0
0, 0, 0, 0
0, 0, 9, 0
0, 0, 0, 0
0, 0, 23, 0
0, 0, 0, 0

USB キーボードを繋いで、キーを押してみた。
反応が返る!

問題点

ネットで、TinyUSB/Host でキーボード接続時、LED を点灯する仕組みを探したら、「tuh_hid_set_report」API を使い、LED ビットに対応するバイトデータを送れば良い事が判り、実装するものの、上記 API は「false」を返して失敗するようだ・・・

色々調べたが、キーボード接続時に、IDLE 設定の転送を行い、それが終了しない為、失敗しているようだが、根本的な原因が判らない・・

他にも、GENRIC デバイス(キーボードを外して、ゲームパッドを接続するとハングアップする)の動作が微妙とか。
最初にゲームパッドを接続して、次にキーボードを接続すると認識しないとか(電源を切るまで認識しなくなる)

色々問題があるようだ・・・
ただ、それが、TinyUSB の問題なのか、RX マイコンのドライバーなのか、切り分けが出来ていない。


まとめ

まだまだ、これからのようだが、将来性を考えると、TinyUSB は良い選択となるものと思える。

今後、色々と環境を整えていきたい。

RX66T、RX72Tのオーバークロック

RX66T、RX72T について

  • RX66T は最大 160MHz
  • RX72T は最大 200MHz
  • 共に RXv3 コア(DFPU は内蔵しない)

同じ RXv3 コアの RX72N、RX72M などは最大 240MHz 動作となっている。

  • RX66T、RX72T は、主にモーター制御などに特化したデバイスで、高クロックで MTU、GPTW などのタイマーを駆動出来る。
  • また、タイマー出力を微妙に遅延させて、より細かい PWM 波形を作る事が出来る機能を内蔵する。
  • CAN を内蔵する事から、ハイブリッドや電気自動車のコントローラーとして需要が大きいのかもしれない。

RX66T は RXv3 コアとして初めての製品なので、160MHz と言う能力は何となく判るが、同時期に出た RX72T の 200MHz は何故なのか疑問を持っていた。
モーター制御としては、下位に RX24T(80MHz) などがあり、それに比べて能力が格段に上がっている。

周波数が高ければ、1個のデバイスで複数のモーターを同時にケア出来る。

普通に考えて、RXv3 コアは共通なのだから、RX66T や RX72T 用に専用のコアを設計する事は考えにくい。
RX66T は、RX72T の選別漏れでは無いかと思っている。(周波数以外は機能は同じ)
多分中身のコアベースは、RX72N と遜色無いのだろうと思える。
ただ、動作周波数が最大 120MHz のRX66N(RX72N の低速版)があり、RX72N として作ったが、選別で落ちて、低速版として再利用しているのは考えられるので、一概に、動作周波数を制限しているのかは、何とも言えない部分でもある。


オーバークロックしてみる

自分の C++ フレームワークでは、クロック設定は、「clock_profile.hpp」に、希望のクロックテーブルを実装すれば、自動で設定されるようにしてある。
SCI、CAN、I2C、タイマーの周期なども、各クラスが、「clock_profile」の設定を継承して、各クラス内で自動で計算するので、気にする必要は無いよう工夫されている。

そこで、まず、RX66T をオーバークロックしてみた。

少しづつ上げて、レイトレースプログラムで実験してみた(浮動小数点などの計算がかなり含まれる)。

結果から言うと、普通に 240MHz で動作するようだー

namespace device {

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  クロック・プロファイル・クラス @n
                ・分周器の仕様を超える値を指定しない事 @n
                ・PLL_BASE は、0.5 倍単位 @n
                ・他は、PLL_BASE を基数とする整数除算値 @n
                ・詳細はハードウェアーマニュアル参照の事
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    class clock_profile {
    public:
    #if defined(USE_USB)
        static constexpr bool       TURN_USB    = true;             ///< USB を使う場合「true」
        static constexpr uint32_t   BASE        =  12'000'000;        ///< 外部接続クリスタル
        static constexpr uint32_t   PLL_BASE    = 144'000'000;        ///< PLL ベースクロック(最大160MHz)

        static constexpr uint32_t   ICLK        = 144'000'000;        ///< ICLK 周波数(最大160MHz)
        static constexpr uint32_t   PCLKA       =  72'000'000;        ///< PCLKA 周波数(最大120MHz)
        static constexpr uint32_t   PCLKB       =  36'000'000;        ///< PCLKB 周波数(最大60MHz)
        static constexpr uint32_t   PCLKC       = 144'000'000;        ///< PCLKC 周波数(最大160MHz)
        static constexpr uint32_t   PCLKD       =  36'000'000;        ///< PCLKD 周波数(最大60MHz)
        static constexpr uint32_t   FCLK        =  36'000'000;        ///< FCLK 周波数(最大60MHz)
        static constexpr uint32_t   BCLK        =  36'000'000;        ///< BCLK 周波数(最大60MHz)
    #else
        static constexpr bool       TURN_USB    = false;            ///< USB を使う場合「true」
        static constexpr uint32_t   BASE        =  10'000'000;        ///< 外部接続クリスタル
#if 0
        static constexpr uint32_t   PLL_BASE    = 160'000'000;        ///< PLL ベースクロック(最大160MHz)

        static constexpr uint32_t   ICLK        = 160'000'000;        ///< ICLK 周波数(最大160MHz)
        static constexpr uint32_t   PCLKA       =  80'000'000;        ///< PCLKA 周波数(最大120MHz)
        static constexpr uint32_t   PCLKB       =  40'000'000;        ///< PCLKB 周波数(最大60MHz)
        static constexpr uint32_t   PCLKC       = 160'000'000;        ///< PCLKC 周波数(最大160MHz)
        static constexpr uint32_t   PCLKD       =  40'000'000;        ///< PCLKD 周波数(最大60MHz)
        static constexpr uint32_t   FCLK        =  40'000'000;        ///< FCLK 周波数(最大60MHz)
        static constexpr uint32_t   BCLK        =  40'000'000;        ///< BCLK 周波数(最大60MHz)
#else
        // Over clock... 動く、動くぞ・・・
        static constexpr uint32_t   PLL_BASE    = 240'000'000;        ///< PLL ベースクロック(最大160MHz)

        static constexpr uint32_t   ICLK        = 240'000'000;        ///< ICLK 周波数(最大160MHz)
        static constexpr uint32_t   PCLKA       = 120'000'000;        ///< PCLKA 周波数(最大120MHz)
        static constexpr uint32_t   PCLKB       =  60'000'000;        ///< PCLKB 周波数(最大60MHz)
        static constexpr uint32_t   PCLKC       = 120'000'000;        ///< PCLKC 周波数(最大160MHz)
        static constexpr uint32_t   PCLKD       =  60'000'000;        ///< PCLKD 周波数(最大60MHz)
        static constexpr uint32_t   FCLK        =  60'000'000;        ///< FCLK 周波数(最大60MHz)
        static constexpr uint32_t   BCLK        =  60'000'000;        ///< BCLK 周波数(最大60MHz)
#endif
#endif
    };
}
RX66T Start for Ray Trace: 320, 240
# Render time: 371ms (1)

続いて、RX72T、こちらも、問題無く動くw

namespace device {

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  クロック・プロファイル・クラス @n
                ・分周器の仕様を超える値を指定しない事 @n
                ・PLL_BASE は、0.5 倍単位 @n
                ・他は、PLL_BASE を基数とする整数除算値 @n
                ・詳細はハードウェアーマニュアル参照の事
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    class clock_profile {
    public:
#if 0
#ifdef USE_USB
        static constexpr bool       TURN_USB    = true;             ///< USB を使う場合「true」
        static constexpr uint32_t   BASE        =  16'000'000;        ///< 外部接続クリスタル
        static constexpr uint32_t   PLL_BASE    = 192'000'000;        ///< PLL ベースクロック(最大200MHz)

        static constexpr uint32_t   ICLK        = 192'000'000;        ///< ICLK 周波数(最大200MHz)
        static constexpr uint32_t   PCLKA       =  96'000'000;        ///< PCLKA 周波数(最大120MHz)
        static constexpr uint32_t   PCLKB       =  48'000'000;        ///< PCLKB 周波数(最大60MHz)
        static constexpr uint32_t   PCLKC       = 192'000'000;        ///< PCLKC 周波数(最大200MHz)
        static constexpr uint32_t   PCLKD       =  48'000'000;        ///< PCLKD 周波数(最大60MHz)
        static constexpr uint32_t   FCLK        =  48'000'000;        ///< FCLK 周波数(最大60MHz)
        static constexpr uint32_t   BCLK        =  48'000'000;        ///< BCLK 周波数(最大60MHz)
#else
        static constexpr bool       TURN_USB    = false;            ///< USB を利用しない場合「false」
        static constexpr uint32_t   BASE        =  16'000'000;        ///< 外部接続クリスタル
        static constexpr uint32_t   PLL_BASE    = 200'000'000;        ///< PLL ベースクロック

        static constexpr uint32_t   ICLK        = 200'000'000;        ///< ICLK 周波数
        static constexpr uint32_t   PCLKA       = 100'000'000;        ///< PCLKA 周波数
        static constexpr uint32_t   PCLKB       =  50'000'000;        ///< PCLKB 周波数
        static constexpr uint32_t   PCLKC       = 200'000'000;        ///< PCLKC 周波数
        static constexpr uint32_t   PCLKD       =  50'000'000;        ///< PCLKD 周波数
        static constexpr uint32_t   FCLK        =  50'000'000;        ///< FCLK 周波数
        static constexpr uint32_t   BCLK        =  50'000'000;        ///< BCLK 周波数
#endif
#else
        static constexpr bool       TURN_USB    = true;             ///< USB を使う場合「true」
        static constexpr uint32_t   BASE        =  16'000'000;        ///< 外部接続クリスタル
        static constexpr uint32_t   PLL_BASE    = 240'000'000;        ///< PLL ベースクロック(最大200MHz)

        static constexpr uint32_t   ICLK        = 240'000'000;        ///< ICLK 周波数(最大200MHz)
        static constexpr uint32_t   PCLKA       = 120'000'000;        ///< PCLKA 周波数(最大120MHz)
        static constexpr uint32_t   PCLKB       =  60'000'000;        ///< PCLKB 周波数(最大60MHz)
        static constexpr uint32_t   PCLKC       = 240'000'000;        ///< PCLKC 周波数(最大200MHz)
        static constexpr uint32_t   PCLKD       =  60'000'000;        ///< PCLKD 周波数(最大60MHz)
        static constexpr uint32_t   FCLK        =  60'000'000;        ///< FCLK 周波数(最大60MHz)
        static constexpr uint32_t   BCLK        =  60'000'000;        ///< BCLK 周波数(最大60MHz)
#endif
    };
}
RX72T Start for Ray Trace: 320, 240
# Render time: 371ms (1)

実用になるのか?

スペックを超えて動作させるのは、現場では決して行わないが、アマチュアの遊びとしては、それなりに面白い。

現在、デバイスの入手性が極めて悪く、RX72T、RX72N、RX72M などはほぼ入手が出来ない状況となっている。
RX66T については、多少割高だが入手出来るようだ。(自分が買った時の倍近い)
※RX66T と RX72T は値段の差が少ないので(以前、潤沢にデバイスが買えた時期には、逆転現象もあった)、RX72T 一択となるだろうと思える。

まだ、タイマーなど、高クロックで駆動するペリフェラルを評価していないので何とも言えない部分もあるし、連続で動かしている時に停止するとか、色々なアクシデントはあるかもしれないが、とりあえず、動作は確認出来た。

周波数を制限する要因が何なのか、想像でしか無い為、非常にグレーな部分が多いものの、240MHz で動くなら、色々と有利な面も多い。

最近の半導体は、昔に比べて、マージンが大きいし、基本的なスペックは、元々の能力では無く、営業的な側面から決められている事も考えると、動いて当たり前なのかもしれない。

自分としては、低価格で購入でき、高性能な RXxxT シリーズは前から好きなので、これからもこのシリーズには注力したいと思う。


それにしても半導体不足はいつまで続くのか・・・

久しぶりに format クラスを更新

format クラスの見直し

  • format クラスは、組み込みマイコン用に、危険な printf を置き換える為に、boost::format の縮小版のようなノリでコツコツ実装してきた。
  • 組み込みマイコンで C++ を使う場合に、重宝するものと思う。
  • 組み込みマイコンでは、リソースの問題で、iostream などを使う事が難しい。
  • かと言って、C++ で printf を使うのも気が引ける。
  • printf は可変引数を使っているので、致命的な欠点を持っている。
  • 通常、組み込みマイコンを使った製品では、内部で printf を使う事はない。
  • 自前 format クラスは、printf を使うよりコンパクトになるものと思う。
  • 細かい部分を見直し、常に品質を向上するようにしている。
  • 今まで、%g の対応をしていなかったので、今回は、それに注力した。
  • テストケースも同時にアップデートしてより広範囲に仕様を満足するようにした。

浮動小数点の扱い

  • printf の場合、浮動小数点は、内部的には double 扱いになっている。
  • format クラスは、リソースを節約する為、float 扱いだが、有効桁が8桁くらいなら精度も問題なく運用出来ると思う。
  • float は IEEE-754 32 ビットフォーマットとなっている。

何と言っても簡単に使える

  • format クラスは、「format.hpp」のヘッダーのみで、インクルードするだけで使える。
  • ライブラリのリンクも必要無い。
  • 名前空間として「utils」を使っている。
  • boost::format は、フォームとパラメーターが食い違う場合、例外を投げる、自前 format は、内部でエラーコードで対応している。
  • C++ のオペレーター機能を使い、パラメーターを与えているので、安全で、printf に近いコンビニエンス性がある。
#include "format.hpp"

int main()
{
    float value = 1.234f;
    utils::format("Value: %4.3f\n") % value;
}
Value: 1.234

拡張機能

  • 拡張機能として、二進表示「%b」を追加してある。
  • ポインターのアドレスを表示する場合「%p」を使う。

標準出力

  • format は、標準出力として、stdout を使う。
  • 組み込みマイコンでは、write 関数に対して、stdout のハンドルを使い出力する。
  • RX マイコンでは、syscalls.c にその実装があり、内部で put_char 関数に文字を出力するようになっている。
  • メイン部などで、put_char 関数を "C" 用に extern しておいて、その関数から、SCI などに送れば、シリアル出力できる。

他の使い方

    typedef basic_format<stdout_buffered_chaout<256> > format;
    typedef basic_format<stdout_chaout> nformat;
    typedef basic_format<memory_chaout> sformat;
    typedef basic_format<null_chaout> null_format;
    typedef basic_format<size_chaout> size_format;

上記のような「typedef」があり、文字列出力の他、サイズだけ取得するなど、色々な応用が出来る。

※通常、format は小さなバッファを通して、標準出力しているので、「改行」を送らない場合、明示的に「flush」を呼んで、バッファに残った文字を掃き出す必要がある。

今回のアップデートでは、%g(有効桁自動表示)対応がメイン

長い間、浮動小数点の「%g」(自動)フォームに対応していなかったので、それに対応した。
※「Test20」がその検査にあたる。

まだ、テストケースが不十分な感じがするので、バグがあるものと思うが、とりあえず大丈夫そうなので、Github にプッシュした。

Github: format_class

テストケースは、以下のように20パターンあるけど、まだ足りないので、追々追加して、強固なものにしようと思う。

Test01, output buffer size check: (0)  Ref: '0123456789' <-> Res: '0123456'  Pass.
Test02(0), decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test02(1), decimal check. Ref: 'form=-12345678' <-> Res: 'form=-12345678'  Pass.
Test02(2), decimal check. Ref: 'form=     12345678' <-> Res: 'form=     12345678'  Pass.
Test02(3), decimal check. Ref: 'form=    -12345678' <-> Res: 'form=    -12345678'  Pass.
Test02(4), decimal check. Ref: 'form=000012345678' <-> Res: 'form=000012345678'  Pass.
Test02(5), decimal check. Ref: 'form=-00012345678' <-> Res: 'form=-00012345678'  Pass.
Test02(6), decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test02(7), decimal check. Ref: 'form=-12345678' <-> Res: 'form=-12345678'  Pass.
Test02(8), decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test02(9), decimal check. Ref: 'form=-12345678' <-> Res: 'form=-12345678'  Pass.
Test03(0), octal check. Ref: 'form=1245667' <-> Res: 'form=1245667'  Pass.
Test03(1), octal check. Ref: 'form=   1245667' <-> Res: 'form=   1245667'  Pass.
Test03(2), octal check. Ref: 'form=001245667' <-> Res: 'form=001245667'  Pass.
Test03(3), octal check. Ref: 'form=1245667' <-> Res: 'form=1245667'  Pass.
Test03(4), octal check. Ref: 'form=1245667' <-> Res: 'form=1245667'  Pass.
Test04(0), binary check. Ref: 'form=10101110' <-> Res: 'form=10101110'  Pass.
Test04(1), binary check. Ref: 'form=    10101110' <-> Res: 'form=    10101110'  Pass.
Test04(2), binary check. Ref: 'form=0000010101110' <-> Res: 'form=0000010101110'  Pass.
Test04(3), binary check. Ref: 'form=10101110' <-> Res: 'form=10101110'  Pass.
Test04(4), binary check. Ref: 'form=10101110' <-> Res: 'form=10101110'  Pass.
Test05(0), hex-dedcimal check. Ref: 'form=12a4bf9c' <-> Res: 'form=12a4bf9c'  Pass.
Test05(1), hex-dedcimal check. Ref: 'form=  12a4bf9c' <-> Res: 'form=  12a4bf9c'  Pass.
Test05(2), hex-dedcimal check. Ref: 'form=012a4bf9c' <-> Res: 'form=012a4bf9c'  Pass.
Test05(3), hex-dedcimal check. Ref: 'form=12a4bf9c' <-> Res: 'form=12a4bf9c'  Pass.
Test05(4), hex-dedcimal check. Ref: 'form=12a4bf9c' <-> Res: 'form=12a4bf9c'  Pass.
Test05(5), hex-dedcimal check. Ref: 'form=12A4BF9C' <-> Res: 'form=12A4BF9C'  Pass.
Test05(6), hex-dedcimal check. Ref: 'form=  12A4BF9C' <-> Res: 'form=  12A4BF9C'  Pass.
Test05(7), hex-dedcimal check. Ref: 'form=012A4BF9C' <-> Res: 'form=012A4BF9C'  Pass.
Test05(8), hex-dedcimal check. Ref: 'form=12A4BF9C' <-> Res: 'form=12A4BF9C'  Pass.
Test05(9), hex-dedcimal check. Ref: 'form=12A4BF9C' <-> Res: 'form=12A4BF9C'  Pass.
Test06(0), positive decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test06(1), positive decimal check. Ref: 'form=4282621618' <-> Res: 'form=4282621618'  Pass.
Test06(2), positive decimal check. Ref: 'form=     12345678' <-> Res: 'form=     12345678'  Pass.
Test06(3), positive decimal check. Ref: 'form=   4282621618' <-> Res: 'form=   4282621618'  Pass.
Test06(4), positive decimal check. Ref: 'form=000012345678' <-> Res: 'form=000012345678'  Pass.
Test06(5), positive decimal check. Ref: 'form=004282621618' <-> Res: 'form=004282621618'  Pass.
Test06(6), positive decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test06(7), positive decimal check. Ref: 'form=4282621618' <-> Res: 'form=4282621618'  Pass.
Test06(8), positive decimal check. Ref: 'form=12345678' <-> Res: 'form=12345678'  Pass.
Test06(9), positive decimal check. Ref: 'form=4282621618' <-> Res: 'form=4282621618'  Pass.
Test07(0), floating point check. Ref: 'form=1.006250' <-> Res: 'form=1.006250'  Pass.
Test07(1), floating point check. Ref: 'form=-1.006250' <-> Res: 'form=-1.006250'  Pass.
Test07(2), floating point check. Ref: 'form=1.006250' <-> Res: 'form=1.006250'  Pass.
Test07(3), floating point check. Ref: 'form=-1.006250' <-> Res: 'form=-1.006250'  Pass.
Test07(4), floating point check. Ref: 'form=1.006250' <-> Res: 'form=1.006250'  Pass.
Test07(5), floating point check. Ref: 'form=-1.006250' <-> Res: 'form=-1.006250'  Pass.
Test07(6), floating point check. Ref: 'form=1.0063' <-> Res: 'form=1.0063'  Pass.
Test07(7), floating point check. Ref: 'form=-1.0063' <-> Res: 'form=-1.0063'  Pass.
Test07(8), floating point check. Ref: 'form=1.0063' <-> Res: 'form=1.0063'  Pass.
Test07(9), floating point check. Ref: 'form=-1.0063' <-> Res: 'form=-1.0063'  Pass.
Test07(10), floating point check. Ref: 'form=     1' <-> Res: 'form=     1'  Pass.
Test07(11), floating point check. Ref: 'form=    -1' <-> Res: 'form=    -1'  Pass.
Test08(0), floating point (exponent) check. Ref: 'form=1.025001e+05' <-> Res: 'form=1.025001e+05'  Pass.
Test08(1), floating point (exponent) check. Ref: 'form=3.250000e-08' <-> Res: 'form=3.250000e-08'  Pass.
Test08(2), floating point (exponent) check. Ref: 'form=-1.075001e+05' <-> Res: 'form=-1.075001e+05'  Pass.
Test08(3), floating point (exponent) check. Ref: 'form=-6.250000e-08' <-> Res: 'form=-6.250000e-08'  Pass.
Test08(4), floating point (exponent) check. Ref: 'form=1.025001e+05' <-> Res: 'form=1.025001e+05'  Pass.
Test08(5), floating point (exponent) check. Ref: 'form=3.250000e-08' <-> Res: 'form=3.250000e-08'  Pass.
Test08(6), floating point (exponent) check. Ref: 'form=-1.075001e+05' <-> Res: 'form=-1.075001e+05'  Pass.
Test08(7), floating point (exponent) check. Ref: 'form=-6.250000e-08' <-> Res: 'form=-6.250000e-08'  Pass.
Test08(8), floating point (exponent) check. Ref: 'form=1.025001e+05' <-> Res: 'form=1.025001e+05'  Pass.
Test08(9), floating point (exponent) check. Ref: 'form=3.250000e-08' <-> Res: 'form=3.250000e-08'  Pass.
Test08(10), floating point (exponent) check. Ref: 'form=-1.075001e+05' <-> Res: 'form=-1.075001e+05'  Pass.
Test08(11), floating point (exponent) check. Ref: 'form=-6.250000e-08' <-> Res: 'form=-6.250000e-08'  Pass.
Test08(12), floating point (exponent) check. Ref: 'form=1.0250e+05' <-> Res: 'form=1.0250e+05'  Pass.
Test08(13), floating point (exponent) check. Ref: 'form=3.2500e-08' <-> Res: 'form=3.2500e-08'  Pass.
Test08(14), floating point (exponent) check. Ref: 'form=-1.0750e+05' <-> Res: 'form=-1.0750e+05'  Pass.
Test08(15), floating point (exponent) check. Ref: 'form=-6.2500e-08' <-> Res: 'form=-6.2500e-08'  Pass.
Test08(16), floating point (exponent) check. Ref: 'form=1.0250e+05' <-> Res: 'form=1.0250e+05'  Pass.
Test08(17), floating point (exponent) check. Ref: 'form=3.2500e-08' <-> Res: 'form=3.2500e-08'  Pass.
Test08(18), floating point (exponent) check. Ref: 'form=-1.0750e+05' <-> Res: 'form=-1.0750e+05'  Pass.
Test08(19), floating point (exponent) check. Ref: 'form=-6.2500e-08' <-> Res: 'form=-6.2500e-08'  Pass.
Test09(0), text check. Ref: 'AbcdEFG' <-> Res: 'AbcdEFG'  Pass.
Test09(1), text check. Ref: '   AbcdEFG' <-> Res: '   AbcdEFG'  Pass.
Test09(2), text check. Ref: '  AbcdEFG' <-> Res: '  AbcdEFG'  Pass.
Test09(3), text check. Ref: 'AbcdEFG' <-> Res: 'AbcdEFG'  Pass.
Test09(4), text check. Ref: 'AbcdEFG' <-> Res: 'AbcdEFG'  Pass.
Test10, format poniter to nullptr, error code: (1)  Pass.
Test11, different type, error code: (3) '%s' (target float)  Pass.
Test11, different type, error code: (3) '%d' (target float)  Pass.
Test11, different type, error code: (3) '%c' (target float)  Pass.
Test11, different type, error code: (3) '%u' (target float)  Pass.
Test11, different type, error code: (3) '%p' (target float)  Pass.
Test12, pointer type check: (0)  Ref: '000000000064fcbc' <-> Res: '000000000064fcbc'  Pass.
Test13, floating point infinity check: (0) Ref: 'inf' <-> Res: 'inf'  Pass.
Test14, different type, error code: (3) '%s' (target integer)  Pass.
Test14, different type, error code: (3) '%f' (target integer)  Pass.
Test14, different type, error code: (3) '%p' (target integer)  Pass.
Test14, different type, error code: (3) '%g' (target integer)  Pass.
Test15(0), fixed point check. Ref: '0.10' <-> Res: '0.10'  Pass.
Test15(1), fixed point check. Ref: '0.49' <-> Res: '0.49'  Pass.
Test15(2), fixed point check. Ref: '0.73' <-> Res: '0.73'  Pass.
Test15(3), fixed point check. Ref: '0.98' <-> Res: '0.98'  Pass.
Test15(4), fixed point check. Ref: '1.00' <-> Res: '1.00'  Pass.
Test16 floating point '-1' check. Ref: '-99.000000' <-> Res: '-99.000000'  Pass.
Test17 floating point '%-' check. Ref: '-99.000000' <-> Res: '-99.000000'  Pass.
Test18 report pointer (char*) '%p' check. Ref: '000000000064fcc0' <-> Res: '000000000064fcc0'  Pass.
Test19 report pointer (int*) '%p' check. Ref: '000000000040f7a0' <-> Res: '000000000040f7a0'  Pass.
Test20(0), floating point auto check. Ref: '1e+06' <-> Res: '1e+06'  Pass.
Test20(1), floating point auto check. Ref: '1.41421e+06' <-> Res: '1.41421e+06'  Pass.
Test20(2), floating point auto check. Ref: '100000' <-> Res: '100000'  Pass.
Test20(3), floating point auto check. Ref: '141421' <-> Res: '141421'  Pass.
Test20(4), floating point auto check. Ref: '-100000' <-> Res: '-100000'  Pass.
Test20(5), floating point auto check. Ref: '-141421' <-> Res: '-141421'  Pass.
Test20(6), floating point auto check. Ref: '1000' <-> Res: '1000'  Pass.
Test20(7), floating point auto check. Ref: '1414.21' <-> Res: '1414.21'  Pass.
Test20(8), floating point auto check. Ref: '1' <-> Res: '1'  Pass.
Test20(9), floating point auto check. Ref: '1.41421' <-> Res: '1.41421'  Pass.
Test20(10), floating point auto check. Ref: '0.001' <-> Res: '0.001'  Pass.
Test20(11), floating point auto check. Ref: '0.00141421' <-> Res: '0.00141421'  Pass.
Test20(12), floating point auto check. Ref: '-1e-05' <-> Res: '-1e-05'  Pass.
Test20(13), floating point auto check. Ref: '-1.41421e-05' <-> Res: '-1.41421e-05'  Pass.
Test20(14), floating point auto check. Ref: '1e-05' <-> Res: '1e-05'  Pass.
Test20(15), floating point auto check. Ref: '1.41421e-05' <-> Res: '1.41421e-05'  Pass.
Test20(16), floating point auto check. Ref: '1e-06' <-> Res: '1e-06'  Pass.
Test20(17), floating point auto check. Ref: '1.41421e-06' <-> Res: '1.41421e-06'  Pass.

format class Version: 96
All Pass: 20/20

まとめ

  • C++17 対応のコンパイラなら、RX マイコンに限らず、他の環境でも使えると思う。
  • やった事は無いが、Arduino でも使えるものと思う。(C++17 がコンパイル出来れば・・)
  • CC-RX は C++11 さえ未対応なので使えない。

RXマイコンのクロック設定に「static_assert」を追加

RX マイコンのクロック設定

  • RX マイコンのクロック設定はかなり柔軟ではあるものの、設定が出来ない組み合わせも多々ある。
  • その場合、現状の実装では、微妙なクロックで動作していても、その異変を検出するのが難しくなってしまう。
  • そこで、C++11 から追加された「コンパイル時アサート static_assert」を使い、コンパイル時に、不整合を検出して、エラーで停止するようにした。
  • constexpr を組み合わせて、コンパイル時に複雑な条件を検出できる。
  • static_assert はコンパイル時に評価されるので、実時間に影響を与えない。

※何故、今まで使わなかったのか・・


clock_profile.hpp

  • 以前、Makefile で、各クロック周波数を外部から与えていたが、最近は、clock_profile クラスで定義するようにした。
  • この定義は、外部から簡単に参照出来るようにしてある。

RX72T の場合:

namespace device {

    class clock_profile {
    public:
#ifdef USE_USB
        static constexpr bool       TURN_USB    = true;             ///< USB を使う場合「true」
        static constexpr uint32_t   BASE        =  16'000'000;        ///< 外部接続クリスタル
        static constexpr uint32_t   PLL_BASE    = 192'000'000;        ///< PLL ベースクロック(最大200MHz)

        static constexpr uint32_t   ICLK        = 192'000'000;        ///< ICLK 周波数(最大200MHz)
        static constexpr uint32_t   PCLKA       =  96'000'000;        ///< PCLKA 周波数(最大120MHz)
        static constexpr uint32_t   PCLKB       =  48'000'000;        ///< PCLKB 周波数(最大60MHz)
        static constexpr uint32_t   PCLKC       = 192'000'000;        ///< PCLKC 周波数(最大200MHz)
        static constexpr uint32_t   PCLKD       =  48'000'000;        ///< PCLKD 周波数(最大60MHz)
        static constexpr uint32_t   FCLK        =  48'000'000;        ///< FCLK 周波数(最大60MHz)
        static constexpr uint32_t   BCLK        =  48'000'000;        ///< BCLK 周波数(最大60MHz)
#else
        static constexpr bool       TURN_USB    = false;            ///< USB を利用しない場合「false」
        static constexpr uint32_t   BASE        =  16'000'000;        ///< 外部接続クリスタル
        static constexpr uint32_t   PLL_BASE    = 200'000'000;        ///< PLL ベースクロック

        static constexpr uint32_t   ICLK        = 200'000'000;        ///< ICLK 周波数
        static constexpr uint32_t   PCLKA       = 100'000'000;        ///< PCLKA 周波数
        static constexpr uint32_t   PCLKB       =  50'000'000;        ///< PCLKB 周波数
        static constexpr uint32_t   PCLKC       = 200'000'000;        ///< PCLKC 周波数
        static constexpr uint32_t   PCLKD       =  50'000'000;        ///< PCLKD 周波数
        static constexpr uint32_t   FCLK        =  50'000'000;        ///< FCLK 周波数
        static constexpr uint32_t   BCLK        =  50'000'000;        ///< BCLK 周波数
#endif
    };

}
  • 上記のように、実際に設定する周波数を、整数で定義してある。
  • RX72T の場合、USB を使う場合と最大周波数で動作させる場合で、異なったプロファイルを使う。
  • 水晶発振子の場合、8MHz~24MHzまでを使う。
  • 細かい周波数の場合は多少注意を要する。
  • この定数は、CMT、MTU、SCI など、タイミングデバイスで、周期を計算する場合に参照される。

system_io::boost_master_clock()

  • 各クロックを切り替える関数では、設定出来ない場合にコンパイルを止める。
  • 分周して余りが出る場合もコンパイルエラーとなる。
  • PLL_BASE は、外部接続クリスタルなどから、PLL で、内部クロックを生成する。
  • PLL_BASE は 0.5 倍単位で、10 倍から 30 倍まで生成出来る。

ベース周波数のチェック

        static constexpr bool check_base_clock_() noexcept
        {
            bool ok = true;
            if(OSC_TYPE_ == OSC_TYPE::XTAL) {
                if(clock_profile::BASE < 8'000'000 || clock_profile::BASE > 24'000'000) ok = false;
            } else if(OSC_TYPE_ == OSC_TYPE::EXT) {
#if defined(SIG_RX72N) || defined(SIG_RX72M)
                if(clock_profile::BASE > 30'000'000) ok = false;
#else
                if(clock_profile::BASE > 24'000'000) ok = false;
#endif
            } else if(OSC_TYPE_ == OSC_TYPE::HOCO) {  // 16MHz, 18MHz, 20MHz
                if(clock_profile::BASE != 16'000'000 && clock_profile::BASE != 18'000'000 && clock_profile::BASE != 20'000'000) ok = false;
            }
            return ok;
        }

...

        static_assert(check_base_clock_(), "BASE out of range.");
  • RX72N、RX72M では、外部入力クロック時に 30MHz までを許容している。
  • それ以外は 24MHz
  • 内臓高速オシレータは、16MHz、18MHz、20MHz を選択出来る。

PLL ベースクロック

  • PLL ベースクロックは、BASE クロックを内部 PLL で0.5倍単位で逓倍する。
  • 10 倍~30 倍の設定が可能
            static_assert((clock_profile::PLL_BASE * 2 / clock_profile::BASE) >= 20, "PLL-base clock divider underflow.");
            static_assert((clock_profile::PLL_BASE * 2 / clock_profile::BASE) <= 60, "PLL-base clock divider overflow.");
            static_assert((clock_profile::PLL_BASE * 2 % clock_profile::BASE) == 0, "PLL-base clock can't divided.");

各モジュール分周器

  • 各モジュール分周器は、1, 1/2,1/4,1/8,1/16,1/32,1/64 を設定出来る。
  • RX72N、RX72M では、バスクロック(BCLK)だけ 1/3 を設定出来る。
        static constexpr uint8_t clock_div_(uint32_t clk) noexcept
        {
            uint8_t div = 0;
            while(clk < clock_profile::PLL_BASE) {
                ++div;
                clk <<= 1;
            }
            if(div > 0b0110) div = 0b111;
            return div;
        }

        static constexpr bool check_clock_div_(uint32_t clk) noexcept
        {
            auto div = clock_div_(clk);
            if(div > 0b0110) {
                return false;  // overflow
            }
            if((clk << div) != (clock_profile::PLL_BASE & (0xffffffff << div))) {
                return false;  // 割り切れない周期
            }
            return true;
        }

        static constexpr uint8_t clock_div_bus_(uint32_t clk) noexcept
        {
#if defined(SIG_RX72N) || defined(SIG_RX72M)
            if((clock_profile::PLL_BASE - (clk * 3)) < 3) {  // 1/3 設定の検出
                return 0b1001;
            }
#endif
            return clock_div_(clk);
        }

        static constexpr bool check_clock_div_bus_(uint32_t clk) noexcept
        {
            auto div = clock_div_bus_(clk);
            if((div & 0b0111) > 0b0110) {
                return false;  // overflow
            }
            if(div == 0b1001) {  // 1/3
                return true;
            } else {
                if((clk << div) != (clock_profile::PLL_BASE & (0xffffffff << div))) {
                    return false;
                } else {
                    return true;
                }
            }
        }

...

            static_assert(check_clock_div_(clock_profile::FCLK), "FCLK can't divided.");
            static_assert(check_clock_div_(clock_profile::ICLK), "ICLK can't divided.");
            static_assert(check_clock_div_bus_(clock_profile::BCLK), "BCLK can't divided.");
            static_assert(check_clock_div_(clock_profile::PCLKA), "PCLKA can't divided.");
            static_assert(check_clock_div_(clock_profile::PCLKB), "PCLKB can't divided.");
            static_assert(check_clock_div_(clock_profile::PCLKC), "PCLKC can't divided.");
            static_assert(check_clock_div_(clock_profile::PCLKD), "PCLKD can't divided.");

USB クロック

        static constexpr uint32_t usb_div_() noexcept
        {
            if(clock_profile::TURN_USB) {
                if((clock_profile::PLL_BASE % 48'000'000) != 0) return 0;  // 割り切れない場合
                return (clock_profile::PLL_BASE / 48'000'000);
            } else {  // USB を使わない場合は、常に「2」(リセット時の値)を返す
                return 0b0001 + 1;
            }
        }

...

            {
                static_assert(usb_div_() >= 2 && usb_div_() <= 5, "USB Clock can't divided.");
                // 1/2, 1/3, 1/4, 1/5
                device::SYSTEM::SCKCR2.UCK = usb_div_() - 1;
            }

まとめ

  • C++11 以降に追加された「static_assert」だが、今まで、何故か使っていなかったのか?(もったいない)
  • この少しの改造で、clock_profile で、無効な設定を書いても、コンパイル時に検出して、止める事が可能となった。
  • constexpr を使い、コンパイル時に計算や分岐も可能となるので、複雑な条件であってもコンパイル時に検出が可能となる。

RXマイコン、データフラッシュ関係更新

データフラッシュ関係更新

今まで、RX72Nなどで、データフラッシュに対するアクセスが正しく動作していなかった。

原因が判らず、放置してあったが、思い出したように取り組んでみた。

            device::FLASH::FENTRYR = 0xAA80;
            if(device::FLASH::FENTRYR() == 0x0080) {
                mode_ = mode::PE;
                return true;
            } else {
                debug_format("FACI 'P/E' not ready: 'turn_pe_'\n");
                return false;
            }

上記の部分で、Program/Erase モードに移行しない状況となっていた・・
※ハードウェアーマニュアルでは、0xAA80 を書き込んで、0x0080 が読める事を確認する事になっている。

クロック設定や、シーケンサに設定するクロックなど確認したが問題無かった。
※RX72N には、「データフラッシュメモリアクセス周波数設定レジスタ (EEPFCLK)」があり、注意書きとして、

FCLK の周波数を変更する場合、以下の手順に従い、変更前後で遅い方の周波数で動作している状態で
データフラッシュメモリアクセス周波数設定レジスタ (EEPFCLK) を変更してください。

なので、system_io クラス内の FCLK 設定クラス内に移動した。


又、「フラッシュシーケンサ処理クロック周波数通知レジスタ (FPCKAR)」があり(RX64、RX71、RX72 共通)
起動時に設定している。

色々デバッグする過程で、直ぐに読み出すと正しく読めないような状況だと判った。
そこで、とりあえず、10 マイクロ秒の遅延を間に入れた。

    static constexpr uint32_t MODE_CHANGE_DELAY = 10;   ///< モード変更における遅延

            device::FLASH::FENTRYR = 0xAA80;
            utils::delay::micro_second(MODE_CHANGE_DELAY);
            if(device::FLASH::FENTRYR() == 0x0080) {
                mode_ = mode::PE;
                return true;
            } else {
                debug_format("FACI 'P/E' not ready: 'turn_pe_'\n");
                return false;
            }

これで正しく動作する事が判った。
※ハードウェアーマニュアルを良く調べたが、この「遅延」に関する記述は見つけられなかった・・・


その他:

  • 「イレースチェック」コマンドを追加。
  • バンク指定の不具合修正。
  • メッセージの見直しなど色々追加した。

RX24T のデータフラッシュでは、1バイト単位で書き込めるが、RX6x 系では、4 バイト単位での書き込みとなっており、この辺りも管理を考え直す必要性がある。

また、RX64M にはあるが、RX72N には無いレジスタとか、細かい違い吸収するスマートな方法を考えないとならないと思っている。
※現状では、「#iddef」等で、分岐している。


対話形式のデータフラッシュ操作の様子

Flash drive clock: 60 [MHz]
Data Flash total size: 0x00008000
Data Flash block size: 64 bytes
Data Flash word size: 4 byte
FCLK base: 60 MHz
# ?
Data Flash Size: 32768, Bank: 512, Block: 64, Word: 4
erase [bank] (erase 0 to 512)
check [bank] (erase check 0 to 512)
r[ead] org [end] (read)
write org data... (write)
uid (unique ID list)
# check 5
Erase check: bank 5: 0x0140 to 0x017F NG
# erase 5
Erase OK: bank 5, 0x0140 to 0x017F
# write 140 aaa bbbb ccccc dddddd
# r 140 10
0x0140: 00000AAA 0000BBBB 000CCCCC 00DDDDDD
# check 6
Erase check: bank 6: 0x0180 to 0x01BF NG
# write 180 abcd
FACI 'write32_' write error: 0x0180
Write error: 0x0180: 0x0000ABCD
# erase 6
Erase OK: bank 6, 0x0180 to 0x01BF
# write 180 abcd
# r 180 10
0x0180: 0000ABCD 00000000 00000000 00000000
#

enum class を活用

組み込みのプログラミングでは、内部レジスタにマジックワードを書き込む事が多い。
仕様書には詳しく書いてあるものの、プログラムを観た場合に非常に判り難い。
最近、それを少しででも改善するような試みをしている。

今回、フラッシュの FACI シーケンサコマンドを「enum class」で定義して、専用のコマンドとした。

        /// FACI シーケンサ・コマンド
        enum class FACI : uint8_t {
            WRITE_TOP = 0xE8,       ///< プログラム(データフラッシュメモリ) 4バイトプログラム
            WRITE_FIN = 0xD0,       ///< プログラム(データフラッシュメモリ) 4バイトプログラム 最終コマンド
            ERASE1 = 0x20,          ///< 1st ブロックイレーズ (データフラッシュメモリ 64バイト)
            ERASE2 = 0xD0,          ///< 2nd ブロックイレーズ (データフラッシュメモリ 64バイト)
            CLEAR_STATUS = 0x50,    ///< ステータスクリア
            BREAK = 0xB3,           ///< 強制終了
            CHECK_BLANK1 = 0x71,    ///< 1st ブランクチェック
            CHECK_BLANK2 = 0xD0,    ///< 2nd ブランクチェック
        };

        inline void faci_cmd_(FACI cmd) const noexcept
        {
            FLASH::FACI_CMD_AREA = static_cast<uint8_t>(cmd);
        }

        inline void faci_cmd_(FACI cmd1, FACI cmd2) noexcept
        {
            FLASH::FACI_CMD_AREA = static_cast<uint8_t>(cmd1);
            FLASH::FACI_CMD_AREA = static_cast<uint8_t>(cmd2);
        }

※ FACI コマンドでは、コマンドによっては 2 バイト長もあるので、xxxx1、xxxx2 に別けてあるのが多少痛いのだけど・・・

            faci_cmd_(FACI::WRITE_TOP);
            FLASH::FACI_CMD_AREA = 0x02;  // 書き込み数

...

            faci_cmd_(FACI::WRITE_FIN);
            faci_cmd_(FACI::CHECK_BLANK1, FACI::CHECK_BLANK2);

このような試みで、多少判りやすくなっていると思う。


まとめ

今回は、放置してあったデータフラッシュアクセスを何とか動作するようにしたが、何故「遅延」を入れる必要があるのか不明だ・・・
※RX64M、RX71M などでは、遅延は必要無い。

怪しいのは、動作速度を設定するレジスタ関係などだが、何回確認しても、問題無いと思える。
もしかしたら、順番があるのかもしれないが、それは試していない、現状、遅延を入れて動いているので、「良し」としている。

RX72T ボード関係ソフトを更新

RX72T で SD カードの読み書き

以前に制作したRX72Tボード、SDカードの実験をしていなかったので、必要な部品をハンダ付けして実験した。

イモハンダ・・・

ところが、思ったように動作しない・・・

以前に RX24T 用に、RSPI のソフトを動かしていたので、問題無いと思っていたが、非常に奇妙な動作をする・・

  • SanDisk のカードだと認識する。
  • 他のカードだと認識しない・・・

原因不明で、随分悩んだ。

何故か、カードの挿入信号が正常では無かった・・・

原因は、カードの挿入信号用端子のイモハンダだった・・・

SanDisk のカードだと、形状がほんの少し微妙に違うのだろうと思うが、何とか接触するようだ・・・
※他のカードだと解放状態になる・・・

原因が判り、ハンダ付けをやり直して、動作するようになった。

表面実装部品のハンダ付けは、相当気を使う必要がある・・・


少し前も、RX72T ボードで I2C の実験を行ったが、動作が不安定で、原因を調べたら、RX72T ピンのハンダ不良だった・・・


リードが凄く遅い・・・

色々実験を行う過程で、mmc_io.hpp を色々改修していた。

その中で、デバッグメッセージ表示を行う部分を改修していた。

以前は、「#ifdef」でメッセージの表示を行っていた。

#ifdef MMC_DEBUG
            utils::format("rcvr_datablock_: 0x%08X, %d\n") % (uint32_t)(buff)
                % static_cast<uint32_t>(btr);
            utils::delay::micro_second(100000);
#endif

しかし、デバッグ時に表示するメッセージを色々付けたり外したりするのには適切では無いので、以下のようにした。

//      typedef utils::format debug_format;
        typedef utils::null_format debug_format;

これで、上記の「typedef」をどちらか有効にするだけで、切り替わる。
※「null_format」は中身が無いので最適化されると、メッセージ表示の仕組みがそっくり無くなる。

ところが、以前にデバッグした時に、メッセージが速く流れて良く判らなかったのだろうと思うが、無効な遅延を入れていた。
そんな事はすっかり忘却の彼方になっているのは言うまでもない・・・

            debug_format("rcvr_datablock_: 0x%08X, %d\n") % (uint32_t)(buff)
                % static_cast<uint32_t>(btr);
            utils::delay::micro_second(100000);

それが残り、1ブロックリードする度に大きな遅延が入っていた・・・

オシロで、SPCK を観測して、初めて気がついた・・・

これを消して、正常になった。

Lexar 633X 16GB (SDHC) Class10
Write Open:  200 [ms]
Write: 151 KBytes/Sec
Write Close: 17 [ms]

Read Open:  2 [ms]
Read: 654 KBytes/Sec
Read Close: 0 [ms]

※遅延が残った状態だと、リードは、4 KBytes/Sec だった・・・


I2C_sample

I2C_sample も少し手を入れた。

以前は、各 I2C デバイス毎にプロジェクトを作成していたが、I2C_sample に統合するようにした。

現在サポートしている I2C デバイスは:

I2C デバイス メーカー 機能
AS5600 ams 12bits 磁気エンコーダ
BMP280 Bosch Sensotec 温度、圧力センサ
DS3231 Maxim Integrated リアルタイムクロック
EEPROM[0-7] Micro Chip EEPROM

※これから順次追加する予定。

I2C マスターコマンド

コマンド 機能
list 対応デバイスの表示
scan [start=0] [end=127] 接続I2Cされた I2C デバイスのスキャン
exec I2C-name list 表示されたデバイス操作を起動
adr [X] I2C アドレスを設定
r [num=1] デバイスから num バイトを表示
s data... デバイスにデータを書き込む

※exec で起動した場合、exit で戻る。

BMP280 コマンド

コマンド 機能
list 温度、圧力、標高を表示

DS3231 コマンド

コマンド 機能
date 日付、時間の表示
date yyyy/mm/dd hh:mm[:ss] 日付、時間の設定

EEPROM コマンド

コマンド 機能
read ORG [END=+16] EEPROM からデータを読み込み表示
write ORG DATA... EEPROM へデータ書き込み

AS5600 コマンド

コマンド 機能
list 磁気検出、回転位置表示
run 連続的に回転位置など表示(CTRL-Cで抜ける)

まとめ

やはり、表面実装部品をハンダ付けするのは無理がある・・

かと言って、ステンシルを使ってハンダペーストとかを塗り、全体を焼くのも、1枚とかだと、逆に大変そうだ・・

YouTube のノートPCなどの修理動画を観ていると、ホットブロアーで、QFN などのハンダ付けを行っていた。
それを見て、ああなるほどと思ったー、今後、機器を揃えて、この方法を試してみたいと思った。

RXマイコン、選択型割り込み設定不具合とテンプレート化

DMA転送要因でMTUを指定すると、おかしな挙動をする・・

以前、DACストリームで、DMACの起動要因として、TPUを使っていた。
※分周器の設定が細かい方が良いので。

しかし、RX66T、RX72TにはTPUが無いので、どのデバイスでもあるMTUを使うように修正した。
※TMRを16ビットで使う事も考えたが、TMRは、DTCの起動には使えるが、DMACには使えない事が判り断念・・
※TMRを16ビットで使い、インターバルタイマーとして使う実装は行った。

まず、MTUで、インターバルタイマーだけ機能するような実装を行い、RX72Nで実験した。
ちゃんと動作する事を確認。

次に、PSG音楽再生を行おうと、RX64Mで実験・・、動作しない・・・

かなり時間を使い、原因が「power_mgr クラス」でMTUを定義していない事が原因だった・・・

そこで、power_mgr クラスを全デバイスで全て保守。


TPUを使うと動作する・・・

ここで、MTU関係クラスで、色々マズイ部分を修正。

色々修正したが、正常に動作しない・・・

良く調べると、DACストリームでは、MTUのインターバル設定を初期化時に、二度呼ぶような仕様だった。

RX64MのMTU割り込みは選択型Bとなっている、二度呼ぶと、選択型テーブルにMTUの割り込み要因が二か所設定されてしまうのが原因だった。
※TPUでは、これを避けていた。

しかし、良く精査すると、選択型割り込み設定に問題がある事が判り、修正。

選択型割り込み設定をテンプレート化する。

選択型割り込み設定は、RX24T以外、全てのデバイスに存在する。

同じコードをコピーするのも、保守の点で問題なので、テンプレート化して、共有出来るように実装した。

        //-----------------------------------------------------------------//
        /*!
            @brief  選択型割り込みA設定テンプレート
            @param[in]  ICU         ICU クラス
            @param[in]  VEC_TYPE    割り込み要因型
            @param[in]  TASK_TYPE   タスクタイプ
            @param[in]  sel         割り込み要因
            @param[in]  task        割り込みタスク
            @param[in]  lvl         割り込みレベル
            @return 成功なら「true」
        */
        //-----------------------------------------------------------------//
        template <class ICU, typename VEC_TYPE, typename TASK_TYPE, uint16_t org, uint16_t end>
        static typename ICU::VECTOR set_interruptSELA(VEC_TYPE sel, TASK_TYPE task, uint8_t lvl) noexcept
        {
            // sel 要因があれば消す。
            for(uint16_t i = org; i < (end + 1); ++i) {
                auto idx = static_cast<typename ICU::VECTOR>(i);
                if(ICU::SLIAR[idx] == sel) {
                    ICU::IER.enable(idx, 0);
                    set_interrupt_task(nullptr, i);
                    ICU::IPR[idx] = 0;
                    ICU::SLIAR[idx] = VEC_TYPE::NONE;
                    ICU::IR[idx] = 0;
                }
            }
            if(lvl == 0 || task == nullptr) return ICU::VECTOR::NONE;

            for(uint16_t i = org; i < (end + 1); ++i) {
                auto idx = static_cast<typename ICU::VECTOR>(i);
                if(ICU::SLIAR[idx] == VEC_TYPE::NONE) {
                    ICU::IER.enable(idx, 0);
                    set_interrupt_task(task, i);
                    ICU::IPR[idx] = lvl;
                    ICU::SLIAR[idx] = sel;
                    ICU::IR[idx] = 0;
                    ICU::IER.enable(idx, 1);
                    return idx;
                }
            }

            return ICU::VECTOR::NONE;
        }

呼び出し側:

        //-----------------------------------------------------------------//
        /*!
            @brief  割り込み設定(選択Aベクター)
            @param[in]  sel     割り込み要因
            @param[in]  task    割り込みタスク
            @param[in]  lvl 割り込みレベル(0の場合、割り込み禁止)
            @return 成功なら「true」
        */
        //-----------------------------------------------------------------//
        static ICU::VECTOR set_interrupt(ICU::VECTOR_SELA sel, utils::TASK task, uint8_t lvl) noexcept
        {
            return icu_utils::set_interruptSELA<ICU, ICU::VECTOR_SELA, utils::TASK, 208, 255>(sel, task, lvl);
        }

※、テンプレートの実装は、イマイチの部分があるのだが、自分のスキルにとって何とか理解出来る範疇に収めてある。
それにしても、テンプレートは難しくて面白い、でも、余裕のある時にしか出来ないw


C言語を使い続ける理由はあるのか?

今回の件とは直接関係無いが、とある掲示板で、RXマイコンのUART(SCI)の設定に関しての質問があった・・

IDEの機能で、ソースコードを出力するもので、そのAPIを使う為の事柄のようだー

そこで、思ったのは、シリアル通信を行うだけなのに、多くの手順を行い、ドキュメントを詳細に読む必要があるのだった・・

プログラム生成で思い付く問題点:

  • プログラムで生成する事で、自分が一部のコードを改修したら再度生成した時にマージが難しくなる。
  • 後に保守する場合に備えて、生成したプログラムのバージョンや手順を全て記録しておく必要がある。
  • シリアルのチャネルを変更したら、関数のプロトタイプが変わり、多くの変更を余儀なくされる。
  • 生成プログラムのコードはブラックボックスで公開されていない為、何かの不具合に即座に対応出来ない。
  • プログラム生成が初めての人に対して、簡単とは言えない。

自分の C++ フレームワークでは、シリアル通信の手順は、これ以上簡単に出来ないくらいにはしてある。

※プログラムで生成しないので、自由に変えられ、コンパイルをやり直すだけ。
※このような柔軟性は、C 言語で実装するのは難しいと思える。

  • 利用するシリアルチャネルを決める。
  • 利用するシリアルポートを決める。
  • 受信、送信バッファのサイズを決める
  • ボーレート、シリアル通信フォーマットを決める。
  • 割り込みレベルを決定する。(ポーリングでも機能する)
    typedef utils::fixed_fifo<char, 512> RXB;  // RX (受信) バッファの定義
    typedef utils::fixed_fifo<char, 256> TXB;  // TX (送信) バッファの定義

    typedef device::sci_io<SCI_CH, RXB, TXB> SCI;
// SCI ポートの第二候補を選択する場合
//  typedef device::sci_io<SCI_CH, RXB, TXB, device::port_map::ORDER::SECOND> SCI;
    SCI     sci_;
  • SCI_CH には利用するシリアルチャネルをtypedefする。(直で書いても構わない)
    typedef device::SCI2 SCI_CH;

利用するポートは、port_map クラスにより決定され、候補を選択出来るようになっている。

たとえば、候補2を使う場合:

    typedef device::sci_io<SCI_CH, RXB, TXB, device::port_map::ORDER::SECOND> SCI;

とする。

候補と、シリアルポートの関係は、port_map を観れば、判るようになっている。

あとは、開始するだけ:

    {  // SCI の開始
        uint8_t intr = 2;        // 割り込みレベル(0を指定すると、ポーリング動作になる)
        uint32_t baud = 115200;  // ボーレート(任意の整数値を指定可能)
        sci_.start(baud, intr);  // 標準では、8ビット、1ストップビットを選択
// 通信プロトコルを設定する場合は、通信プロトコルのタイプを指定する事が出来る。
// sci_io.hpp PROTOCOL enum class のタイプを参照
//      sci_.start(baud, intr, SCI::PROTOCOL::B8_E_1S);
    }

これで、文字を、送ったり、受けたりが簡単に出来る。

A を送る:

    sci_.putch('A');

文字を受け取る:

    auto ch = sci_.getch();

printf で使いたければ、C の関数として、文字の入出力を extern 宣言する。
※この場合、「syscalls.c」をコンパイル、リンクする必要がある。
※ C++ では、printf を使う理由は無いので、utils::format を使う。

extern "C" {

    // syscalls.c から呼ばれる、標準出力(stdout, stderr)
    void sci_putch(char ch)
    {
        sci_.putch(ch);
    }

    void sci_puts(const char* str)
    {
        sci_.puts(str);
    }

    // syscalls.c から呼ばれる、標準入力(stdin)
    char sci_getch(void)
    {
        return sci_.getch();
    }

    uint16_t sci_length()
    {
        return sci_.recv_length();
    }
}

シリアルフォーマットを変更したい場合:

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  SCI 通信プロトコル型
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        enum class PROTOCOL {
            B8_N_1S,    ///< 8 ビット、No-Parity、 1 Stop Bit
            B8_E_1S,    ///< 8 ビット、Even(偶数)、1 Stop Bit
            B8_O_1S,    ///< 8 ビット、Odd (奇数)、1 Stop Bit
            B8_N_2S,    ///< 8 ビット、No-Parity、 2 Stop Bits
            B8_E_2S,    ///< 8 ビット、Even(偶数)、2 Stop Bits
            B8_O_2S,    ///< 8 ビット、Odd (奇数)、2 Stop Bits
        };

※現在、以上のフォーマットをサポートしている(7ビットは使わないだろうから、サポートしていない)

8ビット、1ストップビット、偶数パリティの場合:

    {
        uint8_t intr = 2;        // 割り込みレベル(0を指定すると、ポーリング動作になる)
        uint32_t baud = 115200;  // ボーレート(任意の整数値を指定可能)
        sci_.start(baud, intr, SCI::PROTOCOL::B8_E_1S);
    }

設定されたボーレートと、実際に設定されたボーレートの誤差を知りたい場合:

        utils::format("SCI PCLK: %u\n") % SCI_CH::PCLK;
        utils::format("SCI Baud rate (set):  %u\n") % sci_.get_baud_rate();
        float rate = 1.0f - static_cast<float>(sci_.get_baud_rate()) / sci_.get_baud_rate(true);
        rate *= 100.0f;
        utils::format("SCI Baud rate (real): %u (%3.2f [%%])\n") % sci_.get_baud_rate(true) % rate;

同じコードは、RX24T、RX64M、RX65N、RX66T、RX72T、RX72N などでおなじように使える。

途中で、SCI2 では、無く、SCI1 に変更したい場合は・・

    typedef device::SCI1 SCI_CH;

とすれば良く、合わせて、ポートのマッピングを吟味する。

また、SCI1、SCI2、SCI3 などを同時に使う場合であっても、同じように定義とリソースを用意するだけとなっている。

さらに、C++ では、ソースコード(実装)とヘッダー(定義)を別ける必要性が無い為、「common/sci_io.hpp」をインクルードするだけで、
他の設定は必要無い。
※printf を使う為、syscalls.c をリンクする必要はあるが、これは、C に起因している為、省けない・・


質問者は、RX231 だったので、サポートしていないが、要望があれば、RX231 をサポートする準備はある。
※GitHub のスポンサーになる必要があるけど・・
※RX231 が載ったボードを用意して送ってもらう必要がある・・

良く言われる事が、C++ は難しいので、勧められないと言うのがある・・

しかし、多くの初心者が、Arduino を利用していると思う、自分のフレームワークでは、テンプレートを多く使っており、Arduino よりハードルは高いかもしれないが、サンプルも多くあり、真似るだけなら、そんなにハードルは高く無いと思うのだが・・・

RL78 のフレームワークを久々に改善

C++ は組み込みでは積極利用者が少ない事を実感する日々

最近、特に感じるが、何故か、C++ を組み込みマイコンで積極利用している人が少ないように感じる。

やはり、ハードルがあるのだろうと思う(自分は慣れの問題で感じなくなっている)

どう考えても、C で実装するより、間違いが少なく、構造的に作れて、再利用性が大きく、最適化性能が高い。

C++11 以降、痛い部分が改善されて、より良く自然に書けるようにもなった。

勉強しない人には、何かと敷居が高いのかもしれない・・・

ただ、C++ は、C である程度実装出来る人にとっては、「手っ取り早く、楽で魅力的」とは映らない面もある。
C++ のパワーを理解していないものと思う。

自分は、10年以上前に、C++ を「勉強しなくては」と心の底から思った出来事がある。

非常に複雑で、ボリュームが大きいプロジェクト内で、急遽、モニター用のプログラムを作る案件が生じたー

この案件に対して、C++師匠は、STL を駆使して、あっとゆーまに実装を終え、ちゃんと動くアプリを作った。
※自分がこの仕様を観た時は、これは、そこそこ大変だなーと思っていた・・

また、この師匠は、数ギガヘルツで動くようなCPUのアプリを、何でアセンブラに毛が生えたようなC言語でプログラムしないといけないの?
もっと相応しい作り方があるのでは?

と問うて来たー、この一件で、自分の C++ 敬遠傾向は、一蹴されたと思う。

それ以来、C++ を履修しなおし、現在に至る。

  • C++ の勉強会などに参加して、C++ に精通した人から、色々なレクチャーを受けた。
  • C++ は、正しい道を進まないで我流で進むと、とんでもない迷路にはまる事がある事例をいくつも聞かされた。
  • C++ の最適化で、人間が考えもしない独特の手法で、巧妙に行う事例をいくつも観た。
  • C++ になって大きなプログラムでも、メモリーリークは殆どしないようになった。
  • コンパイルが正常終了すると、大抵プログラムも思ったように動く事が多くなった。
  • テンプレートを少しは理解出来て、自分でもある程度作れるようになった。
  • 警告が出ない、綺麗なソースコードを書くようになった。
  • コードレビューをするようになった。
  • C++ のトレンドや、標準化委員会、boost などのレポートを読むようになった。

ただ、先は長い・・、自分の理解を超えるような考え方、概念がまだ沢山ある・・
まぁ、とりあえず、出来る範囲でゆっくり歩こうと思う。

ある人が言ってた、C++ を学ぶ最良の方法:

C++ をある程度出来る人にコードレビューしてもらう事。
間違った道から、正しい道に戻してもらう。


RL78 関係のフレームワークをメンテナンス

長い間、RX マイコンのフレームワークに集中していたので、RL78 のフレームワークには手を付けていなかった・・

しかし、最近、このフレームワークを使っている人が Qiita にコメントしていた。

使っている人がいるなら、RX マイコンのフレームワーク更新で改善されたエッセンスなどを RL78 にも反映したい。

そこで、それらの実装とテストを行った。

何故か、フラッシュが出来ない・・

Windows10 になって、RL78 の開発環境を作り直して、FIRST_sample を書こうと思ったが、エラーが出てフラッシュが出来ない。

CP2102N が悪いのかと思い、FTDI に変えてみたが、駄目で、エラーメッセージを頼りに、プロトコルのコードのパラメータを変えたり、色々したが駄目・・・

ルネサス・フラッシュ・プログラマーの最新版を導入して、それで書こうと思ったが、やはりうまく行かない・・・

※最新の RFP では、RL78 へのフラッシュ書き込みに、COM ポートで行う最小限のハードを使った場合をサポートしている。

ところが、FTDI で、再度やり直したら、今度は書き込みが正常に行われた・・・
※何が理由なのかさっぱり判らない(RFP のインストールが関係しているのかもしれない・・・)

改善した箇所

テンプレートクラスの実態を定義

以前の実装では、最適化しない(-O0)場合、テンプレート内スタティック変数の実態が無い為、リンクに失敗する。

テンプレートクラスでは、スタテック変数の実態を定義する必要があり、特殊な書き方が必要だ。
これは、書籍や C++ テンプレートの解説でも殆ど見ない方法だと思う。

※最適化を行うと、実態は必要無く(最適化すると、実態を経由しなくなる)問題にならない事が多い。

※デバッグが必要な場合、最適化は行わない。

namespace device {

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  インターバル・タイマー・クラス
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <class _>
    struct itm_t {

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  12ビット・インターバル・タイマ・コントロール・レジスタ(ITMC)
            @param[in]  T   アクセス・クラス
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        template <class T>
        struct itmc_t : public T {
            using T::operator =;
            using T::operator ();
            using T::operator |=;
            using T::operator &=;

            bit_rw_t<T, bitpos::B15>   RINTE;  ///< 12ビット・インターバル・タイマの動作制御
            bits_rw_t<T, bitpos::B0, 12> ITCMP;  ///< 12ビット・インターバル・タイマのコンペア値設定

        };
        typedef itmc_t< rw16_t<0xFFF90> > ITMC_;
        static ITMC_ ITMC;

...

    };
    // テンプレート内、スタティック定義、実態:
    template<class _> typename itm_t<_>::ITMC_ itm_t<_>::ITMC;

    typedef itm_t<void> itm;

↑のように実装する事でリンクエラーを回避出来る。

「itm_t」クラスは、疑似的にテンプレート化してある。
これは、ヘッダーのみで運用する場合、実態を定義するのに都合が良い。

PORT 定義クラスを更新

シングルポートを定義するテンプレートクラスを最新の物にした。

RX マイコンのフレームワークで得た、知見を組み込んである。

ただ、RL78 のポート関係レジスターは、構造的では無い構成なので、多少異なる構成となっている。

namespace {

    // 吸い込みなので、三番目のパラメーターを「false」とする。
    // LED::P = 1 で、実際のポートは、0になり、LED が点灯する。
    typedef device::PORT<device::port_no::P4, device::bitpos::B3, false> LED;

}

int main(int argc, char* argv[])
{
    utils::port::pullup_all();  ///< 安全の為、全ての入力をプルアップ

    LED::DIR = 1;  // 出力設定

    bool f = true;
    while(1) {
        utils::delay::milli_second(250);  ///< 0.25 秒毎の点滅
        LED::P = f;
        f = !f;
    }
}

PSG_sample を追加

ついでなので、先日実装した PSG エミュレーションで音楽演奏のプロジェクトを追加した。

とりあえず、思ったように鳴っているようだ。

VScode の設定を追加

VScode でソースを編集する場合に、インテリセンスを活用する為、設定ファイルを追加してある。

ただ、理由が不明で、赤線がでる場合がある・・・

これは、今後の課題とする・・・


まとめ

RL78 フレームワークを利用している方には、色々細かく修正したので、最新版を取得すると、コンパイルが通らないかもしれない。

多分、修正にはそんなに多くの苦労は無いものと思うが、新しい版を受け入れてもらいたい。