前回の記事で、おおよそ必要な検討は終わっているので、今回は成果を回収して行きます。
まずは、SDカードI/Fの回路図を示します(下図)。以前、「#6-5 microSDカードI/Fを自作する」で作った回路の使い回しです。
今回追加になるMCP4725モジュール(12ビットDA変換器)と、音声信号出力部の回路図は以下の通りです。
MCP4725モジュールは、3.3Vで使いますので、Arduinoと通信するためのI2C信号線には、レベルシフタが必要です。
※前回検索して見つけたMCP4725のデータシートより、各端子に印可可能な電圧の絶対最大定格がVCC+0.3Vと規定されているので、VCC=3.3Vで使用する時に、SCL,SDA端子に5V信号を接続すると、定格違反です。
MCP4725モジュールから先の回路は、アナログの音声信号が通るので、Arduino等デジタルノイズを発生する回路とは、電源(Vcc,Vdd,GND)を分離します。上の回路図中、青破線で引いた境界線で、アナログ部とデジタル部を分離しています。
アナログ側には、以下の様な電源モジュールから給電します。
MCP4725には、電源端子が1組しかない(アナログ/デジタル共通)ので、完全に分離することは無理でしたが、これは止むを得ません。
MCP4725モジュールの出力電圧は、最大3.3Vですので、5V電源を使うLM358であっても、十分フルスケール(3.3V)まで信号を通すことができます。
※オペアンプLM358は、Rail-To-Railではないので、5V電源で使う場合、3.3Vまでしか出力することができません(スペックより。実力では約3.9V程度)。入力信号が3.3V(最大)なので、ぎりぎりフルスケールまで出力できるということです。詳しくは「#6-4 オペアンプを使って増幅回路を作る」参照。
キーとなる部分の解説は、前回記事で済んでいますので、以下に完成版プログラムをリンクしておきます。
完成版プログラム:play_wav.ino
コマンド仕様:
Windowsのカードリーダなどを使って、SDカードへ適切なWAVファイルを転送しておいてください。演奏可能なWAVファイルは、以下の通りです。
以下、実験中の様子です。
だいぶ大掛かりな回路になりました(1^^;)。
ただ、SDカードI/F回路、音声データ出力回路、に関しては、別記事で過去に作っておいた、ミニブレッドボードの回路を崩さずに置いてあったお陰で、筆者の環境では、見た目ほど大変ではありませんでした。
使い回し可能な回路が手元に無くて、0から起こそうとすると、そこそこ大変かと……。
大きなブレッドボードには、アナログ系の信号を扱う回路を集めて、Arduinoおよびロジック系回路とは電源を分離してあります。
実験サンプル:【 】内はシリアルモニタからの送信データ
【who】
OK: play_wav ver.1.00 (12 bit dac)
【pwav,ar7a_spl.wav】
DBG: fmt=1, 1, 22050, 22050, 1, 8
OK: play_wav: 2856128[us]/12845[smpl]=222[us/smpl](4497[Hz])
WAVファイル:
何とか、聞き取れました\(1^o^)/。ちょっと、ノイズがうるさいな……。
再生後に出力される情報は、再生したWAVファイルのフォーマット情報と、再生に当たってDA変換器にデータを出力できた時間間隔(出力レート)、その逆数である出力周波数です。
音声データのビット幅が8ビット/16ビットのケースと、ステレオ/モノラル(チャンネル数)のケースの組み合わせで4通りのデータを準備して再生してみたので、以下表に出力レートと周波数の計測結果をまとめておきます。
WAVファイル |
出力レート [μs] |
出力周波数 [Hz] |
備考 | |
ビット | チャンネル | |||
8 | 1 | 248 | 4,025 | ノイズに混じって、何とか聞き取れる |
8 | 2 | 357 | 2,803 |
原音を知っていると、それらしい 音は聞き取れるが、厳しい。 |
16 | 1 | 369 | 2,708 | |
16 | 2 | 1,050 | 952 | ほぼ、ノイズ。聞き取れず。 |
以下、雑感です。
実は、Windowsとかで再生させると、8ビット1チャンネルのデータは、相当音質が悪いのですよ……。なので、恐らく、皆様のお手元にあるWAVファイルは、大抵、16ビットなのではないでしょうか?8ビット1チャンネルデータにしないと、Arduinoでは使い物にならないので、適切な変換ソフトウェアを使って変換してもらえば良いのですが、拙作sPeachで可能なので、説明しておきます。
インストーラは、ダウンロードコーナから入手可能です(あるいは、ベクターのライブラリからどうぞ)。
8ビット1チャンネルWAVファイルの再生時、出力レート=248μsでしたので、この数値からスタートします。以下の通り、I2C通信速度の考察が必要となります。
I2Cの通信に掛かる物理的な時間は、「#7-9 WAVファイルを再生する」で示した図(以下再掲)より、Fastモード400kHzの場合で、約80μsと推定しました。
しかし、この他に、実行されるWireライブラリの処理時間が見積もられていません。そこで、I2Cへデータを送信するdac12_write()関数の、Wire.beginTransmission()〜Wire.endTransmission()までをコメントして、【pwav】コマンドを実行してみたところ、出力レートが248μsから95μsに短縮されました。勿論、MCP4725へデータは送信されていないので、音は鳴りません。
この短縮した時間248μs−95μs=153μsがWireライブラリ(I2C通信時間含む)の処理時間だ、と言うことです。物理的な送信時間80μsに対して、73μsものオーバヘッドが付いていました(意外と大きいな……(1^^;))。
SDカードの読み出し速度については、前回考察済みで、約35μs/バイトと見積もってあります。
以上より、出力レート248μsの内訳は以下の通りと推定できます。
どこを潰すと高速化できるでしょうね……。もうだいぶ叩いた後なので、これ以上は、ライブラリの内部に踏み込まないと、ちょっと無理かもしれません。もっと高速なMCUを使うとか、一番確実で好きですけど……(1^−^)/。
「#7-6 DA変換器を作る」で手作りしたDA変換器(下図)も使用できる様に、プログラムを組んでおきました。
プログラムの先頭付近に、以下の様な#defineがコメントされていますので、コメントを削除してコンパイルすると、4ビットDA変換器(手作り)を使うようなプログラムになります。
//#define USE_DAC4BIT
4ビットDA変換器を使う様にコンパイルすると、MCP4725モジュールへはデータを送らなくなるので、回路を変更する必要があります(以下図)。
4ビットDA変換器(手作り)では、デジタル電源とアナログ電源が別々なので、綺麗に電源を分離することができました。
MCP4725モジュールは、電圧軸方向の解像度(12ビット)に余裕があったので、3.3Vフルスケールに対してデジタルボリュームでVP-Pを調整して使いましたが、4ビットDA変換器では、デジタルボリュームにするには解像度が低すぎます。そこで、デジタルボリュームは無効(初期値を×1倍)にしておき、代わりにVREFとAGNDに抵抗を挟んで出力電圧範囲を狭める設計にしました。
※「#7-6 DA変換器を作る」で作った4ビットDA変換器では、220Ω×16個の直列抵抗ですので、合計約3.5kΩです。VREFとAGNDに10kΩづつ抵抗を挿入したので、出力電圧範囲は、およそ1.4〜1.9[V]で、VP-P≒0.5Vとなります。これが音量になるので、抵抗値R1=R2で調整してください。
4ビットのパラレルI/Oで制御できるお陰で、DA変換器へのデータ出力は、ほんの数命令(一瞬)で終わってしまうため、一気に出力レートが上がります。
実験サンプル:【 】内はシリアルモニタからの送信データ
【who】
OK: play_wav ver.1.00 (4 bit dac)
【pwav,ar7a_spl.wav】
DBG: fmt=1, 1, 22050, 22050, 1, 8
OK: play_wav: 2855900[us]/26882[smpl]=106[us/smpl](9413[Hz])
録音データ:
MCP4725(12ビット)に比べると、かなり粗い4ビットDA変換器なのに、12ビットのMCP4725と比べても、そん色無いほどの音で聞こえます(ノイズはさておき)。このレベル(Arduinoによる再生)だと、電圧軸方向の解像度より、時間軸方向の解像度の方が、支配的なのかもしれませんね。
注意深く聞いてみると、MCP4725の録音データの方には、「リリリリリリリ……」と、聞こえるノイズが混じっています。喋り終わった直後の無音部分に耳を澄ませてみてください。4ビットDA変換器の録音データの方では、それが聞こえません。
喋り終わった直後の無音部分では、DA変換器の出力はほとんど変化しないはずです。つまり、「階段状のカクカクした波形」は出力されていません。にもかかわらず、「リリリリリリリ……」と言うノイズは聞こえているので、I2C通信に起因するデジタル的なノイズではないか?と推察しています。
4ビットDA変換器の方はというと、無音部分ではデジタル値も固定値となり、パラレルI/Oの信号変化もありませんので、無音部分のデジタルノイズが発生しないのだと思われます。
逆に、4ビットDA変換器の方は、音が鳴っている間の雑音が大きく聞こえます。DA変換器に送る信号が4ビットパラレルであること、信号変化の周波数が出力周波数(約9.4kHz、可聴域)程度であることから、I2C(400kHz、可聴域外)に比べて、可聴域内のノイズが大きくなっているのだと考えられます。
ここで、もう一度、オリジナル/MCP4725版/手作り版を聞き比べられるように、3つのWAVファイルを並べておきます。
階段状の波形によって発生するノイズを警戒して、LPFを通してみたりなど、対策したのですが、結果的には、DA変換器に対するデジタル値の通信に関する、デジタルノイズが支配的な様で、全く効果がありませんでした。デジタル信号は、VP-Pが大きい(3.3Vまたは5V)上に、信号の変化速度(立ち上がり/立ち下がり時間)も速いので、ノイズもたくさん出します。手強いです。
そもそも、DA変換器も、バカ高い(←値段が)オーディオ用のDA変換器を使わないとだめかもしれないですね。
何にせよ、ATmega328ベースのArduinoでは、パフォーマンス不足ですね。と言って、Pi ZeroWでは、軽く、ミリ秒オーダでOSにCPUタイムを取って行かれるので、多分、Arduinoよりひどい結果になるでしょう。
と、なると、手元の機体では、STM32ボードなのですが、あんまり使いたくないんですよ。何故?って、この機体は、Logiscopeを使用するデバッグ用の機体なので。STM32をアプリケーションに使っている間、UNO等を使った他のアプリケーションのデバッグに支障をきたしてしまうから……。
そうも言ってられなくなってきたので、STM32を使ってみましょう。
Arduino IDEで、ターゲットを「Generic STM32F103C series」に変更して、コンパイルします。いくつか変更が必要だった部分は、「__STM32F1__」マクロで条件付きに切り替えてあります。先輩方に「感謝」なのは、SDライブラリも、Wireライブラリも、何の変更も要らず、コンパイルが通ってしまったことです。これはありがたい。
STM32ボードのピンアサイン表は、こちらの記事にまとめてあります。この表の中で、SDカードI/Fに使用するSPI端子はSPI1(PA4〜PA7;3.3V I/Oポート)、MCP4725と通信するためのI2C端子はI2C1(PB6,PB7;5Vトレラント)でした。
なお、回路図は、上で示したArduino用の回路図をそのまま使うことはできないので、各自、再設計をお願いします。
※「#8-6 音声合成、の、マネ事」にて作成したSTM32用の回路が、本ページのプログラム(play_wav.ino)でも使用可能です。ご参考まで。
いつもの如く、深追いしていないのですが、さすがは、Cortex-M3 @72MHz!出力レートは相当早くなりました。と、言っても、I2C(400kHz)の物理的データ転送時間(約80μs)を超越することはできないので、出力レートで100数10μs程度までの向上です。
SDカードへのアクセス速度も、相当向上している印象を受けます。余裕があれば、お試しください。
個人的には、「STM32でも、WireライブラリやSDライブラリが、そのまま使える」という事が判ったのが収穫でした。再生音質の方は、Arduino UNOと比べて大差なく、「STM32を使えば解決します」みたいな結論には至りませんでしたので、この程度でサラッと流しておきます。
総括としては、Arduino UNOによるWAVファイルの再生は、ノイズはすごいことになっているけれど、「聞こえなくはない」レベルでした。う〜ん、ザンネン。もう少し綺麗に再生できる様なら、「人の声(WAVファイル)で起きる目覚まし時計のアラーム」とか、応用が広がったところなのですが……。「実用レベルか?」と、問われると、「否」かな……。
残念ながら筆者の能力では、これ以上のノイズ対策は降参です。得意な何方かにお任せいたしまする。