前回、ソフトウェア周りの検討と詳細設計を済ませたので、今回はハードウェア(回路)の設計と、実験での検証で完結します。
では早速、マイクの出力信号の増幅に使う非反転増幅回路の検討からスタートします。
ここでは、今回使用する非反転増幅回路について、電圧・電流の関係から増幅率、動作などについて簡単に説明しておきます。
まず、 これまで何度か使って来た「反転増幅回路」からおさらいしておきますね(↓)。
非反転増幅回路は下図の様になります。
ぱっと見は、同じ、なのですよね……。回路の形だけ見ると、全く同じです。ただ、入力信号Viと、バイアス電位Vbの位置が逆転しているのです。
では、電圧・電流の関係式を解いて行きましょう。
最後の関係式を読み解くと、バイアス電圧Vbをセンタとして、入力信号Viが(1+Rf/Rs)倍に増幅される作用となります。増幅率が正なので、符号(Vbを基準とした大・小)は反転しません。これが非反転増幅回路の由来です。
反転増幅回路の増幅率が−Rf/Rsだったのに対して、非反転増幅回路の増幅率は1+Rf/Rsなので、どんなに頑張っても、×1倍より小さい増幅率に設定することはできません。「増幅」したいための回路なので、増幅率を×1倍より小さい倍率にしたいケースがあるのかどうかはわかりませんが……。
※無理やり頭を絞ってみると、5V振幅のアナログ信号を3.3V定格のAD変換器に入力したい時とか??
念のため、LTspiceでシミュレーションもしてみました(下図)。
入力信号Vi(青線)が、同位相で出力Vo(赤線)に増幅されていることが解ります。
※LTspiceに関しては、逆引き索引から過去記事を当たってみてください。
回路図が出てきたついでに、ボイスレコーダの回路図を示しておきます。まずは、マイクアンプ部です(↓)。
バイアス電位Vbの生成のところ、注意してください。VbはR1=R2の抵抗を使って、抵抗分圧でVb=Vcc/2を作り出しています。これは、R1に流れる電流とR2に流れる電流が等しいから、成立しています。
ところが、こうして作ったVbを非反転増幅回路のRsに入力してしまうと、Vbノードに電流が流れてしまい、結果としてVb≠Vcc/2となってしまうのです。これを避けるため、入力インピーダンス∞のオペアンプ(ボルテージフォロア)で一旦受けてから、Rsに接続しています。
イヤホンジャックJ1にイヤホンを繋ぎ、可変抵抗VRfを使って音量(増幅率)を調整します。増幅率は、×1〜11倍まで調整可能になっています。あまり増幅率を上げると音が歪んでしまいますので、歪まない程度になるべく大きい音で聞こえる様に調整します。
この他に、SDカードI/Fの回路が必要になります。こちらは以前作った回路の使い回しです(↓)。
SDカードは3.3V定格なので、UNOとの接続にはL/Sが必要です。上の回路図はSTM32との共用になっているので、スラッシュ(/)の左側を参照してUNOと接続してください。
まずは、完成版プログラムをリンクしておきます。
完成版プログラム:voice_rec.ino
コンパイルしてUNOに書き込むと、シリアルモニタから以下のコマンドを受け付けるようになっています。
コマンド仕様:
SDカード関係のコマンドは以下の記事で作ったものの流用なので、使い方などについては以下記事を参照してください。
「#6-5 microSDカードI/Fを自作する」
「#6-6 ポータブル気温・湿度ロガ」
「#6-8 ATmegaのEEPROMを使ってみる」
重複する説明は避けたいと思いますm(1__)m。
以下、実験中の様子です。
実験を進めましょう。
実験サンプル:【 】内はシリアルモニタからの送信データ
【cali】
OK+ send any data to stop calibration
OK+ cali (voicerec.tmp)
OK+ cali: isr=12[us] sdc=57[us] sdc.max=3504[us]
OK+ cali: sps=11904
OK: rec stop 5002[ms]
UNO @16MHzで【cali】コマンドを実行したところ、最適なサンプリングレートは11,904Hzとなりました。……ん?SDカードへの書き込み速度は6,399バイト/sじゃなかった?11,904HzだとSDカードへの書き込み速度を超えているのだけど。
(紆余曲折に、ついてはこちら)
SDカードの書き込み速度測定(プログラム)が間違っていたとは……orz。プログラム(test_eeprom.ino)を修正して測定し直したところ、15,000バイト/sの書き込み速度を確認できました。やられた……。
ともあれ、SDカードの書き込み速度:正しくは15,000バイト/sに対して、サンプリングレート11,904Hzが計測出来ました。
試行(【cali】コマンド)から求めたサンプリング周波数で、そのまま録音してみましょう。
【rec,a118_rec.wav】
OK+ send any data to stop recording
OK+ rec (a118_rec.wav) sps=11904
ERR: rec stop 1372[ms] error=-3
アレレ。エラーが出ちゃいました。
エラーコード=−3は、ErrBufOvf(バッファオーバフロウ)です。エラーが発生したのは1,372[ms]録音したところ、ですので、【cali】コマンドが実行した1秒の録音試行時間を少し、過ぎたところですね。試行録音時間が短すぎたのかもしれません。試行録音時間を変えながら【cali】コマンドを実行してみましょう。
【cali,5】
OK+ send any data to stop calibration
OK+ cali (voicerec.tmp)
OK+ cali: isr=12[us] sdc=57[us] sdc.max=3504[us]
OK+ cali: sps=11904
OK: rec stop 5002[ms]
【cali,10】
OK+ send any data to stop calibration
OK+ cali (voicerec.tmp)
OK+ cali: isr=16[us] sdc=59[us] sdc.max=13060[us]
OK+ cali: sps=7309
OK: rec stop 10002[ms]
【cali,20】
OK+ send any data to stop calibration
OK+ cali (voicerec.tmp)
OK+ cali: isr=16[us] sdc=60[us] sdc.max=13072[us]
OK+ cali: sps=7215
OK: rec stop 20002[ms]
上の試行結果だけを見てみると、10秒くらいの試行録音で安定したように見えるかもしれませんが、実は、そんなに単純ではありません。何度か繰り返し【cali】コマンドを実行すると、同じ試行時間でも大きくばらつきます。
一番の問題は、"sdc.max="で表示されている時間です。
OK+ cali: isr=16[us] sdc=60[us] sdc.max=13072[us]
上の例だと、sdc.max≒13msとなっているのですが、この時間、実は以下の様に計測しています。
// write data
usec_wrt=micros (); // ここから↓
if (fout.write (vrc_buf[vrc_rptr])!=1)
{
// ERROR:
vrc_err=ErrWavWrit;
}
usec_wrt=micros () - usec_wrt; // ↑ここまで
if (usec_max<usec_wrt)
usec_max=usec_wrt;
sdc.maxは、上コードではusec_maxに相当します。一言で言うと、SDカードへの書き込みwrite()メソッドの滞留時間の最大値、です。それが13msと言う事はつまり、write()メソッドから13ms返ってこなかった時間が存在する、と言う事です。これはいただけません。
返ってこなかった13msの間ずっとサンプリングを続けていて、データがサンプリングバッファvrc_buf[ ]に溜まって行くので、この時にバッファがオーバフロウするのです。
完全にwrite()メソッドの滞留時間の問題なので、SDカードのせいなのか、SDライブラリのせいなのか、どのくらいマージンを見たら良いのか想像がつきません。しかも、write()メソッドの外部では対策不可能です。
※エラーの原因は判りましたがSDライブラリの中なので、「叩く」ことはできませんでした。SDライブラリの中身に踏み込めるほどのスキルが無い為。
【cali】コマンドの試行時間を延ばすと、sdc.maxが少しづつ大きくなっていく様にも見えるし、1時間とか試行すると更に長いsdc.maxが測定されるかもしれませんし、キリが有りません。
対策の一つとしては、サンプリングバッファvrc_buf[ ]のサイズアップが有効です。現在、vrc_buf[ ]のサイズは、vrc_MAX_BUF=256バイトに設定してあります。writeメソッドの最長滞留時間の間にvrc_buf[ ]がオーバフロウしない程度までサイズアップしておけば良いだけの話です。
ただし、現時点のvrc_MAX_BUF=256にて、既にUNO(内蔵RAM 2Kバイト)ではカツカツの状態で、倍の512バイトに設定すると、コンパイル時に以下の様な警告が表示されてしまいます。
スケッチが使用できるメモリが少なくなっています。動作が不安定になる可能性があります。 |
尚、筆者環境でvrc_MAX_BUF=384にした時は、警告表示されませんでした。この回秘策は確実で有効なのですが、残メモリ量と相談の上、実施してください。
ひとまず、試行用データを録音する数秒を乗り切れる様、切りの良いサンプリングレートに手動設定して続けます(↓)。
【sps,7200】
OK: sps=7246 …… タイマの解像度で、多少、設定値7,200Hzからのズレがある。
【rec,a118_rec.wav】
OK+ send any data to stop recording
OK+ rec (a118_rec.wav) sps=7246
【】 …… 空文字で良いので、何か送信すると録音を停止する。
OK: rec stop 1891[ms] error=0
2秒そこそこですが、とりあえず、エラー無く録音出来ました。
SDカードに記録したデータは、Tera Term等と【xsend】コマンドを使って、XMODEMプロトコルでPCに転送することが出来ます。シリアル経由で遅いので、ファイルサイズが大きい時はSDカードをカードリーダなどに移してPCで直接読み込んだ方が速いです。
Arduinoで録音したデータをリンクしておきます。
元の音声は、拙作sPeachで喋らせた音声合成の女性の声です。その声をArduinoのvoice_rec.inoで録音しました。ちょっとノイズは聞こえますが、意外と綺麗に聞こえるでしょう?
元の音およびArduinoで録音した音声データをSoundEngineで解析したところ、以下の様になりました。
横軸に時間の経過、縦軸に周波数を取って、信号の強度を色で表現しています。左がオリジナルの音声データで、右がArduinoで録音したデータ(a118_rec.wav)です。
SDカードの書き込み速度を間違えて測定していたせいで、当初の見込みより高速にサンプリング(約7,200sps)することが出来ました。サンプリングレート48kHzには遠く及びませんでしたが、約7,200Hzのサンプリングレートを確保できたので、ボイスレコーダとしては十分許容範囲な音質を確保できたと思います。
当初は、UNO @16MHzではサンプリングレートが足りないため、クロックアップ版328duino @25MHzを使うことまで視野に入れていました。このスピードなら、UNO @16MHzでも十分使い物になりそうです。
今回筆者の実験では、UNO @16MHzを使って、約7,200spsのサンプリングレートで録音することが出来ました。この場合データレートは7,200バイト/sなので、WAVファイルのサイズ上限2Gバイトから推定すると、ざっと、82時間以上の連続録音が可能となります。
3日間超の長時間ですので、さすがに、会議などの記録用として使っても足りなくなることは無いでしょう。
ただし、SDライブラリwrite()メソッドの滞留時間の問題があるので、サンプリングレート7,200Hzの設定で長時間安定して録音できるかどうか?はわかりません。と言って、上の実験で述べた様に、write()メソッドの滞留時間が何に起因する物かもわからないので、プログラム(voice_rec.ino)で自動計測させるのは無理ですから、【cali,20】程度の計測結果に対して更に十分なマージンを持って、サンプリングレートを決定して録音するしかありません。
※これだから他人が作ったプログラムは使い難いです。思い通りに動いている間は良いのですが、一度こちらの意図に沿わない動きを始めると、手が出せません。と言って、SDライブラリ相当のライブラリを自前で起こすほどのスキルも無いので、そこで思考停止してしまいます。
以下、追加実験です。格好は悪いですが、論理的に求められないなら力業で最適値を求めるしかありません。
【sps,10000】
OK: sps=10000
【rec,a.wav】
OK+ send any data to stop recording
OK+ rec (a.wav) sps=10000
ERR: rec stop 1634[ms] error=-3
サンプリングレート10kHzだと、約1.6秒でバッファオーバフロウしました。
【sps,9000】
OK: sps=9009
【rec,a.wav】
OK+ send any data to stop recording
OK+ rec (a.wav) sps=9009
ERR: rec stop 52735[ms] error=-3
サンプリングレート9kHzだと、約52.7秒でバッファオーバフロウしました。
【sps,8000】
OK: sps=8000
【rec,a.wav】
OK+ send any data to stop recording
OK+ rec (a.wav) sps=8000
ERR: rec stop 37882[ms] error=-3
サンプリングレート8kHzだと、約37.9秒でバッファオーバフロウしました。【sps,9000】の結果を下回ってしまいました。サンプリングレート以外にも、何らかのバイチャンスな要因があるとしか思えません。
【sps,7000】
OK: sps=7042
【rec,a.wav】
OK+ send any data to stop recording
OK+ rec (a.wav) sps=7042
【】 …… 空文字を送信して手動停止。
OK: rec stop 584750[ms] error=0
サンプリングレート7kHzだと、約585秒 → 約10分で手動停止するまで、エラーの発生は無く、正常に録音出来ました。ご参考まで……。
元々、クロックアップした時に最適なサンプリングレートを自動計測するために【cali】コマンドを実装したので、328duino @25MHzを使い、【cali】コマンドと【rec】コマンドのテストをしておきたかったのですが、無意味なので止めておきます。SDライブラリwrite()メソッド滞留時間問題の方が支配的で、クリアな解に辿り着けそうになく、検証する意味がないと判断しました。
SDカードの書き込み速度測定を間違っていたことから始まり、せっかく作り込んだ【cali】コマンドも力を発揮することが出来ず、結局力業……。ボロボロではありましたが、ボイスレコーダを作るという目的は果たせました。録音音質も、当初想定をはるかに超えて上出来です👍。