ラベル ESP-NOW の投稿を表示しています。 すべての投稿を表示
ラベル ESP-NOW の投稿を表示しています。 すべての投稿を表示

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ですが、高速処理にハサむとシュレディンガーの猫的な状態になるので気をつけましょう(経験者・談

2016年3月30日水曜日

ESP-NOW 1:N通信




■ESP-NOWの実験■

手前左がスレーブ(受信)、奥の4つがサーバ(送信)です。スレーブには4台分のMACアドレスを登録し、サーバには受信側のMACアドレスを登録してあります。

送信側:
起動してからsetup()でdigitalWrite(13,LOW)が実行されるまでの間は左側LEDがぼんやり点灯しています(deep sleep中はoff)。右側のシリアルTx LEDの2回目の点灯が送信です(deep sleepからの解除時のシステムメッセージで1回光り、送信時にもう一回光ります)。

受信側:
受信時に光ります。ご覧のとおり、送信側の2回目の点灯と受信側のシリアルの点灯が同期しています。よかったよかった。と思ったら1回明らかに抜けているところがあるなぁ。

フレームのレイヤで誤り検出をしているので、よその電波とぶつかって信号が乱れたらいさぎよく破棄されるのが電波通信の習わしです。きっと他のWiFiなどとの輻輳でデータが壊れたのでパケットが破棄されたのでしょう。

以下ソースは受信側(スレーブ)です。複数のサーバを相手にするために「氾濫原」さんのものに少し変更しました。ありがとうございます。


■怪現象■

なお、最初は4枚をミニブレッドボードに乗せたのですが、その時に奇妙な現象が起こりました。ごく接近した2枚が勝手に同期してしまうのです。最初電源の問題かと思ったのですが、ミニブレッドボードの端と端において離せば再現しません。別電源でも1cm程度に近づけると同期します。何が原因でしょうね。

2016年3月28日月曜日

ESP-WROOM-02の直接通信モードESPNOWを試す


ESP NOWは生パケットをやりとりするモード。DHCPどころかチャンネル自動選択もないので基本的にはチャンネル指定して送りっぱなし。つまり、余計な処理がない分オーバーヘッドも消費電力が少ないということです。

いやー、知りませんでした。何か別のことを調べていたらこの記事がヒットして、そんなことがあったのかー、となった次第。ちゃんとリリースノートを読まないといかんですね。


■ビルドする■

ただし、esp-nowは普通のarduino sdkには含まれていないので、ビルド通りません。設定を変える必要があります。

Arduino IDE : 1.6.5
Board Manager : esp8266 2.1.0

一度Arduino IDEを終了し、
~/Library/Arduino15/packages/esp8266/hardware/esp8266/2.1.0/platform.txt
のcompiler.c.elf.libs=-lm -lgcc -lhal -lphy -lpp -lnet80211 -llwip -lwpa -lcrypto -lmain -lwps -laxtls -lsmartconfig -lmesh -lwpa2という行に -lespnowを追加します。

…って書くと一瞬なんですけどね、ここまでたどり着くのに2日かかりました。makeEspArduinoを試したり、PlatformIOをインストールしたり。まぁその過程でSublime TextからESPをビルドする方法が見つかったりしたので、まったくムダになったというわけではありませんが(空元気

■試す■

まず上記ページに掲載されているソースを試してみます。いやー、シンプルで美しい。なお蛇足ですがESP-WROOM-02が2枚必要です。

スレーブは受信側、常時電源入れっぱなしにしておいてデータが届くのを待つイメージ。コントローラは送信側です。このサンプルでは2.5秒ごとにdeep sleepから復帰して適当な文字データを送っています。Deep Sleepを使うので、RESETとIO16を接続する必要があります

なお、実際に送受信する前に、2枚のESPそれぞれのMACアドレスが必要です。今回のアプリを起動すると自分のStation interfaceとSoft AP interfaceのMACアドレスがシリアルに出力されますので、

Slave : 相手側(Controller)STATION_IFのMACアドレス
Controller:相手側(Slave)SOFTAP_IFのMACアドレス

これをそれぞれのソースの最初の方にあるuint8_t macにセットします。相手側のアドレスを設定して登録するというのがミソ、まぁMACアドレスなんて変更できちゃうんですが、一応世界の一つだけのアドレスということになっているので、これで相手をフィルタリングするわけです。そうでないとご近所のWiFiがたまたま同じチャンネルに送信している時のデータをすべて受けてしまうので使いにくくて死にます。

実行結果はこんな感じ。画面下がController、2.5秒ごとにdeep sleepから復帰してデータを送信してます。画面上はSlaveでIO13のLEDは1秒ごとにLチカ、Controllerからデータが送られるたびにシリアルのLEDが点滅しているのがご覧いただけるかと思います。

データを送信する側は起きてすぐ寝るという感じで、通常TCP/IPなどで送信する場合に接続するだけで2-5秒かかってしまうのと比べるとかなりの消費電力削減が期待できます。




今回はこれで力尽きました(結果としてはよそのWebに紹介されてたプログラムを走らせて終わりなんだけどさ、ビルド通すまでが大変でした)。次回はこれでちゃんとセンサーのデータを送ってみます。

■追記■

1度に送れるデータは200バイトまでです。

…センサーからのデータをまとめて送ろうとしてハマりました。送っても送っても届かなくて、散々調べたらこれが原因でした。ちゃんとエラーステータスを見ましょうね、というお話ですね。とほほ。