「電子工作な日々」カテゴリーアーカイブ

電子工作に関連するお話など・・

RX72N Envision Kit でオーディオプレイヤー

はじめに

以前、RX65N Envision Kit で作っていた、オーディオプレイヤーを、RX72N Envision Kit でも動作するようにした。
※GUI は無いが、RX64M でも動作する。

また、操作方法を見直して、GUI での一般的な操作で出来るようにした。
※ファイラーは、多少独特だが、タッチ操作で完結する。

全体的にかなり色々修正、マルチプラットホームで共通化できるように色々な面で改修を行った。

以前は、オーディオインターフェースとして内蔵 D/A を利用していたが、SSIE を使った I2S 出力をサポートした。
※RX72N Envision Kit では、I2S からアナログに変換するデバイスが標準搭載された。

FreeRTOS で、オーディオファイルのデコードと、GUI 操作を別タスクで動かすようにした。

ソースコードなど一式

AUDIO_sample

ソースコードは Github にプッシュしてある。

※コンパイルするには、RX フレームワーク全体が必要なので、RX プロジェクトをクローンする必要がある。
※MSYS2 による、RX マイコン用 gcc をビルドするか、Renesas GNU-RX 8.3.0 をインストールして利用する。
※Renesas の統合環境(CC-RX)では、コンパイルする事は出来ない。

全体の構成

全体は、以下のモジュールで構築されている。(-O3 の最適化で、バイナリー、900キロバイトある)

  • オーディオプレイヤー本体(main.cpp、audio_gui.hpp)
  • libmad (MP3 のデコードを行う)
  • libpng (PNG 画像のデコードを行う)
  • zlib (libpng が利用する)
  • picojpeg (JPEG 画像のデコードを行う)

ハードウェアーの構成

  • RX65N Envision Kit、RX64M ではチップ内蔵の12ビットD/Aからオーディオ出力する。
  • RX72N Envision Kit では、内蔵オーディオから出力する。
  • RX64M では、D/A 出力、SD カードハードウェアー、シリアル入出力などを接続する必要がある。

※RX65N Envision Kit では、D/A 出力を出して、アンプを入れたり、SD カードソケットを取り付ける改造が必要となる。
※RX72N Envision Kit では、改造は一切必要なく、SD カードにオーディオファイルを用意するだけ。
※RX64M は、DIY ボード向けのものになっている。(GR-KAEDE で動かすには、色々なポートの設定などを変更する必要がある)

各ハードウェアー基本設定 (main.cpp)

RX64M DIY:

  • 水晶発振子 12MHz
  • インジケーター LED 、PORT0、B7
  • コンソール接続 SCI は SCI1
  • SD カード MISO、PORTC、B3
  • SD カード MOSI、PORT7、B6
  • SD カード SPCK、PORT7、B7
  • SD カード選択、PORTC、B2
  • SD カード電源制御、PORT8、B2、アクティブ Low
  • SD カード検出、PORT8、B1
  • SD カードの書き込み禁止は使わない
  • SDHI インターフェースによる SD カード制御(候補3のポートマップ)
  • D/A 出力用波形バッファのサイズ指定(8192、1024)
    ※RX64M DIY ボードでは、SD カードのインターフェースとして、ソフト SPI を使っている。
#if defined(SIG_RX64M)
    typedef device::system_io<12'000'000> SYSTEM_IO;
    typedef device::PORT<device::PORT0, device::bitpos::B7> LED;
    typedef device::SCI1 SCI_CH;
    static const char* system_str_ = { "RX64M" };

    // SDCARD 制御リソース
    typedef device::PORT<device::PORTC, device::bitpos::B3> MISO;
    typedef device::PORT<device::PORT7, device::bitpos::B6> MOSI;
    typedef device::PORT<device::PORT7, device::bitpos::B7> SPCK;
    typedef device::spi_io2<MISO, MOSI, SPCK> SDC_SPI;  ///< Soft SPI 定義
    SDC_SPI sdc_spi_;
    typedef device::PORT<device::PORTC, device::bitpos::B2> SDC_SELECT;   ///< カード選択信号
    typedef device::PORT<device::PORT8, device::bitpos::B2, 0> SDC_POWER; ///< カード電源制御
    typedef device::PORT<device::PORT8, device::bitpos::B1> SDC_DETECT;   ///< カード検出
    typedef device::NULL_PORT SDC_WPRT;  ///< カード書き込み禁止
    typedef fatfs::mmc_io<SDC_SPI, SDC_SELECT, SDC_POWER, SDC_DETECT, SDC_WPRT> SDC;
    SDC     sdc_(sdc_spi_, 25'000'000);

    // マスターバッファはでサービスできる時間間隔を考えて余裕のあるサイズとする(8192)
    // DMAC でループ転送できる最大数の2倍(1024)
    typedef sound::sound_out<int16_t, 8192, 1024> SOUND_OUT;
    static const int16_t ZERO_LEVEL = 0x8000;

    #define USE_DAC

RX65N Envision Kit:

  • 水晶発振子 12MHz
  • インジケーター LED 、PORT7、B0
  • コンソール接続 SCI、SCI9
  • SD カードの電源制御、PORT6、B4、アクティブ Low
  • SD カードの書き込み禁止は使わない
  • SDHI インターフェースによる SD カード制御(候補3のポートマップ)
  • D/A 出力用波形バッファのサイズ指定(8192、1024)
#elif defined(SIG_RX65N)
    /// RX65N Envision Kit
    typedef device::system_io<12'000'000> SYSTEM_IO;
    typedef device::PORT<device::PORT7, device::bitpos::B0> LED;
    typedef device::SCI9 SCI_CH;
    static const char* system_str_ = { "RX65N" };

    typedef device::PORT<device::PORT6, device::bitpos::B4, 0> SDC_POWER; ///< '0'でON
    typedef device::NULL_PORT SDC_WP;       ///< 書き込み禁止は使わない
    // RX65N Envision Kit の SDHI ポートは、候補3で指定できる
    typedef fatfs::sdhi_io<device::SDHI, SDC_POWER, SDC_WP, device::port_map::option::THIRD> SDC;
    SDC         sdc_;

    // マスターバッファはでサービスできる時間間隔を考えて余裕のあるサイズとする(8192)
    // DMAC でループ転送できる最大数の2倍(1024)
    typedef sound::sound_out<int16_t, 8192, 1024> SOUND_OUT;
    static const int16_t ZERO_LEVEL = 0x8000;

    #define USE_DAC
    #define USE_GLCDC

RX72N Envision Kit:

  • 水晶発振子 16MHz
  • インジケーター LED 、PORT4、B0
  • コンソール接続 SCI、SCI2(内蔵 USB シリアルインターフェース)
  • SD カードの電源制御、PORT4、B2、アクティブ High
  • SD カードの書き込み禁止は使わない
  • SDHI インターフェースによる SD カード制御(候補3のポートマップ)
  • SSIE 出力用波形バッファのサイズ指定(8192、1024)
#elif defined(SIG_RX72N)
    /// RX72N Envision Kit
    typedef device::system_io<16'000'000> SYSTEM_IO;
    typedef device::PORT<device::PORT4, device::bitpos::B0> LED;
    typedef device::PORT<device::PORT0, device::bitpos::B7> SW2;
    typedef device::SCI2 SCI_CH;
    static const char* system_str_ = { "RX72N" };

    typedef device::PORT<device::PORT4, device::bitpos::B2> SDC_POWER;    ///< '1'でON
    typedef device::NULL_PORT SDC_WP;  ///< カード書き込み禁止ポート設定
    // RX72N Envision Kit の SDHI ポートは、候補3で指定できる
    typedef fatfs::sdhi_io<device::SDHI, SDC_POWER, SDC_WP, device::port_map::option::THIRD> SDC;
    SDC         sdc_;

    // マスターバッファはサービスできる時間間隔を考えて余裕のあるサイズとする(8192)
    // SSIE の FIFO サイズの2倍以上(1024)
    typedef sound::sound_out<int16_t, 8192, 1024> SOUND_OUT;
    static const int16_t ZERO_LEVEL = 0x0000;

    #define USE_SSIE
    #define USE_GLCDC

描画ハードウェアー設定(RX65N/RX72N Envision Kit)audio_gui.hpp

  • LCD_DISP、LCD の選択
  • LCD_LIGHT、LCD バックライト
  • LCD_ORG、描画ハードウェアー、GLCDC 開始アドレス
  • FT5206_RESET、タッチパネルインターフェース、リセット信号
  • FT5206_I2C、タッチパネルインターフェース、SCI(I2C) ポート
#if defined(SIG_RX65N)
        typedef device::PORT<device::PORT6, device::bitpos::B3> LCD_DISP;
        typedef device::PORT<device::PORT6, device::bitpos::B6> LCD_LIGHT;
        static const uint32_t LCD_ORG = 0x0000'0100;
        typedef device::PORT<device::PORT0, device::bitpos::B7> FT5206_RESET;
        typedef device::sci_i2c_io<device::SCI6, RB64, SB64, device::port_map::option::FIRST_I2C> FT5206_I2C;
#elif defined(SIG_RX72N)
        typedef device::PORT<device::PORTB, device::bitpos::B3> LCD_DISP;
        typedef device::PORT<device::PORT6, device::bitpos::B7> LCD_LIGHT;
        static const uint32_t LCD_ORG = 0x0080'0000;
        typedef device::PORT<device::PORT6, device::bitpos::B6> FT5206_RESET;
        typedef device::sci_i2c_io<device::SCI6, RB64, SB64, device::port_map::option::THIRD_I2C> FT5206_I2C;
#endif

メイン部

今回 FreeRTOS を利用して、オーディオコーデックのデコード部と、GUI 操作部を分け、スレッドで平行動作させている。
FreeRTOS ベースなので、起動したら、二つのタスクを生成後、それらを起動する。

int main(int argc, char** argv)
{
    SYSTEM_IO::setup_system_clock();

    {  // SCI 設定
        static const uint8_t sci_level = 2;
        sci_.start(115200, sci_level);
    }

    {  // SD カード・クラスの初期化
        sdc_.start();
    }

    utils::format("\r%s Start for Audio Sample\n") % system_str_;

    {
        uint32_t stack_size = 4096;
        void* param = nullptr;
        uint32_t prio = 2;
        xTaskCreate(codec_task_, "Codec", stack_size, param, prio, nullptr);
    }

    {
        uint32_t stack_size = 8192;
        void* param = nullptr;
        uint32_t prio = 1;
        xTaskCreate(main_task_, "Main", stack_size, param, prio, nullptr);
    }

    vTaskStartScheduler();
}

オーディオ・コーデック・タスク

  • name_t クラスを使って、GUI タスクから、再生ファイル名を受け取っている。
  • 受け取った名前は、コーデックマネージャーに渡して、オーディオ再生している。
    void codec_task_(void *pvParameters)
    {
        // オーディオの開始
        start_audio_();

        while(1) {
            if(name_t_.get_ != name_t_.put_) {
                if(strlen(name_t_.filename_) == 0) {
                    codec_mgr_.play("");
                } else {
                    if(std::strcmp(name_t_.filename_, "*") == 0) {
                        codec_mgr_.play("");
                    } else {
                        codec_mgr_.play(name_t_.filename_);
                    }
                }
                ++name_t_.get_;
            }
            codec_mgr_.service();

            vTaskDelay(10 / portTICK_PERIOD_MS);
        }
    }

操作 (GUI) タスク

  • GUI (GLCDC) を使う場合と、コンソールのみの場合を分けている。
  • GUI では、ファイル名が選択されたら、それを、コーデックのタスクに転送している。
  • GUI では、オーディオファイル再生時、再生経過時間を受け取って、表示に反映している。
  • シリアル入出力のコマンドライン操作をサポートしている。
  • SD カードの操作系をサービスしている(SD カードの抜き差しによるマウント操作など)
    void main_task_(void *pvParameters)
    {
        cmd_.set_prompt("# ");

        LED::DIR = 1;
#ifdef USE_GLCDC
        gui_.start();
        gui_.setup_touch_panel();
        gui_.open();  // 標準 GUI
        volatile uint32_t audio_t = audio_t_;
#endif
        while(1) {
#ifdef USE_GLCDC
            if(gui_.update(sdc_.get_mount(), codec_mgr_.get_state())) {
                // オーディオ・タスクに、ファイル名を送る。
                strncpy(name_t_.filename_, gui_.get_filename(), sizeof(name_t_.filename_));
                name_t_.put_++;
            }
            if(audio_t != audio_t_) {
                gui_.render_time(audio_t_);
                audio_t = audio_t_;
            }
#else
            // GLCDC を使わない場合(コンソールのみ)
            auto n = cmt_.get_counter();
            while((n + 10) <= cmt_.get_counter()) {
                vTaskDelay(1 / portTICK_PERIOD_MS);
            }
            if(codec_mgr_.get_state() != sound::af_play::STATE::PLAY) {
                cmd_service_();
            }
#endif
            sdc_.service();
            update_led_();
        }
    }

FreeRTOS 対応のシリアル入出力

  • FreeRTOS では、共有するシリアルの入出力を排他制御する必要がある。
  • あまり効率は良くないが、その対応をしている。
  • 非常に簡単な方法で、ロック用オブジェクトを作成して、それをロックしてからアクセスし、終わったらロックを外す。
  • 「volatile」を付ける事で、最適化されても、オブジェクトの操作が無効にならないようにしている。
    void sci_putch(char ch)
    {
        static volatile bool lock_ = false;
        while(lock_) ;
        lock_ = true;
        sci_.putch(ch);
        lock_ = false;
    }

    void sci_puts(const char* str)
    {
        static volatile bool lock_ = false;
        while(lock_) ;
        lock_ = true;
        sci_.puts(str);
        lock_ = false;
    }

    char sci_getch(void)
    {
        static volatile bool lock_ = false;
        while(lock_) ;
        lock_ = true;
        auto ch = sci_.getch();
        lock_ = false;
        return ch;
    }

同期オブジェクトを使った通信

  • オーディオコーデック側は、「状態」を生成している。
  • GUI 側は、その状態を見て、操作を切り替えている。
  • GUI 側は、本来、内蔵ハードウェアー(DRW2D)エンジンを使う事が望まれるが、簡潔に済ます為、ソフトウェアーでフレームバッファに直接描画している。
  • また、GUI 側から、コーデック側を制御するオブジェクトを定義してある。
        struct th_sync_t {
            volatile uint8_t    put;
            volatile uint8_t    get;
            th_sync_t() : put(0), get(0) { }
            void send() { ++put; }
            bool sync() const { return put == get; }
            void recv() { get = put; }
        };

※単方向で良いので、簡易な方法を使っている、これなら、オブジェクトを同時にアクセスする事が無いので、競合が発生しない。

送る側:(FF ボタンが押された場合)

            ff_.at_select_func() = [this](uint32_t id) {
                play_ff_.send();
            };

受け取る側:(コーデックの制御を行う)

            if(!play_ff_.sync()) {
                play_ff_.recv();
                return sound::af_play::CTRL::NEXT;
            }

最後に

かなり、ツギハギ感があるが、とりあえず、何とかなっている。

実質的なソースコードは、main.cpp と audio_gui.hpp しか無いが、フレームワークが提供するクラスを色々使っている。

C++ テンプレートや、C++ クラスの場合、ある程度の汎用性をかなり簡単に実現できる為と思う。

複数のプラットホームで共有できるのも、テンプレートクラスに依る部分が大きいと思える。

多くは新規に実装した物もあるが、他のオープンソースを多く利用して実現している、良い時代だ~

RX72N Envision Kit での開発(その2)SCI 編

シリアルコミュニケーション

組み込みマイコンでは、最も手軽に、外部との通信を行えるインターフェースであると言えます。

このフレームワークでは、common/sci_io.hpp (device::sci_io テンプレートクラス)として、「良く使うだろう」場面を想定して実装されています。
C++ テンプレートを使って最初に作り始めたクラスでもあり、感銘深いものがあります。
現在は、その時実装した時から既に数年は経過していて、細かい部分を色々改善して現在に至っています。
なので、この実装は、組み込みマイコンと C++ の親和性が良く発揮されるものだと思います。

まず、「sci_io.hpp」をインクルードします。

#include "common/sci_io.hpp"

C++ では、ヘッダーに全ての実装を書く事が出来、ソースファイルの指定や、ライブラリをリンクする必要は無く、メインのソースにヘッダーをインクルードするだけです。
※当然ですが、使わなければ(実態になる物を定義しなければ)、余分なメモリも消費しません。

SCI を使う為に必要な定義は、以下のようなものです。

    typedef device::SCI2 SCI_CH;  // SCI チャネルの定義
    typedef utils::fixed_fifo<char, 512> RXB;  // RX (RECV) バッファの定義
    typedef utils::fixed_fifo<char, 256> TXB;  // TX (SEND) バッファの定義
    typedef device::sci_io<SCI_CH, RXB, TXB> SCI;  // sci_io の定義

    SCI     sci_;
  • 利用する SCI チャネルを指定します。
  • 受信、送信バッファのサイズを指定します。(16バイト以上が必要)
  • 何も指定しないと、「第一候補」のポートが選択されます。
  • sci_io クラスの定義を typedef して、実態を記述します。

この場合、SCI2 を使い、受信バッファに 512 バイト、送信バッファに 256 バイトを割り当てています。
「バッファ」は、FIFO(First In First Out)で、リングバッファになっていて、固定長です。
※組み込みマイコンの制御では非常に多く利用する頻度があり、組み込み用に実装した専用クラスですが、別にこのクラスを使わなくても、自分でカスタムしたクラスを使う事も出来ます。

RX マイコンでは、SCI2 で利用できるポートが複数あります。
何も指定しないと、port_map クラスで指定されている、第一候補が選択されます。
※これは、sci_io テンプレートのプロトタイプが以下のようになっている為です。

template <class SCI, class RBF, class SBF, port_map::option PSEL = port_map::option::FIRST, class HCTL = NULL_PORT>
class sci_io {

また、SCI のポートモード設定を自分で行いたい場合は、「port_map::option::BYPASS」を選択する事もできます。
この指定を行うと、ポートのモード設定を「バイパス」して何も行われません。

port_map クラス内では、以下のようになっていて、P13(TXD)、P12(RXD) が使われます。

           uint8_t sel = enable ? 0b001010 : 0;
           PORT1::PMR.B3 = 0;
           MPC::P13PFS.PSEL = sel;  // TXD2/SMISO2/SSCL2 (P13 LQFP176: 52)
           PORT1::PMR.B3 = enable;
           PORT1::PMR.B2 = 0;
           MPC::P12PFS.PSEL = sel;  // RXD2/SMOSI2/SSDA2 (P12 LQFP176: 53)
           PORT1::PMR.B2 = enable;

第二候補を選択する場合、SCI の typedef を以下のようにします。

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

この場合、P50(TXD)、P52(RXD) となります。(詳しくは port_map.hpp 参照の事)

定義が出来たら、SCI を使える状態にします。
※ SCI の省電力切り替え等も内部で自動的に行われます。

    {  // SCI の開始
        uint8_t intr = 2;        // 割り込みレベル
        uint32_t baud = 115200;  // ボーレート
        sci_.start(baud, intr);
    }

これで、115200 bps で受信も送信も出来る状態になります。
ボーレートは整数で指定します、もし、内部分周器の能力を超えた場合(設定出来ない場合)「false」を返して失敗します。
RX マイコン内蔵の SCI は、チャネルによってベースとなるクロックが異なる場合があります。
※PCLKA、PCLKB
SCI の各チャネルの定義内には、どのクロックを使うかなどの情報が内包されていて、ボーレートを計算する時にそのクロック値を使う為、どのチャネルでも、全く同じように使えます。
割り込みレベルは、「2」を使っていますが大きな理由はありません。
※割り込みレベルのポリシーは、システム全体で考える必要のある問題なので、ここでは詳しい方法は延べません。

sci_io クラス内部は、色々な計算、条件分岐などあり、内部レジスタに値を直接入れる事に執着する C プログラマーがいます。
しかし、sci_io はテンプレートなので、コンパイル時に行う最適化により、実行時に必要無い計算や分岐は極限まで排除されます。
※アセンブラコードを見ると良く判ります。
※最適化は、「人」がするものでは無く、「マシン」が行うべき問題です。(もちろん例外はあります)


通信フォーマットは、何も指定しないと、8ビット、1ストップビットになります。
変更したい場合は、以下のように指定します。(8ビット、Even、2ストップビットの場合)

    sci_.start(baud, intr, SCI::PROTOCOL::B8_E_2S);

※PROTOCOL は、sci_io.hpp 内で定義されているものが使えます。

後は、データを送信したり、受信したりするだけです。

    auto ch = sci_.get_ch();  // 1 文字を受信

    sci_.put_ch(ch);  // 1 文字送信

    auto len = sci_.recv_length();  // 受信バッファに格納されている文字数

このように、C++ テンプレートクラスを使ったフレームワークでは、複雑な設定を隠蔽して、アプリケーションを実装する事が出来ます。
別プログラムで、設定を生成する事も無く簡単に扱えると思います。

文字を出力する仕組み

通常、printf などを使った場合、データはどのように処理されているのでしょうか?
※C++ では iostream を使います。
POSIX では「標準出力」と呼ばれる物が設定されており、そこに出力するようになっています。
これは、ファイルディスクリプタで、ファイルに書く事と同じ扱いです。

通常、アプリケーションをコンパイルすると、「libc.a」と呼ばれるライブラリがリンクされます。
この中に、ファイルとのやりとりを行う関数が内包されており、OS の制御下に置かれています。
組み込みでは、通常、この仕組みは使わないので、自分で用意する必要があります。

common/syscalls.c

に、libc.a に代わる組み込み専用の仕組みを用意しています。(syscalls.c のコンパイルと、リンクが必要)

実際にする事は、SCI の出力と、繋ぐ実装をするだけです。

main.cpp 内に、以下のように実装します。

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

これで、printf で文字を出力すると、SCI に出力される事になります。

C++ では printf を使わない

printf は便利ですが、重大な欠点があります。
それは可変引数を使ってパラメータを受け渡す為、スタックを経由する点です。
この問題については、ここでは詳しく述べませんので、興味があればご自分で調べてみて下さい。

C++ では、それに代わって、文字を扱う方法として、iostram クラスを使う方法が推奨されています。
iostream クラスは、便利で強力なクラスなのですが、組み込みマイコンのような環境では別の問題が発生します。

それは、メモリ消費が大きい事。

    std::cout << "Hello!" << std::endl;

   text    data     bss     dec     hex filename
 508864   47644    8812  565320   8a048 hello.elf

---
    printf("Hello!\n");

   text    data     bss     dec     hex filename
  13864      48    1924   15836    3ddc hello.elf

上記は、RX マイコンで、iostream と、レガシーな printf を使った場合のメモリ消費の違いです。

これでは、流石に、「常用」するには、問題があります。

そこで、printf に近い使い方が出来て、メモリサイズが小さくなるクラスを実装してあります。
元々は、「boost/format.hpp」のアイデアを真似て作り始めた物ですが、現在は色々な機能を入れてあります。
サイズもそこそこ小さくなります、通常の使い方では、ほぼ printf と遜色なく使えると思います。

    utils::format("Hello!\n");

   text    data     bss     dec     hex filename
   6700      48    2136    8884    22b4 hello.elf

プロジェクトは、別にありますが、common/format.hpp にコピーしてあります。
詳しくは、format.hpp プロジェクトを参照して下さい。

詳しくは、上記プロジェクトを参照してもらえばと思います。
※このクラスは、他の環境(VC、mingw64、Linux)でもインクルードするだけで普通に使えます。

たとえば、以下のように使えます。

    int a = 1000;
    utils::format("%d\n") % a;

実装がヘッダーに集中する事による大きなメリット

C++ で最も改善されたと思う一例は、ヘッダーと実装を分ける必要性が無くなった事にあります。

しかし、現実には、C++ なのに、ヘッダーとソース(実装)に分けている人が多いようです。
※典型的なのは、Arduino のスケッチなど

ヘッダーに実装を書く事で、コンパイル時に全てのコードが評価される為、インクルードヘッダーが多くなると、コンパイル時間が多くかかると思っている人がいると思います。
ですが、実際には、その逆で、全体のコンパイル時間は、ソースの数が多い程その差は大きく、極端に短くなります。

人間の感覚や常識は当てにならないものです。

C++ のクラスをソースに分けて実装する場合、冗長な書き方も必要になり、非常に面倒です。

また、全てがヘッダーにあるので、管理が非常に簡単になり、何かの機能を持ちだす場合など、ヘッダーが一つあれば済むので、コピー忘れなどが減り、修正した場合なども二重に管理する必要もありません、そして、ソースをプロジェクトに追加する必要が無いので利便性が増します。

良い事ずくめなのですが、どんな実装方法でも可能な訳ではなく、「コツ」のような物は必要です。
自分のフレームワークでは、ほぼ、ヘッダーのみなので参考にしてもらえればと思います。

SCI_sample サンプルプログラムの使い方

SCI の使い方を大まかに扱ったサンプルを用意してあります。

SCI_sample

RX72N Envision Kit では、

W10./d/Git/RX % cd SCI_sample
W10./d/Git/RX/SCI_sample % ls
main.cpp  README.md  READMEja.md  RX24T  RX64M  RX65N  RX66T  RX71M  RX72N
W10./d/Git/RX/SCI_sample % cd RX72N
W10./d/Git/RX/SCI_sample/RX72N % make

...

W10./d/Git/RX/SCI_sample/RX72N % ls
Makefile  release  sci_sample.elf  sci_sample.lst  sci_sample.map  sci_sample.mot
  • RX72N Envision Kit の CN8 USB ポートと、PC をマイクロ USB ケーブルで接続する。
  • TeraTerm などのターミナルソフトを起動する。
  • ルネサスの COM ポート、115200 Baud、8 ビット、1 ストップビットに設定。
  • sci_sample.mot をターゲットに書き込みます。

※ PC に、ルネサス社の USB シリアルドライバーがインストールされている必要がありますが、Flash Programmer v3 をインストールする際にインストールされる。

# Start SCI (UART) sample for 'RX72N' 240[MHz]
Baud rate (set):  115200
Baud rate (real): 115355 (0.13 [%])
CMT rate (set):  100 [Hz]
CMT rate (real): 100 [Hz] (0.00 [%])
#

※設定ボーレートと実際のボーレート表示
RX マイコンの SCI は、ボーレートクロック生成の分周器が粗いので、高いボーレートでは、誤差がそれなりにあります。
MDDR ビットレート補正を使って、設定周期に近い値に調整してはいるけど、115200 では、上記値が限界となっています。
歩調同期式の場合、1バイトの転送で、最後のストップビット(10ビット分)までで、ズレが許容されれば、エラーは出ないので、上記の誤差は問題無いと思います。

  • 115200 = 8.7マイクロ秒
  • 8.7 x 10 x 0.0013 = 0.113 マイクロ秒
  • 許容範囲は、8.7 の 2/5 と考えると、3.5 マイクロ秒なので、十分な誤差範囲と言えると思う。

このサンプルでは、キーボードから入力された文字をエコーバックします。
また、「RETURN」キーを押すと、入力された文字列を表示します。

Github/RX にプルリクが来たので、それの対応など

最適化をしないコンパイル

現在(少し前)のフレームワークでは、最適化をしない「-O0」場合、大量にリンクで失敗する。

これは、「static」なオブジェクトの実態が無い為で、以前は最適化が標準となっているので、あまり問題にならなかった。
※static なオブジェクトと言っても、リソースを必要とするような実装は無く、最適化すると、実態を持たないものだった。

それでも、最適化しない場合、実態は必ず必要なので、リンクできずに実行バイナリーを作れず停止する。

たとえば、このような感じ・・・

    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    /*!
        @brief  CMT 定義基底クラス
        @param[in]  base    ベース・アドレス
        @param[in]  per     ペリフェラル
        @param[in]  VEC     ベクター型
        @param[in]  ivec    割り込みベクター
    */
    //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
    template <uint32_t base, peripheral per, typename VEC, VEC ivec>
    struct cmt_t {

...

        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
        /*!
            @brief  CMCR レジスタ
            @param[in]  ofs     レジスタ・オフセット
        */
        //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//
       template <uint32_t ofs>
        struct cmcr_t : public rw16_t<ofs> {
            typedef rw16_t<ofs> io_;
            using io_::operator =;
            using io_::operator ();
            using io_::operator |=;
            using io_::operator &=;

            bits_rw_t<io_, bitpos::B0, 2> CKS;
            bit_rw_t <io_, bitpos::B6>    CMIE;
        };
        static cmcr_t<base + 0x00> CMCR;

...

    };

    typedef cmt_t<0x00088002, peripheral::CMT0, ICU::VECTOR, ICU::VECTOR::CMI0> CMT0;

「CMCR」は、「cmcr_t」テンプレートクラスの「static」オブジェクトとなっている。

このオブジェクトは、「cmt_t」テンプレートクラスのメンバーで、実際には、「実態」となる物を定義する必要がある。

これが、テンプレートクラスでは無い場合、別にソースコードを作成して、その中で、staticなオブジェクトとして宣言しておく。

テンプレートクラスの場合、それはできないので、ヘッダーに特殊な書き方で定義する。

        typedef cmcr_t<base + 0x00> CMCR_;
        static CMCR_ CMCR;
    };

    template <uint32_t base, peripheral per, typename VEC, VEC ivec>
        typename cmt_t<base, per, VEC, ivec>::CMCR_ cmt_t<base, per, VEC, ivec>::CMCR;

上記のように、定義には、「CMCR」オブジェクトの型「cmtt<base, per, VEC, ivec>::CMCR」が必要なので、ペリフェラルの定義全部を大幅に書き換える必要がある。

何度か、修正しようと思った事もあったが、修正範囲が広く、自分には、それほど必要な機能でも無かったので、割愛してきた。

しかし、「-O0」にしないと、デバッガーで止めたりできないとの事で、この莫大な修正を行い、プルリクを送ってくれた方がいたので、このリクエストを受けて、マージする事にした。
※非常にありがたい修正で、修正したソースの分量もすさまじく、この修正を入れる事で、より良い物になるのは明白だ。

ただ、最近、RX72Nの為に色々と、改変している最中で、そのままマージしても、コンフリクトする事は明白なので、内容を精査しながら少しづつマージする事にした。

このプルリクを送ってくれた「Sinsjr2」さんには大変感謝しており、この場を借りて御礼申し上げます。

※自分以外でこのフレームワークを使っている人は、何人くらいいるのだろうか?
少なくとも一人はいる事が判ったw


普通のクラスをテンプレートクラスにするメリット

先ほどの説明で、static なオブジェクトの実態について、通常のクラスの場合は、別にソースを作成して記述する事を説明した。

しかしながら、そうすると、ヘッダーの他にソースコードが増えて、Makefile でソースを列挙する必要が出てくる。

これを回避する方法がある。

普通のクラスを無理やりテンプレートにする方法で、boost などでも使われている。

たとえば、以下のクラスをテンプレート化すると・・・

class flash_io
{

    flash_io() { }

};

template <class _> class flash_io_
{

    flash_io_() { }

};
typedef flash_io_<void> flash_io;

知ってはいたが、以前に実験した時、サイズが少しだけ大きくなるので、敬遠していた。
しかし、最近の RX マイコンは、サイズをあまり気にする程、ROM が小さくないので、存分に使うべきかもしれない・・


Makefile の機能追加

「make」は、「-e」オプションを使うと、外部から、内部で使っている環境変数をオーバーライド出来る。

  -e, --environment-overrides
                              環境変数が makefile 中の記述に優先する

たとえば、最適化は、

    OPTIMIZE = -O3

となっており、

W10./d/Git/RX/FIRST_sample/RX72N % make clean
W10./d/Git/RX/FIRST_sample/RX72N % export OPTIMIZE=-O0
W10./d/Git/RX/FIRST_sample/RX72N % make -e

とすれば、最適化無しで、コンパイルする事が出来る。

しかし、少し工夫して、「debug」と「release」と二つの状態を設ける事にした。
※「release」は元々固定となっている。

BUILD       =   release
#BUILD      =   debug

どちらかを有効にする事で、release(リリース版)と、debug(デバッグ版)を作成可能とした。
※事前に「clean」を行ってオブジェクトを全部廃棄する必要がある。
また、これは、変数をオーバーライド出来るので、「通常」を「release」としておく。

さらに、

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

BUILD 変数により、オプションをいくつか追加している。

これで、かなり自由にオブジェクトの生成を制御できるようになった。


全体ビルドシェルコマンドの強化

プロジェクトは、最近はかなり大きくなっており、サンプルの数も増えて、メンテナンスに苦労が多い。

そこで、ディレクトリーを巡回して、「Makefile」を見つけたら、「make」を起動するスクリプトを shell で作成してある。

W10./d/Git/RX % sh all_project_build.sh help
Usage: all_project_build.sh options [clean]
    -debug      debug build, 'OPTIMIZE=-O0'

このスクリプトに「-debug」オプションを追加してある。


RX600 ディレクトリの「最適化無し」対応状況

2020-04-24 07:14:47 Friday

ファイル 対応 ファイル 対応 ファイル 対応 ファイル 対応
bus.hpp O cmt.hpp O dmac.hpp O icu.hpp O
cac.hpp O can.hpp O cmpc.hpp O cmtw.hpp O
crc.hpp O crca.hpp O doc.hpp O drw2d.hpp O
dsmif.hpp O dtc.hpp O edmac.hpp O elc.hpp O
eptpc.hpp O etherc.hpp O exdmac.hpp O flash.hpp O
glcdc.hpp O gpt.hpp O iwdt.hpp O lvda.hpp O
mmcif.hpp O mpc.hpp O mpu.hpp O mtu3.hpp O
pdc.hpp O pmgi.hpp O poe3.hpp O port.hpp O
ppg.hpp O qspi.hpp O r12da.hpp O riic.hpp O
rspi.hpp O rtc.hpp O s12adc.hpp O sci.hpp O
scif.hpp O sdhi.hpp O sdram.hpp - sdsi.hpp #
src.hpp O ssi.hpp O ssie.hpp O system.hpp O
tmr.hpp O tpu.hpp O usb.hpp O usba.hpp O
wdta.hpp O

※ Ox:中途(現状、必要な部分だけ)
※ X 今後アップデート予定
※ # 実装中

最適化無しでコンパイル、リンク出来るプロジェクト

FIRST_sample
SCI_sample
SDCARD_sample
AUDIO_sample
FLASH_sample
RAYTRACER_sample
SIDE_sample
NESEMU_sample
FreeRTOS

主要なプロジェクトは大体大丈夫と思う。


自動変換スクリプト

複雑な構造の場合は、一筋ではいかないものの、簡単なスクリプトで変換は「できる」・・

s/static \(.*\) \([0-9A-Z]*\);/typedef \1 \2_;static \2_ \2;/
s/;static/;\
                static /

これは、sed による変換スクリプトで、static 宣言された行で、オブジェクト名を分けて、typedef、static を部分コピーする。
次に、行を分けて、二行にする。
※ただ、全てに対して上手くいく訳ではないのだが・・・

次にテンプレートクラスの実態を生成する。

/static.*;/!d
s/static/template <uint32_t base, peripheral per> typename glcdc_t<base, per>::/
s/_ /_ glcdc_t<base, per>::/

※これは、glcdc.hpp クラス用だ、テンプレートの型が違う場合に、微妙に作り替えないとならない・・・

それにしても sed は便利で強力なツールだ・・

RX72N Envision Kit の D2 オーディオを使う

RX72N Envision Kit で搭載された D2 オーディオ

RX72N Envision Kit には、デジタルオーディオ再生のデバイスが搭載されている。

現ルネサス製(インターシル)の D2-41051 で、I2S などのデジタル入力を内蔵 DSP などで処理して、最終的に PWM 出力する。

最初、このデバイス用のドライバーを書く必要があると思い、マニュアルをダウンロードして、初期化を実装する準備をしていた。
しかし、RX72N Envision Kit ユーザーズマニュアルがアップロードされ、中を読むと、

5.15.5 DAE-4 設定について
本ボードは既に I2S 経由で入力された PCM 音源を出力できる様に設定されています。

と書かれており、直ぐに使える事が判った。
※IPL 用 EEPROM が載っており、リセット時、初期化を行うようだ。

SSIE のドライバーを実装

RX72N には、I2S 通信用に SSIE インターフェースが載っている。
又、オーディオクロックとして、24.576MHz の発信器が載っている。
※この周波数は、48KHz のサンプリングに適した周波数となっている。
※ 24.576 MHz / (32 + 32) / 8 ---> 48KHz

また、内蔵クロックジェネレータが、PLL 方式で、周波数をプログラム出来るので、44.1KHz 用も出ると思ったが、48KHz のみのようだ。
なので、44.1KHz などの CD 音源の再生では、アップサンプリングして変換する必要がある。
厳密に行うのは、色々と面倒そうなので、簡易な方法で凌いでいる、そのうちより厳密な方法を行いたい。
※RX 内臓 DSP 命令を使う時が来たかもしれないw

最初「PCM」とあったので、16 ビットで出力していたが、アナログ出力は変化が無かった、色々調べて、24 ビットにしたら、出力される事が判った。
※現在の実装では 32 ビットにしている。(MSB ファーストなので、問題無いハズ)

最初、テストで「ノコギリ波」を出力していたが、出力波形が、思ったように出ない・・・

良く考えたら D2 デバイス内部で、FIR フィルタなどを通っているので、スパイク上の波形変化では、高い周波数成分が乗り、そのようになるだろう事が判った。
サイン波で実験したら、思ったようなアナログ出力が得られた。

オーディオ出力

RX72N Envision Kit には、PWM からアナログに変換するフィルタ回路がオペアンプで実装されている。
マニュアルには、

5.15.1 接続できるスピーカについて 接続できるスピーカについて
音声出力ジャックにはアンプ付きのスピーカを接続出来ます。アンプがない場合は 8Ωのスピーカも接続でき
ますが、イヤホン等インピーダンスの高い物は接続できません。

と書かれているが、意味が良く判らない、オペアンプは、かなり電流を流せるもののようだが、8オームをドライブできて、16や32オームをドライブ出来ないとは???
また、オペアンプには出力に100オームの制限抵抗があるので、なおさらだ・・・
実験的に、10オームくらいの抵抗を繋いでみたが、思った通り振幅が非常に小さくなる。
※少し怖いので、イヤホンは繋いでいない。

RCA 入力のアンプを接続して、普通に音が鳴っている。

※「D2-41051」には、複数の PWM チャネルがあるので、ランドに出しておいて欲しかった・・・

RX65N のコードを移植

RX65N Envision Kit で D/A 出力していた部分を、SSIE にするコードを追加して、音を聴いてみた。
厳密なヒアリングを行っていないが、普通に聞こえる。(12ビットD/Aより良いか、悪いか、何とも言えない)

コード中では、

#define USE_DAC

と

#define USE_SSIE

で切り替えるようにしている。

DAC 用には、

    volatile uint32_t   wpos_;

    /// DMAC 終了割り込み
    class dmac_term_task {
    public:
        void operator() () {
            device::DMAC0::DMCNT.DTE = 1;  // DMA を再スタート
            wpos_ = 0;
        }
    };

    typedef device::dmac_mgr<device::DMAC0, dmac_term_task> DMAC_MGR;
    DMAC_MGR    dmac_mgr_;

    uint32_t get_wave_pos_() { return (dmac_mgr_.get_count() & 0x3ff) ^ 0x3ff; }

    typedef device::R12DA DAC;
    typedef device::dac_out<DAC> DAC_OUT;
    DAC_OUT     dac_out_;

    typedef utils::sound_out<1024, 512> SOUND_OUT;
    SOUND_OUT   sound_out_;

    class tpu_task {
    public:
        void operator() () {
            uint32_t tmp = wpos_;
            ++wpos_;
            if((tmp ^ wpos_) & 64) {
                sound_out_.service(64);
            }
        }
    };

    typedef device::tpu_io<device::TPU0, tpu_task> TPU0;
    TPU0        tpu0_;

    void start_audio_()
    {
        {  // 内臓12ビット D/A の設定
            bool amp_ena = true;
            dac_out_.start(DAC_OUT::output::CH0_CH1, amp_ena);
            dac_out_.out0(0x8000);
            dac_out_.out1(0x8000);
        }

        {  // サウンドストリーム DMAC マネージャー開始
            uint8_t intr_level = 4;
            bool cpu_intr = true;
            auto ret = dmac_mgr_.start(tpu0_.get_intr_vec(), DMAC_MGR::trans_type::SP_DN_32,
                reinterpret_cast<uint32_t>(sound_out_.get_wave()), DAC::DADR0.address(),
                sound_out_.size(), intr_level, cpu_intr);
            if(!ret) {
                utils::format("DMAC Not start...\n");
            }
        }

        // 波形メモリーの無音状態初期化
        sound_out_.mute();
    }

SSIE 用には

    SSIE_IO     ssie_io_;
    SSIE_IO::SOUND_OUT& sound_out_ = ssie_io_.at_sound_out();

    void start_audio_()
    {
        {  // SSIE 設定 RX72N Envision kit では、I2S, 48KHz, 32/24 ビットフォーマット
            uint8_t intr = 5;
            uint8_t adiv = 24'576'000 / 48'000 / (32 + 32);
            auto ret = ssie_io_.start(adiv,
                utils::ssie_t::FORM::I2S,
                utils::ssie_t::D_NUM::_32, utils::ssie_t::S_NUM::_32, intr);
///             utils::ssie_core::D_NUM::_24, utils::ssie_core::S_NUM::_32, intr);
            if(ret) {
                ssie_io_.enable_mute(false);
                ssie_io_.enable_send();  // 送信開始
                uint32_t bclk = 24'576'000 / static_cast<uint32_t>(adiv);
                utils::format("SSIE Start: BCLK: %u Hz\n") % bclk;
            } else {
                utils::format("SSIE No start...\n");
            }
        }
    }

となっている。

発音は、sound_out オブジェクト(クラス)に対して行うので、シンプルとなる。
ssie_io クラス内で、sound_out クラスを持っていて、操作は、それを経由している、なので、外部に、sound_out オブジェクトの参照を出してある。
このような場合 C++ は本当に便利だ。

NESEMU_sample(ファミコン・エミュレータ)

NES エミュレータ(動画)

RX65N、RX72N で共有のソースとした。

NESEMU_sample

各ディレクトリに移動して「make」すればビルド出来る。
※まだ、RX72N Envision Kit の入手性が悪いが、ビルドしたバイナリーを置いてある。

操作には、ファミコン互換パッドが必要だ。
※近々に USB ジョイパッドに対応する予定でいる。

他に、SIDE_sample も動作を確認した(スペースインベーダーエミュレータ)

github のソースをクローンしている場合

RX72N の追加で、ソースコードは、非常に短いスパンで更新されている。
なので、ソースコードを利用している人は、アーカイブをダウンロードせず、git の操作で更新をした方が有益だと思う。

RX72N Envision Kit での開発(その1)

Arduino を使わないという選択

世の中、ほぼ Arduino 一択という状況になってしまったと言っても過言では無いです。
自分が考えるに、良い面と、そうではない面があると思います。

「良い面」は、非常に簡単に言うと、「敷居を極限まで下げた」と言えるのかもしれません。
アプリケーションを作るのに必要な知識だけでマイコンを動かせます。


Arduino は易しいですが、マイコンを独自で動かす事も実はそんなに難しくありません。

実際、「独自」にマイコンを動かして、「main」関数まで来るには、色々と、行わなければならない事や知識が必要です。
Arduino では、これらの知識は「不要」と分類されており、アプリケーションを作るユーザーが考える必要はありません。
※これは、マイコンが違っても、Arduino 環境下では、同じように使う事が出来ます。

Arduino は一応 C++ ですが、元が、AVR と言う 8/16 ビットマイコンからスタートした為、最新の C++ コンパイラを利用できません。
世の中にある、スケッチは、この制限により、C++ とは言えない物が多く、互換性を考えて、今でも、古いスタイルでプログラミングをしています。

自分も昔は、C 言語が主流で、C++ はオマケ程度でしたが、PC でアプリケーションを作るプロジェクトで仕事をした時、C++ を勉強しなおしました。
そこから、数十年、今では、C++ 以外でプログラムを作る事が苦痛になっています。
※ C++ は非常に難しい部分があるので、独学では限界があります、良い師と、時間が必要ですが、最近は、「勉強会」も頻繁に行われており、「学ぶ」には、かなりハードルが下がりました。
また、最高のコンパイラもフリーで利用出来ます。


組み込みマイコンでも、C++ を積極的に使いたいので、国産で、高性能なマイコンを探しました。
ARMが嫌いな訳では無く、単純に、日本人なのに、「わざわざ外国のマイコンを使うのはおかしいだろう」という思いがありました。
昔から、日立は好きで、H8やSHを良く使っていました、しばらくしてルネサスに統合されました。
最近は、RXマイコンを推しているようです、RXマイコンは多分三菱由来のマイコンと思いますが、非常に優れた、マイコンである事が直ぐに判りました。
開発環境も、gcc をサポートしており、十分実用になる事が判りました。

そこから C++ を積極的に利用した組み込みマイコン用フレームワークを整備して、現在に至っています。
※このフレームワークは、いくつかのプロジェクトで利用しています、その関係もあり、ライセンスを MIT にしています。

開発環境の整備

ルネサス社の E2 Studio は、無料版に制限(128Kバイトまでしかバイナリーを作れない)があり、CC-RX コンパイラでは、C++11、C++14, C++17 などの C++ ソースをコンパイルする事が出来ない為、独自にビルドした gcc-6.4.0 を使います。
※ルネサス社は、独自に、gcc-4.8 ベースの開発環境も用意していますが、4.8 系では、C++14, C++17 をコンパイルする事が出来ない。

C++11, C++14, C++17 はそれぞれ、2011 年、2014 年、2017 年に C++ 標準化委員会が策定した仕様を網羅したバージョンです。
年度が更新する(3年毎)度に、より良い機能が使えるようになっており、わざわざ古い仕様の C++ を使う理由は無いと思います。
※ C++17 は 2017 年の仕様です、今は 2020 年なので、ある程度「枯れて」いると言えると思います。

RX72N Envision kit の内蔵 E2-Lite を使って、マイコン内蔵フラッシュプログラムを書き換えするには、Windows 環境が必須となります。

コマンドラインによる開発環境を著しく敬遠する人がいますが、「慣れ」の問題であり、GUI 環境を覚えるよりハードルは低いと思われます。

MSYS2 を利用しています。

gcc のビルドに関しては、hirakuni45 github RX、又は gcc、g++を使ったルネサスRXマイコン開発を参照の事。

Renesas Flash Programmer v3 をインストールしてください。

ソースコードの編集には、VSCode が便利です、馴染みのテキストエディターが無いのなら(あっても)インストールお勧め。
※設定や、使い方は、ぐぐって~

ソースコードの取得

関係フレームワークなど一式を、github からクローンします。
※「D:/Git/RX」にクローンしています。

W10.~ % cd /d/Git
W10./d/Git % git clone git://github.com/hirakuni45/RX.git

他に「boost」が必要です。

W10./d/Git % pacman -S mingw-w64-x86_64-boost

FIRST_sample

FIRST_sample ディレクトリ、RX72N に移動します。

W10./d/Git ~ % cd RX/FIRST_sample
W10./d/Git/RX/FIRST_sample % cd RX72N

ソースコードをビルドします。

W10./d/Git/RX/FIRST_sample/RX72N % make

W10./d/Git/RX/FIRST_sample/RX72N % ls
led_sample.elf  led_sample.lst  led_sample.map  led_sample.mot  Makefile  release

ビルドされた「led_sample.mot」ファイルを、Renesas Flash Programmer で、RX72N Envision kit に書き込みます。

RX72N Envision kit USR LED が 0.25 秒間隔で点滅する。

Flash Programmer v3 の設定

  • PC と RX72N Envision kit の「ECN1」をマイクロUSBで接続します。
  • 電流不足になる場合、外部にACアダプタを接続する必要があります。
  • SW1 の 2 番をOFFにします。
  • 新規プロジェクトを作成し、RX72x を選択します。

  • E2 emulator Lite を選択
  • FINE を選択
  • 1,500,000 bps を選択

  • 供給しない を選択

  • リセット端子をHi-Z を選択

  • 接続出来たら、先ほどビルドしたファイルを選択して、書き込みます。

ソースコードを眺める

ソースコードは、複数のRXマイコン用に実装されており、「SIG_RX72N」が機種依存部分となっています。

#include "common/renesas.hpp"

namespace {

/// ベースクリスタルの定義
/// LED 接続ポートの定義
#if defined(SIG_RX71M)
    typedef device::system_io<12'000'000> SYSTEM_IO;
    typedef device::PORT<device::PORT0, device::bitpos::B7> LED;
#elif defined(SIG_RX72M)
    typedef device::system_io<12'000'000> SYSTEM_IO;
    typedef device::PORT<device::PORT0, device::bitpos::B7> LED;
#elif defined(SIG_RX72N)
    typedef device::system_io<16'000'000> SYSTEM_IO;
    typedef device::PORT<device::PORT4, device::bitpos::B0> LED;
#elif defined(SIG_RX64M)
    typedef device::system_io<12'000'000> SYSTEM_IO;
    typedef device::PORT<device::PORT0, device::bitpos::B7> LED;
#elif defined(SIG_RX65N)
    typedef device::system_io<12'000'000> SYSTEM_IO;
    typedef device::PORT<device::PORT7, device::bitpos::B0> LED;
#elif defined(SIG_RX63T)
    typedef device::system_io<12'000'000> SYSTEM_IO;
    typedef device::PORT<device::PORTB, device::bitpos::B7> LED;
#elif defined(SIG_RX24T)
    typedef device::system_io<10'000'000, 80'000'000> SYSTEM_IO;
    typedef device::PORT<device::PORT0, device::bitpos::B0> LED;
#elif defined(SIG_RX66T)
    typedef device::system_io<10'000'000, 160'000'000> SYSTEM_IO;
    typedef device::PORT<device::PORT0, device::bitpos::B0> LED;
#elif defined(SIG_RX72T)
    typedef device::system_io<8'000'000, 192'000'000> SYSTEM_IO;
    typedef device::PORT<device::PORT0, device::bitpos::B1> LED;
#endif
}

int main(int argc, char** argv);

int main(int argc, char** argv)
{
    SYSTEM_IO::setup_system_clock();

    LED::OUTPUT();  // LED ポートを出力に設定

    while(1) {
        utils::delay::milli_second(250);
        LED::P = 0;
        utils::delay::milli_second(250);
        LED::P = 1;
    }
}

RX マイコンは、内部にクロックジェネレータがあり、起動した場合には、内部の発信器で、最低限の状態で起動します。
そこで、RX72N の動作を最高性能に切り替える為、フレームワークの助けを借ります。

    SYSTEM_IO::setup_system_clock();

この API を呼ぶ事で、RX72N は最大の 240MHz で動作します。

「SYSTEM_IO」は、以下のように定義されており、外部 16MHz のクリスタルを接続している設定です。

    typedef device::system_io<16'000'000> SYSTEM_IO;

240MHz の指定は、Makefile でされており、ソースをコンパイルする時に、定数を指定しています。
※「F_ICLK」の値

USER_DEFS   =   SIG_RX72N \
                F_ICLK=240000000 \
                F_PCLKA=120000000 F_PCLKB=60000000 F_PCLKC=60000000 F_PCLKD=60000000 \
                F_FCLK=60000000 F_BCLK=120000000

※これらの定数を使って、内部で、クロックジェネレータの設定を自動で行います。
※詳しくは、system_io.hppを参照の事。

LED の定義では、PORT テンプレートクラスにより、1ビットのポートとしています。
※この場合、PORT4 の B0 で、ボード上のユーザーLEDに接続されています。

    typedef device::PORT<device::PORT4, device::bitpos::B0> LED;

LED ポートを出力に指定します。

    LED::OUTPUT();  // LED ポートを出力に設定

LED ポートに「0」、「1」を送る。

        LED::P = 0;
...
        LED::P = 1;

0.25 秒の間隔を作るのはソフトウェアータイマーによるものです。(あまり正確ではありません)
※250ミリ秒

    utils::delay::milli_second(250);

このように、非常に簡単にオリジナルプログラムを走らせる事が出来ます。


次回、SCI を利用したプログラムを解説する予定です。

RX72N Envision Kit が到着

RX72N Envision Kit

RX65N Envision Kit は、かなり良く出来ていたけど、不満な部分もあった。

RX72N Envision Kit は、その「不満」の大部分が解消されて、かなり完成度が高い仕上がりとなっている。
※現状では、設計データや、インストールプログラムについての情報などがアップロードされていないのが残念ではあるが、時間が解決するものと思う。
※チップ・ワン・ストップで、4620円だった。
ソースコードは github に公開された。
※プロジェクトは、ルネサスの統合環境、CC-RX

内容を考えると、かなりコストパフォーマンスが高い製品となっている。

開発環境の問題等もあるが、M5Stack に比べて人気が低いのは残念と思う。
何か自作アプリを作るのなら、より良い物が創れると思う。
※独自にコンパイルした、gcc-6.4.0 ベースの環境で、問題なく開発が可能。

  • WiFi/Bluetooth モジュール(ESP32)
  • マイクロ SD ソケット
  • Ethernet (10/100)機能、RJ45 コネクタを含む
  • DSP 内臓のオーディオインターフェース、クロックジェネレータ
  • USB Serial インターフェース(PC とのシリアル通信機能)
  • 480x272 の静電容量タッチ付き液晶
  • 多目的に使える 32M の EEPROM
  • ステレオマイク
  • USB ホスト機能(クライアント機能も可能と思える)
  • E2 Lite 相当のエミュレーター機能(Fine)

※ オーディオインターフェース、D2-41051(インターシル/ルネサス)
※クロックジェネレータ 5X35023(IDT/ルネサス)

RX マイコンのフラッグシップモデル RX72N(R5F572NNHDFB) の特徴
※ 4MB プログラムフラッシュ、暗号機能アリ、144ピンLFQFP

  • コアは最大 240MHz 動作
  • 高機能な液晶コントローラと、グラフィックスの描画をブーストする DRW2D エンジン
  • 512K + 512K バイトの内蔵 RAM と、4MB の内蔵フラッシュメモリ
  • 豊富なチャネルを持ち、高速で高精度な A/D 変換ユニット(最大 2MBPS/12bits)
  • 倍精度浮動小数点命令を実行可能な RXv3 コア
  • 三角関数演算ユニット
  • 72 ビットのアキュムレータを持つ積和演算器、DSP 命令
  • I2S インターフェース内蔵(デジタルオーディオ入出力)

細かい事だけど、非常に便利なテストポイント

ルネサス(インターシル)D2-41051

Renesas/D2-41051 Intelligent Digital Amplifier PWM Controller and Audio Processor

新規にオーディオ用として投入されたこのデバイスは、このボードにピッタシの選択なのかもしれない。

かなり多くの品種を展開する「インターシル」は「ルネサス」の傘下になり、今回のプロジェクトにピッタリなICを持っていた。

このプロセッサは、非常にユニークで、I2Sからのデジタル信号をフルデジタルで出力してスピーカーを直接駆動する事が出来る。
※最終はPWM出力なので効率が良く、構造上、一般的なオーディオ用D/A、フィルター、プリアンプが必要無い。
※ボリューム調整もフルデジタルで行える。
※単価を調べると、1個購入時でも500円程度なので、他の用途にも使いたいと思える。(マウサー調べ)

ただ、I2Cで通信して、レジスターの意味を理解して、適切なパラメータをセットする手順は複雑そうだー
※サンプルがあるかもしれないけど、まだあまり調べていない・・
※TI にも同じようなデバイスがあるが、TI よりこちらの方が機能的に優れておりコストも安い。

Wifi/Bluetooth

今回はイーサーネットが「Ready」状態なのだが、無線も最初から用意されている。

ESP32 が乗っている、速度はあまり期待出来ないが、WiFi や Bluetooth が使えると、アプリの自由度が格段に上がる。
※ソフトの構成が複雑になるので、アプリに組み込んで利用するには、相応のスキルは必要だが、FreeRTOS 下で 240MHz 動作となると、まぁ何とでもなると思える。

USB Serial

ルネサス社の仮想シリアルが載っており、PC と接続して、ターミナルソフトなどでデバッグ情報などやりとりが行える。
もちろん特定のアプリで、PC と通信する事も出来る、シリアル通信はシンプルで簡単なので、小回りが利いて便利だ。
※ルネサス社のシリアルドライバをインストールする必要があるが、FlashProgrammer V3 をインストールする時に入るようだ。

それとは別に E2 Emulator Lite の USB 接続も出来る、こちらは、ルネサス純正の統合環境なら、より洗練されたデバッグ環境を使えると思われる。

アプリ

電源を入れ、初期インストールアプリで一通り遊んでから、早速、自前のプログラムを動かしてみた。

~~まだ、回路図などが公開されていないので(ソースを調べて、どのポートがアサインされているのか調べれば良いのだが・・)、とりあえず
LED 点滅だけやってみた。~~

※ユーザー LED はパターンを見ると「P40」に接続しているようだ。

また、メインクロックは 16MHz となっている。
※これは、RX72x で新たに追加された PLL 回路を使い、Ethernet PHY 向け 25MHz を生成するのに適した周波数となる。
※ 16MHz を 1/2 にして PLL で 25 倍すると 200MHz が得られる、これを 1/8 すると 25MHz が得られる。
※自前のフレームワークでは、テンプレートで、メインクロック周波数を指定すれば、内部のクロック設定は自動で行われる。


#elif defined(SIG_RX72N)
    typedef device::system_io<16'000'000> SYSTEM_IO;
    typedef device::PORT<device::PORT4, device::bitpos::B0> LED;
#elif defined(SIG_RX64M)

恒例のレイトレースによるベンチマーク

※とりあえず、RXマイコン最速をマーク!(RX71Mも240MHzだけど、ポートバスでの描画の評価しかしていない)
※倍精度浮動小数点演算命令をサポートしていない状態(本格的にコンパイラをテコ入れする必要がある)

とりあえず、今日はここまで。

4月7日追記

SDHIの動作検証も行い、問題無くSDカードがアクセスできる事を確認している。

現在動作可能なサンプル

  • First_sample/RX72N
  • SCI_sample/RX72N
  • RAYTRACER_sample/RX72N
  • SDCARD_sample/RX72N

Renesas RX72N Envision Kitのページに、各種ドキュメントがアップされました。
それにより判明した事:

  • デジタルオーディオは、初期設定で、48KHz の I2S を受け付けるように IPL ROM が載っている。
  • 3.5mm のオーディオジャックは、インピーダンスの関係か、イヤホンのようなインピーダンスが高い物は「不可」となっているようだ。
  • 「8オームのスピーカーは接続出来る」とある。

※少し残念なのは、44.1KHz 等に対応しておらず、44.1KHz の音源は 48KHz に変換して出さないといけない点・・・

RX72N Envision Kit がリリース


ついに、ルネサス社がやってくれた

RX72N Envision Kit

RX72N となっていて、まだ現状では RX72N の情報は無いが、ボード写真のデバイスには RX72N と読めるので、新たに追加されたデバイスのように思う。

Renesas RX72N

これでやっと、「STM32H7」を載せたボードに対抗出来る製品が出た事になる。
とゆーか、RX72N Envision Kit の方が、かなり強力なように思う。

値段も5000円以下!

※Chip One Stop で4620円
RX72N Envision kit (Chip One Stop)


  • 240MHz で動くRXv3 コア(倍精度浮動小数点をサポート)
  • 1024K の内部メモリ(512K+512K)

豊富で強力な外部インターフェース

まだ詳細な情報が少ないが、ボード写真を見ると、かなり強力なボードのようだ。

  • Ethernet (10/100)
  • Wifi/BLE モジュール(ESP32?)
  • SD Card interface
  • USB Host
  • Audio (DSP, D/A, Clock generator インターシル製)
  • LCD (GLCDC/DRW2D)
  • LCD Touch controller
  • On Board Emulator
  • USB Serial on board

RX72M 関係のリソースも既にかなり準備しているので、直ぐにソフト開発も進められると思う。
※RX72N 関係のリソースを準備中

JDS6600 60MHz 版を購入

周波数ジェネレータを購入

中華製の周波数ジェネレータ「JDS6600」(60MHz版)を購入した。

値段が微妙に異なるが、一番高い周期を発生出来るバージョンにした。

ただ、実際に波形を見ると、その実力は無いものと思える。

内部アンプや、フィルターの問題か、周波数が高くなると振幅が減衰するので、実用性はせいぜい2MHz程度しか無い。
それでも8600円程なので、まぁ、値段相応なのかもしれない。

付属品

今の騒動もあるものの、注文してから10日くらいで到着。

早速開封したのだが、ACアダプターの電源ソケットが・・・

ソケットの変換を持っていなかったが、センター+の5Vなら何でも使えるようだ。
※自宅にあった5V2Aのアダプターを使って普通に動作した。

他に、ワニ口付きのBNCケーブル2本、USBケーブル、ソフトのメディア、説明書などが入っている。

GUIは、英語と中国語が選択できる。

機能豊富

機能は豊富で、周波数、振幅、オフセット、フェーズなどを変更できる。

また、CH1、CH2と独立に設定が可能だ。(内部基準発信機が同一のようだ)

実用性

「実際の実用性」を聞かれると、少し疑問ではあるが、何も無いよりは随分マシと思う。


同じ日に「RX72T」も届いた。
こちらも、速いとこ実験したいとこだ・・・

RX72T 関係リソースの整備

最新のRXデバイスの入手

いつもお世話になっているチップワンストップでは、現状、RX66T、RX72T、RX72M のデバイス登録は無い。

百個単位なら、営業に相談すれば入手出来そうだが、人気で、色々な企業が使うデバイス以外は入手が難しいし、数十個でも個人では無理。

RX66TはRSコンポーネンツで1個単位で入手出来た。

そもそも、新規デバイスが1個単位で入手出来るのは、どこかの企業が、そのデバイスを使う機器を開発する為オーダーしたものと思う。
それの余剰品が、個人に回ってくると考えると納得できる。

RX72Mは、とりあえず最新なので、入手して動かしてみたいが、現在でも入手は難しい状態のようで、生産はしているものの、入手は難しい状況となっている。
先日、マウサーのページにRX72Tの扱いがある事に気が付き注文した(1500円くらいなので割高)。
RX72Mもリストには無いが部品の登録はあったので注文したが、現在バックオーダーで8月入荷とある。
※一応2個注文したが・・(@2500と高い)

RX72Tフラッシュ書き込みプログラム

最近のRXは、デバイスが異なっても、フラッシュの書き込みプロトコルを変更する事が少なくなっており
新しいデバイスに対応するのは簡単になっている。

RX72Tのフラッシュ関係プロトコルを斜め読みした感じでは、RX66Tと同じで書き込み出来るようだ。

とりあえず、「rxprog」に、RX72Tプロトコルを追加して、デバイスのコンフィグを追加しておいた。
※現状ではデバイスを入手していないので、書き込めるかは不明

/Git/RX/rxprog % ./rx_prog --device-list
R5F563T6 (RAM: 8K, Program-Flash: 64K, Data-Flash: 8K)
R5F524T8 (RAM: 16K, Program-Flash: 128K, Data-Flash: 8K)
R5F524TA (RAM: 16K, Program-Flash: 256K, Data-Flash: 8K)
R5F564MF (RAM: 512K, Program-Flash: 2048K, Data-Flash: 64K)
R5F5671F (RAM: 512K, Program-Flash: 2048K, Data-Flash: 64K)
R5F564MG (RAM: 512K, Program-Flash: 2560K, Data-Flash: 64K)
R5F571MG (RAM: 512K, Program-Flash: 2560K, Data-Flash: 64K)
R5F564MJ (RAM: 512K, Program-Flash: 3072K, Data-Flash: 64K)
R5F571MJ (RAM: 512K, Program-Flash: 3072K, Data-Flash: 64K)
R5F564ML (RAM: 512K, Program-Flash: 4096K, Data-Flash: 64K)
R5F571ML (RAM: 512K, Program-Flash: 4096K, Data-Flash: 64K)
R5F565NE (RAM: 640K, Program-Flash: 2048K, Data-Flash: 32K)
R5F566TA (RAM: 64K, Program-Flash: 256K, Data-Flash: 32K)
R5F566TE (RAM: 64K, Program-Flash: 512K, Data-Flash: 32K)
R5F566TF (RAM: 128K, Program-Flash: 512K, Data-Flash: 32K)
R5F566TK (RAM: 128K, Program-Flash: 1024K, Data-Flash: 32K)
R5F572MD (RAM: 1024K, Program-Flash: 2048K, Data-Flash: 32K)
R5F572MN (RAM: 1024K, Program-Flash: 4096K, Data-Flash: 32K)
R5F572TF (RAM: 128K, Program-Flash: 512K, Data-Flash: 32K)
R5F572TK (RAM: 128K, Program-Flash: 1024K, Data-Flash: 32K)

RX72T関係デバイスクラス

RX72TはRX66Tの高速版みたいな扱いなので、デバイスクラスを作るのは、RX66Tのリソースを使えるので工数が少ない。
そこで、RX72T関係の整備を始めた。

今回入手できるRX72TはUSB内蔵タイプなので、192MHzで動かす事になる。
ただ、USBを使わない場合200MHzで動かしたいだろうから、外部オシレーターのクロックは8MHzにした。
8MHzなら、PLLの倍率調整で、192MHzも200MHzも調整可能だと思う。

他はRX66Tとほとんど同じ構成で、ファイルをコピーして何も変更していない。

icu.hpp
icu_mgr.hpp
port_map.hpp
peripheral.hpp
power_mgr.hpp
R5F572TK.ld
R5F572TF.ld
README.md

LED 点滅プログラムのコンパイル

デバイスクラスを整備したら、とりあえずLED点滅。

コンパイルとリンクを出来るようにした。
※ソフトディレイループのパラメータは、デバイスを入手してから調整する。

また、標準で使うLEDポートをP01とした。
※P00はUSBブート時の切り替えポートとなっている。

終わりに

※Github にはそのうちマージする。

早く、デバイス来ないかな~
※テスト基板をどうするか・・・


先ほど知ったのだが、C++ の constexpr などを利用した強力なライブラリーをリリースしていた「ボレロ村上」氏が他界したようだ。
ボレロ村上逝去
以前に、C++勉強会で合った事があり、他人事とは思えない、まだ32歳だったようだ、残念だ・・・

RXマイコン、選択型割り込みを使う

最近、ようやくUSB関係を始めた。

フレームワークは、ルネサス純正の物を試用して実験している。
USBのフレームワークでは、イベント関係で割り込みを使っている。

RX65Nでは、選択型割り込みを使っている。

自分のフレームワークと、ルネサス社のフレームワークを合わせるのは、工夫が必要で、そのままではリンクが難しい。

少し調べると、割り込み関係は、「basic/src/hw/r_usb_rx_mcu.c」で設定されており、これを改修すれば良さそうだ。

それで、色々かち合う部分に手を入れ、USBマスストレージ関係をコンパイル、リンクする事が出来るようになった。
早速動作検証するのだが、動かない・・・
どうやら、割り込みがかからないようだった・・・

よくよく調べると、選択型割り込みクラスにバグがあり、それが原因だった。
※以前に実装していたが、選択型割り込みを扱うデバイスが無かったので、動作検証されていなかった・・

RXマイコンでは割り込み要因は256個まで設定できるが、非常に沢山あるペリフェラルで発生する割り込みをアサインするのは無理があり、物理的に足りない。

そこで、RXマイコンには、割り込みをシェアしたり(グループ割り込み)、使いたい割り込みをアサインしてプログラマブルに使う(選択型割り込み)などの仕組みが用意されている。

ただ、同じようなペリフェラルでも、デバイスによって異なるグループだったり、要因が異なったり、複雑で、ドライバーを作る場合に個々に対応しなければならない。

自分の C++ フレームワークでは、この問題を隠蔽して、簡単に扱えるように、色々な仕組みを実装してある。
※この仕組みこそが C++ を使う最大のメリットの一つと思う。
※特殊なツールを使ってコード生成する必要が無い。

例えば、CMT(コンペアマッチタイマー)を使いたい場合、「cmt_io」クラスを使う。

#include "common/cmt_io.hpp"

typedef device::cmt_io<device::CMT0> CMT;
CMT     cmt_;


    {
        uint8_t intr = 3;  // 割り込みレベル
        uint32_t freq = 1000;  // 1000Hz の周期 
        cmt_.start(freq, intr);
    }


    while(1) {

        cmt_.sync();  // 同期

    }

上記の例では、リソースとして「CMT0」を使い、1000Hz の周期を持ったタイマーを割り込みレベル「3」で起動している。

CMTには、チャネルが4つあり、CMT0~CMT3まで使える。

RX65N の場合 CMT0、CMT1 は通常の割り込みだが、CMT2、CMT3 は選択型割り込みを使う必要があるが、それらは隠蔽されていて、特別な設定を行う必要が無い。

#if defined(SIG_RX24T) || defined(SIG_RX66T) || defined(SIG_RX72T)
    typedef cmt_t<0x00088002, peripheral::CMT0, ICU::VECTOR, ICU::VECTOR::CMI0> CMT0;
    typedef cmt_t<0x00088008, peripheral::CMT1, ICU::VECTOR, ICU::VECTOR::CMI1> CMT1;
    typedef cmt_t<0x00088012, peripheral::CMT2, ICU::VECTOR, ICU::VECTOR::CMI2> CMT2;
    typedef cmt_t<0x00088018, peripheral::CMT3, ICU::VECTOR, ICU::VECTOR::CMI3> CMT3;
#elif defined(SIG_RX64M) || defined(SIG_RX71M) || defined(SIG_RX65N) || defined(SIG_RX72M)
    typedef cmt_t<0x00088002, peripheral::CMT0, ICU::VECTOR, ICU::VECTOR::CMI0> CMT0;
    typedef cmt_t<0x00088008, peripheral::CMT1, ICU::VECTOR, ICU::VECTOR::CMI1> CMT1;
    typedef cmt_t<0x00088012, peripheral::CMT2, ICU::VECTOR_SELB, ICU::VECTOR_SELB::CMI2> CMT2;
    typedef cmt_t<0x00088018, peripheral::CMT3, ICU::VECTOR_SELB, ICU::VECTOR_SELB::CMI3> CMT3;
#endif

CMT0~CMT3の定義は、上記のようになっており、割り込み要因をデバイス毎に定義してある。

auto vec = CMT::get_ivec();
if(level_ > 0) {
    if(task != nullptr) {
        icu_mgr::set_interrupt(vec, task, level_);
    } else {
        icu_mgr::set_interrupt(vec, i_task_, level_);
    }
    CMT::CMCR = CMT::CMCR.CKS.b(cks) | CMT::CMCR.CMIE.b();
} else {
    icu_mgr::set_interrupt(vec, nullptr, 0);
    CMT::CMCR = CMT::CMCR.CKS.b(cks);
}

上記は「cmt_io」の割り込み関係を設定する部分で、「auto」を使って、割り込み型「ICU::VECTOR」、「ICU::VECTOR_SELB」を自動で再定義している。

「set_interrupt」APIは、割り込み型の違いを定義してあり、それぞれ、割り込みの管理方法が異なる。

static ICU::VECTOR set_interrupt(ICU::VECTOR vec, utils::TASK task, uint8_t lvl) noexcept {
    set_task(vec, task);
    set_level(vec, lvl);
    return vec;
}

static ICU::VECTOR set_interrupt(ICU::VECTOR_SELB vec, utils::TASK task, uint8_t lvl) noexcept
{
    for(uint16_t i = 144; i <= 207; ++i) {
        if(lvl > 0) {
            if(ICU::SLIXR[i] == 0) {
                ICU::IER.enable(i, 0);
                set_task(static_cast<ICU::VECTOR>(i), task);
                ICU::IPR[i] = lvl;
                ICU::SLIXR[i] = static_cast<uint8_t>(vec);
                ICU::IR[i] = 0;
                ICU::IER.enable(i, 1);
                return static_cast<ICU::VECTOR>(i);
            }
        } else if(ICU::SLIXR[i] == static_cast<uint8_t>(vec)) {
            ICU::IER.enable(i, 0);
            set_task(static_cast<ICU::VECTOR>(i), nullptr);
            ICU::SLIXR[i] = 0;
            ICU::IR[i] = 0;
            return static_cast<ICU::VECTOR>(i);
        }
    }
    return ICU::VECTOR::NONE;
}

※選択型割り込みでは、SLIXR レジスタに、割り込み要因番号を設定する事で行われ、割り込み番号144~207まで定義できる。
※選択型割り込みのバグは、「ICU::SLIXR」レジスタアドレスがタイポしていたものだった・・・