search
LoginSignup
2
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

posted at

updated at

RXマイコン、FreeRTOS、FatFs、で MP3、WAV の再生

はじめに

前回、FreeRTOS を使い、FatFs をスレッドセーフで動かす実験を行いました。

今回はそれの応用で、MP3、WAV などの音楽ファイルを再生してみました。

  • 自作の RX64M ボード
  • GR-KAEDE
  • RX65N Envition kit (RTK5)

での動作を確認しました。

MP3 コーデックは mad ライブラリを使っています。
また、ID3 タグの表示(コンソール、シリアル出力)に対応しています。

ハードウェアーの準備

RX64M、RX65N、などには、2 チャネルの 12 ビット D/A が内臓されており、意外なほどノイズが少なく音が良いです。
※M5-STACK などでは、D/A の品質は良くない話を聞くのですが、RX マイコン内臓の D/A は非常に高品質だと思います。
※ 4 ビット捨てているのだけど、意外と 16 ビットの D/A 出力と音を聞き比べても、大きな違いを感じません(これは個人差があると思いますが・・)

D/A 出力は、以下のようになっています。

CPU DA0(Left) DA1(Right)
RX64M P03 P05
RX65N P03 P05

※GR-KAEDE では、DA0 は LED4 に接続しています。
※RX65N Envition kit では、P05(DA1) が SW5 に接続しています。

D/A 出力は 3.3V フルスケール、GND 電位は中点電圧(1.65V)なので、アンプなどに接続する場合は、多少の工夫をする必要があります。

自分は、R/Cによるローパスフィルターとインピーダンス変換、電圧オフセットを行う為、オペアンプによる回路を使っています。


※この回路は RL78 の PWM 変調用の物ですが、代用しています。
※この出力にアンプを繋ぐ場合、電源のGND電位には注意が必要です、基本的に別電源でGND電位が全く異なる場合にしか使えません。

ソフトウェアーの構成

前回、指定ファイルを間欠的(25ミリ秒毎に1024バイト)に読み込み、そのサイズを表示するだけのシンプルなものでした。

今回は、そこに、MP3 や WAV のデコードと再生を行うプログラムを組み込みました。

オーディオ再生では、TPU に設定した周期で、DMA を起動して、DA0、DA1 のレジスタに、波形値を送っています。
波形値はリングバッファにして管理し、バッファが空いたら、デコードした波形データを必要な分コピーしています。

「TPU」は RX64M などに内臓されている高分解のタイマーで、一般的なサンプリング周期、48KHz や 44.1KHz の間隔を作ります。
※ただ、44.1KHz は、タイマーの分解能と、CPU の発振周期の関係で、割り切れない為、正確ではありませんが十分と思います。
※48KHz は綺麗に割り切れるので正確です。

オーディオ再生に必要なコンテキストは以下のようなもので、C++ テンプレートクラスで管理しています。

    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<8192, 1024> SOUND_OUT;
    SOUND_OUT   sound_out_;
  • DMA はリピートモードで動かして、それが完了した場合に再スタートして、再生が途切れないようにしています。
  • リングバッファ(SOUND_OUT)は、8192ワードで、出力バッファ(DMA 用)は 1024 バイトとなっています。
  • 8192 ワードは、デコーダーがデコードして貯めた、音と、DMA により逐次消費される量が追い越す事はないような値です。
  • 元々は、シングルタスク(16ミリ毎)で動作させる事を念頭に設定された大きさなので、RTOS で動かす場合(今回は10ミリ秒)は、もっと小さく出来ると思います。
  • MP3 の場合は、フレーム単位(1152)のデコードなので、その関係も考慮する必要があります。
    {  // 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");
        }
    }
  • DA0、DA1 のレジスターは連続する32ビットのレジスターとしてアクセス出来るので、32ビット転送を使っています。
     ※ DMAC_MGR::trans_type::SP_DN_32
    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_;
  • TPU の定義は上記のようになっていて、64ワード進む毎に隙間を埋めるようにしています。
  • 64ワード(16ビットステレオなら256バイト)をリングバッファから取り出して、DMA 用のバッファに詰めていきます。
  • tpu_task は、ファンクタで、TPU の割り込みから呼ばれます。
  • テンプレートのパラメータとして定義してあるので、コンパイル後は内部に埋め込まれて、最適化されます。
  • 周期が比較的短い割り込みなどでは有効な方法だと思います。

動作の様子

コンソール出力例

# cd ロミオとシンデレラ
# dir
   7300590 Oct  1 2011 23:16  01 キャットフード.mp3
   6566445 Oct  1 2011 23:16  02 茜コントラスト.mp3
   6306857 Oct  1 2011 23:16  03 エトランゼ.mp3
   7627078 Oct  1 2011 23:16  04 Mermaid.mp3
   6273652 Oct  1 2011 23:16  05 ZERO.mp3
   6600923 Oct  1 2011 23:16  06 Paradise Cage.mp3
   5727593 Oct  1 2011 23:16  07 六月病.mp3
   6222142 Oct  1 2011 23:16  08 ローレライ.mp3
   7056714 Oct  1 2011 23:16  09 ロミオとシンデレラ.mp3
   6740736 Oct  1 2011 23:17  10 Red Garden -type M-.mp3
   6816588 Oct  1 2011 23:17  11 曲がり角.mp3
   7385846 Oct  1 2011 23:17  12 飴か夢.mp3
   7604026 Oct  1 2011 23:17  13 ストラップ.mp3
   2869353 Oct  1 2011 23:17  14 [Secret Track].mp3
      8648 Jul 11 2016 07:48  AlbumArtSmall.jpg
     46768 Jul 11 2016 07:48  Folder.jpg
    184832 Jun 26 2018 17:00  Thumbs.db
Total 17 files
# play *
# ID3v2: Ver: 0200, Flag: 00 (356606)
V2.2: 'JPG'
Album:  'ロミオとシンデレラ'
Title:  'キャットフード'
Artist: 'doriko Feat. 初音ミク'
Year:    2010
Disc:
Track:   1/14
Sample Rate: 44100
00:04:49ID3v2: Ver: 0200, Flag: 00 (356606)
V2.2: 'JPG'
Album:  'ロミオとシンデレラ'
Title:  '茜コントラスト'
Artist: 'doriko Feat. 初音ミク'
Year:    2010
Disc:
Track:   2/14
Sample Rate: 44100
00:04:18ID3v2: Ver: 0200, Flag: 00 (356571)
V2.2: 'JPG'
Album:  'ロミオとシンデレラ'
Title:  'エトランゼ'
Artist: 'doriko Feat. 初音ミク'
Year:    2010
Disc:
Track:   3/14
Sample Rate: 44100

※再生中に、dir 等のコマンドを実行出来ます。

最後に


RX65N Envition kit


DIY RX64M ボード

ソースコードは GitHub にあります。

FreeRTOS は使ってみると、非常に応用範囲が広く、簡単に扱える事が判りました。

元々、シングルタスクで動いていた物ですが、何の問題(タスクのスタックサイズを大きくした程度)もなく移行でき、調整も殆どいりませんでした。
これから、ネットワークスタックなどに応用してみようと思います。

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
What you can do with signing up
2
Help us understand the problem. What are the problem?