hira のすべての投稿

WR250Xチェーン交換

先延ばししすぎ・・・

少し前から、純正チェーンは限界を超えて、のびのびでダラダラだった・・
※タイヤのエッジに当たってる状態だった。

交換用チェーンは一昨年には購入してあったが、作業スペースが狭いので、なかなかやる気が起きなくて、まだ何とか使えると先延ばししていた。
でも、流石に、交換しないと、と、ゆー事で、重い腰を上げ、やっと交換した。

メンテナンスローラー

これも、前に購入していたが、初めて使った、もの凄い便利だw

チェーンカット

まず、古いチェーンをカットする。

純正チェーンは、つなぎ目が判らなかったので、適当な場所のピンを抜いた。

まず、グライダーで、かしめてある部分を削って飛ばす。

そしてピンを押し出して、カットする。
※ちゃんとした工具(EK製)なので、スムーズに作業が進む。

新しいチェーンをかしめる

新しいチェーンは、純正と同じ108リンクを買ったので、そのまま取り付け出来る。

いつも思うが、初期のグリスがベタベタなのが・・、軍手を一つ駄目にしたw

出来上がり

意外とスムーズに作業が進んだ、こんな事なら、さっさと済ませておけば良かったと思う。

潤滑剤を塗り直す

ベルハンマーを塗布して、ベタベタを取り除いて、遊び調整などを行い完了。
※最近、チェーングリスを使わなくなった。

まとめ

近所を少し走ってみて、遊びを確認、問題無し、3時を過ぎると寒い寒い・・

スプロケ交換、オイル交換、フロントブレーキのパッド交換、ブレーキオイル交換など、やる事が沢山ある・・・

山梨はガソリンが高い、ハイオク180円だった、まだ上がるらしい・・・

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 よりハードルは高いかもしれないが、サンプルも多くあり、真似るだけなら、そんなにハードルは高く無いと思うのだが・・・

RL78 のフレームワークを久々に改善

C++ は組み込みでは積極利用者が少ない事を実感する日々

最近、特に感じるが、何故か、C++ を組み込みマイコンで積極利用している人が少ないように感じる。

やはり、ハードルがあるのだろうと思う(自分は慣れの問題で感じなくなっている)

どう考えても、C で実装するより、間違いが少なく、構造的に作れて、再利用性が大きく、最適化性能が高い。

C++11 以降、痛い部分が改善されて、より良く自然に書けるようにもなった。

勉強しない人には、何かと敷居が高いのかもしれない・・・

ただ、C++ は、C である程度実装出来る人にとっては、「手っ取り早く、楽で魅力的」とは映らない面もある。
C++ のパワーを理解していないものと思う。

自分は、10年以上前に、C++ を「勉強しなくては」と心の底から思った出来事がある。

非常に複雑で、ボリュームが大きいプロジェクト内で、急遽、モニター用のプログラムを作る案件が生じたー

この案件に対して、C++師匠は、STL を駆使して、あっとゆーまに実装を終え、ちゃんと動くアプリを作った。
※自分がこの仕様を観た時は、これは、そこそこ大変だなーと思っていた・・

また、この師匠は、数ギガヘルツで動くようなCPUのアプリを、何でアセンブラに毛が生えたようなC言語でプログラムしないといけないの?
もっと相応しい作り方があるのでは?

と問うて来たー、この一件で、自分の C++ 敬遠傾向は、一蹴されたと思う。

それ以来、C++ を履修しなおし、現在に至る。

  • C++ の勉強会などに参加して、C++ に精通した人から、色々なレクチャーを受けた。
  • C++ は、正しい道を進まないで我流で進むと、とんでもない迷路にはまる事がある事例をいくつも聞かされた。
  • C++ の最適化で、人間が考えもしない独特の手法で、巧妙に行う事例をいくつも観た。
  • C++ になって大きなプログラムでも、メモリーリークは殆どしないようになった。
  • コンパイルが正常終了すると、大抵プログラムも思ったように動く事が多くなった。
  • テンプレートを少しは理解出来て、自分でもある程度作れるようになった。
  • 警告が出ない、綺麗なソースコードを書くようになった。
  • コードレビューをするようになった。
  • C++ のトレンドや、標準化委員会、boost などのレポートを読むようになった。

ただ、先は長い・・、自分の理解を超えるような考え方、概念がまだ沢山ある・・
まぁ、とりあえず、出来る範囲でゆっくり歩こうと思う。

ある人が言ってた、C++ を学ぶ最良の方法:

C++ をある程度出来る人にコードレビューしてもらう事。
間違った道から、正しい道に戻してもらう。


RL78 関係のフレームワークをメンテナンス

長い間、RX マイコンのフレームワークに集中していたので、RL78 のフレームワークには手を付けていなかった・・

しかし、最近、このフレームワークを使っている人が Qiita にコメントしていた。

使っている人がいるなら、RX マイコンのフレームワーク更新で改善されたエッセンスなどを RL78 にも反映したい。

そこで、それらの実装とテストを行った。

何故か、フラッシュが出来ない・・

Windows10 になって、RL78 の開発環境を作り直して、FIRST_sample を書こうと思ったが、エラーが出てフラッシュが出来ない。

CP2102N が悪いのかと思い、FTDI に変えてみたが、駄目で、エラーメッセージを頼りに、プロトコルのコードのパラメータを変えたり、色々したが駄目・・・

ルネサス・フラッシュ・プログラマーの最新版を導入して、それで書こうと思ったが、やはりうまく行かない・・・

※最新の RFP では、RL78 へのフラッシュ書き込みに、COM ポートで行う最小限のハードを使った場合をサポートしている。

ところが、FTDI で、再度やり直したら、今度は書き込みが正常に行われた・・・
※何が理由なのかさっぱり判らない(RFP のインストールが関係しているのかもしれない・・・)

改善した箇所

テンプレートクラスの実態を定義

以前の実装では、最適化しない(-O0)場合、テンプレート内スタティック変数の実態が無い為、リンクに失敗する。

テンプレートクラスでは、スタテック変数の実態を定義する必要があり、特殊な書き方が必要だ。
これは、書籍や C++ テンプレートの解説でも殆ど見ない方法だと思う。

※最適化を行うと、実態は必要無く(最適化すると、実態を経由しなくなる)問題にならない事が多い。

※デバッグが必要な場合、最適化は行わない。

namespace device {

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  インターバル・タイマー・クラス
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <class _>
    struct itm_t {

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  12ビット・インターバル・タイマ・コントロール・レジスタ(ITMC)
            @param[in]  T   アクセス・クラス
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        template <class T>
        struct itmc_t : public T {
            using T::operator =;
            using T::operator ();
            using T::operator |=;
            using T::operator &=;

            bit_rw_t<T, bitpos::B15>   RINTE;  ///< 12ビット・インターバル・タイマの動作制御
            bits_rw_t<T, bitpos::B0, 12> ITCMP;  ///< 12ビット・インターバル・タイマのコンペア値設定

        };
        typedef itmc_t< rw16_t<0xFFF90> > ITMC_;
        static ITMC_ ITMC;

...

    };
    // テンプレート内、スタティック定義、実態:
    template<class _> typename itm_t<_>::ITMC_ itm_t<_>::ITMC;

    typedef itm_t<void> itm;

↑のように実装する事でリンクエラーを回避出来る。

「itm_t」クラスは、疑似的にテンプレート化してある。
これは、ヘッダーのみで運用する場合、実態を定義するのに都合が良い。

PORT 定義クラスを更新

シングルポートを定義するテンプレートクラスを最新の物にした。

RX マイコンのフレームワークで得た、知見を組み込んである。

ただ、RL78 のポート関係レジスターは、構造的では無い構成なので、多少異なる構成となっている。

namespace {

    // 吸い込みなので、三番目のパラメーターを「false」とする。
    // LED::P = 1 で、実際のポートは、0になり、LED が点灯する。
    typedef device::PORT<device::port_no::P4, device::bitpos::B3, false> LED;

}

int main(int argc, char* argv[])
{
    utils::port::pullup_all();  ///< 安全の為、全ての入力をプルアップ

    LED::DIR = 1;  // 出力設定

    bool f = true;
    while(1) {
        utils::delay::milli_second(250);  ///< 0.25 秒毎の点滅
        LED::P = f;
        f = !f;
    }
}

PSG_sample を追加

ついでなので、先日実装した PSG エミュレーションで音楽演奏のプロジェクトを追加した。

とりあえず、思ったように鳴っているようだ。

VScode の設定を追加

VScode でソースを編集する場合に、インテリセンスを活用する為、設定ファイルを追加してある。

ただ、理由が不明で、赤線がでる場合がある・・・

これは、今後の課題とする・・・


まとめ

RL78 フレームワークを利用している方には、色々細かく修正したので、最新版を取得すると、コンパイルが通らないかもしれない。

多分、修正にはそんなに多くの苦労は無いものと思うが、新しい版を受け入れてもらいたい。

RXマイコンで PSG 音源をエミュレーション

PSG 音源を生成するテンプレートクラス

最近、R8C 関係の GitHub を整理している。
将来的に、初心者(小学生、中学生)向けの電子工作向けボードを作る予定で、それに向けたものだ。
1000円くらいで、電子工作とプログラミングの初級が学べて、何か作れるような物をリリースしたい。

そこで、R8C 用色々なコンテンツを作成している、その一環で PSG 音源を使った音楽再生を行ってみた。

詳しい記事は、Qiita にある。

とりあえず、R8C でそれなりに鳴ったので、RX マイコンにも移植してみた。

実験したのは、RX72N Envision Kit で、このデバイスには、SSIE 接続の D/A があるので、簡単だ。
サンプリング周波数は 48KHz 固定なので、多少オーバースペックかもしれない。

PSG とは?

PSG は、大昔に、矩形波を基本とする音源で、音楽を演奏するデバイスが売られていたのがルーツと思う。
AY-3-8910 とか、そんなデバイスが発売されて、Apple][ 用のボードで演奏したのを覚えている・・
※多分高校生くらいだったから、電子音楽の衝撃は今でも忘れない。
※今考えると楽譜を入力するオーサリングツールは素晴らしく良く出来ていた。

ファミコンでは、それに似た仕様の音源が内蔵されている。

C++ テンプレートの柔軟性

元は、R8C 用に実装したものだが、ほぼ無改造でそのまま再利用出来た。
※実際は、サンプリング周波数が高く、内部で演算がオーバーフローしたので、多少改修した。

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  PSG Manager テンプレート class
        @param[in]  SAMPLE  サンプリング周期
        @param[in]  TICK    演奏tick(通常100Hz)
        @param[in]  BSIZE   バッファサイズ(通常512)
        @param[in]  CNUM    チャネル数(通常4)
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <uint16_t SAMPLE, uint16_t TICK, uint16_t BSIZE, uint16_t CNUM>
    class psg_mng : public psg_base {

psg_mng テンプレートクラスのプロトタイプは上記のようになっていいて、

  • サンプリング周期
  • 演奏 TICK
  • バッファサイズ
  • チャネル数

を与えるようになっている。
※バッファサイズはこのモジュールとは直接関係無いので、除く方が良さそうだが、とりあえず、R8C 版との互換でそのままにしてある。

TICK を 100Hz、サンプリングを 48KHz とすると、480 バイトのデータ列を生成する必要があるので、バッファは 512 バイト。
※1サンプルづつ波形を引っ張るのなら、バッファサイズは含めなくて良い。

R8C では、メモリを節約する為、psg_mng で波形を生成したデータを、直接、PWM ストリームのバッファにしていたが、RX マイコンでは、構成が異なるので、一旦テンポラリに生成して、それを、サウンド出力クラスに食わすようにしている。

        {
            uint32_t n = SAMPLE / TICK;
            psg_mng_.set_wav_pos(0);
            psg_mng_.render(n);
            typename SOUND_OUT::WAVE t;
            for(uint32_t i = 0; i < n; ++i) {
                uint8_t w = psg_mng_.get_wav(i);
                w -= 0x80;
                t.l_ch = t.r_ch = (static_cast<int16_t>(w) << 8) | ((w & 0x7f) << 1);
                sound_out_.at_fifo().put(t);
            }

            if(delay > 0) {
                delay--;
            } else {
                psg_mng_.service();
            }
        }

SSIE と波形出力テンプレート定義

これは、オーディオを扱うアプリで散々利用してきたテンプレートなので、定義も使い方も、変わる処が無い。
この辺りの柔軟性は C++ で実装した場合の特典みたいなものだと思う。

#ifdef USE_DAC
    typedef sound::dac_stream<device::R12DA, device::TPU0, device::DMAC0, SOUND_OUT> DAC_STREAM;
    DAC_STREAM  dac_stream_(sound_out_);

    void start_audio_()
    {
        uint8_t dmac_intl = 4;
        uint8_t tpu_intl  = 5;
        if(dac_stream_.start(48'000, dmac_intl, tpu_intl)) {
            utils::format("Start D/A Stream\n");
        } else {
            utils::format("D/A Stream Not start...\n");
        }
    }
#endif

#ifdef USE_SSIE
    typedef device::ssie_io<device::SSIE1, device::DMAC1, SOUND_OUT> SSIE_IO;
    SSIE_IO     ssie_io_(sound_out_);

    void start_audio_()
    {
        {  // SSIE 設定 RX72N Envision kit では、I2S, 48KHz, 32/16 ビットフォーマット固定
            uint8_t intr = 5;
            uint32_t aclk = 24'576'000;
            uint32_t lrclk = 48'000;
            auto ret = ssie_io_.start(aclk, lrclk, SSIE_IO::BFORM::I2S_32, intr);
            if(ret) {
                ssie_io_.enable_mute(false);
                ssie_io_.enable_send();  // 送信開始
                utils::format("SSIE Start: AUDIO_CLK: %u Hz, LRCLK: %u\n") % aclk % lrclk;
            } else {
                utils::format("SSIE Not start...\n");
            }
        }
    }
#endif

SSIE インターフェース用と、内蔵 D/A 用の二種類がある。
※RX72T、RX66T、RX24T、など、D/A 内臓のデバイスでも簡単に流用出来ると思う。

他、オーディオ出力コンテキストにサンプリング周波数を設定する関数を出してある。

    void set_sample_rate(uint32_t freq)
    {
#ifdef USE_DAC
        dac_stream_.set_sample_rate(freq);
#endif
#ifdef USE_SSIE
        sound_out_.set_output_rate(freq);
#endif
    }

初期化

初期化では、TICK タイマー、オーディオ出力など初期化する。
また、サンプルで入れた楽曲のスコアテーブルを設定する。

    {
        uint8_t intr = 4;
        cmt_.start(TICK, intr);
    }

    start_audio_();

    {  // サンプリング周期設定
        set_sample_rate(SAMPLE);
    }

    psg_mng_.set_score(0, score0_);
    psg_mng_.set_score(1, score1_);

メインループ

後は、psg_mng テンプレートクラスで波形を生成して、オーディオ出力に渡すだけとなる。
その際、8ビットの波形を16ビットに拡張している。
又、モノラルなので、ステレオにしている。

    uint8_t cnt = 0;
    uint8_t delay = 200;
    while(1) {
        cmt_.sync();
        {
            uint32_t n = SAMPLE / TICK;
            psg_mng_.set_wav_pos(0);
            psg_mng_.render(n);
            typename SOUND_OUT::WAVE t;
            for(uint32_t i = 0; i < n; ++i) {
                uint8_t w = psg_mng_.get_wav(i);
                w -= 0x80;
                t.l_ch = t.r_ch = (static_cast<int16_t>(w) << 8) | ((w & 0x7f) << 1);
                sound_out_.at_fifo().put(t);
            }

            if(delay > 0) {
                delay--;
            } else {
                psg_mng_.service();
            }
        }

        ++cnt;
        if(cnt >= 50) {
            cnt = 0;
        }
        if(cnt < 25) {
            LED::P = 0;
        } else {
            LED::P = 1;
        }
    }

楽曲の定義

最近覚えた、便利な使い方:

C++11 になって、不便で不完全な「enum」から解放された。
「enum class」は、型に厳密で、異なった型を受け付けない、もちろん、整数型から派生しているので、「static_cast」を使って変換可能だ。

厳密になった為、色々な「型」を組み合わせるようなデータ列では不便となる。

そこで「union」を使って、利用出来る型を受け付けるようにしておき、コンストラクターで、異なる型からデータ列を生成するようにした。

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  スコア・コマンド構造 @n
                    ・KEY, len @n
                    ・TR, num @n
                    ・TEMPO, num @n
                    ・FOR, num
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        struct SCORE {
            union {
                KEY     key;
                CTRL    ctrl;
                uint8_t len;
            };
            constexpr SCORE(KEY k) noexcept : key(k) { }
            constexpr SCORE(CTRL c) noexcept : ctrl(c) { }
            constexpr SCORE(uint8_t l) noexcept : len(l) { }
        };

これで、スコアデータ列を構造的に作れるようになった。
※コンパイル時に値を決定して ROM 化するので「constexpr」を付加している。

この簡単な構造体で以下のように楽譜データを記述出来る。

    constexpr PSG::SCORE score0_[] = {
        PSG::CTRL::VOLUME, 128,
        PSG::CTRL::SQ50,
        PSG::CTRL::TEMPO, 80,
        PSG::CTRL::ATTACK, 175,
        // 1
        PSG::KEY::Q,   8,
        PSG::KEY::E_5, 8,
        PSG::KEY::D_5, 8,
        PSG::KEY::E_5, 8,
        PSG::KEY::C_5, 8,
        PSG::KEY::E_5, 8,
        PSG::KEY::B_4, 8,
        PSG::KEY::E_5, 8,

まとめ

やはり、C++ の柔軟性と、再利用性は優れている。

8/16 ビットマイコン用ソースコードが、RX マイコンでも、ほぼそのまま再利用出来た。

当然だが、R8C の PWM 再生に比べると、高品質だ。

YouTube:
https://www.youtube.com/watch?v=4ZHuMYcSQko

R152 で大月から浜松へ

突然実施

第三日曜日は、地区の朝礼が8時からある、連絡事項の後、掃除。

それが終わり、天気を確認したら、台風が去って温帯低気圧に変わり、晴天。
大月は、今回の台風では、あまり雨が降らなかった。

アトピーでバイクに長い間乗って無かったのもあって、少し遠出してみる事にした。
※アトピーが直った訳では無いが、今の季節、バイクで走っていれば、そこそこ涼しいし別の事に集中してる方が気が楽な事もある。

ルートは事前に考えていた

今回は、R152 よりも、R20 から R152 に至る途中の道が主で、「富士見パノラマリゾート」手前から、「入笠山」を抜ける林道を通り、R152 に連絡出来る。
※上記写真は、そのルート上で何とも整った風景を観たので撮影したもの。
※入笠山周辺は、マイカーの規制があるので、注意が必要なようだ、途中停車も出来ない区間がある。
※警備に止められた場合は、「伊那」に抜けると言えば大丈夫なようだ。
※規制は8時~3時までのようだ。

浜松行は急に決めた

R152 に合流したのが2時過ぎ、このまま R152 を戻って諏訪から R20 で帰るのも何だかなぁと思い、前から思っていた R152 で浜松まで行く事にした。
帰りは遅くなると思うが、どうしようも無く眠くなったらルートインとかに泊まれば良いと軽く考えてた。

WR250X は長距離のツーリングに適さない

WR250X の燃料タンクは7.4リッター程、大体5リッター消費で給油ランプが点く。
リッター30キロ程は走るので、150キロで給油が必要となる。

以前に、山中で給油しようと思ったら、GS が休みで、仕方なくそのまま戻ったが途中でガス欠になった・・
仕方なく数キロバイクを押したが、宛にしていた GS は、やはり営業しておらず、難儀していた。
※バイクを置いて、自宅まで何とか戻り、ステップワゴン(トランポ仕様にしている)を取りに行こうと考えていた。
農作業をしていたやさしい方に耕運機用に備蓄していたガソリンを別けてもらい自宅まで戻れた経緯がある。

一応、1リットル入る携行缶は持っているが、+30キロでは、場所や時間によっては無理がある。
※二つあれば安心感があるが、それなりに重い・・

結局、150キロの範囲で、大きな街にアクセス出来るようなプランじゃないとマズイ事になる・・
※夜は営業していない場合もある、大きな都市なら、セルフで24HのGSもあるが、小さい集落では夕方にはGSは閉まってしまう。

今回、R152 は「通行止め」区間があった・・

R152 は、一応「国道」だが、途中狭く、離合も困難な場所がある、一方、整備された区間は、道幅も広く、綺麗で、走っていて気持ちが良い。
そして、非常に空いている。

最初に、「地蔵峠」手前のクネクネで不通になっており「全面通行止め」となっていた。
仕方なく、戻って、県道59号との分岐(大鹿村役場)まで戻り、一旦、R153 まで出て、ガソリンを補給した。
懲りずに、県道251号を通り、「喬木 I.C.」から「程野 I.C.」を経て R152 に合流したw
※この自動車専用道路は、ほぼトンネルで、無料区間となっていた。(5キロ程)

二度目の通行止め・・

次に、「兵越峠」を抜ける手前でやはり「全面通行止め」となっていた・・・
規制看板の手前でバイクを止め文面を読んでいたら、地元の車が上がっていったので、アレ?、通れるのかな?と思い、付いていった。
そしたら、上から降りて来たキャンピングカーが、「全面通行止め」で先には行けない「浜松に抜けられない」と教えてくれた。
何でも、3日前くらいに起こったらしい・・・

うーーん、ここまで来てかぁーと思ったが仕方無いので、少し戻り、R418、県道1号を進み、「佐久間ダム」の標識がある分岐まで来た。
この時、既に暗くなっており、あまり良く考えずに「ダムの方に向かったらマズイ」のでは?と思ってしまった。
※ここで、停車して地図を確認すれば良かったが、電波の状況とか、残燃料とか、暗かったとか、面倒にも思って、あまり考えずに県道426号に行ってしまった・・
これが、今回の大失敗となった・・・
県道426号はかなりの難所で、何とか R151 までたどり着いたが、この段階で7時位?
こんな山の中だが、集落に 24H のファミマがあり、簡単な食事をした。(ファミリーマート 北設楽東栄町店)

燃料給油が急務

現在の距離(燃料消費量)から、早急に GS を見つけないとならない・・・
色々検討したが「豊橋」辺りまで抜けないと 24H のセルフは無いかもしれない、携行缶に1リッターあるので、何とかギリギリ間に合う計算。
途中、集落に GS はあったが、やはり閉まっていた。

「新庄市」に入ったら、大きな都市で、24H のセルフがあり、給油出来た、この時10時前、携行缶のガソリンは使わなかった。
※6.44リッター給油(残り800cc)

自宅に戻る

給油して、少し休んだら、気力が戻り、疲労感も少ない(クラッチ側の手も痛くない)ので、到着は夜中になるけど、自宅まで戻る事にした。
※自粛期間だし、宿泊するのも気が重い、途中色々なホテルの駐車場を見たが、かなり車が停まっていたので、宿泊してる人は意外に多いようだ。

まず、R301 で浜松(浜名湖)まで抜けた。
ここで、ファミマで休憩して、ルート検索を行ったが、大月まで戻るのに「高速で5時間」、「下道で6時間」だった。
高速使っても1時間しか変わらない?
うーーん、高速だと、WR250X は結構色々辛い!、また、途中で眠くなったら、SA まで我慢しないとならないしと思い、下道で帰る事にした。
帰りは、R1、R139 とシンプルなルートだ。

途中眠くなったらコンビニで休めるし、R1 沿いなら GS も沢山ある。

R1 は高速道路だった!

今回初めて浜松から R1 を利用したが、自動車専用道路となっており、高速道路と大差無い・・
制限速度は60キロだが、60で走ってるとバンバン抜かれるが、この時間空いているし、かなり快適、速度以外は東名と変わらない。
WR250X は、高速で100キロ連続とかだと、エンジンの回転をかなり上げないとならないし、風圧がそれなりにあり疲れる。
※70~80くらいが丁度良い。(メーターは、1割くらい低く表示するので、66キロくらいで、正確に60キロだと思う)

途中何回か休憩をして、R139に合流、富士市でガソリン補給。(午前2時、5.34リットル)
※ここから大月まで90キロ程度、給油は必要無い。

R139 で大月までー

寒くなるだろうから、今回珍しく持参したバイク用レインスーツを上下着た。

R139 はこの時間、ほぼ車はいない、そして凄く寒い!、14度。

あまりに寒いので、富士吉田で24H営業のなか卯で、少し早い朝食(牛丼とうどんのせっと)を食べ、自宅に戻ったのが4時位。

まとめ

今回、それなりに疲れたが、峠を満喫した、軽量バイクの楽しみは、狭い林道w

R152 は面白い、狭い林道と、そこそこ広い整備された道で、緩急があり飽きないし、昼間でも下手なマイカーが少ない!
昼間なら、休憩スポットも色々ある。

また、細々と整備も進められており、完成が待たれる・・

二度の通行止めで、消化不良の面もあるので、通行止め解消の折には、もう一度通っていない部分を含め挑戦したい!
次は、もっと早く出発して、宿泊を前提にしたい。

RX マイコン I2C モニター

I2C モニター

RX マイコンのサンプルで中途のプロジェクトを更新した。

I2C デバイスを接続して、操作を行うサンプルだが、雛形は作ったが、中身は無い状態だった。
※サンプルはそれなりに多いので、作ったつもりで忘れている事もあるw

それを、ある程度実装した。

※実験は、RX72T ボードで行った、IICA ペリフェラルを利用している。


対話形式で、I2C デバイスと通信する

とりあえず、最低限必要なコマンドを実装。

コマンド パラメーター 機能
scan start end I2C デバイスをスキャンする
adr address 操作を行う I2C デバイスのアドレスを設定
r num I2C からデータを受け取る(recv)
s data... I2C へデータを書き込む(send)

操作画面

操作の入出力は、シリアル接続されたターミナルで行う。

接続したデバイスは、DS3231 のモジュールで、24C32(EEPROM)も載っている。

起動時:

Start I2C sample for 'RX72T' 200[MHz]
SCI Baud rate (set):  115200
SCI Baud rate (real): 115287 (0.08 [%])
CMT rate (set):  100 [Hz]
CMT rate (real): 100 [Hz] (0.00 [%])
I2C Start ok: (intrrupt level = 0)
#

スキャン:

# scan
Ditect I2C address: 0x57 ( 87, 0b1010111)
Ditect I2C address: 0x68 (104, 0b1101000)
  Ditect I2C Device(s): 2
#

書き込みと読出し:

# adr 0x68
# s 0
# r 16
43 39 03 06 03 09 21 00 00 00 00 00 00 00 00 88

# s 0
# r 16
50 39 03 06 03 09 21 00 00 00 00 00 00 00 00 88

#
  • I2C アドレス設定(0x68)
  • アドレスレジスタを設定(0)
  • 16バイトの読出し
  • 秒カウンタが変化している。(43 ---> 50)

まとめ

とりあえず、動作しているので、コミットしてある。

まだまだ、初期段階なので、今後拡張していきたい。

IICA の割り込みモードも完成させたい。

Makefile を共通化

Makefile 共通化

RX マイコンの各プロジェクトで、Makefile で共有出来る部分を抜き出して、共通化を行った。

今まで、同じような「手順」を各プロジェクト毎にコピーしていたが、二度手間だし、プロジェクト数が多く、作業が大変だった。
※Makefile を複数に別けたく無かったとゆーこだわりがあったのだが、現在のように手順がそこそこ複雑だと、そうする「こだわり」はあまり意味が無い。

共通部分

# -*- tab-width : 4 -*-
#=======================================================================
#   @file
#   @brief  RX microcontroller share Makefile
#   @author 平松邦仁 (hira@rvf-rc45.net)
#   @copyright  Copyright (C) 2021 Kunihito Hiramatsu @n
#               Released under the MIT license @n
#               https://github.com/hirakuni45/RX/blob/master/LICENSE
#=======================================================================

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

まず、これは、Windows、Linux、OS-X の環境を判断して、微妙な違いを吸収する。
※一番重要な事は、boost のパスを各環境で同じように扱う事。


LIB_ROOT    =   ../../rxlib/lib

INC_SYS     =   ../../rxlib/include $(LOCAL_PATH)/include

PROG_VERIFY = --verify
  • LIB_ROOT は、RX マイコン専用ライブラリのパスを設定している。
  • INC_SYS は、RX マイコン専用ライブラリのインクルードパスと、ローカルのインクルードパス(boost)などを設定している。
  • PROG_VERIFY は、rx_prog でフラッシュ書き込みする際に、「VERIFY」を行う場合のキーワードとなっている。
  • RX24T は、通常の手順で、VERIFY が行われない為、無効にする必要がある。

ifeq ($(RX_DEF),SIG_RX24T)
  RX_CPU = RX24T
  RX_OPT = v2
  LDSCRIPT  =   ../../RX24T/$(DEVICE).ld
  PROG_VERIFY =
endif

ifeq ($(RX_DEF),SIG_RX64M)
  RX_CPU = RX64M
  RX_OPT = v2
  LDSCRIPT  =   ../../RX64M/$(DEVICE).ld
endif

ifeq ($(RX_DEF),SIG_RX71M)
  AS_OPT    +=  --defsym MEMWAIT=1
  RX_CPU = RX71M
  RX_OPT = v2
  LDSCRIPT  =   ../../RX71M/$(DEVICE).ld
endif

ifeq ($(RX_DEF),SIG_RX65N)
  LIB_ROOT += ../../RX600/drw2d
  INC_APP +=  ../../RX600/drw2d/inc/tes
  USER_LIBS += drw2d
  RX_CPU = RX65N
  RX_OPT = v2
  LDSCRIPT  =   ../../RX65x/$(DEVICE).ld
endif

ifeq ($(RX_DEF),SIG_RX66T)
  RX_CPU = RX66T
  RX_OPT = v3
  LDSCRIPT  =   ../../RX66T/$(DEVICE).ld
endif

ifeq ($(RX_DEF),SIG_RX72T)
  RX_CPU = RX72T
  RX_OPT = v3
  LDSCRIPT  =   ../../RX72T/$(DEVICE).ld
endif

ifeq ($(RX_DEF),SIG_RX72N)
  LIB_ROOT += ../../RX600/drw2d
  INC_APP +=  ../../RX600/drw2d/inc/tes 
  USER_LIBS += drw2d
  RX_CPU = RX72N
  RX_OPT = v3
  LDSCRIPT  =   ../../RX72N/$(DEVICE).ld
endif

ifeq ($(RX_DEF),SIG_RX72M)
  LIB_ROOT += ../../RX600/drw2d
  INC_APP +=  ../../RX600/drw2d/inc/tes
  USER_LIBS += drw2d
  RX_CPU = RX72M
  RX_OPT = v3
  LDSCRIPT  =   ../../RX72M/$(DEVICE).ld
endif

この条件文で、RX マイコン毎に異なる設定を行っている。
現状では、「SIG_xxx」のキーワード毎に行っているが、将来的には、DEVICE で IC の型番を設定しているので、それに従って行うのが妥当だと思う。

※型番で行えば、ピン数の違いによる変更をソースコードへ伝達する事が出来る。

RX71M だけは、ちょっと特殊で、スーパーバイザーモードでしかアクセス出来ないレジスタがある。
そこで、そのレジスタを、start.s アセンブラブログラム内で行っている。「--defsym MEMWAIT=1」

FreeRTOS の場合、ユーザーモードに移行しない事が重要だが、それは、各プロジェクト毎に設定する。

# for FreeRTOS option
AS_OPT      =   --defsym NOT_USER=1

# Renesas GNU-RX gcc compiler version check
TARGET_ISA_TEXT := $(shell rx-elf-gcc --target-help | grep ISA)

# Renesas GNU-RX (8.3.0) compiler 
ifeq ($(TARGET_ISA_TEXT),)
  # for gcc-7.5.0 current gcc source build
  AS_DEFS       =   -mcpu=rx600
  CC_DEFS       =   -mcpu=rx600 -Wa,-mcpu=rxv2
  CP_DEFS       =   -mcpu=rx600
else # Renesas GNU-RX gcc 8.3.0
  AS_DEFS       =   -misa=$(RX_OPT)
  CC_DEFS       =   -misa=$(RX_OPT)
  CP_DEFS       =   -misa=$(RX_OPT)
endif

これは、Renesas GNU-RX gcc コンパイラと、プレーンな gcc コンパイラで、オプションが異なるので、それを自動判別する。


# You should not have to change anything below here.
AS          =   rx-elf-as
CC          =   rx-elf-gcc
CP          =   rx-elf-g++
AR          =   rx-elf-ar
LD          =   rx-elf-ld
OBJCOPY     =   rx-elf-objcopy
OBJDUMP     =   rx-elf-objdump
SIZE        =   rx-elf-size

AFLAGS      =   $(AS_OPT) $(AS_DEFS)
CFLAGS      =   -std=gnu99 $(CC_OPT) $(OPTIMIZE) $(CC_DEFS) $(DEFS)
PFLAGS      =   -std=c++17 $(CP_OPT) $(OPTIMIZE) $(CP_DEFS) $(DEFS)
FLAGS       = $(AS_OPT) $(AS_DEFS) $(CC_OPT) $(CP_OPT) $(OPTIMIZE) $(CC_DEFS) $(CP_DEFS) $(DEFS)

# FLAGS_CMP := $(shell cat $(TARGET).opt)

override LDFLAGS = $(MCU_TARGET) -nostartfiles -Wl,-Map,$(TARGET).map -T $(LDSCRIPT)

OBJCOPY_OPT =   --srec-forceS3 --srec-len 32

OBJECTS =   $(addprefix $(BUILD)/,$(patsubst %.s,%.o,$(ASOURCES))) \
            $(addprefix $(BUILD)/,$(patsubst %.c,%.o,$(CSOURCES))) \
            $(addprefix $(BUILD)/,$(patsubst %.cpp,%.o,$(PSOURCES)))

DOBJECTS =  $(addprefix $(BUILD)/,$(patsubst %.c,%.o,$(CSOURCES))) \
            $(addprefix $(BUILD)/,$(patsubst %.cpp,%.o,$(PSOURCES)))

DEPENDS =   $(patsubst %.o,%.d, $(DOBJECTS))

# all, clean: optional make command
.PHONY: all clean clean_depend run text
.SUFFIXES :
.SUFFIXES : .hpp .s .h .c .cpp .d .o

all: $(BUILD) $(TARGET).elf text

$(TARGET).elf: $(OBJECTS) $(LDSCRIPT) Makefile
    $(CC) $(LDFLAGS) $(LIBINCS) -o $@ $(OBJECTS) $(LIBS)
    $(SIZE) $@

$(BUILD)/%.o: %.s
    mkdir -p $(dir $@); \
    $(AS) -c $(AOPT) $(AFLAGS) $(AINCS) -o $@ $<

$(BUILD)/%.o : %.c
    mkdir -p $(dir $@); \
    $(CC) -c $(COPT) $(CFLAGS) $(CINCS) $(CCWARN) -o $@ $<

$(BUILD)/%.o : %.cpp
    mkdir -p $(dir $@); \
    $(CP) -c $(POPT) $(PFLAGS) $(PINCS) $(CPWARN) -o $@ $<

$(BUILD)/%.d: %.c
    mkdir -p $(dir $@); \
    $(CC) -MM -DDEPEND_ESCAPE $(COPT) $(CFLAGS) $(APPINCS) $< \
    | sed 's/$(notdir $*)\.o:/$(subst /,\/,$(patsubst %.d,%.o,$@) $@):/' > $@ ; \
    [ -s $@ ] || rm -f $@

$(BUILD)/%.d: %.cpp
    mkdir -p $(dir $@); \
    $(CP) -MM -DDEPEND_ESCAPE $(POPT) $(PFLAGS) $(APPINCS) $< \
    | sed 's/$(notdir $*)\.o:/$(subst /,\/,$(patsubst %.d,%.o,$@) $@):/' > $@ ; \
    [ -s $@ ] || rm -f $@

clean:
    rm -rf $(BUILD) $(TARGET).elf $(TARGET).mot $(TARGET).lst $(TARGET).map

clean_depend:
    rm -f $(DEPENDS)

これは、gcc の設定で、従属規則を自動生成する仕組みを内包する。


lst: $(TARGET).lst

%.lst: %.elf
    $(OBJDUMP) -h -S $< > $@

# Rules for building the .text rom images

text: mot lst

lst: $(TARGET).lst
mot: $(TARGET).mot
bin: $(TARGET).bin

%.lst: %.elf
    $(OBJDUMP) -h -S $< > $@

%.mot: %.elf
    $(OBJCOPY) $(OBJCOPY_OPT) -O srec $< $@

%.bin: %.elf
    $(OBJCOPY) -O binary $< $@

これは、リンカーで実行バイナリーを作成後、リストファイル、モトローラーファイル、バイナリーファイルを作成する手順だ。


# Serial Flash write 
run:
    $(MAKE)
    rx_prog -d $(RX_CPU) --progress --erase --write $(PROG_VERIFY) $(TARGET).mot

最後は、シリアル接続で、フラッシュ書き込みを行うツールの設定などになっている。

個別部分

上記のように、共通出来る部分を追い出したので、個別部分はシンプルとなった。

# -*- tab-width : 4 -*-
#=======================================================================
#   @file
#   @brief  RX72N Makefile
#   @author 平松邦仁 (hira@rvf-rc45.net)
#   @copyright  Copyright (C) 2020, 2021 Kunihito Hiramatsu @n
#               Released under the MIT license @n
#               https://github.com/hirakuni45/RX/blob/master/LICENSE
#=======================================================================
TARGET      =   raytracer_sample

DEVICE      =   R5F572NN

RX_DEF      =   SIG_RX72N

BUILD       =   release
# BUILD     =   debug

VPATH       =   ../../

ASOURCES    =   common/start.s

CSOURCES    =   common/init.c \
                common/vect.c \
                common/syscalls.c

PSOURCES    =   RAYTRACER_sample/main.cpp \
                graphics/font8x16.cpp \
                graphics/color.cpp \
                common/stdapi.cpp

USER_LIBS   =

USER_DEFS   =

INC_APP     =   . ../ ../../

AS_OPT      =

CP_OPT      =   -Wall -Werror \
                -Wno-unused-variable \
                -Wno-unused-function \
                -fno-exceptions

CC_OPT      =   -Wall -Werror \
                -Wno-unused-variable \
                -fno-exceptions

ifeq ($(BUILD),debug)
    CC_OPT += -g -DDEBUG
    CP_OPT += -g -DDEBUG
    OPTIMIZE = -O0
endif

ifeq ($(BUILD),release)
    CC_OPT += -DNDEBUG
    CP_OPT += -DNDEBUG
    OPTIMIZE = -O3
endif

-include ../../common/makefile

-include $(DEPENDS)

これは、RX72T の RAYTRACER_sample の Makefile で、基本、リンクするファイルの記述が殆どだ。

リリースビルドと、デバッグビルドの違いで切り替える部分は共通化しなかった。
最適化を変えたり、プロジェクト毎に微妙な違いを許容する事が出来るように配慮した。


まとめ

共通化する過程で、オプションなどを変更した場合に、フルコンパイルが自動で行えるように出来ないか検討したが、make を完全に理解していない為、思ったように作れなかった・・

これは今後の課題にしたいと思う。

RX72T ボード出来たが、残念な結果・・

RX72T ボードが到着

PCBgogo からボードが届いて、早速組み立て動かしたが、色々と問題がある・・

何故、10枚作ったのか(かなり確認したので、パーフェクトと思っていたが・・・)

まぁそれでも、ツギハギすれば何とかなる程度ではあるけど・・

  • GPTW コネクタのピンアサインが間違っている・・
  • マイクロ USB コネクタのフットプリントを自作したが、ピンアサインが逆・・

※以前に、CP2102N の基板を作った時に使った、マイクロ USB のコネクタと同じフットプリントを使ったのだが逆だった・・
良く調べると、マイクロ USB コネクタには、二種類あり、上下が逆になっていた・・・
何も考えずに、ボトムマウントを買って取り付けてしまった・・・

フラットパッケージのハンダが難しい

今回初めて、自作基板に 0.5mm ピッチのデバイスをハンダ付けしたが、これが意外と難しい。

  • 今までは、金メッキされた変換基板にハンダ付けしていたが、それに比べて難しい
  • ハンダメッキが均一ではなく、部品の位置を決めるのが難しい
  • 視力が落ちて、ルーペなどを使わないと、細かい作業が出来ない

それでも、何とかコツを攫んでハンダ付けした。
※何かツールを用意しないと、量産は時間がかかりそうだ・・・
部品を吸引などして掴んで、微調整するハンドルがあれば良いのだが・・

0.5mm ピッチだと、ブリッジはするが、ハンダ吸い取り線とフラックスを使えば、何とかリカバリーは出来るようだ。

自作基板でも、部品を正確に配置する事が出来れば、殆ど問題は起こらないと感じた。
それと、部品を正確に置けても、その状態で固定するのが難しい、何か「ジグ」が必要だと痛感した。

1603 の部品(抵抗、コンデンサ)は、コツを攫めば簡単だが、綺麗(位置を正確に決める)に付けるのは難しい。

とりあえず、動作はしている

LED 点滅や、シリアル通信などを行って、問題なく動作する事を確認した。

SD カードの確認を行おうと思ったが、電源切り替え IC が行方不明で止まっている・・
※確かに買ったハズだが、何処を探しても見つからない・・・

エミュレータの接続は OK

ルネサスの評価ボードの回路を参考に、エミュレーター関係のコネクターを設けている。
E1 を接続して、フラッシュの書き込みを行ってみたが、問題無かった。

また、シリアル接続で内蔵フラッシュの書き込みも問題無い。

IICA の確認を優先

IICA(I2C) の確認を優先している。
IICA ドライバーは、ポーリングは出来ていたが、割り込みを使うドライバーは未完だった。

そこで、これを完成させる事から始めようと思う。

RX24T である程度確認していたが、RX72T では、グループ割り込みを使う必要がある。
※RX24T では通常割り込み。

グループ割り込みのハンドリングがイマイチだったので、RX マイコンフレームワーク全体で関係する部分を修正した。

まとめ

USB が逆だったのは、かなりショックだったが、部品が二種類(トップ、ボトムマウント)ある事に気が回らなかった・・・

マイクロ USB コネクタを基板にダメージを与えないで外す事が難しそうだ・・・
※低融点ハンダを買う必要があるようだ・・・

RX72T ボードを試作

RX72T が安い!

いつもお世話になっている Chip1Stop で100ピン、PGA、USB の RX72T が 10 個で @600 程だった。
丁度、RX72T の実験ボードが必要だったので、ボードの制作も考えて10個購入した。

RX72T は 200MHz 動作で、TFU(三角関数演算器)内蔵なので、CP が非常に高い。
※現状、RX24TやRX66Tと同程度か、むしろ安い!
RAM(128KB)がもう少し多ければと思うが・・・

いつも、デバイスは買ったものの、ボードを作るのが後回しになり、新型のRXマイコンが出て、モチベーションが駄々下がり、結局肥やしになる事多数、今回は、ちゃんと最後まで作る事を誓った。

基本設計

基本的には、あまり欲張らずに、そこそこの完成度とする事でボードの設計が楽になると考えた。

  • RSPI で SD カードをサポートする。
  • GPTW ポートを出す(ブラシレスモーター制御向け)
  • CAN をサポート
  • IICA(I2C)をサポート
  • RS-485 をサポート
  • Renesas エミュレータ接続をサポート
  • USB マスター、クライアントをサポート
  • シリアルポートを複数サポート
  • D/A 出力
  • アナログ入力

など、複数の用途を盛り込んだ。
やはり、ポートの配分に苦労したが、何とか回路設計は満足いくものになった。

プロジェクトは、github に KiCAD のプロジェクトとして置いてある。

ルート(ブロック):

全回路図(PDF)


最近、アトピーが酷くなって、甲府にある、漢方の病院に通院している。(既に3か月になる・・)
なかなか良くならず(時間がかかると言われた)、集中出来ないので、本業はお休みしている。
起きていると、痒みが強いので、なるべく安静にしている、この苦しみは、患った人にしか理解してもらえない・・

KiCAD の利用

KiCAD で基板を制作するのは、2度行ったので、かなり経験値が高くなった。
それでも、本格的なマイコンボードは初なので、色々ハードルがあると思える。

最近のKiCADは本当に完成度が高く、回路部品、フットプリント、など、自分で制作する事が少なくなった。

自分は、特殊なパーツ(主に秋月)に関して、部品や、footprint を作成している。

問題なのは、「二層基板で十分品質の高い電源を引けるか?」なのだが、部品配置をある程度行い、オートルーターに食わせてみたら、塩梅良かったので、電源を手動で引いて、オートルーターで自動配線、評価を繰り返していった。

オートルーターは、かなり細かい設定が出来るものの、全自動で引いても、思ったようにはいかない。

  • 部品の配置を良く吟味する。
  • 重要なトラックを手動で引く。(オートルーターが引ける余地を残し、配慮する)
  • 電源関係も重要な部分は自分で引く。

これだけ行えば、後はオートルーター任せで何とかなる事が判った。

まとめ

とりあえず、大体出来たが、もう少し煮詰めてから、基板屋に発注する事にしたい。

追記 2021-08-14 07:26:06 Saturday

電源の手動ライン、部品位置など見直して、再度オートルートを行った、今回は、最適化が終了するまでオートルーターを動かし続けた。
※ルートは、数分で終了したが、最適化には、この基板規模で1日くらいはかかっている・・・

そこで判明したのは、部品名が基板外形の外にあると、そこが、基板外形の最大エリアとして認識する事だ・・

最適化には時間がかかるので、手動で上記二本のトラックを修正した。

もう一度、確認を行い、ベタアースを追加してから基板の発注を行いたいと思う。

追記 2021-08-14 17:31:52 Saturday

下記 PCB でガーバーを作成し、基板屋(PCBgogo)に注文しました。

  • 基板サイズ 120mmx80mm
  • 枚数10枚
  • 銅箔2oz
  • 配送 OCS(DHLより$5安い)

で、7200円程、@720円