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

## はじめに

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

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

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

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

MP3 コーデックは [mad ライブラリ](https://www.underbit.com/products/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）なので、アンプなどに接続する場合は、多少の工夫をする必要があります。

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

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58667/a3ac0278-6189-4b05-1720-3b16c27b2653.png" width="75%">
※この回路は RL78 の PWM 変調用の物ですが、代用しています。
※この出力にアンプを繋ぐ場合、電源のＧＮＤ電位には注意が必要です、基本的に別電源でＧＮＤ電位が全く異なる場合にしか使えません。

## ソフトウェアーの構成

前回、指定ファイルを間欠的（２５ミリ秒毎に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 により逐次消費される量が追い越す事はないような値です。
 - 元々は、シングルタスク（１６ミリ毎）で動作させる事を念頭に設定された大きさなので、RTOS で動かす場合（今回は１０ミリ秒）は、もっと小さく出来ると思います。
 - MP3 の場合は、フレーム単位（１１５２）のデコードなので、その関係も考慮する必要があります。

```
    {  // 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 のレジスターは連続する３２ビットのレジスターとしてアクセス出来るので、３２ビット転送を使っています。   
　※ 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 の定義は上記のようになっていて、６４ワード進む毎に隙間を埋めるようにしています。
- ６４ワード（１６ビットステレオなら２５６バイト）をリングバッファから取り出して、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 等のコマンドを実行出来ます。

## 最後に

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58667/27276aa2-4966-7b6a-c8e2-b62fc435ee45.jpeg" width="50%">
RX65N Envition kit

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/58667/4b3c8994-b0dc-43a2-e10a-ee68f6b02e95.jpeg" width="50%">
DIY RX64M ボード

ソースコードは [GitHub](https://github.com/hirakuni45/RX/tree/master/FreeRTOS_FatFs) にあります。

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

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