「RX」カテゴリーアーカイブ

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

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

mpfr について

gmp をコンパイルして動かす事は出来たので、今度は mpfr をコンパイルする。

mpfr は gmp を使い、初等関数などをサポートしたライブラリで、ルート、ログ、三角関数など色々な関数を使えるようになる。

  • 電卓には必須のライブラリだ
  • gmp は基本的に四則演算のみを行う

mpfr をコンパイルする

まず、mpfr のソースコードを取って来る。

GNU MPFR Library

展開してコンパイル、ここでのキモは、gmp ライブラリが置いてある場所を指定する事。
※指定しないと、gmp がシェアードライブラリじゃないとリンク出来ないと思う。(前回、gmp はスタテックライブラリを作成した)

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

mpfr を使ってみる

    void test_mpfr_()
    {
        mpfr_t a, c;

        mpfr_init2 (c, 50);
        mpfr_set_d (c, 2.0, MPFR_RNDD);
        mpfr_init2 (a, 50);

        mpfr_sqrt(a, c, MPFR_RNDD);

        mpfr_printf("sqrt(2): %.50RNf\n", a);

        mpfr_clear (c);
        mpfr_clear (a);
    }
Start SCI (UART) sample for 'RX64M' 120[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 [%])
    7612058254738945
*
    9263591128439081
--------------------
70514995317761165008628990709545
sqrt(2): 1.41421356237309403525159723358228802680969238281250

なるほど、簡単だー

mpfr C++ ラッパーを試す

mpfr を使う C++ ラッパーは色々ある、boost もその一つだが、RX マイコン用にカスタマイズしないと、コンパイルが通らない。

他のラッパーも試してはみたものの、基本、どれも iostream に依存していて、組み込みマイコンとは相性が悪い・・・
※非常に巨大になる・・・

つまり、これは、またしても車輪の再発名か・・・

まぁ、でも、機能を絞るのでそんなに大変じゃないのかなと思って、実験的に作ってみた。

とりあえず、四則演算が出来れば、俺俺 Arith クラス(数式解析クラス)に組み込める。

  • 数式解析は、数学的な数式を入力して、それを計算する。
  • 掛け算、割り算が優先されるとか、括弧が優先されるとか、意外と面倒だ。
  • シンボル(変数)の展開
  • 関数の展開
  • べき乗の展開
    basic_arith クラス

とりあえず、最低限必要な部分を作ってみた

オブジェクトに対してのオペレーターをそれなりに作れば、正しく動くと思う。

mpfr.hpp を実装、最低限必要そうな部分のみ実装してある。

実装では、mpfr 名前空間を作り、「value」クラスを定義した。

value クラス内に、mpfr_t 構造体を置いて、それに対する操作を列挙した。

重要なのは、四則演算などのオペレータをオーバーロードする事だ。

        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 == (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; }

このくらいで、とりあえず、basic_arith で扱えるようになった。

早速、RX72N Envision Kit で動かしてみた、問題なく計算出来るようになったー

~~ 表示関係とかが、イマイチなので、整理したら、git のマスターブランチにプッシュする。~~
電卓アプリは、引き続き、機能追加などを行っていく予定。

関数電卓サンプル

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

多倍長浮動小数点数

前回、電卓アプリを実験的に作ってみた。

ただ、「240MHz で動く32ビットマイコンなのに精度が64ビットの浮動小数点」なの?

これは、かなり痛い・・・

そこで、多倍長浮動小数点数などを扱えるライブラリを利用する事にした、最初、boost にある「Boost Multiprecision Library」を利用してみた。
何の問題もなく、簡単に数十桁の演算が出来る事を確認した。
しかし、RX マイコンの環境(Renesas GNU RX gcc compiler 8.3.0)では、これらのライブラリを含んだコードをコンパイル出来ない。
これは、mingw64 用の boost ソースコードを使っている為で、本来なら、boost のソースコードを RX マイコン用にカスタマイズしなければならない。

  • 多くのライブラリは、専用の物を作らなくても、対応可能な事から、この問題を避けてきた。
  • コンパイルで失敗する主な理由は、スレッド関係のようだ・・

しかし、boost を RX マイコン用にカスタマイズするのは、それはそれで大変そうだと思い、少し発想を変えてみようと思った。
また、boost のライブラリは、速度的には、古くからある、gmp、mpfr ライブラリより劣ると言われている。
※しかしながら、gmp や mpfr はライセンスが GNU なので、あえて boost を使う理由があると思える。

  • boost には、これら(gmp mpfr)のライブラリをラップしたクラスもあるが、これは完全に環境依存となっている。
  • Multiprecision のコード関係を RX マイコンに対応させるのはそれなりの労力だと思える。

とりあえず、gmp ライブラリをコンパイルしてみる。

操作は、MSYS2 上のコンソールで行う。

gmp.org

ソースコードを取ってくる。

見慣れない「lz」拡張子に対応する為、lzip をインストールしておく。

 % pacman -S lzip

続いて解凍する。

tar --lzip -xvf gmp-6.2.1.tar.lz

.configure するには、少し問題がある、gmp の configure は、Renesas GNU rx-elf 関係があるパスを正しく認識しない。
これは、パスにスペースが含まれる為で、configure を直すのも大変そうなので、とりあえず、rx-elf 関係を、mingw の「/usr/local」以下にコピーして対応した。

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

rx-elf 関係のパスを変更して、bash を再起動 (.bash_profile)

# rx-elf path
# PATH=$PATH:/usr/local/rx-elf/bin
# PATH=$PATH:/C/'Program Files (x86)'/'GCC for Renesas RX 8.3.0.202002-GNURX-ELF'/rx-elf/rx-elf/bin
# PATH=$PATH:/C/Users/hira/AppData/Roaming/'GCC for Renesas RX 8.3.0.202004-GNURX-ELF'/rx-elf/rx-elf/bin
PATH=$PATH:/usr/local/rx-elf/rx-elf/bin

configure を実行:

% rx-elf-gcc --version
rx-elf-gcc.exe (GCC_Build_20201001) 8.3.0.202004-GNURX 20190222
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
% cd /d/Git/RX/gmp/gmp-6.2.1
% ./configure --host=rx-elf --prefix=/usr/local/rxlib --disable-shared

...

  Version:           GNU MP 6.2.1
  Host type:         rx-unknown-elf
  ABI:               standard
  Install prefix:    /usr/local/rxlib
  Compiler:          rx-elf-gcc
  Static libraries:  yes
  Shared libraries:  no

% make
% make install

本来、RX コア別の最適化オプションを指定した方が良いが、v1, v2, v3 で利用出来るように、「素」の状態でコンパイルした。

実際に使ってみる

とりあえず、RX64M で実験した。

/usr/local/rxlib 以下に gmp 関係のライブラリがあるので、その設定を追加する。

実験コードを(WEB からコピペ)

    void test_gmp_()
    {
        mpz_t x, y, result;

        mpz_init_set_str(x, "7612058254738945", 10);
        mpz_init_set_str(y, "9263591128439081", 10);
        mpz_init(result);

        mpz_mul(result, x, y);
        gmp_printf("    %Zd\n"
             "*\n"
             "    %Zd\n"
             "--------------------\n"
             "%Zd\n", x, y, result);

        /* free used memory */
        mpz_clear(x);
        mpz_clear(y);
        mpz_clear(result);
    }
Start SCI (UART) sample for 'RX64M' 120[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 [%])
    7612058254738945
*
    9263591128439081
--------------------
70514995317761165008628990709545
#

とりあえず問題無さそうだ。

RX マイコンに最適化するには、アセンブラのコードを gmp に追加する必要がありそうだが、今後の課題とする。

今回はここまで。

RX マイコン GUI フレームワークのエミュレーション

フラッシュROMの書き換えに時間がかかる・・

RX65N、RX72N Envision Kit で、GUI アプリケーションを作成する場合、オブジェクトがそこそこ大きくなる。
それを、マイコンにロードして動作を確認していると、一度のターンで、それなりに時間がかかり、開発効率が悪い。
GUI 系のアプリでは、現状、細かい調整や見た目重視になるので、さらにターン数は多くなり、完成度を上げるハードルが高くなる。
そこで、Windows のアプリ上で、動作を確認出来るように、エミュレーター的なアプリを作成した。

RX72N Envision Kit では、マイコン内蔵のフラッシュメモリ書き込みは、1本の端子で J-TAG をシリアライズして接続している。
※FINED 端子
その制約で、書き込み速度が RX65N Envision Kit に比べてさらに遅くなっている。(RX65N Envision Kit は J-TAG 接続)


エミュレーターと言っても、RXマイコンのバイナリーレベルで動かすのではなく、単純なAPIレベルでの動作に限定している。

俺俺フレームワークの「glfw_app」を利用し、RXマイコンから必要なソースコードを持っていき、
関係する部分をコンパイル出来るようにする。
そして、描画したフレームバッファを表示するだけとなっている。
また、マウスでクリックした状態を、タッチパネルでの操作に似せて、情報をフレームワークに送る。
※残念ながら、マルチタッチはエミュレーション出来ない。

RX マイコンの GUI フレームワークは、テンプレートで実装している部分が多く、描画部と、タッチ入力部は分離している。
なので、非常に簡単なパスを作るだけで実現できる。

実際のパス接続

GUIフレームワークで要求されるクラスは主に二つとなっている。

  • レンダリングクラス
  • タッチ入力クラス

上記二つのクラスをテンプレートパラメーターとして widget_director に参照で渡す構造となっている。

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  Widget ディレクター
        @param[in]  RDR     レンダークラス
        @param[in]  TOUCH   タッチクラス
        @param[in]  WNUM    widget の最大管理数
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <class RDR, class TOUCH, uint32_t WNUM>
    struct widget_director {

また、ソフトウェアーレンダリングクラスは、描画ハードウェアーのインスタンスを参照で渡している。
描画クラスには、フォントのインスタンスが含まれる。

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  レンダリング
        @param[in]  GLC     グラフィックス・コントローラー・クラス
        @param[in]  AFONT   ASCII フォント・クラス
        @param[in]  KFONT   漢字フォントクラス
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <class GLC, class FONT = font_null>
    class render {

描画ハードウェアーは、フレームバッファを管理している。
中身は、描画スクリーンのサイズと、先頭アドレスだけとなっている。

そこで、以下のようなクラスを用意した。

    static const int16_t LCD_X = 480;
    static const int16_t LCD_Y = 272;

    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;

また、タッチクラスは、マウス入力をそのまま使うので、以下のようなクラスとした。

    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; }
    };

RX マイコンのサンプル「GUI_sample」を使って実験

※アプリケーションが出力する文字列を受け取り、ターミナルフレームに出力している。

これで、アプリの動作を細かく調整出来る。

GUI ビルダーも作る事が出来ると思うが、それはそれで、別の問題もある。
自分のフレームワークでは、widget の部品は、全てソースコードレベルで行っているので、ビルダーでソースコードを生成するのは簡単だが、その逆は、結構大変そうだ・・・
※一方通行で「ヨシ」とするなら簡単だが、ソースコードの管理が、それはそれで面倒・・
とりあえず、大がかかりになりそうなので、後で考える事にする・・・

何か作ってみる

とりあえず、簡単なところで、「電卓」を作ってみた、実用性もあるし、そこそこの難易度なので、それなりに有益かと思う。
もう少し、リッチなボタンが欲しいとこだが、それは、次の課題とする。
※現在の実装では、ボタンのコーナーのラウンドは、アンチエイリアスしていない。
また、プログラム電卓的な機能や、グラフ表示、など、拡張性がかなりあると思うので、今後機能を追加していこうと思う。

  • グラフ表示が出来る電卓は、購入するとなると、それなりの値段がするので、自分で作るには面白いガジェットかもしれない。
  • スマホで動く高機能な電卓が色々あるのだけど自分で作るのは、それだけで楽しい。

RX72N Envision Kit で動かした場合。


右の空きスペースは、関数電卓用のボタンを配置する予定。

とりあえず、PC で動作確認を行い、実際にターゲットでも同じように動作する事を確認できた。
今まで複雑な描画を含むアプリ作成は、効率が悪かったが、これからは、かなり効率良く開発出来ると思う。

CALC_sample

C++によるRXマイコンCANドライバー(その4)

不具合修正

不具合を修正してブラッシュアップした。

  • リモートフレームを受信しない不具合を修正
  • 複数メールボックス割り込みの修正
  • 拡張 ID 受信の不具合修正

通常モードでは、メールボックスのフラグ設定により、受信するメールボックスが決定する仕様のようだ・・・

  • メールボックスの「RTR」が「1」なら、リモートフレームを受信
  • メールボックスの「RTR」が「0」なら、データフレームを受信
  • メールボックスの「IDE」が「1」なら、拡張 ID フレームを受信
  • メールボックスの「IDE」が「0」なら、標準 ID フレームを受信

最低でも4つのメールボックスで「待ち」状態にしないと、全てを受信できない・・
また、先のバージョンでは、メールボックスに対する割り込みフラグが正しく設定されていないようだったので、それも修正した。
※割り込み受信処理中にも受信が可能なように、8つのメールボックスをアクティブにしている。

Start CAN sample for 'RX64M' 120[MHz]
CAN command version: 0.86
CAN0: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7, SJW: 4
    RX Interrupt level: 1, TX Interrupt level: 1
CAN1: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7, SJW: 4
    RX Interrupt level: 1, TX Interrupt level: 1
#
# ch 1
# send 0x123
# map
R      1 S:x0000123 (291)
ID = 1 / Total = 1, Records = 0, Df = 0, Rf = 1
# send_loop 50
# map
D      1 S:x0000079 (121)
D      1 S:x00002B2 (690)
D      1 S:x00001C8 (456)

...

D      1 S:x000014E (334)
D      1 S:x000028C (652)
R      1 S:x0000123 (291)
D      1 S:x00002CB (715)
D      1 S:x00002BD (701)
D      1 S:x000000C (12)

...

D      1 S:x00000DB (219)
ID = 49 / Total = 51, Records = 204, Df = 48, Rf = 1
#
# dump 0x123
R      1 S:x0000123 (291)
R: ID: std 0x123 (291)
(0):
TS: 47248
# dump 219
D      1 S:x00000DB (219)
D: ID: std 0x0DB (219)
(2): D8 68
TS: 22193

リモートフレーム、データフレームの扱いについて、理解したので、表示も修正した。
※send 時、ID だけ設定するとリモートフレームを送信するようになっている。

RX66T の CAN ポート設定

RX66T では、CAN は1チャネルのみだが、候補が7番まであるので、それに伴って、port_map クラスを修正した。

        static bool sub_can_(option opt, bool enable)
        {
            uint8_t sel = enable ? 0b10000 : 0;
            switch(opt) {
            case option::FIRST:
                // PE0/CRX0 (22/144) 1ST
                // PD7/CTX0 (23/144)
                PORTE::PMR.B0 = 0;
                PORTD::PMR.B7 = 0;
                MPC::PE0PFS.PSEL = sel;
                MPC::PD7PFS.PSEL = sel;
                PORTE::PMR.B0 = enable;
                PORTD::PMR.B7 = enable;
                break;
            case option::SECOND:
                // PF3/CRX0 (31/144) 2ND
                // PF2/CTX0 (32/144)
                PORTF::PMR.B3 = 0;
                PORTF::PMR.B2 = 0;
                MPC::PF3PFS.PSEL = sel;
                MPC::PF3PFS.PSEL = sel;
                PORTF::PMR.B3 = enable;
                PORTF::PMR.B2 = enable;
                break;
            case option::THIRD:
                // PB6/CRX0 (40/144) 3RD
                // PB5/CTX0 (41/144)
                PORTB::PMR.B6 = 0;
                PORTB::PMR.B5 = 0;
                MPC::PB6PFS.PSEL = sel;
                MPC::PB5PFS.PSEL = sel;
                PORTB::PMR.B6 = enable;
                PORTB::PMR.B5 = enable;
                break;
            case option::FOURTH:
                // PA7/CRX0 (52/144) 4TH
                // PA6/CTX0 (53/144)
                PORTA::PMR.B7 = 0;
                PORTA::PMR.B6 = 0;
                MPC::PA7PFS.PSEL = sel;
                MPC::PA6PFS.PSEL = sel;
                PORTA::PMR.B7 = enable;
                PORTA::PMR.B6 = enable;
                break;
            case option::FIFTH:
                // PA1/CRX0 (58/144) 5TH
                // PA0/CTX0 (59/144)
                PORTA::PMR.B1 = 0;
                PORTA::PMR.B0 = 0;
                MPC::PA1PFS.PSEL = sel;
                MPC::PA0PFS.PSEL = sel;
                PORTA::PMR.B1 = enable;
                PORTA::PMR.B0 = enable;
                break;
            case option::_6TH:
                // PC6/CRX0 (62/144) 6TH
                // PC5/CTX0 (63/144)
                PORTC::PMR.B6 = 0;
                PORTC::PMR.B5 = 0;
                MPC::PC6PFS.PSEL = sel;
                MPC::PC5PFS.PSEL = sel;
                PORTC::PMR.B6 = enable;
                PORTC::PMR.B5 = enable;
                break;
            case option::_7TH:
                // P23/CTX0 (96/144) 7TH
                // P22/CRX0 (97/144)
                PORT2::PMR.B3 = 0;
                PORT2::PMR.B2 = 0;
                MPC::P23PFS.PSEL = sel;
                MPC::P22PFS.PSEL = sel;
                PORT2::PMR.B3 = enable;
                PORT2::PMR.B2 = enable;
                break;
            default:
                return false;
            }
            return true;
        }

有効な ID だけ通すフィルター

can_io クラスでは、フィルター機能を使っていない。
RX マイコンの CAN では、フィルターの設定が面倒で、数に制限がある為、実際のアプリでは、適応するのが難しいかもしれない。

ハードウェアーの機能を使えば、処理負荷を軽減できるものの、実用性が怪しい、また、異なるハードウェアー(RSCAN)などで、コードの再利用が難しくなる。

C++ では、特定の ID だけ通す(通さない)フィルターを簡単に実装出来る。
今回「boost::unordered_set」を使った。
※「std::unorderd_set」よりバイナリーが小さくなる。

まず、通すリストを構築する。

    // 有効な ID だけ通すフィルター
    typedef boost::unordered_set<uint32_t> VALID;
    VALID   valid_{ 0x123, 0x200, 0x300, 0xaaa, 15, 21, 33 };

非常に簡単だw

次に、メインループで、受信したフレームをディスパッチする際に、上記フィルターがヒットするか確認する。

        while(can1_.get_recv_num() > 0) {
            auto frm = can1_.get_recv_frame();
            if(valid_.find(frm.get_id()) != valid_.end()) {
                utils::format("\nCAN1:\n");
                CAN::list(frm, "  ");
            }
        }

この場合、CAN1 に受信したフレームで、「有効」な ID だけ表示する。

# send 0x123 0
CAN1:
  D: ID: std 0x123 (291)
  (1): 00
  TS: 29604
#
# send 0x1 1 2 3
# send 200 5 6 7
# send 0x200 5 6 7
#
CAN1:
  D: ID: std 0x200 (512)
  (3): 05 06 07
  TS: 41568
#

「unorderd_set」は、ハッシュを使って効率よく探すので、探す ID が多くても、検索にかかる負荷は小さくて済むものと思う。
また、「通す ID」を追加したり、削除したりも簡単に出来る。

今回はここまで。

C++によるRXマイコンCANドライバー(その3)

複数メールボックスの利用

以前の実装では、送信も受信も1つのメールボックスを使っていたが、少なくとも「受信」では、1つの受信時、割り込み処理で、メールボックスから、フレームを取り出してバッファに積む間に、別の ID を受信していると、そのフレームをロストしてしまう。
そこで、複数のメールボックスを利用するように修正した。

送信側も複数のメールボックスを利用するように修正。

can_io テンプレートのプロトタイプは以下のようになった。

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  CAN 制御クラス
        @param[in]  CAN     CAN 定義クラス
        @param[in]  RBF     受信バッファクラス (utils::fixed_fifo<can_frame, N>)
        @param[in]  TBF     送信バッファクラス (utils::fixed_fifo<can_frame, N>)
        @param[in]  PSEL    ポート候補
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <class CAN, class RBF, class TBF, port_map::option PSEL = port_map::option::FIRST>
    class can_io : public can_io_def {

アプリケーションからは、以下のように「typedef」して使う。

    // CAN 受信バッファの定義
    typedef utils::fixed_fifo<device::can_frame, 256> CAN_RXB;
    // CAN 送信バッファの定義
    typedef utils::fixed_fifo<device::can_frame, 128> CAN_TXB;

    typedef device::can_io<device::CAN0, CAN_RXB, CAN_TXB, CAN0_PORT> CAN0;
    CAN0    can0_;

CAN/ID の収集と解析

とりあえず、「can_io」クラスは、送信、受信が行えるようになったので、CAN バスの解析機能を実装してみた。

新規に「can_analize」テンプレートクラスを追加して、内部で、CAN/ID を動的に収集して、表示する。

ID の収集には、「boost/unordered_map.hpp」を利用したが、標準で用意されている「std::unordered_map」は、関連クラスの関係でリンクが難しい事が判った為だ。
boost の方が小回りが利く。
※この利用では、何故か、浮動小数点ライブラリをリンクする必要があるようだ・・・

ID を動的にソートして収集するには「std::map」が良いが、「std::map」は、ツリー式で、件数が増えるとメモリの肥大化が問題になると思われる。
※計測はしていないが、経験的にハッシュを使う「unordered_map」の方が高速で省メモリなのではと思う。
※使用メモリも、件数によると思う、これも調査していないので何とも言えない。
※また、たとえば、車の CAN、工作機械などの CAN などで、標準的に利用している ID の数も、今はまだ理解していない。
※コード的には、コンテナの扱いが同等なので、どちらでも利用出来る。

        typedef boost::unordered_map<uint32_t, info_t> MAP;
//      typedef std::map<uint32_t, info_t> MAP;
  • どちらかを有効にする。
  • 実行ファイルは、「std::map」の方が若干大きくなる。

解析機能は、CAN0 側にのみ付けているので(両方付ける事も出来る)、ループ接続で実験する際は、CAN1 側から送信する必要がある。


これらの機能実装は、C++ ならではで、組み込みマイコンでも、boost がまともに動くのは、便利としか言いようが無いw

送信と受信の様子:

Start CAN sample for 'RX64M' 120[MHz]
CAN0: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7, SJW: 4
    RX Interrupt level: 1, TX Interrupt level: 1
CAN1: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7, SJW: 4
    RX Interrupt level: 1, TX Interrupt level: 1
# ch 1
# send 0x100 1 2 3
# map
     1 S:x0000100 (256)
Total = 1
# dump 256
     1 S:x0000100 (256)
ID: std 0x100 (256)
DATA(3): 01 02 03
TS: 11269
# send 123 5 6 7
# send 500 67 90 200
# send 300 100 99 100
# map
     1 S:x000012C (300)
     1 S:x00001F4 (500)
     1 S:x000007B (123)
     1 S:x0000100 (256)
Total = 4
# dump 500
     1 S:x00001F4 (500)
ID: std 0x1F4 (500)
DATA(3): 43 5A C8
TS: 14498
# send 500 0x8A 0xcb 0xfe 0x24
# map
     1 S:x000012C (300)
     2 S:x00001F4 (500)
     1 S:x000007B (123)
     1 S:x0000100 (256)
Total = 4
# dump 500
     2 S:x00001F4 (500)
ID: std 0x1F4 (500)
DATA(4): 8A CB FE 24
TS: 9925
#

「send_loop」コマンドを追加。
ランダムなデータを送信するテストを行った:

# ch 1
# send_loop 100
# map
     1 S:x00002A4 (676)
     1 S:x0000301 (769)

...

     1 S:x000013D (317)
     2 S:x0000260 (608)
     1 S:x0000015 (21)
     1 S:x000031D (797)
     1 S:x00000D6 (214)
     1 S:x0000228 (552)
Total ID = 92 / Total count = 100, Total Record = 367
# send_loop 100
# map
     1 S:x000008A (138)
     1 S:x000026E (622)
     1 S:x0000028 (40)
     1 S:x0000116 (278)
     1 S:x00001BD (445)
     1 S:x00001AC (428)

...

     2 S:x00000F7 (247)
     1 S:x00000DB (219)
     1 S:x000013D (317)
     1 S:x00002E9 (745)
     1 S:x00000A6 (166)
     1 S:x0000228 (552)
Total ID = 170 / Total count = 200, Total Record = 678
# send_loop 50
# map
     1 S:x000012E (302)
     1 S:x00000C8 (200)
     1 S:x0000070 (112)
     1 S:x0000077 (119)
     1 S:x00000DA (218)

...

     2 S:x00000F7 (247)
     1 S:x000013D (317)
     1 S:x00002E9 (745)
     1 S:x0000228 (552)
Total ID = 210 / Total count = 250, Total Record = 853
#

とりあえず、ちゃんと動いているようだ。

    ch CH-no               set current CAN channel (CH-no: 0, 1)
    send CAN-ID [data...]  send data frame
    stat MB-no             stat mail-box (MB-no: 0 to 31)
    list MB-no             list mail-box (MB-no: 0 to 31)
    map [CAN-ID]           Display all collected IDs
    clear                  clear map
    dump CAN-ID            dump frame data
    send_loop NUM          random ID, random DATA, send loop
    help                   command list (this)

新しいコマンドを追加したサンプルはプッシュ済み

今回はここまで。

C++によるRXマイコンCANドライバー(その2)

MB (メールボックス)レジスタクラスを一応改修

CAN で、厄介なのは、MB レジスタ。
ここだけは、概念がかなり他と違う、一つのパーテーションが 16 バイトなので、通常のレジスタアクセスクラスではサポート出来ない。

そこで、専用の実装を考えた。
※本来は、直接アクセスしなくても、サポートクラスを定義して、丸ごとコピーすれば良い。
※技術的な興味から実装

  • 「DLC」はバイトアクセス
  • 「DATA」は 8 バイト分のアクセスを行うクラスを追加
  • 「TS」は 16 ビットアクセス
  • それ以外は 32 ビットアクセス
  • SID、EID をまとめて設定、読出し出来るように関数を追加「get_id()、set_id()」
  • 全体を初期化する関数を追加「clear()」
  • 全体をコピーする関数を追加「copy()」(他のメールボックスから丸ごとコピーする場合)
  • 「=」オペレーターを「private」に(代入禁止)
        //-----------------------------------------------------------------//
        /*!
            @brief  メールボックスレジスタ j ( MB[j] )( j = 0 ~ 31 )
        */
        //-----------------------------------------------------------------//
        struct mb_t {
            typedef rw32_index_t<base +  0> io0_;
            typedef rw8_index_t <base +  4 + 1> io1_;
            typedef rw16_index_t<base + 12 + 2> io3_;

            bits_rw_t<io0_, bitpos::B0,  18>  EID;
            bits_rw_t<io0_, bitpos::B18, 11>  SID;
            bit_rw_t <io0_, bitpos::B30>      RTR;
            bit_rw_t <io0_, bitpos::B31>      IDE;

            bits_rw_t<io1_, bitpos::B0, 4>    DLC;

            struct data_t {
                volatile uint8_t& operator [] (uint32_t n) {
                    return *reinterpret_cast<volatile uint8_t*>(io0_::address() + 6 + n);
                }
            };
            data_t  DATA;

            io3_    TS;

            void set_index(uint32_t j) {
                if(j < 32) {
                    io0_::index = j * 16;
                    io1_::index = j * 16;
                    io3_::index = j * 16;
                }
            }

            void clear(uint32_t d = 0) {
                auto a = io0_::address();
                wr32_(a,      d);
                wr32_(a +  4, d);
                wr32_(a +  8, d);
                wr32_(a + 12, d);
            }

            void copy(uint32_t idx) {
                wr32_(io0_::address() +  0, rd32_(base + idx * 16 +  0));
                wr32_(io0_::address() +  4, rd32_(base + idx * 16 +  4));
                wr32_(io0_::address() +  8, rd32_(base + idx * 16 +  8));
                wr32_(io0_::address() + 12, rd32_(base + idx * 16 + 12));
            }

            uint32_t get_id() {
                return SID() | (EID() << 11);
            }

            void set_id(uint32_t id) {
                SID = id & 0x7ff;
                EID = id >> 11;
            }

            mb_t& operator [] (uint32_t idx) {
                set_index(idx);
                return *this;
            }
        private:
            void operator = (const mb_t& t) {
            };
        };
        typedef mb_t MB_;
        static  MB_ MB;

これで、CAN 関係で、気になっていた部分は、納得いく範囲で、抽象的にアクセス出来るようになったと思う。

割り込み関係の整備と設計

本格的な上位層を実装する前に、割り込み関係の動作を確認しようと思う。

また、FIFO の機能をどうするか?
ハードウェアーで持ってる FIFO も、中途半端で微妙だよなぁー・・・
FIFO が4つくらいあっても、1パケット(フレーム)が100マイクロ秒前後なので、激しい通信がある場合は足りないと思う。
結局、ソフトで FIFO を作って、突発的に増えた場合の通信量を吸収するしかない。

経験的に、割り込み関数内でガッツリ実装するのは良くないと判っている。(それしか方法が無い場合もあるけど・・)
大抵、そんな事をしなくても、何とかなるし、割り込み内からコールバックを呼んでも、そこから出来る事は限られる。
※本当はコールバックも呼びたくない、もし、重い処理を実装されたら、終わってしまうから・・


RX64M の CAN では、割り込みは、SELB 選択型割り込みになっていて、4つの割り込みがある。
エラー割り込みは、BE0 グループ割り込みになっている。

    typedef can_seli_t<0x00090200, peripheral::CAN0,
        ICU::VECTOR_SELB::RXF0, ICU::VECTOR_SELB::TXF0,
        ICU::VECTOR_SELB::RXM0, ICU::VECTOR_SELB::TXM0, ICU::VECTOR_BE0::ERS0> CAN0;

RX66T では、エラー割り込みの「型」は同一だが、他は通常割り込みになっている。

    typedef can_norm_t<0x00090200, peripheral::CAN0,
        ICU::VECTOR::RXF0, ICU::VECTOR::TXF0,
        ICU::VECTOR::RXM0, ICU::VECTOR::TXM0, ICU::VECTOR_BE0::ERS0> CAN0;

icu_mgr クラスは、「割り込み型」により異なる関数が定義してあるので、C++ コンパイラが自動的に適切な関数を選ぶ、ドライバー側は「型」を意識する必要が無く、プログラムがシンプルになる。

  • #ifdef などで、CPU の違い毎に動作を別ける実装をしなくて済む。
  • 実際には、can のレジスタ定義では、CPU 毎の定義で「別けて」いるけど、その上位層では、感知しなくて済む。
  • つまり、この仕組みは、新規に CPU が追加されても、下位層でそのサポートを行えば、上位層のコードをケアする必要が無い。
 static ICU::VECTOR set_interrupt(ICU::VECTOR vec, utils::TASK task, uint8_t lvl) noexcept;
 static ICU::VECTOR set_interrupt(ICU::VECTOR_SELB sel, utils::TASK task, uint8_t lvl) noexcept;

※ちょっと不思議なのは、MIER メールボックス割り込み許可レジスタが、RESET 時不定になっている。
他にも不定になっているレジスタがいくつかある・・・


割り込み関係では、厄介な事がある、割り込みが起動した時、どのメールボックスによる要因なのかが判らない。
一応、調べる機構はあるが、送信と受信がシェアされているので、受信にしか使えない・・

送信は、同時に複数のフレームを送信しないので、それでも良いかもしれない・・

とりあえず、メールボックス 4 を送信専用にして、それを使って送信を行った。
問題無く、送信出来ていて、送信割り込みも機能する事を確認した。
※割り込みを使わない場合は物理的に難しいので、割り込みを使う事が前提となる。

CAN での受信は、色々と考える事が多い:

  • 固定 ID で来るデータを周期的に観るだけの場合。
  • 固定 ID で来るデータ全てに応答する場合。
  • どんな ID が来ているかを集計したい場合。(アナライザ的な機能)
  • ID 集計後に、無視する ID、観測する ID、応答する ID を決定する場合。
  • ID に個別の処理(コールバック)を設定する場合。

アプリケーションを作る場合、柔軟な構成にしたいので、上記の事を考えた設計をしたいが、RX マイコンの CAN 機能だけでは、難しい・・・

ID の範囲が「標準」のみなら、2048個なので、ルックアップするのは現実的だが、「拡張 ID」だと29ビットあるので、少し難しい。
STL の場合、「std::set]、「std::unorderd_set」を使えば、簡単に出来るが、内部で記憶割り当てを使うので、メモリの少ないマイコンの場合は使いたくないのも事実・・・
さてどうするか?

CAN 動作モードの切り替え(注意)

            // CAN オペレーションモードに移行
            uint8_t idfm = 0b10;  // 標準 ID モード、拡張 ID モード、ミックス
            uint8_t bom  = 0b00;  // ISO 11898-1 規格、バスオフ復帰モード
            CAN::CTLR = CAN::CTLR.CANM.b(0b00) | CAN::CTLR.SLPM.b(0) | CAN::CTLR.IDFM.b(idfm)
                | CAN::CTLR.BOM.b(bom);

            // CAN オペレーションモードに移行するまで待機
            while(CAN::STR.RSTST() != 0 || CAN::STR.HLTST() != 0) {
                sleep_();
            }

上記のように、CAN の動作モードを切り替えると、実際にモードが切り替わるまで遅延があるようだ。
オペレーションモードに移行した事を確認してから、次の動作を行う必要がある。
※そうしないと、制御レジスターの書き換えが失敗するなどの問題がある。
※一応、それらしい事が書いてあるが、レジスタの説明に書くべきだろうと思う。

MCTL レジスタの書き換えには注意

ハードウェアーマニュアルに書いてあるが、ビットを OFF にするには、色々と制約と順番などがある。
これは、注意を要する。

RX マイコンの CAN 関係の実装は、落とし穴が沢山あって、簡単では無いなぁーと感じる・・・
要は、あまり完成度が高く無いと感じる、だから RX24T で新しい CAN (RSCAN) になったのかもしれない。
そんな事を言っても仕方無いので、少しづつ機能を確認しつつ追加して、何とか完成させるしかない。

受信割り込みの確認

結局、SCI と同じように FIFO を使って受信動作を実装した。
なので、現状では、ID をマスクしたり、監視する機能は、全てソフトで行う必要がある。
パフォーマンスは犠牲になるが、当面はそれで良い、柔軟性が重要だ、パフォーマンスに問題が起こったら、マスクフィルタの設定などを追加すれば良い。

Start CAN sample for 'RX64M' 120[MHz]
CAN0: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7, SJW: 4
    RX Interrupt level: 1, TX Interrupt level: 1
CAN1: SPEED: 1000000 [bps], BRP: 3, TSEG1: 12, TSEG2: 7, SJW: 4
    RX Interrupt level: 1, TX Interrupt level: 1
# send 0x100 0 1 2 3 4 5
#
CAN1:
  ID: std 0x100 (256)
  DATA(6): 00 01 02 03 04 05
  TS: 51713

# ch 1
# send 0x200 10 20 30 99 0x7f
#
CAN0:
  ID: std 0x200 (512)
  DATA(5): 0A 14 1E 63 7F
  TS: 43785

# ch 0
# send 0x450 0
#
CAN1:
  ID: std 0x450 (1104)
  DATA(1): 00
  TS: 12821

#

※ CAN0、CAN1 の送信、受信の確認


今回はここまで。

ソースコードは、github RX/CAN_sample にある。