はじめに
前回、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 は使ってみると、非常に応用範囲が広く、簡単に扱える事が判りました。
元々、シングルタスクで動いていた物ですが、何の問題(タスクのスタックサイズを大きくした程度)もなく移行でき、調整も殆どいりませんでした。
これから、ネットワークスタックなどに応用してみようと思います。