2016年4月24日日曜日

WiFiワイヤレスオーディオ(失敗)

送信側

受信側。手前の補聴器1個15万円ナリ

■やりたいこと■

赤外線ヘッドフォンアダプタが壊れました。私は耳が悪いので、これがないとテレビが見られません。アニメ見られないのは死活問題ですw ただ、最近市販されているワイヤレスヘッドフォンは音量が低すぎてほぼ聞こえません。特にBluetoothものは出力が小さくて先日ヨドバシで試聴してみたのですが、全滅でした。なんせ私はiPhone+標準添付のイヤホンだと最大音量にしても音楽ほぼ聞こえないんです。やぁねぇ、難聴orz

ということで、ESP-WROOM-02を使ってライン入力した音声信号をWiFiで飛ばし、受信側もESP-WROOM-02を使って受けたデータをDACで出力しそれなりに増幅してヘッドフォンへ…ということを試してみました。ラズパイなら簡単なんですが首からラズパイぶら下げるわけにもいきませんしw

ESP内蔵のADCはあまりにも貧弱なのでMCP3204、DACはMCP4922を使いました。どっちも秋月で入手できます。アンプとしてはポタアン用オペアンプを試してみて、音量が出ないようなら適当なパワーアンプを使ってみようと思ってます。

…とスタートしたのですが。

■先に結論■

今のところ、意図したようには動いていません。ADC/DACからのやり取りとESP-NOWまたはUDPでの通信はそれぞれはちゃんと動いているのですが、一緒にするとデータが欠落してしまいます。

サインカーブを送信して受信側の信号をイヤホンで聞いていると、基本的には連続音がずーっと聞こえていて、数秒ごとにバタバタとノイズが入り、ときたまプチっと100mSecぐらい空白ができる…という感じです。HiFiはまったく期待していなかったのですが、ノイズはいかんです。

ADC/DACを扱うためのインターバルがESP-NOW/UDPの送受信処理によってブロックされているようです。FIFOバッファを持ったADC/DACでないと無理かなぁ。

当初の目標に関しては失敗なのですが、とりあえずADC/DACの接続、ESP-NOW/UDPでの伝送などの参考にしていただければ幸いです。

関係ないですが、インターフェース誌のMATLAB特集を読んでフィルタなどを生成できることを知りまして、「あれ、もしかしてMATLABで補聴器作れるんじゃね?」と思ってググってみたら、すでにやってるヒトが。世界は広い。ただ、帯域ごとのコンプレッションは含まれていないので、そこは遊べる余地があるなぁ。

■高速DA変換テスト■

苦手なSPIなので、メモリ上に作ったサイン波形を連続出力してみました。イヤホンを直結すると「ぷー」という200hzの単音が聞こえます。

■高速AD変換テスト■

標準SPIライブラリで読み込み処理を書きました。

昭和臭ただよう可変抵抗器

その簡易的な動作テストとしてボリューム(可変抵抗)をつないですべてのビットが出てくるかを確認します。



これはそのための文字列表示処理
  int a = readADC(kRightCh);
  char buf[32], buf2[32];
  itoa(a, buf, 2);
  sprintf(buf2, "%16s %5d", buf, a);
  Serial.println(buf2);


読み込み処理はMCPのタイミングチャート(page 21)の通りに書けばこうなります。
  digitalWrite(SS, LOW);
  SPI.transfer(0b00000110);
  uint16_t result = word(0x0f & SPI.transfer(0x00), SPI.transfer(0x00));  
  digitalWrite(SS, HIGH);
でも、これだとch選択が分かれてしまって、今ひとつ面倒くさいです。その前のページを見るとスタートビットさえあれば動くっぽいので
  digitalWrite(SS, LOW);
  SPI.transfer(0b00011000 | ch);
  uint16_t result = word(0x3f & SPI.transfer(0x00), SPI.transfer(0x00)) >> 2;  
  digitalWrite(SS, HIGH);
と書きました。ちなみに連続した24bitのパターンが崩れなければいいので、
  digitalWrite(SS, LOW);
  SPI.transfer(0b01100000 | ch << 2);
  uint16_t result = word(SPI.transfer(0x00), SPI.transfer(0x00)) >> 4;  
  digitalWrite(SS, HIGH);
と書いても動きます。計算量は同じなので、どちらでも。

■ESP-NOWについて■

一度に送れるパケット長は最大200バイトです。また、CRCをつけて調べてみたのですが、パケット単位の誤り検出が行われているようで、化けたデータは届きません。

別のESPから送信しっぱなしにしておいてデータを受信したらLEDが点灯する状態で走らせれば、本来なら常時点灯したままになるはずです。しかし、時々0.5-1Hzぐらいの周期でLEDが消えます。これはたぶん他のWiFiからの干渉なのだと思います。本来、WiFiはチャンネルを自動選択しますが、ESP-NOWは固定したままなので、これはどうしようもないですね。

UDPではその現象は起こりません。が、正弦波を流してイヤホンで聞いていると、前記の通り数秒ごとにバタバタとノイズが入ります。ノイズの入り方はESP-NOWでのLEDの点滅と同じような感じなので、共通した原因なのだろうと思います。

■動作テストと考察■

オーバーランやアンダーランの処理を真面目に書いてないですが、テストとして送信側では計算で作ったサインカーブをバッファにセットし、ADC読み込みをスキップした状態でパケットを送ります。受信側は受信したパケットを順次DACに送出して、オシロで波形を見ます。

きれいな正弦波が見えるのですが、ときどき直流になりますw 受信データが化けていないのはCRCで確認済(このソースには含まれていません)なので、WiFiの受信処理によってDACへ出力するためのインターバルが止まっているのが原因と思われます。ただ、送った波形が少なくとも数パケット分なんの障害もなく受信されていることの方が多いので、定常的な受信処理そのものでインターバルがブロックされるわけではなく、それ以外のオーバーヘッドによって止まっているのではないかと推測しています。

一般的に音声信号は微小時間単位で見ると同じパターンの繰り返しなので、パケットの受信にときどき失敗することがあってもDACへの出力が止まらなければ聴覚上それほど酷い状態にはならないのですが、ときどき止まるってのは冒頭に書いたようにわりと耳障りな状態になります。

■ソース■

githubに置いておきます。


■今後■

ESPでI2Sで動作させた事例を見つけたので、DACチップを入手できたら試してみます。

■メモ■

  • ADC/DACともに、CSはずっとLOWにしててもダメです。特にDACはラッチがついているから問題ないんじゃないかと思ったんですが、1データごとにCSを上げ下げしないと動いてくれません。
  • 前にも書いたかもしれませんが、Serialは結構CPUパワーを食いますしTimerの動きも邪魔します。動作の様子を見るためのSerialですが、高速処理にハサむとシュレディンガーの猫的な状態になるので気をつけましょう(経験者・談

0 件のコメント:

コメントを投稿