「ソフトウェアー・エンジニアリング」カテゴリーアーカイブ

ソフトウェアー関係の話題など・・

最近は、github にプッシュしてます~

RX72N Envision Kit での開発(その7)GPTW を使う

GPTW とは?

  • RX66T、RX72T、RX72N には、汎用 PWM 機能(GPTW)が備わっています。
  • 一般に PWM 生成は MTU を使いますが、GPTW はさらに細かい設定が可能で、MTU の拡張版的な扱いのようです。
  • カウンタは32ビットになっており、高いクロックでの駆動が可能なようになっています。
  • RX66T、RX72T は GPTW は 10 チャネル、RX72N には 4 チャネルあります。
  • RX66T、RX72T はより高いクロック(PCLKC、最大160MHz、200MHz)を分周器のクロックとして使えます。
  • RX72N は(PCLKA、最大120MHz)を分周器のクロックとして利用します。
  • 「汎用」とありますが、かなり細かい設定が可能で、主に FET や IGBT などのパワーデバイスの制御に向いています。
  • RX66T、RX72T には、さらに、高分解能波形成型器を通す事で、より細かい PWM 波形を生成できます。
  • インプットキャプチャーや、位相入力(エンコーダー入力)などにも使えます。
  • A/D コンバーターを同期して動かす事が出来るので、正確な電圧、電流の検出が行えます。

GPTW 用ポートの設定クラスを作る。

  • 最近の RX マイコンは、ポートのアサインを行う候補が増えて、より柔軟性が増したと思えます。

  • そこで、「port_map_gptw」クラスを新規に追加して、専用クラスを用意しました。

  • 候補のポリシーは、ハードウェアーマニュアルにある「MPC」にある説明に沿った物にしてあります。

  • 自分のフレームワークでは、別プログラムで設定を生成しないので、判りやすさと柔軟性を与える為、「候補」(ORDER)型を使い、設定します。

  • ポートマッピングオーダー型

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  ポート・マッピング・オーダー型
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        enum class ORDER : uint8_t {
            BYPASS,     ///< ポートマップの設定をバイパスする場合
            FIRST,      ///< 第1候補
            SECOND,     ///< 第2候補
            THIRD,      ///< 第3候補
            FOURTH,     ///< 第4候補
            FIFTH,      ///< 第5候補
            SIXTH,      ///< 第6候補
            SEVENTH,    ///< 第7候補
        };
  • GPTW0 A チャネルのポート候補
        static bool gptw0_(CHANNEL ch, bool ena, ORDER opt) noexcept
        {
            bool ret = true;
            uint8_t sel = ena ? 0b011110 : 0;
            switch(ch) {
            /// GTIOC0A (入出力)
            ///       224 176 144 100
            /// P23     ○   ○   ○   ○
            /// P83     ○   ○   ○   ×
            /// PA5     ○   ○   ○   ○
            /// PD3     ○   ○   ○   ○
            /// PE5     ○   ○   ○   ○
            /// PH6     ○   ×   ×   ×
            case CHANNEL::A:
                switch(opt) {
                case ORDER::FIRST:
                    PORT2::PMR.B3 = 0;
                    MPC::P23PFS.PSEL = sel;
                    PORT2::PMR.B3 = ena;
                    break;
                case ORDER::SECOND:
                    PORT8::PMR.B3 = 0;
                    MPC::P83PFS.PSEL = sel;
                    PORT8::PMR.B3 = ena;
                    break;
                case ORDER::THIRD:
                    PORTA::PMR.B5 = 0;
                    MPC::PA5PFS.PSEL = sel;
                    PORTA::PMR.B5 = ena;
                    break;
                case ORDER::FOURTH:
                    PORTD::PMR.B3 = 0;
                    MPC::PD3PFS.PSEL = sel;
                    PORTD::PMR.B3 = ena;
                    break;
                case ORDER::FIFTH:
                    PORTE::PMR.B5 = 0;
                    MPC::PE5PFS.PSEL = sel;
                    PORTE::PMR.B5 = ena;
                    break;
                case ORDER::SIXTH:
                    PORTH::PMR.B6 = 0;
                    MPC::PH6PFS.PSEL = sel;
                    PORTH::PMR.B6 = ena;
                    break;
                default:
                    ret = false;
                    break;
                }
                break;

制御クラス(gptw_mgr)

  • MTU のマネージャークラスと同じような構成にして、「gptw_mgr」クラスを実装しました。
  • 現状では、PWM 波形を出力するだけですが、テンプレートを使い設定を参照する事で、複数のマイコンでもシンプルな構成にする事が出来ます。

動作モード

  • 動作モードとして、以下の型があります。(今後拡張予定)
  • 現状、のこぎり波以外は実装されていません。
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  動作モード型
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        enum class MODE : uint8_t {
            PWM_S_HL,       ///< のこぎり波 PWM (AB: H --> L) 
            PWM_S_LH,       ///< のこぎり波 PWM (AB: L --> H)
            PWM_S_HL_LH,    ///< のこぎり波 PWM (A: H --> L, B: L --> H)
            PWM_S_LH_HL,    ///< のこぎり波 PWM (A: L --> H, B: H --> L)
            SINGLE,         ///< ワンショット・パルス
            PWM_T1,         ///< 三角波 PWM 1(谷32ビット転送)
            PWM_T2,         ///< 三角波 PWM 2(山/谷32ビット転送)
            PWM_T3,         ///< 三角波 PWM 3(谷64ビット転送)
        };

出力制御と型

  • 出力制御では、以下の型のどれかを設定出来ます。
  • RX66T、RX72T では「反転出力」を指定出来ます。
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  出力型 @n
                    ※反転出力は、RX66T、RX72T の場合にのみ有効
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        enum class  OUTPUT : uint8_t {
            NONE,   ///< 無効

            A,      ///< A を利用
            B,      ///< B を利用
            AB,     ///< AB

            NA,     ///< 反転 A を利用
            NB,     ///< 反転 B を利用
            NA_B,   ///< 反転 A, 正 B を利用
            A_NB,   ///< 正 A, 反転 B を利用
            NA_NB,  ///< 反転 A, 反転 B を利用
        };

gptw_mgr テンプレートクラス

  • gptw_mgr テンプレートのプロトタイプは以下のようになっています。
  • パラメータとして、GPTW のチャネル型、割り込み時に起動させる事が可能なファンクタクラスを指定します。
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  GPTW マネージャー・クラス
        @param[in]  GPTWn   GPTW[n] ユニット
        @param[in]  CMTASK  コンペアマッチタスク型
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <class GPTWn, class CMTASK = utils::null_task>
        class gptw_mgr : public gptw_base {

...

    };
  • gptw_mgr の宣言では、以下のようにします。(RX72N GPTW1 を使った場合)
  • RX72N Envision Kit では、PMod コネクタ CN6 に (7)PD0、(8)PD1 がアサインされており、GPTW1 の出力をマッピング出来ます。
    /// PMOD Connector: PD0(CN6-7), PD1(CN6-8)
    typedef device::GPTW1 GPTW_CH;
    const auto ORDER_A = device::port_map_gptw::ORDER::FOURTH;
    const auto ORDER_B = device::port_map_gptw::ORDER::FOURTH;

    typedef device::gptw_mgr<GPTW_CH, utils::null_task> GPTW;
    GPTW    gptw_;

GPTW の開始

  • プロトタイプは以下のようになっています。
  • 周期は、整数値で周波数を指定します。
  • RX72T、RX72N ではベースクロックが異なりますが、その定義は、GPTx インスタンスで指定されている為、自動で最適な値を計算します。
  • ポート候補で、A、B 出力の「候補」を指定します。
  • 「バッファー動作」は、DUTY 設定をバッファリングする事で、DUTYを変更した場合にノイズが出ません。
  • 通常、PWM 周期とは非同期に DUTY を変更すると思うので、標準でバッファー動作になっています。
  • 設定に反故があると「false」を返して失敗します。
        //-----------------------------------------------------------------//
        /*!
            @brief  開始
            @param[in]  mode    動作モード
            @param[in]  out     出力型
            @param[in]  freq    周期
            @param[in]  ord_a   ポート候補A
            @param[in]  ord_b   ポート候補B
            @param[in]  ilvl    割り込みレベル(0 なら割り込み無し)
            @param[in]  buffer  バッファー動作を無効にする場合「false」
            @return 設定が適正なら「true」
        */
        //-----------------------------------------------------------------//
        bool start(MODE mode, OUTPUT out, uint32_t freq, typename port_map_gptw::ORDER ord_a, typename port_map_gptw::ORDER ord_b,
            uint8_t ilvl = 0, bool buffer = true) noexcept

  • GPTW の開始では、動作モード、出力ポート候補、PWM 周波数、などを指定します。
  • モードは、PWM_S_HL(初期'H' で 'L' になる)
  • 出力は AB
  • 周期は 100KHz
  • 初期状態で、33%、66%のデューティー幅のパルスを出力します。
    {  // GPTW の開始 (PWM / AB 出力)
        uint32_t freq = 100'000;  // 100KHz
        if(gptw_.start(GPTW::MODE::PWM_S_HL, GPTW::OUTPUT::AB, freq, ORDER_A, ORDER_B)) {
            utils::format("GPTW%d start: freq: %u\n") % GPTW::value_type::CHANNEL_NO % freq;
            duty_a_ = 0.33f;
            gptw_.set_duty_a(duty_a_);
            duty_b_ = 0.66f;
            gptw_.set_duty_b(duty_b_);
        } else {
            utils::format("GPTW%d start fail...\n") % GPTW::value_type::CHANNEL_NO;
        }
    }

DUTY の変更

  • サンプルでは、ターミナルを接続して、コマンド入力で、A、B チャネルの DUTY を指定できるようにしてあります。
  • a duty(0 to 1.0)
  • b duty(0 to 1.0)
  • help
Start GPTW sample for 'RX72N Envision Kit' 240[MHz]
SCI PCLK: 60000000
SCI Baud rate (set):  115200
SCI Baud rate (real): 115384 (0.16 [%])
CMT rate (set):  100 [Hz]
CMT rate (real): 100 [Hz] (0.00 [%])
GPTW1 start: freq: 100000
# a
A: duty: 0.330
# b
B: duty: 0.660
# b 0.5
#

まとめ

  • PWM 波形の出力は、意外と複雑なので、マネージャークラスの介入が欠かせません。
  • 出来る範囲で、柔軟な設定が可能なように工夫してありますが、十分ではありません。
  • 足りない設定は、gptw_mgr クラスの構成に習って、改修すれば良いと思います。
  • ソースコードはコメントも多く、動作の概要を掴みやすいように実装されています。
  • 詳細な解説が無くてもソースコードやサンプルコードを少し眺めれば理解できるものと思います。
  • gpt_mgr クラスは、単一のソースなので、他のコードを余り意識しなくても構造が判ると思います。
  • 他に「RX600/gptw.hpp」、「RX72N/port_map_gptw.hpp」を参照する必要があるかもしれません。
  • 「レジスター名」はハードウェアーマニュアルと同一にしてあるので、何かの機能を追加する場合に実装しやすいと思います。

GPTW_sample

RX72Tを動かしてみる

RX72T

以前、RX72M 発表の際、デバイス単体で購入すべく、色々探して、最短で入手できる(マウサー)処から購入した。
その時、RX72T も販売していたので割高だったけど「ついでに」購入していた。

144ピンタイプで、変換基板が手元に無く、動かせていなかったが、変換基板を入手したので、動かしてみた。

RX72T は最大 200MHz で動作し、標準で USB を内蔵しており、エアコンなど家電向けのデバイスとなっている。
自分が買った時は、1500 円くらいだったと思うが、現在は 1000 円くらい( 100 ピンタイプ)で入手出来るようだ、RX66T と余り変わらない・・
※ RX66T は入手性が悪い。

チップワンストップ(RX72T)

基本的なスペック:

  • 3.3V~5V 動作
  • RXv3 コア
  • 最大 200MHz 動作
  • ROM (512K/1024K)
  • RAM 128KB
  • データフラッシュ 32KB
  • ECC 付 RAM 16KB
  • USB 内臓

※RXv3 コアだけど、DFPU はサポートしていない。


基本的なピン接続

最低限必要なピンだけ配線して、動作させた。
※自分はシリアル接続を基本としているので、SCI ブートモードを利用する。

ピン番は144ピンタイプのデバイスなので注意

ピン名 ピン番 通常動作 ブート時
MD/FINED 11 PU(1) PD(0)
P00/UB 9 PD(0) PD(0)
PD5/RXD1 25 TXD TXD
PD3/TXD1 27 RXD RXD
EMLE 7 PD(0) PD(0)
/RES 15 PU(1) PU(1)
P37/XTAL 16 16MHz 16MHz
P36/EXTAL 18 16MHz 16MHz
VCL 10 0.47uF 0.47uF

PD: プルダウン (4.7K)
PU: プルアップ (4.7K)
Vcc、Vss を全て接続して、バイパスコンデンサ(0.1uF)を接続する。
AVcc、AVss も同様に接続。
※ A/D変換で SN を上げる為には、アナログ系の電源に工夫をする必要がある。

  • 動作レベル設定では、直で Vcc、Vss に接続しない事、必ず適当な抵抗を介して接続する。(入出力の場合がある)
  • VCL は 0.47uF のセラミックコンデンサで Vss に接続。
  • クリスタルは 16MHz を選んだ。(共振コンデンサは、8pF)
  • 「/RES」にはリセット SW を設ける。
  • USB ブートの場合は、「P00/UB」端子を「High」とする。

詳しくは、「RX72Tグループ ユーザーズマニュアル ハードウェア編」、「45. フラッシュメモリ」、「45.7.1 ブートモード (SCI インタフェース )」を参照


RX72T 対応サンプルコード

USER_DEFS   =   SIG_RX72T \
                F_ICLK=192000000 \
                F_PCLKA=96000000 F_PCLKB=48000000 F_PCLKC=192000000 F_PCLKD=48000000 \
                F_FCLK=48000000 F_BCLK=48000000

RX72N 対応の時、RX72T も大体対応していたと思うので、FIRST_sample は普通に動作した。
※RX72T は、RX72N より、RX66T に仕様が近い。

#elif defined(SIG_RX72T)
    static const char* system_str_ = { "RX72T" };
    typedef device::system_io<16'000'000, 192'000'000> SYSTEM_IO;
    typedef device::PORT<device::PORT0, device::bitpos::B1> LED;
    typedef device::SCI1 SCI_CH;

FIRST_sample では、LED は P00 に接続するのが通例だったが、P00 は USB ブート時のサイン入力なので避け、P01 にしてある。

クリスタルは、USB 使用時は 192MHz 動作が必要で、USB を使わない最大速度 200MHz も可能なように 16MHz を選択した。

ソフトウェアーループの遅延を調整した。

    static void micro_second(uint32_t us)
        {
            while(us > 0) {

...

#elif defined(SIG_RX72T)
                // 192MHz: 250KHz: (63) 3008239 -> 253.304KHz
                // 192MHz: 250KHz: (64) 3000000 -> 249.357KHz
                for(uint32_t n = 0; n < (F_ICLK / 3000000); ++n) {
                    asm("nop");
                }

...

SCI_sample を試したら、上手く通信出来ない・・
調べると、通信速度が半分になっていた。
最初、クロックデバイダの不具合なのかと思い、system_io クラスを調べたが問題無い。

原因は、sci_io クラスで、ボーレートクロックを微調整するパラメーターの問題だった。
「微調整機構 (MDDR)」では、全体のボーレートを、n/256 で微調整する。
誤差が、1/256 以下の場合(誤差 0.39% 以下)の場合、微調整をバイパスする必要がある。

if(mddr >= 128) brme = true;

mddr は、誤差が0の場合、256 が来る、それで、MDDR には「0」が設定されてしまう・・・

以下のように修正した。

if(mddr >= 128 && mddr < 256) brme = true;

結構、実装には自信があったクラスだけに多少ショックを受けている・・、まだまだだなーと思う瞬間だった・・

ついでに、ボーレートクロックの精度を高めるようなコードを追加した。


現状でサポートして動作確認したサンプルは以下のようになっている。

  • FIRST_sample
  • SCI_sample
  • RAYTRACER_sample
  • CALC_sample

フラッシュ書き込みプログラム「rx_prog」は対応済みで、問題無く書き込めた。

まとめ

RX72T はチップ単体の価格が1000円くらいでありながら、極めて高性能で、それなりに RAM もあるので、小物を作る際には重宝しそうなデバイスだと思う。

5V でも動作して、200MHz で動くのは、それなりにメリットがあるものと思う。
USB も標準で持っているので、PC に接続するようなデバイスを作成する場合にも便利そうだー

今後、サンプルコード対応をしていく。

RX72N Envision Kit での開発(その6)FMシンセサイザー 編


FM シンセサイザー用 GUI を作る

以前に、DX7 FM シンセサイザー用オープンソースをポーティングしました。

当初は、スタンダード MIDI ファイルの演奏を考えていましたが、パースするのが意外と大変そうなので、ストールしていました。

その後、MIDI 演奏は、とりあえず後回しにして、鍵盤を作り、音色を変更する GUI を実装しました。

当初、鍵盤の GUI も、widget_director クラスで管理できるものと思いましたが、鍵盤の場合、同時押しなど、通常の GUI とは異なるケアを行う必要があります、現在の widget クラスを改修するのは、かなり複雑になると考え、専用のクラスを実装しました。

また、タッチパネル(FT5206)サービスクラスでは、同時二点押しまでしかサービスしていませんでしたが、FT5206 の最大数4点までのサービスに修正しました。
※和音を出すのに3点以上が必要です。

DX7 の音色ファイルのロードと切り替え

FM シンセサイザーのソースには、音色は1つだけしか設定されていません。

実機は最大32色まで変更が可能なバンクがあります。

音色ファイルは、「DX7_0628.SYX」というもので、ネットで見つけました。
※たったの4Kバイト程度です。

このアプリでは、SD カードのルートに「DX7_0628.SYX」に置いておくと、起動して3秒くらいしてからロードします。
※SDカードのマウントに遅延がある為

使える音色は以下のものです:

 0: 'PIANO 1   '
 1: 'FM PIANO A'
 2: 'PIANO 1   '
 3: 'HARD ROADS'
 4: 'PIANOBELL2'
 5: 'T 23      '
 6: 'RHODES-CHO'
 7: 'PIPES    A'
 8: 'PIPES   2 '
 9: 'ROADSFLUTE'
10: 'ROADFLUTE2'
11: 'PLUCKEDRUM'
12: 'LO/HI STR2'
13: 'OBEHIND   '
14: 'ANLGBRASS '
15: 'FATSYNTH A'
16: 'JL PONTY 1'
17: 'Strings #2'
18: 'Orchestra '
19: 'PLUCKIN'  '
20: 'OOH AHH EE'
21: 'T 22      '
22: 'BassFlute2'
23: 'BassFlute3'
24: 'TIGHTPIANO'
25: 'BASS FLUTE'
26: 'PAUL STRGS'
27: 'BASS/PIA.1'
28: '2 OR MORE '
29: 'TIMPANI  2'
30: 'MALE CHOIR'
31: 'F.CHORUS 2'

シンセサイザークラスに音色をセットするのは、内部的には、MIDI データとして食わせるようです。
※ロードしたデータをそのままMIDIのストリームに食わせるだけです。

    bool read_synth_color_(const char* filename) noexcept
    {
        utils::file_io fin;
        if(fin.open(filename, "rb")) {
            uint8_t tmp[4096 + 8];
            if(fin.read(tmp, sizeof(tmp)) == sizeof(tmp)) {
                ring_buffer_.Write(tmp, sizeof(tmp));
                {  // データを処理させる為、エンジンを動かす。
                    const uint32_t len = SYNTH_SAMPLE_RATE / 60;
                    int16_t tmp[len];
                    synth_unit_.GetSamples(len, tmp);
                }
                for(int i = 0; i < 32; ++i) {
                    char tmp[12];
                    synth_unit_.get_patch_name(i, tmp, 12 - 1);
                    tmp[11] = 0;
                    utils::sformat(" %2d: %s", &synth_color_name_[i * 16], 16) % (i + 1) % tmp;
                }
            }
            return true;
        } else {
            return false;
        }
    }

マルチタッチと発音

RX72N Envision Kit のタッチパネルは、静電容量タイプで、4点までのマルチタッチが可能なタイプです。

音の強弱は難しいとしても、キーボード(ピアノ)のようなインターフェースには向いています。

    void service_note_() noexcept
    {
        for(int i = 0; i < 21; ++i) {
            const auto& key = synth_gui_.get_keyboard().get(static_cast<SYNTH_GUI::KEYBOARD::key>(i));
            if(key.positive_) {
                uint8_t tmp[3];
                tmp[0] = 0x90;
                tmp[1] = 0x3C + i;
                tmp[2] = 0x7f;
                ring_buffer_.Write(tmp, 3);
            }
            if(key.negative_) {
                uint8_t tmp[3];
                tmp[0] = 0x80;
                tmp[1] = 0x3C + i;
                tmp[2] = 0x7f;
                ring_buffer_.Write(tmp, 3);
            }
        }
    }

GUI キーボードで得た、押した状態、離した状態で、それぞれ、MIDI データを作成して、食わせると音が鳴ります。
押している間、音が連続して鳴ります。

また、マルチタッチでは「和音」が鳴らせる事が大きいです。

流石に、このサイズ(1.5オクターブ程)では、簡単な曲しか演奏できませんが、ガジェットとして楽しいものです。


GUI を作成する要点

widget_director クラスでは、現在は、widget の配置ツールなどは無く、プログラムコードによって widget の配置などを行う必要があります。

例えば、電卓のような物なら、グリッド状にボタンが配置されているので、配置ツールが無くても簡単です。


今回は、音色を変更する為、二つのスイッチと、音色名を表示するボックスを作成しました。

このような場合、座標は、比較的簡単な計算で求められるので、定数を定義して、計算式で widget のリソースを生成しています。

とりあえず、ルールとして:

  • 中央の上部に配置する
  • 左右に音色を変更するボタンを配置する
  • 中央に音色名を表示する
  • オクターブボタンは、画面の端に表示する
  • オクターブ領域表示は、中央に表示する
        static const int16_t SC_NAME_LEN = 16;   ///< With EOT
        static const int16_t SC_NUM = 32;   ///< 音色最大数
        static const int16_t OCT_NUM = 5;   ///< オクターブ域

...

        static const int16_t SC_LOC = 10;   ///< ボタン関係、縦の位置
        static const int16_t SC_SPC = 10;   ///< ボタンとの隙間
        static const int16_t CENTER = 480/2;   ///< X 中心
        static const int16_t SC_BTN_SZ = 30;   ///< ボタンサイズ
        static const int16_t SC_TEX_W = 8 * SC_NAME_LEN;  ///< テキスト横幅
        static const int16_t SC_TEX_H = 24;       ///< テキスト高さ

        static const int16_t OCT_LOC = 40;   ///< オクターブ関係、縦の位置
        static const int16_t OCT_AREA_W = 300;
        static const int16_t OCT_AREA_H = 30;
        static const int16_t OCT_BTN_SZ = 50;   ///< ボタンサイズ

...

        typedef gui::widget WIDGET;
        typedef gui::button BUTTON;
        typedef gui::text TEXT;
        typedef gui::slider SLIDER;
        BUTTON      sc_idx_m_;
        TEXT        sc_name_;
        BUTTON      sc_idx_p_;

        BUTTON      octave_m_;
        SLIDER      octave_d_;
        BUTTON      octave_p_;

...

        synth_gui(RENDER& render, TOUCH& touch) noexcept :
            render_(render), touch_(touch), widd_(render, touch),
            keyboard_(render, touch),
            sc_idx_m_(vtx::srect(CENTER-SC_TEX_W/2-SC_BTN_SZ-SC_SPC, SC_LOC, SC_BTN_SZ, SC_BTN_SZ), "<"),
            sc_name_ (vtx::srect(CENTER-SC_TEX_W/2, SC_LOC+(SC_BTN_SZ-SC_TEX_H)/2, SC_TEX_W, SC_TEX_H),  ""),
            sc_idx_p_(vtx::srect(CENTER+SC_TEX_W/2+SC_SPC,        SC_LOC, SC_BTN_SZ, SC_BTN_SZ), ">"),
            octave_m_(vtx::srect(0,              OCT_LOC, OCT_BTN_SZ, OCT_BTN_SZ), "<<"),
            octave_d_(vtx::srect(CENTER-OCT_AREA_W/2, OCT_LOC+(OCT_BTN_SZ-OCT_AREA_H)/2, OCT_AREA_W, OCT_AREA_H)),
            octave_p_(vtx::srect(480-OCT_BTN_SZ, OCT_LOC, OCT_BTN_SZ, OCT_BTN_SZ), ">>"),
            sc_idx_(0), sc_idx_before_(0), sc_name_org_(nullptr),
            oct_idx_(2)
        { }

BUTTON、TEXT、SLIDER クラスは、コンストラクターで座標を計算して設定しています。

このように定数を設けて、それを起点に座標を生成すると、簡単なルールで、座標の設定を自動化出来て便利です。
ボタンの大きさや隙間など、パラメーターとしているので、簡単に変更出来ます。
計算で行うと、計算式が正しければ、理にかなった正しい表示が行えます。

C++17 では、より複雑なルールや条件分岐など、プログラム的な物を、constexpr を使って定義する事も出来ます。
※ constexpr については、ググッて下さい。
constexpr はコンパイル時に計算されるので、どんなに複雑でも、実機の処理負荷には影響を与えません。

まとめ

マルチタッチを有効に活用するアプリとして、「鍵盤」は、最適なアプリと思えます。

今後、MIDI ファイルの演奏など、アプリを充実させていきたいと思います。

SYNTH_sample


追記

Arduino 環境用に、スタンダード MIDI のプレイヤー(パーサー)があったので、ポーティングしてみました。
多少、改造しましたが、想定の範囲で演奏出来るようです。
ソースコードは、コミットしてあります。
スタンダード MIDI ファイルはネットにあるものが大体使えるようですが、演奏出来ないファイルもあり、その点は調査中です。

「@」ボタンを押すと、ファイラーが開くので、MIDIファイルを選択すれば演奏が始まります。

演奏中、音色を変更する事も出来ます。

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



アナログフロントエンドの実験

最終的には基板を作る予定だが、事前に実験を行い、定数を決めたりしなければならない。

とりあえず、入力アンプ、レベルシフター、カップリングと DC 結合などを実験してみた。

LTSpice でも、多少シュミレーションをしている。

とりあえず、手元にあったオーディオ用 OPA2134 を使ったが、スピードが少し足りないように思う。
それでも、この OP アンプは安い割には超高性能で CP が高いと思う。

計測器のフロントエンドに使うような高速で、SN が高く、ローノイズ、高入力インピーダンスとなると、値段が高い・・
サンプリングが 2MHz 程度なので OPA2134 でも十分な気もする・・

とりあえず、JDS6600 オシレータの波形を入れてみた。
周期は、問題無く正確だ、電圧は計算とかなり違う。


次に、MTU で作った 10KHz の矩形波を使って、各ポイントを計測してみた。

SIGLENT のオシロスコープで観測すると、インピーダンスのマッチングが取れていないようだ・・・

まずはそれを合わす事から・・

DSO で使うプローブは、50MHz 対応の物で 1X、10X 切り替え式だが、回路構成の都合で 10X は使わない予定。
※だったが、やはり、対応は必須なのかもしれないので、構成などを再考している・・

インピーダンスを合わすのは意外と難しい・・・

フレームバッファをダブルバッファにする

シュミレータでは、ダブルバッファ(トリプルバッファ)なので、問題ないが、実機で操作すると、描画時間との関連で、リアルタイムな描画で問題が起こる。
これを解決するには、フレームバッファをダブルバッファにして、描画用と表示用でフリッピングを行う必要がある。

この仕様にすると、RX65N では、別の方法を考えなければならないが、RX72N の場合、潤沢にメモリがあるので問題は無い。
※RX65N では、ダブルバッファ構成にする余分なメモリが無い・・・
※これから購入する人は、あえて RX65N を買う人はいないだろうから、RX65N は切っても良いかもしれない。


簡単なコードを glcdc_mgr クラスに突っ込んで実験したが、思ったように動作せず、大きくはまった・・・
単純な勘違いが重なって、悩んだが、何とか思った動作が出来るようにはなった。

ただ、元々、シングルバッファで運用しようと設計していた部分を大きく変更しなければならず、しばらく改修が続いた。
その過程で、他のアプリも、DRW2D で描画出来るように drw2d_mgr クラスを改修した。

widget 管理は、シングルバッファでの運用を考えて実装してあるので、ダブルバッファにした場合にどうするか・・
これも、widget クラスに機能を追加して、ダブルバッファ対応に改修した。

波形の描画などをダイナミックに行うには、ダブルバッファは必須だ・・・

DRW2D エンジンを利用

ダブルバッファにすると、基本的に、常に描画する方向なので、DRW2D エンジンを積極利用する方が良い。
普通に考えて、描画はソフトで行うより高速だろうと思う。

また、DRW2D エンジンでは、線の太さや、アンチエリアスも簡単に出来る。

ただ、資料が少ないので、フラグの効果や、描画パラメーターの効果については、試すしか無い。

ビットマップテクスチャーを描画する(主にフォント)のに悩んだ。
普通に描画するのは直ぐに出来たけど、ビット「0」の部分を描画せずに透過させたい・・
で、やっと判った・・

        //-----------------------------------------------------------------//
        /*!
            @brief  ビットマップイメージを描画する
            @param[in]  pos     開始点を指定
            @param[in]  img     描画ソースのポインター
            @param[in]  ssz     描画ソースのサイズ
            @param[in]  back    背景を描画する場合「true」
        */
        //-----------------------------------------------------------------//
        void draw_bitmap(const vtx::spos& pos, const void* img, const vtx::spos& ssz, bool back = false)
        noexcept {
            if(img == nullptr) return;

            const uint8_t* src = static_cast<const uint8_t*>(img);
            int16_t w = ssz.x;
            int16_t h = ssz.y;

            // setup_();
            d2_color clut[2];
            clut[0] = back_color_.rgba8.rgba;
            auto copyflag = d2_bf_filter;
            if(!back) {
                clut[0] &= 0xffffff;
                copyflag |= d2_bf_usealpha;
            }
            // d2_setalphaex(d2_, 0, 0);
            clut[1] = fore_color_.rgba8.rgba;
            d2_settexclut_part(d2_, clut, 0, 2);
            d2_setblitsrc(d2_, src, w, w, h, d2_mode_i1 | d2_mode_clut);
            d2_blitcopy(d2_, w, h,
                0, 0, w * 16, h * 16, pos.x * 16, pos.y * 16, copyflag);
        }

Widget 関係のケア

widget_director では、描画は、変更が必要な場合にだけ行うようにしている。
※描画はやはりコストが大きいので、必要な場合にしか行わないようにしている。

そこで、機能を追加して、描画が起こった事を検出して、それを次フレームに持ち越して再描画を行うようにした。
こうすると、両方のフレームが常に同じ状態に保たれる。
「refresh()」では、内部的に特殊なフラグを設けて、「refresh」で描画した場合に、描画ステートを残さないようにした。

        widd_last_ = widd_.update();
        // ダブルバッファ時の widget 管理のケア
        if(render_.is_double_buffer()) {
            if(!widd_last_) {
                widd_.refresh();
            }
        }

破線の描画

点線などを描画する場合など
※この API は利用方法が意外と難しく、かなり苦労した・・
※サンプルコードが無いので、ドキュメントを観ながらだったが、各 API がどのように機能するのか不明だった。
その為、色々試して、ようやく理解した。

とりあえず、縦、横にラインを引く場合のみサポートした。
斜めに引く場合、「d2_setpatternparam」で、方向ベクトルを設定する必要がある。

        void setup_stipple_(const vtx::spos& d)
        {
            if(stipple_ != 0xffffffff) {
                d2_setpatternsize(d2_, 8);
//              d2_setpatternalpha(d2_, 0, 255);
//              d2_setpatternalpha(d2_, 1, 255);
                d2_setpatternparam(d2_, 0, 0, d.x, d.y);
                d2_setpattern(d2_, stipple_);
                d2_setfillmode(d2_, d2_fm_pattern);
            }
        }

短径領域の転送

フレームバッファ内で、短径領域をコピーするのも DRW2D で出来る。

        //-----------------------------------------------------------------//
        /*!
            @brief  移動
            @param[in]  src     ソース位置と大きさ
            @param[in]  dst     転送位置
        */
        //-----------------------------------------------------------------//
        void move(const vtx::srect& src, const vtx::spos& dst) noexcept
        {
            d2_utility_fbblitcopy(d2_, src.size.x, src.size.y, src.org.x, src.org.y, dst.x, dst.y,
                d2_bf_filter);
        }

まとめ

今回は、ダブルバッファ関係と、DRW2D 関係のケアで、終止して、アナログフロントエンドをケア出来なかった・・
それでも、DRW2D の本格移行が出来た感じで、それはそれで大きい。

RX72N Envision Kit でダブルバッファとTinyGLの実験

ダブルバッファ

RX72N では、内蔵メモリが 512K + 512K と増えたので、480x272 の 16 ビットカラーで、ダブルバッファを使う事が出来る。

ダブルバッファは、デジタルストレージオシロスコープで波形をレンダリングするのに都合が良いので、実験する必要性があった。

RX65N では、フレームバッファが1枚分しかなく、16 ビットカラーでは、ダブルバッファにする事が出来ない。
※ 8 ビットカラーなら可能かもしれないが、ラインアドレスは 64 バイトの倍数にする制約があり変則的な設定となる。

描画オブジェクトが GUI のような性質ならまだやりようもあるが、ダイナミックな描画をしようとすると、どうしても、ダブルバッファが必要となる。
※描画速度を気にしなければ、他にも方法はある・・

ダブルバッファの描画では、毎フレーム以下の手順で描画を行う。

  • 表示フレーム同期
  • B のバッファを表示用にする
  • A のバッファを全面クリア
  • A のバッファに全てのオブジェクトを描画
  • A、B を入れ替える

簡単には以上の手順を繰り返す。

通常、全面クリア+全てのオブジェクト描画が、1フレーム(16.6ミリ秒)以内に収まれば、フルフレーム(60Hz)で遅延なくスムーズに描画出来る。
描画オブジェクトが物理的に増えて、1フレームに収まらないと、フレームレートはどんどん落ちていく。
それでも、描画は裏で行っており、見えないので、チラツキは出ない。

glcdc_mgr は、フレームバッファの先頭アドレスを管理している。
GLCDC ハードウェアーは、水平ラインのアドレスは64の倍数でなければならない・・
ダブルバッファが有効な時、ページフリップに従ったアドレスを戻す。

        static const int16_t line_width =
            (((width * static_cast<int16_t>(PXT) / 8) + 63) & 0x7fc0) / (static_cast<int16_t>(PXT) / 8);
        static const uint32_t frame_size =
            line_width * (static_cast<uint32_t>(PXT) / 8) * height;

        void* get_fbp() const noexcept
        {
            uint32_t ofs = 0;
            if(enable_double_) {
                ofs = (flip_count_ & 1) != 0 ? 0 : frame_size;
            }
            if(layer2_org_ != nullptr) {
                uint32_t org = reinterpret_cast<uint32_t>(layer2_org_);
                return reinterpret_cast<void*>(org + ofs);
            } else if(layer1_org_ != nullptr) {
                uint32_t org = reinterpret_cast<uint32_t>(layer1_org_);
                return reinterpret_cast<void*>(org + ofs);
            }
            return nullptr;
        }

垂直同期のタイミングで先頭アドレス(GR2FLM2)がロードされる。
※「GR2VEN」を有効にしておかないと、レジスタを書き換える事が出来ない。

        void sync_vpos() const noexcept
        {
            if(enable_double_) {
                uint32_t ofs = (flip_count_ & 1) != 0 ? 0 : frame_size;
                uint32_t org = reinterpret_cast<uint32_t>(layer2_org_);
                GLC::GR2VEN = 1;
                GLC::GR2FLM2 = org + ofs;
            }
            volatile auto n = ctrl_blk_.vpos_count;
            while(n == ctrl_blk_.vpos_count) {
                asm("nop");
            }
        }

また、RX651/RX65N から搭載された描画エンジン DRW2D は、ソフトの描画に比べて、格段に高速なので、これを有効に活用するのに適している。


OpenGL 的なフレームワーク

「ダブルバッファ」と言えば、やはり3Dグラフィックスだろうと思う。
※スプライトと言う人もいるが・・・

昔から、リアルタイム 3D グラフィックスには、注力しているので、最も馴染みが深い OpenGL を縮小にした API を実装して実験してみた。

とりあえず、L チカに相当するキューブの回転w

RX マイコン内蔵の DRW2D 描画エンジンは、2D の描画用だが、基本的な構造が、3D にもマッチするように構成されている。

オブジェクトの座標をマトリックス演算して、透視変換、スクリーン座標変換まで行えば、2D 座標となり、そのまま描画出来る。
※テクスチャマッピングは多少難解かもしれないが、透視変換に適した API があるようだ。(これは今後の課題)

ポリゴンの描画では、裏と表の概念があり、親和性が良い。
※殆ど何もする事が無いと言いたいところだけど、立体ボリュームに対するクリッピング、ライティングなどを考えると、ちゃんと実装するのはかなり大変ではある。
端折ったテスト的なレンダリングなら簡単!

ほぼ OpenGL と同じような手順で描画出来るようにした、C++ なので、色々と便利機能を実装出来る。

    float ax = 0.0f;
    float ay = 0.0f;
    while(1) {
        render_.sync_frame();

        render_.clear(DEF_COLOR::Black);

        auto& m = tgl_.at_matrix();
        m.set_viewport(0, 0, LCD_X, LCD_Y);
        m.set_mode(gl::matrixf::mode::modelview);
        m.identity();
        m.translate(0.0f, 0.0f, -10.0f);
        m.rotate(ax, 1.0f, 0.0f, 0.0f);
        m.rotate(ay, 0.0f, 1.0f, 0.0f);

        ax += 1.0f;
        if(ax >= 360.0f) ax -= 360.0f;
        ay += 1.5f;
        if(ay >= 360.0f) ay -= 360.0f;

        m.set_mode(gl::matrixf::mode::projection);
        m.identity();
        m.perspective(45.0f, static_cast<float>(LCD_X) / static_cast<float>(LCD_Y), 1.0f, 50.0f);

//      draw_box_(2.0f, TGL::PTYPE::LINE_LOOP);
        draw_box_(2.0f, TGL::PTYPE::QUAD);

        tgl_.renderring();
    }

RX72N TFU を有効に!

RX72N は、三角関数演算器を持っているので、これを有効に利用するコードを追加。

回転行列の演算では、以下の API を利用しているので、その部分で、専用 API を呼ぶようにした。

    static inline void deg_sin_cos_(float deg, float& si, float& co) noexcept
    {
#ifdef SIG_RX72N
        __builtin_rx_sincosf(deg  * vtx::deg2rad_f_, &si, &co);
#else
        si = std::sin(deg * vtx::deg2rad_f_);
        co = std::cos(deg * vtx::deg2rad_f_);
#endif
    }

むすび

非常に初歩的ではあるが、3D オブジェクトの描画をテストした。
実用的に使うには、まだまだこれから色々入れないとならない・・・

とりあえず、ダブルバッファの実験から脱線したので、このプロジェクトはストールして、デジタルストレージオシロスコープの実装に戻る。

DRW2D エンジンは、今までほとんど使って来なかったので、良いきっかけになったと思う。

DRW2D エンジンは、アンチエリアスもサポートされており、このハードウェアーの情報は少ないが、かなり良く出来ていると思う。

RXマイコン、MTUテンプレートクラスを更新

MTU テンプレートクラスをクリーンアップ

RXマイコンの MTU は、非常に高機能で、シリーズが異なると微妙に仕様が異なっている場合もある。

以前に、RX24T で、MTU を使った時に、未完全な状態で実装したが、少し整理してみた。
これは、RX72N Envision Kit で、デジタルストレージオシロを作る場合に MTU の出力が必要な為だ。
※プローブのインピーダンスマッチングを行う場合など、基準波を出力する必要がある為だ。
Pmod2 コネクタに PD1 がアサインされており、PD1 は、MTIOC4B に設定可能となっている。
※この端子は、MTU4、コンペアマッチBに対応した出力として利用可能となっている。


テンプレートの複雑な型を回避

テンプレートクラスの場合、実装時の問題として、「にわとりの卵」問題が起こる場合がある。

これは、テンプレート内に、そのクラス特有の構造体や、enum class 型を定義している場合などで、起こる。

テンプレート内に定義すると、テンプレートの正確な型を知らないと、その定義にアクセス出来なくなる。

template <class A, class B, class C>
class asdfg {
public:

    enum class TYPE {
        AAA,
        BBB,
        CCC,
    };

    struct setting_t {

        ...
    };

};

上記のテンプレートクラスでは、asdfg クラスの「TYPE 型」や、「setting_t 構造体」にアクセスしたい場合、class A、B、C の正確な「型」を知らないと出来ない。

そこで、この回避策として、これら定義を分離してプレーンなクラスとして定義する。

class asdfg_base {
public:
    enum class TYPE {
        AAA,
        BBB,
        CCC,
    };

    struct setting_t {

        ...
    };
};

そして、そのクラスを継承する。

template <class A, class B, class C>
class asdfg : public asdfg_base {

    ...

};

こうすれば、クラス A、B、C の型を知らなくても、「asdfg_base::TYPE」、「asdfg_base::setting_t」として参照出来る。
※「asdfg_base」クラスは、メンバー変数などを置かない、「定義」だけのクラスにしておく、そうすれば、結びつきが緩和されて、全体的な構成がシンプルとなる。
※ C++ での「継承」は、クラスとの結びつきが強力で、なるべく使わない設計が好まれる。

タイマー関係のポート設定

現在のフレームワークでは、比較的簡単な定義で、ペリフェラル固有のポート設定を自動化している。
これは、外部プログラムの助けを借りて、設定を生成しなくても、簡単な指定で、ポートのアサインを自動で行う事が出来る仕組みとなっている。
また、ソースコードはオープンソースとしているので、足りない機能は、個々に追加できる事も大きい。

ポートのアサインは、非常に多義に渡り、複雑でボリュームが大きく、マイコン種別で異なる。
C++ では、コンパイラの最適化で、余分なコードは極限まで排除出来る為、最終的に必要無いコードは、実行バイナリーに含まれない。
※コンパイル時にパラメーターが決定されている必要がある。

arduino などでは、ポートは、決められた整数値を通して、普通の関数で行っている。
この場合、関数内では、switch 文などで、場合別けをして、それぞれの設定を行っている。
しかし、このような実装では、コンパイル時の最適化で余分なコードを排除する事は難しいし、速度が出ない場合がある。
テンプレートパラメーターの場合は、同じように場合別けで実装しても、コンパイル時に、決定された固定値以外のルートにあるコードは削除される。
これが、C++ テンプレートの強みであると思う。


ポートのアサインを行うクラスは、「port_map」で行っている、しかし、MTU 関係は、ポートの指定が複雑なので、専用のクラスを実装する事にした。
※「port_map_mtu.hpp」に移動した。

以下は、MTU 関係のポートを RX72N 176 ピンバージョンから、抜き出して、並べた。
※基本的に、144 ピンやそれ以下のバージョンでは、部分的にポートが無いだけで、対応は同じとなっている。

        ///< P34 ( 27)  MTIOC0A
        ///< PB3 ( 98)  MTIOC0A / MTIOC4A
        ///< P15 ( 50)  MTIOC0B / MTCLKB
        ///< P13 ( 52)  MTIOC0B
        ///< PA1 (114)  MTIOC0B / MTCLKC / MTIOC7B
        ///< P32 ( 29)  MTIOC0C
        ///< PB1 (100)  MTIOC0C / MTIOC4C
        ///< P33 ( 28)  MTIOC0D
        ///< PA3 (110)  MTIOC0D / MTCLKD

        ///< P20 ( 45)  MTIOC1A
        ///< P21 ( 44)  MTIOC1B / MTIOC4A

        ///< P26 ( 37)  MTIOC2A
        ///< PB5 ( 96)  MTIOC2A / MTIOC1B
        ///< P27 ( 36)  MTIOC2B

        ///< P17 ( 46)  MTIOC3A / MTIOC3B / MTIOC4B
        ///< P14 ( 51)  MTIOC3A / MTCLKA
        ///< PC7 ( 76)  MTIOC3A / MTCLKB
        ///< PC1 ( 89)  MTIOC3A
        ///< P22 ( 43)  MTIOC3B / MTCLKC
        ///< PC5 ( 78)  MTIOC3B / MTCLKD
        ///< P80 ( 81)  MTIOC3B
        ///< PB7 ( 94)  MTIOC3B
        ///< PJ3 ( 13)  MTIOC3C
        ///< P56 ( 64)  MTIOC3C
        ///< P16 ( 48)  MTIOC3C / MTIOC3D
        ///< PC6 ( 77)  MTIOC3C / MTCLKA
        ///< PC0 ( 91)  MTIOC3C
        ///< P23 ( 42)  MTIOC3D / MTCLKD
        ///< P81 ( 80)  MTIOC3D
        ///< PC4 ( 82)  MTIOC3D / MTCLKC
        ///< PB6 ( 95)  MTIOC3D
        ///< PE0 (135)  MTIOC3D

        ///< P24 ( 40)  MTIOC4A / MTCLKA
        ///< P82 ( 79)  MTIOC4A
        ///< PA0 (118)  MTIOC4A / MTIOC6D
        ///< PE2 (133)  MTIOC4A
        ///< P30 ( 33)  MTIOC4B
        ///< P54 ( 66)  MTIOC4B
        ///< PC2 ( 86)  MTIOC4B
        ///< PE3 (132)  MTIOC4B
        ///< PD1 (156)  MTIOC4B
        ///< P25 ( 38)  MTIOC4C / MTCLKB
        ///< P87 ( 47)  MTIOC4C
        ///< P83 ( 74)  MTIOC4C
        ///< PE5 (130)  MTIOC4C / MTIOC2B
        ///< PE1 (134)  MTIOC4C / MTIOC3B
        ///< P31 ( 32)  MTIOC4D
        ///< P86 ( 49)  MTIOC4D
        ///< P55 ( 65)  MTIOC4D
        ///< PC3 ( 83)  MTIOC4D
        ///< PE4 (131)  MTIOC4D/MTIOC1A
        ///< PD2 (154)  MTIOC4D

        ///< P12 ( 53)  MTIC5U
        ///< PA4 (109)  MTIC5U / MTCLKA
        ///< PD7 (143)  MTIC5U
        ///< P11 ( 67)  MTIC5V
        ///< PA6 (107)  MTIC5V / MTCLKB
        ///< PD6 (145)  MTIC5V / MTIOC8A
        ///< P10 ( 68)  MTIC5W
        ///< PB0 (104)  MTIC5W
        ///< PD5 (147)  MTIC5W  /MTIOC8C / MTCLKA

        ///< PJ1 ( 59)  MTIOC6A
        ///< PE7 (125)  MTIOC6A
        ///< PJ0 ( 60)  MTIOC6B
        ///< PA5 (108)  MTIOC6B
        ///< P85 ( 61)  MTIOC6C
        ///< PE6 (126)  MTIOC6C
        ///< P84 ( 62)  MTIOC6D

        ///< PA2 (112)  MTIOC7A
        ///< P67 (120)  MTIOC7C
        ///< P66 (122)  MTIOC7D

        ///< PD4 (148)  MTIOC8B
        ///< PD3 (150)  MTIOC8D

ピンのアサインで、痛いとこは、ピンの割り付けは自由に出来ない点だ・・
何故、自由に出来ないのか?、ノイズや、複雑度、回路設計上の事情など様々と思うが、内部の機能を全て有効に使う事は通常出来ない。

毎回、特定のボードで、色々な機能を割り振る場合に、ピンのアサインが可能かどうかを調べる作業が難航する。
使う機能が少ない場合は、難しくないのだが、色々な機能を使いたい場合に、いつも苦労する。
場合によっては、ピンのアサインが重複して、機能を使えない場合も多々起こる。
ボードが出来てから、機能を追加する場合などに、どう頑張ってもアサインが出来ずに、パターンを切って貼ってを行う場合も起こる・・
このような制約は、今後回避出来るようにしてもらいたいものだが・・

また、ハマリポイントとして、MTU3、MTU4 は、ポートが、他のチャネルと重複している為、特定のレジスタを有効にする必要がある。

TOERA レジスタは、出力端子の MTIOC4D、MTIOC4C、MTIOC3D、MTIOC4B、MTIOC4A、MTIOC3B
の出力設定の許可 / 禁止を行うレジスタです。
これらの端子は TOERA レジスタの各ビットの設定をしないと正しく出力されません。TOERA レジスタ
は MTU3、MTU4 の TIOR レジスタ設定の前に値をセットしてください。
MTU.TOERA レジスタは、MTU.TSTRA レジスタの CST3、CST4 ビットを “0” にした後で設定してくださ
い。

RX72N Envision Kit でのテスト

RX72N Envision Kit では、Pmod2 コネクタの 8 番ピンに、PD1 がアサインされている。
PD1 は、MTU4 の B チャネル出力として利用できる。

定義は以下のように行う:
※ポートの候補は「FIFTH」となっている。

    typedef device::MTU4 MTU;
    static const auto PSEL = device::port_map_mtu::option::FIFTH;
    typedef device::mtu_io<MTU, utils::null_task, utils::null_task, PSEL> MTU_IO;
    MTU_IO  mtu_io_;

10000Hz、チャネル B、コンペアマッチでトグル出力としている。
※トグル出力の場合、内部では、倍の 20000Hz でカウンタが動作するように自動で設定される。

    {
        uint32_t freq = 10'000;
        if(!mtu_io_.start_normal(MTU::channel::B, MTU_IO::OUTPUT::TOGGLE, freq)) {
            utils::format("MTU4 not start...\n");
        } else {
            list_mtu_freq_();
        }
    }

内部のカウンタ誤差を表示する:
mtu_io クラスでは、MTU の分周器の機能を使い、なるべく設定値に近い周期を設定しようとする。
それでも、設定誤差が大きい場合はある。

    void list_mtu_freq_()
    {
        utils::format("MTU rate (set):  %d [Hz]\n") % mtu_io_.get_rate();
        auto rate = 1.0f - static_cast<float>(mtu_io_.get_rate()) / mtu_io_.get_rate(true);
        rate *= 100.0f;
        utils::format("MTU rate (real): %d [Hz] (%3.2f [%%])\n")
            % mtu_io_.get_rate(true) % rate;
    }

ターミナル出力:

Start test for 'RX72N' 240[MHz]
SCI Baud rate (set):  115200
SCI Baud rate (real): 115355 (0.13 [%])
CMT rate (set):  100 [Hz]
CMT rate (real): 100 [Hz] (0.00 [%])
MTU rate (set):  10000 [Hz]
MTU rate (real): 10000 [Hz] (0.00 [%])

※この全ソースコードは、Github の RX/test にある。
※RX/test は、プロジェクトに組み込む単機能のテストを行うもので、場合によりコードが残らない場合がある。

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

シュミレーター:

マイコン内蔵A/Dコンバーター

RXマイコンのメリットとして、内蔵A/DコンバーターのSNが高い事があると思う。
※最近の他社マイコン、ARM、ESP32、PIC32などの現実はあまり詳しく無い。
※かなり昔に比較した時に、確かに優位性があった。

RXマイコン内蔵、A/Dコンバーターも、豊富な変換モード、サンプルホールド、細かいチャネル設定など、非常に柔軟な設定が可能になっている。
広範囲の事柄が関係するので、理解して応用するのは大変だ・・
また、アナログ回路も絡むので、その部分で、デジタル回路には無い難しさがある。


RX72N の場合:

項目 内容
ユニット数 2ユニット(S12AD, S12AD1)
分解能 12ビット
変換時間 1チャネル当たり(0.48μs)(12ビット変換モード)
1チャネル当たり (0.45μs)(10ビット変換モード)
1チャネル当たり (0.42μs)(8ビット変換モード)
備考 (A/D変換クロック ADCLK = 60MHz動作時)

12 ビットの変換時、チャネル辺り、最大 0.48 マイクロ秒で変換出来る。
RX65N Envision Kit、RX72N Envision Kit では、同時に2チャネルが変換可能なアナログチャネルが、pmod コネクタなどにアサイン可能となっている。
※多少残念なのは、電圧リファレンス端子が、内蔵電源に接続されている点がある・・

2M サンプルで同時 2 チャンネルなら、まぁ仕様としては何とか実用性があると思える。

以上の仕様で、簡易なデジタルストレージオシロが作れると思っていた。
そこで、RX65N Envision Kit で簡単な実験を行い、実際にキャプチャーしたデータをLCDに表示して基本的な実験は成功していた。
ただ、RX65N ではCPUのクロックが 120MHz なので、2M サンプルだと、複雑なトリガー条件を作成すると、割り込み処理がオーバーフローするかもしれない・・
DMA 転送にすれば良いと思うかもしれないが、トリガー条件により、電圧の変化を検出する必要があるので、ソフトの介入がどうしても必要となる。

また、アナログのフロントエンド部のハードウェアーとして、どのようなものが適当なのか、検討していた。

最近、PC 上で API レベルシュミレーターを実装した事で、ようやくプロジェクトが動き始めた。

ソフトが重要

データをキャプチャーするのは簡単だが、問題は GUI 関係のソフトウェアーだと思う。

デジタルストレージオシロを実用的に使うには、ソフトの機能、基本的なデザイン(操作の考え方)が重要で、複雑で、大きなボリュームになる。

以前に、工場で使う検査装置として、デジタルストレージオシロの機能が入った物を作った事がある。
その時は、波形のキャプチャーだけ、外部のFPGAとマイコンを組み合わせた機器で行い、波形の表示などは、PCで行った。
その時は PC 上のソフトなので、効率やメモリ、速度などは気にする必要が無く、自由に作れた。
それでも、非常に面倒な作業で、時間や電圧の変換、操作性など、色々苦労した事が蘇る・・

RX65N Envision Kit や RX72N Envision Kit の場合、液晶の解像度も小さいので、操作性も考慮する必要がある。
また、タッチパネルの操作が基本なので、そのようなデザインを考える必要がある。
加えて、外部に接続するハードウェアーもあまり大掛かりにならないようにする事も考慮する必要がある。
最近では、数万円出せば、高性能なデジタルストレージオシロが買えるので、作っても、あまり利用頻度は大きいとは思えないが、ソフトの構成や、基本的な構成などは、一つのアプリケーションとして参考になると思う。
教材として考えれば、それなりに利用価値があるものと思って進めている。

また、オープンソースとする事で、誰でも、「欲しい機能」を追加する事も出来る。
※特殊なトリガー条件などを実装する事が出来ると思う。


PC 上の、API レベルシュミレーションは便利で、暇を見つけてコツコツ進めている。
GUI Widget フレームワークも完成度と機能が充実しつつあるので、GUI などの面でも実装が楽になってきた。

PC では、A/D コンバーターの部分をシュミレーションにより作成して、それを A/D の変換値12ビット整数データとして与え、ソフトを実装している。
キリの良いとこで、実機でも動かして確認する。

A/D の疑似波形を生成

疑似波形としては、サイン波、三角波、矩形波などをサポートして実験を行っている。
※実機では、ノイズなども考慮する必要があるので、最終段階では、ソフトでノイズを混ぜる事も考える必要がある。

        //-----------------------------------------------------------------//
        /*!
            @brief  SIN/COS 波形を生成
            @param[in]  freq    周波数
            @param[in]  ppv     電圧 (peak to peak)
            @param[in]  num     生成数
            @param[in]  ch0     CH0 波形型
            @param[in]  ch1     CH1 波形型
        */
        //-----------------------------------------------------------------//
        void make_wave(uint32_t freq, float ppv, uint32_t num, PWAVE_TYPE ch0, PWAVE_TYPE ch1)
        noexcept
        {
            static int32_t count = 0;
            auto smpl = get_samplerate();
            auto& task = at_cap_task();
            auto unit = static_cast<float>(smpl) / static_cast<float>(freq);
            auto vgain0 = voltage_to_value(0, ppv);
            auto vgain1 = voltage_to_value(1, ppv);
            for(uint32_t i = 0; i < num; ++i) {
                auto a = static_cast<float>(count % static_cast<int32_t>(unit)) / unit;
                task.adv_.x = -pwave_(ch0, a, vgain0);
                task.adv_.y = -pwave_(ch1, a, vgain1);
                if(task.adv_.x < -CAP_OFS) task.adv_.x = -CAP_OFS;
                else if(task.adv_.x > (CAP_OFS-1)) task.adv_.x = CAP_OFS-1;
                if(task.adv_.y < -CAP_OFS) task.adv_.y = -CAP_OFS;
                else if(task.adv_.y > (CAP_OFS-1)) task.adv_.y = CAP_OFS-1;
                task();
                ++count;
                if(count >= CAP_NUM) {
                    count = 0;
                }
            }
        }

サイン波や、三角波、矩形波は、以下のような関数で生成する。

        static float ftri_(float in) noexcept
        {
            auto a = fmod(in, 1.0f);
            if(a >= 0.5f) {
                a = 1.0f - a;
            }
            a -= 0.25f;
            return a * 4.0f;
        }

        static float fsqu_(float in) noexcept
        {
            auto a = fmod(in, 1.0f);
            if(a < 0.5f) {
                return 1.0f;
            } else {
                return -1.0f;
            }
        }

        static float fsquf_(float in, float& back) noexcept
        {
            auto a = fsqu_(in);
            auto d = (a - back) * 0.707f;
            auto ans = back + d;
            back = a; 
            return ans;
        }

        static int16_t pwave_(PWAVE_TYPE pw, float phase, float gain) noexcept
        {
            static float fsqu_s_ = 0;
            static float fsqu_c_ = 0;
            int16_t ret = 0;
            switch(pw) {
            case PWAVE_TYPE::SIN:
                ret = static_cast<int16_t>(sinf(phase * vtx::radian_f_) * gain);
                break;
            case PWAVE_TYPE::COS:
                ret = static_cast<int16_t>(cosf(phase * vtx::radian_f_) * gain);
                break;
            case PWAVE_TYPE::TRI_C:
                ret = static_cast<int16_t>(ftri_(phase) * gain);
                break;
            case PWAVE_TYPE::TRI_S:
                ret = static_cast<int16_t>(ftri_(phase + 0.25f) * gain);
                break;
            case PWAVE_TYPE::SQU_C:
                ret = static_cast<int16_t>(fsqu_(phase) * gain);
                break;
            case PWAVE_TYPE::SQU_S:
                ret = static_cast<int16_t>(fsqu_(phase + 0.25f) * gain);
                break;
            case PWAVE_TYPE::FSQU_C:
                ret = static_cast<int16_t>(fsquf_(phase, fsqu_c_) * gain);
                break;
            case PWAVE_TYPE::FSQU_S:
                ret = static_cast<int16_t>(fsquf_(phase + 0.25f, fsqu_s_) * gain);
                break;
            default:
                break;
            }
            return ret;
        }

現在の実装は「capture.hpp」に含めている。
※これは、見直すべきかもしれないが・・

SIN、COS は、同じ物で、位相が 45度異なるだけとも言える。


A/D コンバーターのプリアンプ

サンプリングが 2MHz だと、実用的に扱える周期は 1/10 の 200KHz くらいだろうか・・
その場合でも、1 周期、10 サンプルしか無い・・

オシロスコープとしては、かなり低機能なものなので、あまり高性能なフロントエンドを考える必要は無いものの、経験が無いので、どのような構成にすべきなのか良く判らない。
一番良いのは、実際のオシロスコープを分解して、リバースエンジニアリングを行うのが手っ取り速い気がする・・
最初から最高の物を目指すと、完成しないので、改修していけばいいだろうと思う。

1X、プローブを接続する場合、入力インピーダンスは 1000K オーム程?
※10X プローブでは、9000K オームの入力抵抗が直列に入っているものと思う。

最初、1X、10X プローブを両方使えるようにしようと思ったが、回路が大掛かりになりそうなので、1X のみにしようと思う。
※10Xでは、既に1/10になるので、分圧は必要無いが、それを直でオペアンプに入れると、1X を接続した場合に、過電圧からの保護が難しい。
※アナログスイッチで、過電圧保護機能が充実しているデバイスを使えば良さそうだが、値段が高い。

何となく、機械式リレーを使うのが嫌なので、フォトモスリレーを使って、DC、AC を切り替えている。

オペアンプは、オーディオ用の物だが、以前にオーディオ用 DAC に使った部品が沢山余っている。

この回路には、過大な電圧を制限して、A/D の入力を制限する回路が含まれていない・・


まだ、実際の回路で試していないので、この回路では、駄目かもしれないが・・

レンジ切り替えや、AC、DC 切り替えは、機械式スイッチにすれば良いのかもしれないが、スマートでは無くなる気がする・・・

全体の機能

現在シュミレーターを使い、以下の機能を大体実装した。

チャネル電圧切り替え:

  • AC, GND, DC, OFF
  • 10V,5V,2V,1V,500mV,200mV,100mV,50mV,10mV

サンプリング切り替え:

  • 1us,2us,5us
  • 10us,20us,50us
  • 100us,200us,500us
  • 1ms,2ms,5ms
  • 10ms,20ms,50ms
  • 100ms,200ms,500ms

トリガー切り替え:

  • None,One,Run,CH0-Pos,CH1-Pos,CH0-Neg,CH1-Neg

計測:

  • Time Sub,CH0 Sub,CH1 Sub,Time Abs,CH0 Abs,CH1 Abs

まとめ

シュミレーターのおかげで、ソフト部はかなり進んだが、やはりハード部は、進捗が遅い・・・

現状のコードは、シュミレーター「glfw_app/rx_gui_emu」から、定期的に「RX/DSOS_sample」にマージしている。

RXマイコンオーディオプレイヤー関係を更新

MP3, WAV の全体時間を表示

色々さぼっていた部分を更新した。
ただ、イマイチとなってしまった事もある・・

WAV ファイルで RAW の場合、圧縮は無いので、全体の時間を取得する事は、割と簡単に出来る。

これが、MP3 の場合は、結構面倒な処理を通す必要がある。

可変ビットレートの場合は、特に面倒だ・・

今まで全体時間は表示していなかったので、サウンド関係クラスを全体的に、細かく色々修正をした。

ただ、問題もある、3分程度の MP3 ファイルの全体時間を取得するのに2秒くらいかかる・・・
余分な処理は通していないハズだが、240MHz の RX72N でもそのくらいかかる・・・

何がネックになっているのか解析していないが・・
詳しく読んだ訳では無いが、MP3 のフレームのヘッダー情報をパースするだけなのだが・・・

mad ライブラリは、あまり詳しく理解している訳では無いので、API の使い方が悪いのかもしれない・・・

とりあえず、これは、今後の課題とする・・

ID3 タグ関係を更新

MP3 のタグ関係も細かく更新した。

ここでの問題は、APIC 情報で、プログレッシブ方式で格納された画像をデコード出来ない問題だ。
picojpeg では、プログレッシブ方式はサポートしていない。
※サポートしようとすると、メモリを消費するので仕方無いと思うが、余分なメモリを使わないで強引にパースする事は出来そうに思う。
ただ、メモリ使用量の制約では、ソフトは、もの凄く面倒になるのは間違いない・・・
なので、これは保留とする。
※それでなくとも、デコードしながら、リアルタイムに画像をスケールして、直でフレームバッファに書き込むようにして、メモリを節約している。

プログレスバー表示

全体時間が取得できるようになったので、プログレスバー表示を追加した。

その過程で、widget 関係の管理を少しだけ見直した。
以前の電卓アプリでも、色々拡張したが、まだまだ足りない機能がある。

色々やりたい事はあるが

既にバイナリーは860キロを超えており、フラッシュの書き換えに時間がかかる・・・
このアプリでは、シュミレータで実験するには、色々とサポートしなければならない機能があるので、それも面倒。
※やろうとすると、実装に時間がかかりそう・・
それに、速度のシュミレーションは現状では出来ないし・・

ID3 タグのパースも色々問題があるかもしれない・・

ID3 タグのパースは、自前のコードを使っている。
一応、ちゃんと説明を理解して実装してはいるが、MP3 タグの場合、それだけでは、パース出来ない場合がある。

これはつまり、厳密には間違った仕様で作られたMP3ファイルでも、それを出来るだけ回避してパースする工夫を盛り込む必要がある。
厳密に間違った仕様を弾いていると、よの中に流通しているMP3のタグをパース出来ない場合がかなり出現する。
かなり知名度があったアプリでも、間違った TAG を吐くアプリも存在する為、既成事実的にそれで作成した物をパースする必要がある。

また、文字コードに起因する解釈の違いもある、これはかなり厄介だ、UTF-8 や UTF-16 ならほぼ問題無いが、OEM コードの場合、国別で意味が異なるので、日本語圏以外で作られた物を日本語圏でデコードすると化ける。

また OEM コードなのに UTF-8 であったり、UTF-8 のハズなのに OEM コードだったり・・

APIC のプログレッシブの件は別としても、色々問題は多く、全てに対応する事は不可能なので、最悪「死ぬ」事を防いで、スルーするしかない。

picojpeg の問題

JPEG のデコーダーは、フルセット版を利用出来ない。
これは、殆どの場合、メモリ不足になる為で、picojpeg を利用している。

ベースラインでエンコードされた画像であっても、picojpeg ではサポートしていない機能があるようだ。

その場合、タグの画像を張り替えるしかない・・・

現在判明している、サポート出来ないマーカー:

ID3v2: Ver: 0400, Flag: 80 (153579)
V2.3: 'image/jpeg'
Album:   'sasakure.UK - プロトタイプ ナナクジャク'
Title:   'トゥイー・ボックスの人形劇場'
Artist:  'sasakure.UK'
Artist2: 'sasakure.UK'
Year:
Disc:     1/1
Track:    3/11

Audio file: '03 トゥイー・ボックスの人形劇場.mp3'
JPEG status: 20

picojpeg.c

      case M_SOF1:  /* extended sequential DCT */
      default:
      {
         return PJPG_UNSUPPORTED_MARKER;
      }

文字列が長い場合に対応

アルバム名、曲名、アーティスト名などが、長い場合がある。
以前は、気にせず描画していたので、ジャケット画像に重ねて表示されていた。

これを、正確にクリッピングして、表示しきれない場合は、スクロールをして全体を表示させるようにした。

この機能は、widget text クラスで行っているので、オーディオプレイヤーの機能ではなく一般化した GUI の機能となる。

描画は、オフラインで行っていない為、多少ちらつくが、まぁ「良し」としている。
かなり細かい描画を組み合わせているので、色々と問題が起こるかもしれないが・・

どんな感じに表示されるのかは、動画じゃないとわかりづらい。

レベルメーター表示

オーディオ出力時のレベルをサンプリングして、レベルメーター的な物を追加した。
ピークホールド表示も追加。

CD をリッピングして MP3 を作成するには?

ところで、CD をリッピングして MP3 を作成する場合、どんなアプリを使うべきか?

自分の知見では、SONY の「Music Center for PC」が一番良いのではと思っている。
※SONY の Music プレイヤーを持っていなくても利用できる。

全ソースコードなど

このプロジェクトは、RX72N Envision Kit 専用では無い、RX65N Envision Kit でも動作する。
※オーディオ出力は、12 ビットの D/A アナログ出力となるが・・

AUDIO_sample (Github)

FreeRTOS を更新

FreeRTOS カーネルを最新版に更新

ようやく、FreeRTOS で TCP、WiFi などを扱うので、この機会にカーネルを最新版にアップデートした。

以前は、バージョン番号で管理されていたが、最近は、更新日がバージョンとなっているようだ。

今回は「FreeRTOS V202011.00」となっている。

RX700v3 コア用のポートが追加された

RX72N などで、DFPU がサポートされた為、「RX700v3_DPFPU」のポートが追加された。

ただ、ソースコードを見ると・・・

#warning Testing for DFPU support in this port is not yet complete

とあるので、まだ、テストが完了していないようだ・・・
※現状のコードでは、コンパイルが通らない・・・

これは、つまり、「-dfpu」を有効にして、double の演算を行った場合は問題が起こると言う事なのか・・・
多分、double の演算を、一つのタスクで行い、他のタスクで利用しなければ問題無いと思うが・・・

FreeRTOSConfig.h の設定に関するメモ

  • このヘッダーは、基本、自分がサポートする RXマイコンで共有出来るようにしている。
  • メモリーモデルで「FreeRTOS/Source/portable/MemMang/heap_1.c」を利用する場合。(RX24T)
  • Makefile で「RTOS_HEAP_SIZE=10」などのようにサイズ(KB)を設定する。(RX24T)
  • メモリーモデルで「FreeRTOS/Source/portable/MemMang/heap_3.c」を使う場合は、内部で「malloc, free」を呼ぶ為、必要無い。
  • Plus TCP を使う場合、heap_4.c、heap_5.c を使う必要があるようなので、RTOS_HEAP_SIZE は設定する必要がある。
  • 独自のメモリアロケータを使うのには抵抗があるが、仕方無い・・・(以前に、微妙なバグで酷い目にあった事がある・・)

RX72N 用を追加

既に、オーディオプレイヤーなどで、FreeRTOS を利用していたが、サンプルのプロジェクトが無かったので追加した。

ソースコードなどを整理

合わせて、コードの整理、README.md、READMEja.md などを追加修正した。


次は、イーサーネットドライバーなどを整理して、ネットワーク関係に本格着手したい・・
※今まで、ルネサス社のネットスタックを使っていたが、FreeRTOS を使うようになると、より汎用的なネットスタックに移行する方が全ての面で恩恵が大きいものと思う。
シングルタスクで、ネット関係を運用するのは、かなり面倒で難しい問題が発生し、パフォーマンスを最適化するのも困難になる。

RX72N Envision Kit での開発(その5)関数電卓を作る

関数電卓をでデザインする

何か、実用的なアプリを作りたくて、今回「関数電卓」を選んでみました。

グラフ表示付きの関数電卓が、それなりの値段で売られているので、それなら、自分で作ればいいのでは?
と言う、不憫な動機も少しはありますw

実際、作ってみると、けっこう奥が深く、C++ の学習にも良い教材と思います。

  • 実際のコードは、それなりに巨大で、サンプルと言うには無理があるかもしれないです。
  • GUI 関係の挙動をプログラムすると、物理的にコード量が多くなります。
  • 通常、演算には double を使えば、それなりの精度なので実用上十分かもしれませんが面白味に欠けると思います。
  • 240MHz で動く RX72N で動かすのならと思い、「多倍長」のライブラリを利用してみました。
  • メモリが許す限り大きな桁が可能なのですが、とりあえず250桁にしています。(簡単に増やせますが、表示方法を工夫する必要があります)
  • 将来的には、WiFi を有効にして、ブラウザ経由で操作する事も行う予定です。
  • また、プログラム電卓としての機能やグラフ表示、数式処理も付けたいと思っています。
  • まだ及第点と言う訳ではなく、とりあえず、最低限の機能を使える状態です。

API レベルシュミレータの導入

組み込みマイコンで開発を行う場合、フラッシュメモリへの書き込みを行い、ターゲットを起動して動作確認をする流れになります。
しかし、実行バイナリーサイズが大きくなると、フラッシュへの書き込み時間が増え、「修正」→「確認」のサイクルが長くなります。
GUI 系プログラムでは、「良い見た目と」、「良い操作性」を実現するには、細かい調整や修正がどうしても必要になるので、「試す」サイクルが長いのは、かなりの問題で、開発効率が悪いです。

  • 漢字のビットマップフォントだけでも260Kあります。
  • 実機では、UTF-8 を OEM(S-JIS)のコードに変換する為、FatFs を利用しています。

前もって、PC 上で「見た目」や「操作の挙動」を確認できるだけでも、開発効率が格段に上がります。

今回、PC で動作するシュミレータを作り、その助けを借りて、このアプリを作成しました。
電卓の機能もシュミレータ上で確認出来るよう、RX マイコンのソースコードを共有しています。


RX マイコンの GUI フレームワークでは、描画は全てソフトで行っており、DRW2D エンジンをまだ有効に利用していません。
これは、DRW2D エンジンが内蔵されていない環境でも動作させる事と、現在、ソフトで描画している部分を DRW2D で置き換える事が可能な構造になるようにする意味もあります。
※ DRW2D エンジンによる描画クラスは現在研究、開発中です。

シュミレータは、以前から開発を続けている「glfw3_app」フレームワークを利用しています。

このフレームワークは、OpenGL、GLFW3、OpenAL などオープンソースを組み合わせた、リアルタイム描画に適したものです。
glfw3_app は、マルチプラットホームで、OS-X でも同じように動作します。
自分で開発しているので、Unity や VS の C# 環境より扱い易く、思った事が短時間で実現出来ます。

シュミレータの構成

RX マイコンの GUI 描画では、表示ハードウェアーとして GLCDC を利用します。
ソフトレンダラーは、GLCDC のフレームバッファに対して描画を行う構造なので、これを真似たクラスを作ります。
シュミレータは、このクラスのフレームバッファを PC のフレームにコピーしています。
コピーは毎フレーム行っているので、リアルタイムな動作が出来ます。

    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;

タッチパネルインターフェースとして I2C 接続された、FT5206 デバイスの代わりに、マウスの移動と、クリックをタッチパネル操作に模倣するようにします。

    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; }
    };
    typedef touch_emu TOUCH;

RX マイコンフレームワークで利用しているクラスを、シュミレータ側に持ってきて、利用します。
RX マイコンのフレームワークは、別のプラットホームでもコンパイル可能なように、依存関係をなるべく排除した実装なので、無改造で利用出来ます。

#ifndef EMU
#include "common/renesas.hpp"

#include "common/fixed_fifo.hpp"
#include "common/sci_i2c_io.hpp"
#include "chip/FT5206.hpp"
#endif

#include "graphics/font8x16.hpp"
#include "graphics/kfont.hpp"
#include "graphics/graphics.hpp"
#include "graphics/simple_dialog.hpp"
#include "graphics/widget_director.hpp"
#include "graphics/scaling.hpp"

詳しくは、プロジェクトのコードを参照して下さい。

このシュミレータのおかげて、細かい調整が効率よく出来ました、今後他のプロジェクトでも利用したいと思います。


多倍長ライブラリの選択

最初、boost の 多倍長浮動小数点ライブラリ Boost Multiprecision Library を利用していましたが、RX マイコンの環境では、コンパイルが出来ない事が判りました。
これは主に、マルチスレッド時における、オブジェクトのロックに起因する API が、RX マイコン側の環境で見つからない為のようでした。

本来、boost のソースコードは、RX マイコン用に対応させる必要がありますが、多くのクラスでは、それを行わなくても動作するので、mingw64 環境用の boost を利用していました。
boost を RX マイコンに対応させるのは、今後の課題です。

RX マイコンでは、スレッド関係のモデルは、FreeRTOS を利用する事になるので、対応する部分を修正する必要があると思います。
これが、どのくらいのハードルなのかも未定だったので別の選択枠を考えました。

boost はライセンスが緩いので、なるべく使いたいのですが仕方ありません。

そこで、見つけたのは、GNU gmp、mpfr ライブラリです。

これらは、古いプロジェクトで、C 言語ベースです、多分 RX マイコン用にビルド出来るものと思いました。

gmp、mpfr の RX マイコンへのポート(コンパイル)

最近、RX マイコンの開発環境として、Renesas GNU RX gcc-8.3.0 を利用しています。
これらのツールチェインは、mingw 環境で独自にコンパイルされたパッケージで提供されています。

パスを通せば、MSYS2 のシェルから動かせるのですが、「./configure」を動かす場合、パス中にスペースが含まれていたり、マルチバイト文字があると、configure が正しく動作しません。
そこで、MSYS2 上の「/usr/local」に、ツールチェインをコピーしました。

cp -r /c/Users/hira/AppData/Roaming/GCC\ for\ Renesas\ RX\ 8.3.0.202004-GNURX-ELF/rx-elf /usr/local/.

※現在、アップデートされたパッケージをインストールしている為、上記パスとなっています。


gmp のソースコードを取得して展開、コンパイル

.lz 形式のファイルを展開する為、lzip を扱えるようにインストールする必要があります。

% pacman -S lzip
% tar --lzip -xvf gmp-6.2.1.tar.lz
% cd gmp-6.2.1
% ./configure --host=rx-elf --prefix=/usr/local/rxlib --disable-shared
% make
% make install

RXマイコン用多倍長ライブラリ (gmp) のコンパイル


mpfr のソースコードを取得して展開、コンパイル

% ./configure --host=rx-elf --prefix=/usr/local/rxlib --with-gmp=/usr/local/rxlib
% make
% make install

RXマイコン用多倍長ライブラリ (mpfr) のコンパイル


mpfr のラッパー

mpfr を C++ から扱うラッパーは色々な選択肢があります。

しかし、組み込みマイコンで使うとなると色々制約があります。

組み込みマイコンでは、iostream は巨大になるので利用する事が出来ない。

殆ど全てのラッパーは、結果出力として、iostream を使ったものが主流です。(C++ なので当然そうなります・・)
RX マイコンでも、iostream を利用する事は出来るのですが、巨大で、RAM も沢山消費する為、できれば利用したくありません。

結局、自分のシステムにマッチする mpfr ラッパーを実装しました。

basic_arith テンプレートクラスが利用する範囲に限られるので、シンプルなものです。

common/mpfr.hpp にあります。

mpfr では、数値を扱うのは「mpfr_t」構造体です。

ラッパーでは、この構造体をクラスで包んで、クラスオブジェクトとして扱い、このオブジェクトに対する「操作」として、オペレータをオーバーロードするだけです。

名前空間を mpfr として、value クラスとしています。

#include <cmath>
#include <mpfr.h>

namespace mpfr {

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  mpfr オブジェクト
        @param[in]  num     有効桁数
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <uint32_t num>
    class value {

        mpfr_t      t_;
        mpfr_rnd_t  rnd_;

オペレータのオーバーロード関係:

        bool operator == (int v) const noexcept { return mpfr_cmp_si(t_, v) == 0; }
        bool operator != (int v) const noexcept { return mpfr_cmp_si(t_, v) != 0; }
        bool operator == (long v) const noexcept { return mpfr_cmp_si(t_, v) == 0; }
        bool operator != (long v) const noexcept { return mpfr_cmp_si(t_, v) != 0; }
        bool operator == (double v) const noexcept { return mpfr_cmp_d(t_, v) == 0; }
        bool operator != (double v) const noexcept { return mpfr_cmp_d(t_, v) != 0; }

        value& operator = (const value& th) noexcept {
            mpfr_set(t_, th.t_, rnd_);
            return *this;
        }
        value& operator = (long v) noexcept {
            mpfr_set_si(t_, v, rnd_);
            return *this;
        }
        value& operator = (double v) noexcept {
            mpfr_set_d(t_, v, rnd_);
            return *this;
        }

        const value operator - () noexcept
        {
            value tmp(*this);
            mpfr_neg(tmp.t_, tmp.t_, rnd_);
            return tmp;
        }

        value& operator += (const value& th) noexcept
        {
            mpfr_add(t_, t_, th.t_, rnd_);
            return *this;
        }

        value& operator -= (const value& th) noexcept
        {
            mpfr_sub(t_, t_, th.t_, rnd_);
            return *this;
        }

        value& operator *= (const value& th) noexcept
        {
            mpfr_mul(t_, t_, th.t_, rnd_);
            return *this;
        }

        value& operator /= (const value& th) noexcept
        {
            mpfr_div(t_, t_, th.t_, rnd_);
            return *this;
        }

        value operator + (const value& th) const noexcept { return value(*this) += th; }
        value operator - (const value& th) const noexcept { return value(*this) -= th; }
        value operator * (const value& th) const noexcept { return value(*this) *= th; }
        value operator / (const value& th) const noexcept { return value(*this) /= th; }

mpfr.hpp


basic_arith クラス

四則演算などの記号や括弧、シンボル名、関数名を含んだ文字列をパースして、実際の演算を行うクラスです。
このクラスは、数値オブジェクト、シンボル名、関数名、クラスをパラメータとするテンプレートです。

basic_arith.hpp

    static const uint32_t CALC_NUM = 250;  ///< 250 桁

    typedef mpfr::value<CALC_NUM> NVAL;

    typedef utils::calc_symbol<NVAL> SYMBOL;
    SYMBOL  symbol_;

    typedef utils::calc_func<NVAL> FUNC;
    FUNC    func_;

    typedef utils::basic_arith<NVAL, SYMBOL, FUNC> ARITH;
    ARITH   arith_;

まとめ

CALC_sample

今回、シュミレータの導入で、GUI を含んだアプリを効率良く開発する事が出来ました。
また、gmp、mpfr などのライブラリを扱う事で、これらの学習もある程度出来ました。

アプリは、タッチパネル付き LCD のガジェットにも適当なものと思います。

ライセンス

基本は、MIT ライセンスですが、gmp、mpfr ライブラリは注意が必要です。

libgmp: GNU LGPL v3 and GNU GPL v2
libfrmp: GNU LGPL v3