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 は、プロジェクトに組み込む単機能のテストを行うもので、場合によりコードが残らない場合がある。