LoginSignup
4

More than 3 years have passed since last update.

RX72N Envision Kit を使った音楽プレイヤー

Last updated at Posted at 2020-05-01



はじめに

  • RX72N Envision Kit はまだ流通量が少ないようで入手が難しいようですが、CPは高いので、何かガジェットを作るのなら、是非入手した方が良いと思います。
  • 今回は、RX65N/RX72N Envision Kit、RX64M(DIY) ボードなどで動作可能なオーディオプレイヤーの紹介です。
  • 内部的な動作や要点を簡単に説明してあります、「音」と「画像」を扱う場合の参考に出来ると思います。

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

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

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

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

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

ソースコードなど一式

github AUDIO_sample project

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

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

全体の構成

全体は、以下のモジュールで構築されています。(-O3 の最適化で、バイナリー、900キロバイトくらいあります。)
- オーディオプレイヤー本体(main.cpp、audio_gui.hpp)
- FatFS (SD カードのファイルアクセス)
- 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 で動かすには、色々なポートの設定などを変更する必要があります。)


※RX65N Envision Kit で D/A 出力などの配線。

各ハードウェアー基本設定 (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_;
            }
                        cmd_service_();
#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++ クラスの場合、ある程度の汎用性をかなり自由に実現できる為と思います。

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

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


ライセンス

Audio Player: (MIT open source license)

FreeRTOS: (MIT open source license)
FatFs: BSD like
libmad: See libma/libmad/COPYRIGHT (G.P.L. v2)
libpng: See libpng/libpng/LICENSE (libpng license)
zlib: (zlib License)
picojpeg: Public domain, Rich Geldreich richgel99@gmail.com

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4