組み込み(RXマイコン)向けGUI管理クラスの実装

主に、RX65N Envision kit 用にまともなGUI管理を用意しておこうと作業を進めている。

既に、Windows、OS-X、用マルチプラットホームのGUI管理があるのだが、こちらは、リッチな環境向けであり、かなり高機能となっている。
描画はOpenGLで行っている為、そのようなインフラが無いシステムでは、ルックアンドフィールなど多くの面で適合しない。
組み込みマイコン用では、グラフィックスの能力も低いし、解像度も低いので、高機能過ぎるのは逆に使いにくいし、リソースも食ってしまい実用的ではない。

既に、RX65N用には、emWin GUI ライブラリがあり、利用できるようだが、C言語ベースなのでとても使う気にならない。
※GUIのような複雑な機構をC言語ベースで実装する合理的な理由が見当たらない。

そこで、glfw_app/widgets で養ったエンジニアリングを土台に、機能を絞って、設計をやり直してみた。
既に、1度実装した経験があるので、アプリを作る場合の利便性や、どのような構成、構造にすれば良いかは十分理解しているつもりなので、組み込み環境を考えながら実装する。
C++ ベースの GUI では、各GUIに応答する機能の実装は、ラムダ式を使う事で、非常にシンプルに書けるのが判っているが、それ以外の方法でも GUI の応答をサービスする方法を提供する必要性がある。

組み込み系なので、new、delete などの記憶管理を使わない方針で進めた。
※経験的に、テンプレートデザインパターンを使うと何とでもなると判っている。
また、オペレーターやテンプレートなど C++ の機能を盛り込む事で、アプリケーション実装時の「判り易さ」を追及できる。
RX65N 専用では無いので、DRW2D のようなハードウェアーを使った描画機能が無い場合も考えて、ソフトウェアだけで描画する事も視野に入れて、そこそこ見栄え良く、軽く、簡単に扱えるように、実験などしながら進めている。

RX65Nで基本的な部分が動作したら、glfw_app で動作するエミュレーションを実装して、機能をチェック等を実機で行わなくても良いようにする予定でいる。
※実機確認では、作業効率が悪すぎると思われる。
GUI の部品としては、非常に多くあるが、簡単に実装できるものと、最低限必要な物を最初に実装して、後は、必要に応じて追加していこうと思う。

GUI の見た目は、経験的に、ビットマップで細かく作るより、円、直線などを組み合わせた幾何学的な図形をベースにする方が良いと思う。
これは、プログラムで細かく描画する事になるが、その方がリソースの節約にもなり、制御がしやすく、応用が利く。
※様々なサイズに対応出来る。
※たとえば、少なくとも3つの状態が必要となる場合。
・通常の状態
・活性した状態(ボタンが押された等)
・ストールした状態(表示されているが、無効になっている状態)
もしビットマップで作る場合、これらの状態を用意しておく必要があり、それだけでも実用的とは思えないし、ビットマップリソースをリンクするツールが必要となる。
自分が作りたい「ルック」が欲しいのなら、近いGUIのソースをコピーして、それを自分の要求する見た目に合わせて改造すれば良いだろうと思う。

  • フレーム
  • グループ
  • ボタン
  • チェックボックス
  • ラジオボタン
  • スライダー

全ての部品は、親子関係を持つ事が出来る。(構造的には、親の部品を知っている)
※親が全ての子供を知っている方が良い場合が多いのだが、構造が複雑になってしまう。
もし、全ての子供を知りたい場合、管理リストをスキャンして子供のリストを作るしか無い。

また、親子関係を利用する事で、相対的な部品位置で管理できたり、部品をグループ化して、全体を制御したり出来る。

「ラジオボタン」は、自分の変化により他の部品の状態を変える必要があるので、グループ化しておき、親を指定しておく。
その中のボタンで変化が起きた場合、親が同一のリストを作成して、他のボタンの状態を自動で変更する。

「スライダー」は、縦、横のサイズにより、水平か垂直か判断して対応する。
通常、部品からフォーカスが外れると、部品のハンドリングが終了してしまう、しかしその仕様だと、スライダーのような部品では、操作性が悪い(スライダーの領域は通常狭いのと、フォーカスしたままスライドするのが困難)なので、小細工として、スライダーの場合は、初期に領域内でタッチされた時にフォーカスを有効にして、タッチが外れるまでホールドする事で、操作性を改善している。

RX65N envision kit
// widget の定義
    gui::button     button_;
    gui::check      check_;
    gui::group      group_;
    gui::radio      radio1_;
    gui::radio      radio2_;
    gui::radio      radio3_;
    gui::slider     slider_;

// widget のコンストラクタ
setup() noexcept :
    button_(vtx::srect( 30, 20, 80, 30), "OK"),
    check_(vtx::srect(  30, 70 + 40*0, 0, 0), "Check"),
    group_(vtx::srect(  30, 70 + 40*1, 0, 0)),
    radio1_(vtx::srect( 30, 70 + 40*1, 0, 0), "Red"),
    radio2_(vtx::srect( 30, 70 + 40*2, 0, 0), "Green"),
    radio3_(vtx::srect( 30, 70 + 40*3, 0, 0), "Blue"),
    slider_(vtx::srect(150,20, 120, 0))
{
    // ラジオボタンのグループ化
    group_ + radio1_ + radio2_ + radio3_;
}

// 初期化
void init() noexcept override
{
    button_.enable();
    // ボタンが押された時の応答ラムダ式
    button_.at_select_func() = [this](uint32_t id) {
        change_scene(scene_id::root_menu);
    };
    check_.enable();
    // チェックボックスの応答ラムダ式
    check_.at_select_func() = [this](bool ena) {
        utils::format("Check: %d\n") % static_cast<int>(ena);
    };
    radio1_.enable();
    radio2_.enable();
    radio3_.enable();
    slider_.enable();
}

// シーン終了処理
void exit() noexcept override
{
    button_.enable(false);
    check_.enable(false);
    radio1_.enable(false);
    radio2_.enable(false);
    radio3_.enable(false);
    slider_.enable(false);
}

※GUI の構築と、それに対応する「応答」などを含めたアプリケーションプログラム。

GUI関連のソースコードは:

graphics/widget.hpp
graphics/widget_director.hpp
graphics/group.hpp
graphics/frame.hpp
graphics/button.hpp
graphics/check.hpp
graphics/radio.hpp
graphics/slider.hpp

graphics/color.hpp
graphics/graphics.hpp

となっており、他のヘッダーにも依存しているが、内部は、なるべく依存が低くなるようにしているつもり。(実際はレビューしないといけない)
また、widget の追加と削除を行う関数をスタティック扱いとして参照している。(この仕組みは、推奨されるべきものでは無いが、現状の設計ではそのようにしている、今後より良い方法に変更していくかもしれない・・

USBシリアル、どれがいい?

RXマイコンのフラッシュ書き込みでは、自分は、シリアルインターフェースによる書き込みが基本だ。

E1も持ってはいるが、OS-XやLinuxでは使えないし、14ピンのコネクタを繋ぐのも億劫だ。
それに、エミュレーターをほとんど使わないので、フラッシュの書き込みにしか用途が無い。
そうなるとUSBシリアルを使って、シリアル通信で書き込む方が簡単だと思う。
※書き込みツールも独自に対応している。

最近まで、アマゾンで買える格安の中華USBシリアルとしてCP2102を押していたが、状況が変わってきている。
問題として、以下の事項がある。

  • 使っているデバイスは古いCP2102(現在はCP2102Nが主流、古いデバイスを使っているのが安い理由なのかもしれない)
  • 複数のCP2102を一旦接続するとCOMポートの移り変わり現象が発生してまともに使えなくなる。
  • 複数接続するには、専用ツールで、内部シリアルIDを変更して、固体識別をさせる必要があるようだ。
  • CP2102では、専用ツールが正常に機能しないし、ツールのインストールからしてハードルが高い。
  • 色々やったが、CP2102では内部シリアルIDを変更する事が出来なかった。(理由が不明)

そこでやっぱり正統派FTDIだと思い、FTDIでも格安なFT231XSを使おうと思ったが、これには別の問題がある事が判った。(以前に自作したもの)

通信速度が明らかに遅い!

RX66T にレイトレーシングのプログラム(バイナリーで47K程度)を書き込んだ場合の時間:
※230400ボー、8ビット、1ストップビット
FT231XS: 16.6 秒
CP2102:   6.6 秒
上記のように、同じボーレートで条件は同じなのに、3倍近く通信速度が違う。
FT231XSは安い廉価バージョンなので、高級バージョンのFT232RLの場合も確かめてみた。
FT232RL: 18.1 秒
驚きだ、FTDIのデバイスは値段に関係無く、シリコンラボ製に比べてかなりパフォーマンスが劣るようだ・・・
※以前にシリアル通信を使った機器を作った時に感じたが、詳しく評価しなかった。(そんな事が起こるとは思ってない)
J-TAGなどの代用によく使われる、FT232HLも手元にあったので、試してみた。
FT232HL: 17.5 秒
※全然ハイスピードじゃねぇ・・・

上記書き込み時間は、イレース時間、書き込み時間、比較時間などが組み合わさったもので、送信、受信をかなり頻繁に行い、目安なのだが、単純な送受信ではないアプリケーションレベルでの利用なので、逆に、ベンチマークには適しているとも思う。

そこで、今度は、CP2102Nを評価してみたい(主にシリアルIDの変更)と思ったが、調べてみると、CP2102Nを使ったモジュールは少ないようで、入手出来ていない。
※CP2102Nを使った、メーカーの標準キットが1000円くらいなのだが、送料がかかるのが痛いとこだ(同時購入品が何かないと・・・)

現状は、そんな状態だ、進展があったらまた報告するが、どちらにしてもFTDIを使う理由は無いだろうな・・・

RXマイコンでシーン管理(タスク管理)テンプレート

多少複雑なアプリなどを実装する場合、動作の推移を行う管理機構が必要になる事が多い。
やり方は色々だが、組み込みシステム向けに、シンプルな機構を実装したので紹介する。
平行処理にする程でも無い場合など、少し大きい単位で処理を分けて、影響範囲を限定的にする事が出来る。
※この方法は既に色々なシステムで利用しているが、多少アレンジした点として、以下のトピックがある。

  • シーンオブジェクトによる推移管理。
  • 可変引数テンプレートの利用。
  • std::tuple テンプレートを使い、シーンオブジェクトを管理。
  • 記憶割り当てを使わない。

まず、一つの推移単位をシーンクラスとして、インターフェースクラスを継承させている。
インターフェースクラスは以下のようなものとなっている。
※C++ では「= 0; 」とする事で「純粋仮想関数」とする事が出来る。(薄い皮のような物)
※デストラクタを「仮想」として実装しておく必要がある。

struct scene {
    virtual ~scene() { }        ///< デストラクタ
    virtual void init() = 0;    ///< シーン開始前処理
    virtual void service() = 0; ///< シーン・サービス
    virtual void exit() = 0;    ///< シーン終了処理
};
  • シーンを開始する前に必ず「init()」を呼び出す。
  • シーン中は「service()」が呼ばれ続ける。
  • シーンが切り替わる時、シーンを終了するタイミングで「exit()」が呼び出される。

シーン作成時、上記「scene クラス」を継承させて実体を実装する。
以下は「title」クラス。

class title : public scene {

public:
    void init() override
    {
    }

    void service() override
    {
    }

    void exit() override
    {
    }
};

通常シーンは複数あり、シーン管理機構で管理される、アプリケーション側は、各シーンの中身を実装する。

シーン管理クラス(scene_director)では、個々のシーンの実体をstd::tuple で保持しており、切り替えの操作や手順を提供する。
scene_director は、可変引数テンプレートを使って、ユーザー定義のシーンクラスを複数受け取る。

 template<class... Args>
 class scene_director {

    typedef std::tuple<Args...> SCENES;
    SCENES  scenes_;

    scene*  cur_scene_;
    scene*  new_scene_;

public:
    scene_director() noexcept : scenes_(), cur_scene_(nullptr), new_scene_(nullptr)
    { }

    template <class T>
    void change() noexcept
    {
        auto& news = std::get<T>(scenes_);
        new_scene_ = &news;
    }

    void service() noexcept
    {
        if(new_scene_ != nullptr) {
            if(cur_scene_ != nullptr) cur_scene_->exit();
            new_scene_->init();
            cur_scene_ = new_scene_;
            new_scene_ = nullptr;
        }

        if(cur_scene_ != nullptr) {
            cur_scene_->service();
        }
    }
};

このテンプレートを使い、以下のように全体を定義する。

#include "scene_director.hpp"
#include "title.hpp"
#include "menu.hpp"
#include "setup.hpp"

typedef scene_director<title, menu, setup> SCENES;
SCENES   scenes_;

これで大体全てなのだが、大きな問題がある、通常シーンの切り替えは、scene_director の change メソッドを使うのだが、各シーンから、scene_director にアクセスするには、scene_director のインスタンスと定義、各シーンの型などが必要で、それにアクセスする手段を設けるのは難しい。
そこで、かなり冗長でシンプルでは無いが、各シーンに通し番号を振っておき、この番号で切り替える機構を設けて、この関数呼び出しを extern しておく事にした。
各シーンの切り替えは「整数」なので、定義がシンプルでどこからでも呼び出せる。
※ただ、シーンとIDを二重に管理しなければならず、かなり痛い・・・

void change(scene_id id)
{
    switch(id) {
    case scene_id::title:
        scenes_.change<title>();
        break;
    case scene_id::menu:
        scenes_.change<menu>();
        break;
    case scene_id::setup:
        scenes_.change<setup>();
        break;
    }
}

※他の注意点として、インスタンス化の順番を理解しておく必要がある。
scene_director がインスタンス化されるタイミングで、各シーンのコンストラクターが呼び出されるので、各シーン中で何かをインスタンス化している場合、それが関わるクラスは、事前にインスタンス化されておかなければならない。
システムが複雑になると、忘れがちなので注意する必要がある。

※ソースコードは、「RX/common/scene.hpp」にある。

RXマイコン別性能(レイトレースによる)その2

RX66Tを入手したので、レイトレースを実行してみた。

その中で、RX65N Envision kit のコードを標準的にして、他のデバイスも評価していたが、LCDへの転送は、ポートバスによるソフトウェアー書き込みになっている。
水平ライン毎に、レンダリング時間を描画しているが、ポート経由だと、フォント描画がボトルネックになっている事実などがあり、コーディングを整理した。

まず、各マイコン別のタイムを表にしたので参考にしてもらいたい。
※今回、RX64M も加えた。

型番 動作周波数 [MHz] ROM RAM 実時間 [ミリ秒] 価格 [円]
R5F524TAADFP RX24T 80 256K 16K 1224 974 (572/10)
R5F565NEDDFB RX65N 120 2M 640K 784 1910 (1320/10)
R5F564MFCDFC RX64M 120 2M 512K 751 2120 (1570/10)
R5F566TEBDFP RX66T 160 512K 64K 602 900(参考)
R5F571MFDDFC RX71M 240 2M 512K 439 2600 (1940/10)

RX66Tの価格は、RSコンポーネンツで1個購入時のもので、チップワンストップで扱うようになれば、多少異なると思う。

描画ハードに関して、RX64M、RX66Tは、実際にはLCDを接続していないが、RX64MはRX71M、RX66TはRX24Tと同一のインターフェースがあるものとしているので正確だと思う。
※ポート経由の描画オーバーヘッドは、RX66Tで118ミリ秒もある。(ただRAMに書くだけだと484ミリ秒)

通常、描画時間は、ライン毎に%表示されるが、ソフトによるポートバスでは、オーバーヘッドがかなり大きいので、その部分をコメントアウトして、描画後に1回だけ表示するようにしている。

RX66TのRXv3コアの評価は、この結果からはイマイチ判らないが、RX71MやRX64Mの値からすると、やはり少し効率が高いようだ、ただ、浮動小数点演算が多いので、イマイチ参考にならない。

現段階でも、CPはRX66Tがもっとも優れていると言える。

ソースコードなどは、Github のRX/RAYTRACER_sample に全てある。


EMLE 端子のプルダウンを入れてから、フラッシュの書き込みは一度も失敗しないようになった。

ルネサスRX66T(その1)

ルネサスRXマイコンを購入する場合、自分の場合「チップワンストップ」がほとんどで、他ではほぼ買わない(値段が一番安く、種類が豊富、個数割引もあるので)。

RX66Tは、発表されてからかなりたつものの、未だにチップ単体での販売はされていない。

ところが、ツイッターで「RX66TでLチカした」とゆーツイートを見つけた・・
※変換基板だったので、デバイス単体で入手したようだった。
ツイートした人はマイクロマウスをやってる人のようで、「どうやって入手したの?」と聞いたが連絡は取れず、色々調べたら「RSコンポーネンツ」で販売している事を見つけた。
1個900円と少し高いと思ったが、早く試してみたかったので、早速1個購入した。
※それにRXマイコンも10周年らしぃのでw
※現在(4月9日)、ルネサスのHPでも、RX66Tの販売先としてはどこも登録されていない。
※次の日には届いて、変換基板にハンダ付け。
※購入した型番は「R5F566TEBDFP」で、PGA 差動入力なし、USB なし、100 ピン、プログラムフラッシュ 512K、RAM 64K、データフラッシュ 32K

早速、変換基板に取り付け

写真のハンダ付けはちょっと汚いが、一応ブリッジや接触不良は無いものと思う。(よく確認した)
いつものように「直付」する為、裏側をカプトンテープで保護する。

ユニバーサル基板に直接乗せ、ガイドとして、適当なヘッダーピンを刺しておき、対角のピンにハンダを流して、変換基板と、下のユニバーサル基板を固定する。
後は、電源ライン、MD端子、クリスタル関係、リセットSW、シリアルラインなど、最低限必要な線だけ配線する。


  • (20) PD5/RXD1 <--- TXD (USB Serial)
  • (22) PD3/TXD1 ---> RXD (USB Serial)
  • (6) MD L:Boot、H:Run
  • (1) /RES
  • (5) VCL GND 間に 0.47uF
  • (11) XTAL 10MHz、GND間に 8pF
  • (13) EXTAL 10MHz、GND間に 8pF
  • (2) EMLE 4.7K で プルダウン(エミュレーターイネーブル)
  • (15) PE2/NMI 10K でプルアップ

rxprog も、RX66T 用にプロトコルを追加して、LED 点滅は直ぐに動作した。
RX66T関係のデバイス定義は、発表と同時くらいに、ハードウェアーマニュアルをダウンロードして、実装していたので、ソフトの準備はそれなりに出来ていた。
RX66T の内部構成は、RX24T よりRX64M 系に近い。
今回入手したデバイスは、USB は内臓していないバージョンだが、ラインナップには USB 付きもある、その場合、内臓クロックは12の倍数にする必要があり、144MHz 動作になってしまうと思う(最高速は160MHzなので少しもったいない)。

static void micro_second(uint32_t us)
{
    while(us > 0) {
#if defined(SIG_RX64M) || defined(SIG_RX63T) || defined(SIG_RX71M)
        for(uint32_t n = 0; n < (F_ICLK / 4285714); ++n) {
            asm("nop");
        }
#elif defined(SIG_RX65N)
        for(uint32_t n = 0; n < (F_ICLK / 4444444); ++n) {
            asm("nop");
        }
#elif defined(SIG_RX24T)
        for(uint32_t n = 0; n < (F_ICLK / 4444444); ++n) {
            asm("nop");
        }
#elif defined(SIG_RX66T)
        for(uint32_t n = 0; n < (F_ICLK / 3346666); ++n) {
            asm("nop");
        }
#else
#  error "delay.hpp requires tune dummy operations"
#endif
        --us;
    }
}

RX66T は、RXv3 コアとなっており、実測による簡単なソフトループだが、今までのRXコアより15~20%くらい速いようだ。
※同じバイナリーでこれだけ高速なのは、驚くべき性能だと思う。
※ちゃんとしたベンチマークは後ほど行う予定

現在の問題として、フラッシュプログラム中に停止する症状がある。
これは、原因を特定出来ていない・・・
とりあえず、rx_prog.conf のシリアル速度を、230400から115200に落としたら、多少安定して書き込めるようなので、当面そうしておく。
※RX64M、RX71M、RX24T では、問題無いのだが・・・

とりあえず
FIRST_sample/RX66T(LED 点滅)
SCI_sample/RX66T(シリアル通信)
は、動作する事を確認した。

※RX66T では、SCI のベースクロックは、PCLKBを利用するが、このクロックの最大は60MHzで、内臓クロックを160MHzで動かす場合、最大では40MHzとなる。


フラッシュ書き込み時にハングアップする原因が判ったと思う。
RX66Tでは、EMLE端子が増設された、「1」でエミュレーターイネーブルなので、とりあえずGNDに落としておいたが、これが原因のようだ、4.7Kでプルダウンしたら、安定して書き込めるようになった。
多分、フラッシュ書き込み時などに一瞬出力になる場合があるのだろう・・
※ボーレートは関係無かった。

Makefile 、環境毎の違いを補正

既に、「glfw_app」などで行っていたのだが、RXマイコンの開発環境でも、boost を使うようになった為、Makefile の修正を行った。
※プロジェクトの数が多いので、主要な部分のみ行った。

boost は、パッケージマネージャーでインストールするのが標準的となったので、環境毎にパスを追加で指定しなければならない。
そこで、以下のようなスクリプトで、環境を識別して、内部変数を設定している。

# Include path for each environment
ifeq ($(OS),Windows_NT)
SYSTEM := WIN
LOCAL_PATH  =   /mingw64
else
  UNAME := $(shell uname -s)
  ifeq ($(UNAME),Linux)
    SYSTEM := LINUX
    LOCAL_PATH = /usr/local
  endif
  ifeq ($(UNAME),Darwin)
    SYSTEM := OSX
    OSX_VER := $(shell sw_vers -productVersion | sed 's/^\([0-9]*.[0-9]*\).[0-9]*/\1/')
    LOCAL_PATH = /opt/local
  endif
endif

INC_SYS     =   $(LOCAL_PATH)/include

これで、Windows(MSYS2)、Linux、OS-Xの違いを識別して、適切な「パス」を設定できる。
OS-X では、OS のバージョンも抜き出している。

他、emacs を起動した場合の TAB を強制的に指示する為、以下の「おまじない」をファイルの先頭に追加した。

# -*- tab-width : 4 -*-

さらに、既に2019年だが、今まで「C++14」を指定していたコンパイルオプションを「C++17」に変更しておいた。

PFLAGS      =   -std=c++17 $(CP_OPT) $(OPTIMIZE) $(MCU_TARGET) $(DEFS)

WR250Xのメンテナンス

昨日、バイクで出かける用事があり、気になっていたメンテナンスを行った。

フレームが微妙に錆びていて、その部分を補修した。

色々な方法があると思うが、今回は、「サビ転換剤」を使った。

方法は簡単で、ワイヤーブラシで、赤サビの部分の汚れや、サビなどを落として、上記の液体を塗るだけ。
マフラーにも塗っておいたが、高温になるので、効果があるのかは微妙。

様子を見て、上から塗装しておこうと思う。