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

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

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

RXマイコン、C++、sci_io クラスの機能追加(RS-485)

RS-485

RS-485 は、非同期シリアルに RS-485 用ドライバを付け、若干のソフトを追加するだけで、利用出来る。
ドライバーは、差動信号を使い、ノイズに強く、長い通信路でも、信号の品質を維持する事が出来る。
ネットワークのトポロジーとして、CAN のような構成にできて、複数の機器を相互に接続出来る。
詳しくは、RS-485 の解説を参照してもらいたい。

但し、半二重通信なので、送信を行うには、送信ゲートを適切にコントロールする必要がある。
また、プロトコルを実装して、クライアントと通信するので、全体の構成を考える必要がある。
受信だけなら、流れてくるパケットをデコードするだけなので簡単だが、送信するには、受信データを監視して、タイミングを守る必要がある。
プロトコルは規定されていないが、PLC、インバーターなどで良く使われるものがあるようだ。

今回は、三相モーターのインバーターと通信するのがとりあえずのゴール。


sci_io クラスに機能を追加

sci_io クラスは、SCI を使った非同期シリアル通信を扱うクラスとなっている。

今回、RS-485 の送信ゲート制御を追加した。

一つのクラスに、あまり多くの機能を盛り込むのは、設計上別の問題もあるが、色々検討した結果、少しの機能追加で行える事が判ったので、機能追加とした。

RS-485 のゲート制御は、テンプレートパラメータで決定する為、通常の SCI 通信では、余分なコードが残らないように配慮してある。


機能を切り替える仕組み

定義は以下のようなもので、通常の SCI 定義を拡張したものとなっている。
ハードフロー制御(RTS)と共用となっているので、enum class の FLOW_CTRL 型に、RS485 を含めてあり、テンプレートパラメータで指示する。

    typedef utils::fixed_fifo<char, 1024> RS485_RXB;  // RS-485/RX (受信) バッファの定義
    typedef utils::fixed_fifo<char, 512>  RS485_TXB;  // RS-485/TX (送信) バッファの定義

    typedef device::PORT<device::PORT3, device::bitpos::B3> RS485_DE;   // for MAX3485 DE
    typedef device::sci_io<RS485_CH, RS485_RXB, RS485_TXB, device::port_map::ORDER::SECOND, device::sci_io_base::FLOW_CTRL::RS485, RS485_DE> RS485;
    RS485   rs485_;
  • RS-485 ドライバーの DE(送信ゲート)を制御する為、ポート定義を同時に行う。
  • RS-485 ドライバーの受信ゲートは、常に有効にしてある。
  • RS-485 ドライバは、ループバックしているので、送信した文字列をそのまま受信する。
  • 送信した文字列と受信した文字列が等しければ、衝突は発生せず、正しく送信出来た事を保障する事が出来る。
  • RS-485 ドライバーは、MAX3485 を使っている。(3.3V 動作品)

DE ポートの制御

  • RS-485 の送信ゲート制御は、送信データ列が出ている間だけ、送信ゲートを有効にする必要がある。
  • RX マイコンの SCI では、シリアル送信中のデータ列が出ている期間を知る手法はいくつかあるのだが、送信データがバッファリングされている為、単純な方法では、うまくいかない。
  • 送信割り込みは、送信が可能になったタイミングで発生する。
  • ストップビットを含めたシリアルデータが全て送られたタイミングで「送信終了割り込み」が発生する。
  • 色々検討して実験した結果、送信開始前に、ゲートを有効にして、送信 FIFO が「空」になったら、送信終了割り込みを発生させ、そのタイミングでゲートを閉じるようにした。


9600bps


送信終了割り込み(TEI)のハンドリング

送信終了割り込み(TEI)は多少厄介で、一工夫が必要となっている。
それは、マイコンによって、通常割り込みの場合と、グループ割り込みの場合がある為で、それを正しく管理する必要がある。

  • RX621/RX62N/RX63T/RX24T では通常割り込み。
  • RX64M/RX71M/RX65N/RX72N/RX66T/RX72T ではグループ割り込み。
        static inline void tei_task_()
        {
            if(send_.length() == 0) {
                RTS::P = 0;
            }
            SCI::SCR.TEIE = 0;
        }

        static INTERRUPT_FUNC void tei_itask_()
        {
            tei_task_();
        }

        void set_intr_(uint8_t level) noexcept
        {
            if(level > 0) {
                icu_mgr::set_interrupt(SCI::RXI, rxi_task_, level);
                icu_mgr::set_interrupt(SCI::TXI, txi_task_, level);
                if(FLCT == FLOW_CTRL::RS485) {
                    auto gv = icu_mgr::get_group_vector(SCI::TEI);
                    if(gv == ICU::VECTOR::NONE) {
                        icu_mgr::set_interrupt(SCI::TEI, tei_itask_, level);
                    } else {
                        icu_mgr::set_interrupt(SCI::TEI, tei_task_, level);
                    }
                }
            } else {
                icu_mgr::set_interrupt(SCI::RXI, nullptr, level);
                icu_mgr::set_interrupt(SCI::TXI, nullptr, level);
                if(FLCT == FLOW_CTRL::RS485) {
                    icu_mgr::set_interrupt(SCI::TEI, nullptr, level);
                }
            }
        }

現在、RX マイコン C++ フレームワークでは、グループ割り込みは、標準のディスパッチルーチンから呼ばれるので、割り込みタスクの属性を適切に設定する必要がある。

SCI::TEI(送信終了割り込み)がグループベクターの場合は、通常のタスクを登録し、グループベクターでは無い場合には、「INTERRUPT_FUNC」属性を付与した関数を登録する。
「INTERRUPT_FUNC」属性では、「RTE」オペコードで終了する。(通常関数は「RTS」オペコード)

RX24T の場合、「TEI」は通常ベクターで、グループでは無いので、「VECTOR::NONE」が返る。

        static ICU::VECTOR get_group_vector(ICU::VECTOR vec) noexcept {
            return ICU::VECTOR::NONE;
        }

RX72T の場合、「TEI」は「VECTOR_AL0」なので、「VECTOR::GROUPAL0」が返る。

        static ICU::VECTOR get_group_vector(ICU::VECTOR_AL0 vec) noexcept {
            return ICU::VECTOR::GROUPAL0;
        }

「icu_mgr::get_group_vector(SCI::TEI);」で、「SCI::TEI」がグループベクターなのか、そうでないのか判定出来る。
C++ では「型」の違いで、コンパイラが自動で関数を呼び分ける事ができ、処理を切り替える事が可能なので、柔軟性が大きい。
この判定は、定数が返るだけなので、最適化がされると、分岐や判定が綺麗に消えて、無駄なコードが残らない為、余分なメモリとマシンサイクルを節約する。
このような精妙な仕組みがスマートに実装出来るのは C++ の特徴となっている。
このような仕組みはC言語では難しい。


グループベクターのハンドリング

自分のフレームワークでは、割り込みなどは登録制にしてあり、グループベクターも登録制となっている。

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  GROUPAL0・ベクター型
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        enum class VECTOR_AL0 : uint8_t {
            TEI11 = 12,     ///< SCI11 / TEI11
            ERI11,          ///< SCI11 / ERI11

            SPII0 = 16,     ///< RSPI0 / SPII0
            SPEI0,          ///< RSPI0 / SPEI0
            NUM_ = 4
        };

SCI11 の送信終了割り込み「TEI11」は「グループベクター AL0」のグループで、上記番号は、レジスターのビット位置となっている。
グループ割り込みが発生した場合に該当する処理を呼び分けるディスパッチルーチンはなるべく高速に実行したいので、割り込みタスク登録時に必要なデータを登録してある。

        typedef icu_utils::dispatch<ICU::VECTOR_AL0> GROUPAL0_dispatch_t;
        static GROUPAL0_dispatch_t GROUPAL0_dispatch_;

        template<typename GRPV>
        class dispatch {
...
            void set_task(GRPV grpv, GTASK task) noexcept
            {
                uint32_t bits = 1 << static_cast<uint32_t>(grpv);
                for(uint32_t i = 0; i < NUM; ++i) {
                    if(bits_[i] == bits) {
                        bits_[i] = 0;
                        task_[i] = nullptr;
                        break;
                    }
                }
                for(uint32_t i = 0; i < NUM; ++i) {
                    if(bits_[i] == 0) {
                        bits_[i] = bits;
                        task_[i] = task;
                        break;
                    }
                }
            }

            void run(uint32_t togo) const noexcept
            {
                for(uint32_t i = 0; i < NUM; ++i) {
                    if(bits_[i] == 0) break;
                    if((bits_[i] & togo) != 0 && task_[i] != nullptr) {
                        (*task_[i])();
                    }
                }
            }
        };

        static INTERRUPT_FUNC void group_al0_handler_() noexcept
        {
            GROUPAL0_dispatch_.run(ICU::GRPAL0());
        }

        static void install_group_task(ICU::VECTOR_AL0 grpv, icu_utils::GTASK task) noexcept
        {
            ICU::GENAL0.set(grpv, false);
            set_task(get_group_vector(grpv), group_al0_handler_);
            GROUPAL0_dispatch_.set_task(grpv, task);
            if(task != nullptr) {
                ICU::GENAL0.set(grpv);
            }
        }
  • dispatch::run(togo) は割り込み要因 GRPAL0 レジスタを引数に呼ばれる。
  • 登録されたデータに従い、タスクを呼び出す。
  • この辺り、部分的な実装を抜き出しているので雰囲気だけなので、実際は、github のコードを観て下さい。

RX72T 関係
icu_utils 関係


まとめ

  • 現在サポートしているRXマイコンは10種程度に及ぶので、良く考えて実装しないと、修正が大掛かりになる場合もある。
  • 同じような機能を提供するクラスや関数をなるべく共有できるようにしないと、全ての品種で恩恵が薄くなる。

C++ だと、テンプレートや、C++11、C++14、C++17 で備わった機能など色々使って、かなりシンプルに実装が出来る、速度も速い!
テンプレートの場合、コンパイルがエラー無く通ると、大体ちゃんと動く場合が多い。
そして、ほとんどが、ヘッダーのみに全て実装出来る、なんて便利なんだろうといつも思う、利用する人が増えれば良いのにと思う。

RX62Nのフラッシュ書き換えツール実装でまた発見!

基板の提供ありがとう~

基板の提供を受けました、「Lathe」さん大変ありがとうございます。


RX62N の フラッシュ書き換えツール対応

RX62N の C++ フレームワーク対応も、ほぼ出来上がり、これで、本格的に使えるようになった感じ。

一番厄介だった、MTU 関係も、何とかなり、ブログに記事をまとめていたのだが、


とりあえず、フラッシュの書き換えは、「Renesas Flash Programmer V3.06.01(無償版)」を使って USB 接続で書き換えしていた。

しかし、書き換えの度に、USB 接続をしたり切ったりするのが何だかなぁーと思って、遅くてもシリアルで書き換え出来るようにしておこうと思い、SCI1 にコネクタを接続しておいた。

そして、フラッシュ書き換えツールも対応しておこうと思い、ブートモードの解説を観ていたら、どうやら、これは RX63T にかなり近い仕様だと判った。
RX63T のリソースを使い、それを改造すれば割と簡単に対応可能だと判り実装を始めた。


奇妙な事を発見

まず、最初は、

  • ブートモードで起動
  • 接続
  • シリアルの通信速度をキック
  • デバイス情報の取得
  • 書き換え

ざっと、こんな感じなのだが、その過程で、「デバイス情報取得」で奇妙な事が起こっていた。

 % rx_prog -d RX62N --verbose
# Platform: 'Cygwin'
# Configuration file path: 'rx_prog.conf'
# Device: 'RX62N'
# Serial port path: 'COM7'
# Serial port speed: 230400
# Erase Page Wait: 2000 [uS]
# Write Page Wait: 5000 [uS]
# Serial port alias: COM7 ---> /dev/ttyS6
# Serial port path: '/dev/ttyS6'
Connection OK. (RX62x)
#01/01: Device: RX600 Series
#01/01: Device ID: 0x35307936
#01/01: Clock Mode: 0x00
#01/02: Multiplier: 1, 2, 4, 8
#02/02: Multiplier: 1, 2, 4, 8
#01/02: Frequency Min: 8 MHz, Max: 100 MHz
#02/02: Frequency Min: 8 MHz, Max: 50 MHz
#01/01: Change baud rate: 115200
#01/01: Boot Area: FF7FC000, FF7FFFFF
#01/01: Area: FFF80000, FFFFFFFF
#01/54: Area: FFFFF000, FFFFFFFF
#02/54: Area: FFFFE000, FFFFEFFF
#03/54: Area: FFFFD000, FFFFDFFF
#04/54: Area: FFFFC000, FFFFCFFF
#05/54: Area: FFFFB000, FFFFBFFF
#06/54: Area: FFFFA000, FFFFAFFF
#07/54: Area: FFFF9000, FFFF9FFF
#08/54: Area: FFFF8000, FFFF8FFF

...

#50/54: Area: 00102000, 001027FF
#51/54: Area: 00101800, 00101FFF
#52/54: Area: 00101000, 001017FF
#53/54: Area: 00100800, 00100FFF
#54/54: Area: 00100000, 001007FF
#01/01: Program size: 256 Bytes
#01/01: ID Protect: false

このデバイスは、「R5F562N7」なので、「フラッシュ:384K、RAM:64K」品のハズなので、
「Area:」は、FFFA0000 から始まるハズなのだが・・・

ん??

これは、もしや・・・

物理的なデバイスは、全て「R5F562N8」と同じ物で、デバイスIDで識別しているだけなのでは?

そこで、Makefile のデバイス名を切り替えて、プログラムを書き換えると、普通に書き換え出来たーーー

# BlueBoard-RX62N_100pin
#DEVICE     =   R5F562N8
# FRK-RX62N (CQ出版)
#DEVICE     =   R5F562N7
DEVICE      =   R5F562N8
USER_DEFS   =   CQ_FRK
Memory Configuration

Name             Origin             Length             Attributes
RAM              0x00000000         0x00017a00         w
USTACK           0x00017e00         0x00000004         w
ISTACK           0x00018000         0x00000004         w
ROM              0xfff80000         0x0007ffd0         w

そして普通に動く!!!

やっぱりね・・

まぁ、そういう事なのだろう。

驚いた事に、ルネサス、フラッシュプログラマーでも、普通に書き換えが出来、動くようだ・・

とゆー事は、「刻印」が違うだけなのか・・・


まぁ、ROM が 384K から 512K に増えても、そんなに大きなプログラムを作らない限り変わらないかもしれないが、RAM が 64K から 96K だと、やはり使いかってが違うと思う。


まとめ

これは、今回のボードに載っているデバイス特有の事なのかもしれない・・

このボード、かなりの数を出荷したと思うが、当時、「7」の384K品が不足していた為、512K品を刻印だけ変えて出荷したとも考えられる。

他に試せるデバイスを持っていないので、判らないが・・・

まぁ、どちらにしても、うれしい誤算である事は言うまでもない。

このボードを持っている人は試してみる事を勧める~

R8C、RX開発環境のアップデート

R8C の開発環境が作れないとのツイートをもらう・・

先日、Twitter で 過誤出来ないツイートをもらった。

ツイートは短く、詳細は不明なので、実際にどんな事が起きているのか判らなかった。

何度かやりとりをする中で、どうやら、これは Linux 環境で発生している事が判った。

その要因は、最新の gcc 環境(Version 11)などで、gcc-4.9.4 の C++ ビルドで失敗する事が判った。

そういえば、Windows の MSYS2 環境もしばらくアップデートしていない、そこで、まずは、MSYS2 をアップデートする事から始めた。
※Linux 環境は、ノートPC にあり、古いので、新しい環境に入れ直す必要があり、確認するのも時間がかかる。

pacman -Sy pacman
pacman -Syu

この段階で、データベースの更新に失敗して、先に進まない・・

MSYS2 は、かなり更新しているようで、根本的にシステムが大幅に変わったようだ、そこで、インストールからやり直す事にした。

念の為、MSYS2 のコア部分、「C:\msys64」をリネームして、残しておいた。

最新版をインストールして、必要なツールをインストール。
※複数パッケージのインストールが、マルチスレッドで同時進行するようになっているw

MSYS2 はアイコンが少し新しくなり、大きな変更があったようだ。

また、新たな環境が二つ追加された:

  • MSYS
  • mingw32
  • mingw64
  • ucrt64(追加)
  • clang64(追加)

※追加された環境が、どのような特徴があるのか調べていない。(clang64 は何となく判る)

MSYS のカレント gcc は11系になっている。

gcc (GCC) 11.3.0
Copyright (C) 2021 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.

次に、R8C 用 gcc のビルドにとりかかる。
gcc のビルドは、MSYS で行う。(この環境がもっとも Linux などに近い)

手順を進めていって、やはり、gcc C++ の構築でエラーが発生して止まる・・・

うーーん、これは、どうしたものか・・・

そもそも、gcc-4.9.4 は古すぎると以前から思っていた。
しかし、開発環境を変更する事で、発生する別の問題を恐れて「まぁ動いているから枯れた方がいいかな」とも思っていた。

この機会に、手始めに R8C の開発環境をアップデートして、gcc-11 でエラー無くコンパイル出来るようにした方が建設的ではと思った。
この段階で、エラーを報告してくれた人は、ネットの情報を精査して、configure で生成された Makefile に、C++11 のオプションを付ける事で回避したようだ。
※なるほど、その回避もあるよなぁーと思ったが、やはり、gcc のビルドで特別な事を行うのは少し抵抗がある。


RX マイコンの開発環境では、最近は GNU-RX(魔改造されている)を使っている為、素の gcc を使う事が無くなっている。

RX のツールチェインは、以下の組み合わせとなっている。

binutils-2.34
gcc-7.5.0
newlib-2.4.0

組み合わせは重要で、リリースが同じくらいのパッケージを組み合わせないと、ビルドが通っても、コンパイル中にインターナルエラーで止まったり、
出来たコンパイラで作ったバイナリーをマイコンに焼いても動作しないなど、色々なトラブルが起こったりする。

そこで、R8C(m32c-elf)のビルドを上記組み合わせで作ってみた。

ビルド途中で、エラーで、失敗する・・・

うーーーん・・・

この組み合わせは、m32c-elf では相性が悪い・・・

そこで、少し古いバージョンの組み合わせで、色々試してみた。
m32c-elf は多分、メンテナンスされていないので、欲張らずにそこそこ新しい物で我慢する方が安全と考えた。
SSD(1TB) と、大きな主記憶(32GB)で武装された、最新の PC(Rizen7) でも、gcc のビルドは、そこそこの時間を要する。

色々、試して:

  • binutils-2.28.1.tar.gz
  • gcc-6.4.0.tar.gz
  • newlib-2.4.0.tar.gz

この組み合わせで、ビルドが通った。


boost 問題・・・

r8cprog など、一部のコードは、boost を使っている。

本来なら、MSYS 環境に boost を入れるのが良いと思うが、mingw64 などには対応しているが、MSYS 環境には無い。
そこで、mingw64 環境に入れた boost を間借りする形で使っていた。
しかし、新しい MSYS2 ではそのやり方では問題が発生する事が判った。

そこで、boost_1_74_0 のアーカイブを取ってきて、C ドライブのルートに展開し、そのパスを食わせる事で解決した。

この変則的なやり方は、あまりスマートとは言えないが、手順は難しく無いので、まぁ及第点だろうか・・


R8C のプロジェクトを全ビルド

新しく出来たツールチェインを使って、R8C のプロジェクトを全てビルドしてみた。

エラー無くビルドが通った。

とりあえず、「UART_sample」を動かしてみて、ちゃんと動作するバイナリーが出来る事も確認した。


RX 関係も確認

RX 関係も、一応確認した。

RX の gcc ビルドは問題無く通ったので、「良し」としたが、boost のパスは、R8C と同じく変更した。


RL78 環境

RL78 も、R8C と同じ環境にして、gcc をビルドしてみた。
これも、順調に通り、構築出来た。

しかし、プロジェクトをビルドすると、gcc がインターナルエラーで停止する・・・

rl78-elf-g++ -c  -std=c++17 -Wall -Werror -Wno-unused-variable -Wno-exceptions -Wno-unused-function -Os -mmul=g13 -DSIG_G13 -DF_CLK=32000000  -I. -I../ -I../G13  -o release/main.o main.cpp
In file included from main.cpp:15:0:
../common/format.hpp: In member function 'void utils::basic_format<CHAOUT>::out_fixed_point_(VAL, uint8_t, bool) [with VAL = long long unsigned int; CHAOUT = utils::stdout_chaout]':
../common/format.hpp:589:3: internal compiler error: in push_reload, at reload.c:1348
   }
   ^
unrecognized DWARF version in .debug_info at 6
Please submit a full bug report,
with preprocessed source if appropriate.
See <http://gcc.gnu.org/bugs.html> for instructions.
make: *** [Makefile:112: release/main.o] Error 1

うーーーん、これは痛い・・・

そーいえば、ルネサスさん、RL78 用に LLVM ベースのコンパイラをリリースしているんだよなぁー、アレ試してみたいんだよなぁー

今ここ。


まとめ

とりあえず、R8C、RX 関係は、何とかなったので、RL78 も何とかしたいけど、時間切れ。
Linux 環境も試しておく必要があるし、まだ、時間がかかる。

R8C の教材も中途だし、中々先に進まない。

しかし、Windows の最新環境にマッチするように改修したのは、大きな進歩と思える。

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

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

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


プローブを買い直す。

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

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

60MHz プローブ(TEXAS60)


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

プリアンプを考える

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

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

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

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

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

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

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

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

主な特徴は:

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

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

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

DC カップリング

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

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

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

一応入力保護

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

Littlefuse GDT-SG90

レンジ切り替え

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

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

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

本来の波形:

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

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

まとめ

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

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

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

RXマイコンGUI関係更新

GUI 更新

  • 本当は、GUI の前に、優先度が高いタスクが色々あるのだが・・・
  • 気軽に実装出来るのが「要」なので、色々修正、追加を行った。
  • ソースコードを精査すると、何でこんな実装になってるの?(自分で実装したのに)部分もあり、修正した。
  • GUI Widget は、プリミティブの組み合わせで描画するので、新規に何か作る場合、見た目や操作性を踏まえて修正が続く。
  • 描画は、ソフトレンダーと DRW2D エンジンを選べるが、バグがあり、その修正も行った。
  • 追加した widget は、「スピンボックス」、「トグルスイッチ」、「プログレスバー」となっている。
  • 同時に GUI_sample で実装も追加して、機能を試している。(もうそろそろ画面が一杯になった感じ・・)
  • まだ足りない物があると思うが、これくらいの種類があれば、アプリを作る場合に困らないと思える。

スピンボックス(spinbox.hpp)

  • 範囲の決まった数値を増減する機能を提供する。
  • 「長押し」で増減が加速するようにしたので、そこそこ大きい範囲でも設定出来る。
  • 他の widget とは異なり、フレームレスにしているが、やはり無いと統一性が無いので、どうするか検討中・・
  • 増減は、ボックスの左側、又は右側押すのだが、サインが、小さい四角なので、そこも甘い。
  • 三角を描画するには、描画 API を追加する必要があるので、とりあえず四角にしている。

トグルスイッチ(toggle.hpp)

  • 実際は「スライドスイッチ」だが、「トグル」の方が馴染みが良いと思ったので「toggle クラス」とした。
  • この widget は「チェックボックス」と同等の機能だが、見た目や動作がモダンなので、必要と考えた。
  • 実装は、そんなに複雑な部分は無いが、見た目や動作を考えて、試行錯誤した。

プログレスバー(progress.hpp)

  • 実装は、表示のみなので簡単だった。
  • 基本的に、輝度のみで状態を示すので、動かしてみて、吟味はした。

全体の見直し

  • widget_director クラスや、前に実装した widget も見直して、気にくわない部分を修正した。
  • API の名称なども、間際らしい部分があり、色々考えて変更した。

drw2d_mgr クラスの修正

  • フォントの描画で、クリッピング領域を設定する部分にバグがあり、修正した。
  • この修正で、ソフトレンダリングと drw2d エンジンによる描画がほぼ切り替えても同じような見た目になった。
  • 実際は、アンチエリアスの表現などが異なり、完全に一致しない。
  • drw2d エンジンによる描画の方が品質が高いので、ソフトレンダリングもモディファイしないとならない・・・

まとめ

  • GUI Widget 関係のドキュメント(使い方や仕様)が不十分なので、この辺りも強化が必要と感じている。
  • ただ、「GUI_sample/main.cpp」を観れば、使い方は判ると思う。
  • 現状では、GUI Widget の配置ツールが無いが、C++ 11 以降 constexpr などの機能を使えば、かなり複雑な配置でも精妙に出来るので、自分としては必要性を感じない。
  • widget の応答についても、「ラムダ式」で実装して、シンプルに出来るように配慮してある。
  • 組み込み機器で C 言語ベースの GUI ライブラリを使っている人が本当に気の毒でならない。(今時、良く C 言語で作るよなぁー)
  • 下に示したコードが全て、widget の表示とそれに伴う動作に必要な実装のみ、これで全てとなっている。

GUI サンプル、widget の定義:

    typedef gui::button BUTTON;
    BUTTON      button_(vtx::srect(10, 10, 80, 32), "Button");
    BUTTON      button_stall_(vtx::srect(100, 10, 80, 32), "Stall");
    typedef gui::check CHECK;
    CHECK       check_(vtx::srect(   10, 10+50, 0, 0), "Check");  // サイズ0指定で標準サイズ
    typedef gui::group<3> GROUP3;
    GROUP3      group_(vtx::srect(   10, 10+50+40, 0, 0));
    typedef gui::radio RADIO;
    RADIO       radioR_(vtx::srect(   0, 40*0, 0, 0), "Red");
    RADIO       radioG_(vtx::srect(   0, 40*1, 0, 0), "Green");
    RADIO       radioB_(vtx::srect(   0, 40*2, 0, 0), "Blue");
    typedef gui::slider SLIDER;
    SLIDER      sliderh_(vtx::srect(200, 20, 200, 0), 0.5f);
    SLIDER      sliderv_(vtx::srect(460, 20, 0, 200), 0.0f);
    typedef gui::menu MENU;
    MENU        menu_(vtx::srect(120, 70, 100, 0), "ItemA,ItemB,ItemC,ItemD");
    typedef gui::text TEXT;
    TEXT        text_(vtx::srect(240, 70, 150, 20), "16ピクセル漢字の表示サンプル~");
    typedef gui::textbox TEXTBOX;
    TEXTBOX     textbox_(vtx::srect(240, 100, 160, 80), "");
    typedef gui::spinbox SPINBOX;
    SPINBOX     spinbox_(vtx::srect(20, 220, 120, 0),
                    { .min = -100, .value = 0, .max = 100, .step = 1, .accel = true });
    typedef gui::toggle TOGGLE;
    TOGGLE      toggle_(vtx::srect(160, 220, 0, 0));
    typedef gui::progress PROGRESS;
    PROGRESS    progress_(vtx::srect(240, 220, 150, 0));

    float       progress_ratio_ = 0.0f;

GUI widget の応答部分:

    void setup_gui_()
    {
        button_.enable();
        button_.at_select_func() = [=](uint32_t id) {
            utils::format("Select Button: %d\n") % id;
            if(button_stall_.get_state() == BUTTON::STATE::STALL) {
                button_stall_.set_state(BUTTON::STATE::ENABLE);
                button_stall_.set_title("Active");
            } else if(button_stall_.get_state() == BUTTON::STATE::ENABLE) {
                button_stall_.set_state(BUTTON::STATE::STALL);
                button_stall_.set_title("Stall");
            }
        };
        button_stall_.enable();
        button_stall_.set_state(BUTTON::STATE::STALL);

        check_.enable();
        check_.at_select_func() = [=](bool ena) {
            utils::format("Select Check: %s\n") % (ena ? "On" : "Off");
            if(ena) {
                radioR_.set_base_color(DEF_COLOR::White);
                radioG_.set_base_color(DEF_COLOR::White);
                radioB_.set_base_color(DEF_COLOR::White);
                radioR_.set_font_color(DEF_COLOR::White);
                radioG_.set_font_color(DEF_COLOR::White);
                radioB_.set_font_color(DEF_COLOR::White);
            }
        };

        // グループにラジオボタンを登録
        group_ + radioR_ + radioG_ + radioB_;
        group_.enable();  // グループ登録された物が全て有効になる。
        radioR_.at_select_func() = [=](bool ena) {
            utils::format("Select Red: %s\n") % (ena ? "On" : "Off");
            if(ena) {
                radioR_.set_base_color(DEF_COLOR::Red);
                radioG_.set_base_color(DEF_COLOR::Red);
                radioB_.set_base_color(DEF_COLOR::Red);
                radioR_.set_font_color(DEF_COLOR::Red);
                radioG_.set_font_color(DEF_COLOR::Red);
                radioB_.set_font_color(DEF_COLOR::Red);
            }
        };
        radioG_.at_select_func() = [=](bool ena) {
            utils::format("Select Green: %s\n") % (ena ? "On" : "Off");
            if(ena) {
                radioR_.set_base_color(DEF_COLOR::Green);
                radioG_.set_base_color(DEF_COLOR::Green);
                radioB_.set_base_color(DEF_COLOR::Green);
                radioR_.set_font_color(DEF_COLOR::Green);
                radioG_.set_font_color(DEF_COLOR::Green);
                radioB_.set_font_color(DEF_COLOR::Green);
            }
        };
        radioB_.at_select_func() = [=](bool ena) {
            utils::format("Select Blue: %s\n") % (ena ? "On" : "Off");
            if(ena) {
                radioR_.set_base_color(DEF_COLOR::Blue);
                radioG_.set_base_color(DEF_COLOR::Blue);
                radioB_.set_base_color(DEF_COLOR::Blue);
                radioR_.set_font_color(DEF_COLOR::Blue);
                radioG_.set_font_color(DEF_COLOR::Blue);
                radioB_.set_font_color(DEF_COLOR::Blue);
            }
        };
        radioG_.exec_select();  // 最初に選択されるラジオボタン

        sliderh_.enable();
        sliderh_.at_select_func() = [=](float val) {
            utils::format("Slider H: %3.2f\n") % val;
        };
        sliderv_.enable();
        sliderv_.at_select_func() = [=](float val) {
            utils::format("Slider V: %3.2f\n") % val;
        };

        menu_.enable();
        menu_.at_select_func() = [=](uint32_t pos, uint32_t num) {
            char tmp[32];
            menu_.get_select_text(tmp, sizeof(tmp));
            utils::format("Menu: '%s', %u/%u\n") % tmp % pos % num;
        };

        text_.enable();

        textbox_.enable();
        textbox_.set_title("(1) 項目\n(2) GUI サンプルについて。\n(3) まとめ");
        textbox_.set_vertical_alignment(TEXTBOX::V_ALIGNMENT::CENTER);

        spinbox_.enable();
        spinbox_.at_select_func() = [=](SPINBOX::TOUCH_AREA area, int16_t value) {
            static const char* st[3] = { "Minus", "Stay", "Plus" };
            utils::format("Spinbox: %s Value: %d\n")
                % st[static_cast<uint8_t>(area)] % value;
        };

        toggle_.enable();
        toggle_.at_select_func() = [=](bool state) {
            utils::format("Toggle: %s\n") % (state ? "OFF" : "ON");
            if(!state) {
                progress_ratio_ = 0.0f;
            }
        };

        progress_.enable();
        progress_.at_update_func() = [=](float ratio) {
            if(toggle_.get_switch_state()) {
                ratio += 1.0f / 120.0f;  // 2 sec
                if(ratio > 1.0f) ratio = 1.0f;
            } else {
                ratio = 0.0f;
                progress_ratio_ = 0.0f;
            }
            return ratio;
        };
    }

GUI_sample

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

format クラスの見直し

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

浮動小数点の扱い

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

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

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

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

拡張機能

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

標準出力

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

他の使い方

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

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

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

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

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

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

Github: format_class

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

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

format class Version: 96
All Pass: 20/20

まとめ

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

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

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

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

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


clock_profile.hpp

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

RX72T の場合:

namespace device {

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

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

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

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

system_io::boost_master_clock()

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

ベース周波数のチェック

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

...

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

PLL ベースクロック

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

各モジュール分周器

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

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

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

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

...

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

USB クロック

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

...

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

まとめ

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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


その他:

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

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

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


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

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

enum class を活用

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

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

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

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

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

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

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

...

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

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


まとめ

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

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

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

RX72T で SD カードの読み書き

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

イモハンダ・・・

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

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

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

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

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

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

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

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

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


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


リードが凄く遅い・・・

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


I2C_sample

I2C_sample も少し手を入れた。

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

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

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

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

I2C マスターコマンド

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

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

BMP280 コマンド

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

DS3231 コマンド

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

EEPROM コマンド

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

AS5600 コマンド

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

まとめ

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

            return ICU::VECTOR::NONE;
        }

呼び出し側:

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

とする。

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

あとは、開始するだけ:

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

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

A を送る:

    sci_.putch('A');

文字を受け取る:

    auto ch = sci_.getch();

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

extern "C" {

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

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

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

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

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

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

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

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

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

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

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

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

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

    typedef device::SCI1 SCI_CH;

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

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

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


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

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

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