Help us understand the problem. What is going on with this article?

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 は使ってみると、非常に応用範囲が広く、簡単に扱える事が判りました。

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

hira_kuni_45
ハード、ソフト、金属加工、内燃機関、色々やってます。
http://www.rvf-rc45.net/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした