ひょんなことから、I2SのデータをWiFiで飛ばせないかという要件が出てきました。
そこで、今や電子工作の間でもよく知られるWiFiモジュール「ESP-WROOM-02」を使って、I2Sデータの送信テストをやってみました。
先に結論を言うと、要件では最低限16Mbps(2MByte/Sec)の速度が必要だったんですが、ESP-WROOM-02 ではその速度ではムリ!ということが分かり断念することに…。
しかしレートの低い、例えば音声信号として一般的な 16bit/32Fs/48KSPS くらいの転送量ならできそうなので、I2Sの送信方法などと共に検証結果を残すことにします。
ESP-WROOM-02
MIPS系のコアにWiFi機能が載っているSoCチップ「ESP8266EX」を搭載した、メーカー公式のモジュール。2015年ごろ発売なので、もう4年くらい経ちますね。
今では、ESP32-WROOM-32 という機能と性能が強化された後継種も出ていますが、小型省電力といったニーズから今だ需要があります。
ESP-WROOMシリーズの特徴は、なんといってもその安さと、開発リソースやドキュメントが充実している点にあります。そのため、ネットにいくらでも情報があるので詳しくは触れませんが、最近の情報を書いておきます。
開発環境
ESP-WROOM-02 には、デフォルトでATコマンドプログラムが書き込まれているので、購入後起動してすぐに簡単なテストができるんですが、ちゃんと使おうと思ったらユーザーが開発したプログラムで上書きして使う必要があります。
書き込みやロギングには、シリアル通信を使うため、専用のライタは不要ですが、USBシリアル(もしくはそれを内蔵するブレークアウト基板)を使うのが一般的。
開発環境は3つ用意されていて、好きな方法を選べます。
ESP-WROOM-02 は Arduino として使えます。
メーカー(Espressif Systems)が用意している独自APIベースのSDK。
こちらもメーカー用意の、フリーRTOSベースのSDK。NONOSと一部互換。
Arduino IDE だとグラフィカルで簡単でとっつきやすいため、ネット上の記事ではほとんどが Arduino になっていますね。
Arduino は簡単に使えるようにするのが目的のシステムなので、お遊びや実験、簡易評価などには良いかもしれませんが、真にデバイスを使いこなすようなことをやろうとすると、それが裏目に出て逆に苦労することもあります。今回は、Arduinoは使いません。
今ではRTOS SDKでもWindows上で簡単に開発できる!
SDKを使った開発はひと手間必要で少々面倒な点もありましたが、最近になって少し変わりました。Arduinoは使いたくない…という方には朗報かも知れません。
公式のリリースでは、2019/01/31 に「ESP8266 RTOS SDK (IDF Style)」というRTOSベースのSDKが出ています。これを使うとIDF(メーカー独自の開発環境)が使えるため、これまでより簡略化された手順で開発することができます。
ESP8266EX Resources | Espressif Systems
Windows上でもコンパイルできるので、従来は推奨されていたVirtualBoxは不要。ターミナルソフトもいらないし、Flash Download Tools も不要! mingw32のコマンド画面から make flash monitor と打てば、ビルドした後フラッシュへの書き込み待機状態となり、書き込みが終わるとそのままシリアルコンソール画面になります。
GUI環境が欲しければEclipseが使えるので、Arduino IDEと同じくらいの開発環境が整います。これらのセットアップ方法は次のページからたどれます。
EESP8266_RTOS_SDK/docs/en/get-started/
この開発環境は「MSYS2」ベースのものとなっていて、上のページからたどれるwindows-setup.rst にある「esp32_win32_msys2_environment_and_toolchain-20180110.zip」を、自分のPCに解凍して置くだけでOK!
・・・のハズなんですが、実はこれには肝心の「ESP8266」のコンパイラが含まれていないので、下の手順でコンパイラを入れて、パスを通してあげればOKです。
cd ~/esp; git clone https://github.com/espressif/ESP8266_RTOS_SDK.git
1 2 3 4 | export PATH="$PATH:/opt/xtensa-lx106-elf/bin" #コンパイラを置いた場所 export IDF_PATH=~/esp/ESP8266_RTOS_SDK #SDKを置いた場所 |
後はmingw32のコマンド画面を再起動すればパスが設定されます。
コンパイラ(xtensa-lx106-elf)は tar.gz 形式で圧縮されていますが、シンボリックリンクが含まれているため、7-ZipやLhacaなどWindowsの解凍ツールでは正しく解凍できず、サイズ0のファイルになったりします。
これを避けるために、msys2 のコマンド画面から tar -zxvf FileName を使って解凍する必要があります。
その前に、msys2 には tar がインストールされていないので、pacman -S vim git wget sed diffutils grep tar unzip にてインストールのこと。「msys2 tar インストール」で調べてください。
※SDKや開発環境は割とよく更新されます。今後、手順や必要なファイルなどが変更になるかも知れませんので注意してください。
I2Sドライバ
ユーザープログラムからI2Sを使うためのドライバやライブラリは、今のところ、公式リリースされている RTOS SDK にも NONOS SDK にも、なぜか入っていません。
Arduino版では、(今はどうかわかりませんが)I2Sのライブラリは用意はされているものの、出力しかサポートされておらず入力ができないという情報を見かけました。
I2Sレジスタを操作して自力でなんとかしようにも、仕様が完全に公開されていません。
一番詳しく載っているのは ESP8266 Technical Reference の9章なんですが、このドキュメントからだけでは完全なプログラミングはできないんです。でも、GitHubや海外のコミュニティーなどで、出処は不明ですがI2Sのヘッダやサンプルコードが落ちていたりして、なんとか試せるみたいな状況ではありました。
しょうがないので、出所不明のヘッダを頼りにガリガリしていたところ、つい最近10日ほど前に、GitHubにある最新の ESP8266_RTOS_SDK にI2Sのドライバが追加されていることに気付きました。
調べてみると、このドライバはDMAを使ったI2SのRead/Writeができるようです。
なんというタイミング!これはラッキー!つことで、早速それを利用してみることに。
なお、メインのヘッダとソースは次の位置にあります。
ESP8266_RTOS_SDK/components/esp8266/include/driver/i2s.h
ESP8266_RTOS_SDK/components/esp8266/driver/i2s.c
I2Sピンの割当て
I2Sで使うピンは次の通りです。
ピン | GPIO | 信号名 | マスター時 | スレーブ時 |
---|---|---|---|---|
5 | GPIO13 | I2SI_BCK | 出力 | 入力 |
4 | GPIO12 | I2SI_DATA | 入力 | 入力 |
3 | GPIO14 | I2SI_WS | 入力 | 入力 |
ピン | GPIO | 信号名 | マスター時 | スレーブ時 |
---|---|---|---|---|
6 | GPIO15 | I2SO_BCK | 出力 | 入力 |
11 | GPIO3 | I2SO_DATA | 出力 | 出力 |
7 | GPIO2 | I2SO_WS | 出力 | 出力 |
また、マスターとして使う場合には、ピン10(GPIO4)を Function2(CLK_XTAL)に設定することにより、マスタークロック(MCLK)出力として使えるようです。
I2Sフォーマット
I2Sの信号フォーマットは、LRCLK(WS)から1クロックずれてデータが始まる正規のフォーマット(フィリップスフォーマット)の他に、亜種であるRight-Justifiedが指定できるようです。(Left-Justifiedはできないっぽい)
検証の構成図
I2S出力する例
よくありそうなのは、WiFiを使ってMP3などのエンコードされたストリームを受信し、デコード後のデータをI2Sで出力してDACへ流し込むというもので、ネット上でもいくつか実験例を見ることができます。
ちなみに、ESP-WROOM-02 はRAMが少なく十分な量のバッファが確保できないことから、そのままでは満足できるストリーミング再生は厳しいようです。
I2S入力する例
デジタルマイクから音声データをI2Sで入力してファイルに保存する、といった使い方をいくつか見たことがあります。
ただ、さすがにコーデックまで載せてWiFi送信するのは厳しい?かも知れません。そもそも ESP-WROOM-02 は、IoTでの利用を想定したもので…なんて言い出すと元も子もありませんが。。
今回想定する構成
今回は、16bit/32Fs/0.5MSPS(2ch)のI2Sデータを外部から取り込み、エンコードせずにそのままWiFiで送信できるのか?という検証なので、このような構成を想定しています。
ソケットを使って、LとRの16bit値を単に交互に送信します。
検証の回路図
PICからテストデータをI2Sで送信する回路です。
ESP-WROOM-02 周辺は、データシートのFigure 5-2. にある最も基本的な回路です。
ネット上では、起動モードに関わるピンのプルアップがいるいらないで色々書かれているようですが、ESP8266EX のデータシートを見ると、多くのピンが内部でプルアップされていることが明記されており、ENピン以外のプルアップは不要です。つまり、ESP-WROOM-02 のデータシートに載っている回路そのままで良いかと。
電源容量は少なくとも250mAを確保する必要があります。当方の実験では、安定化電源の出力設定が230mA未満だと正常に起動しませんでした。データシートには平均電流80mAと書かれていますが、起動時には瞬間的に多くの電力を消費するようです。
ブレークアウト基板は秋月電子で売っている AE-ESP-WROOM-02-1100MIL、USBシリアルには AE-FT234X を使用しました。
USB内蔵の基板だともっと簡単に済みます。
ESP-WROOM-02モジュール ESP-WROOM-02本体に加えてUSBシリアルとUSBからの給電のための3.3Vレギュレターを内蔵した基板。USBで接続するだけ。 |
ESP-WROOM-02モジュール スイッチサイエンスのESP-WROOM-02ピッチ変換済みモジュールのフル版。フル版でないとI2Sピンが出ていないので要注意。 |
ESP-WROOM-32モジュール ESP-WROOM-02の後継機種で同様に使える。160MHz→240MHzにUPしBluetoothが追加された等。USBシリアル内蔵。 |
I2S受信速度の検証
まずは、WiFi送信抜きで、I2Sの受信パフォーマンスだけを見てみます。
I2Sにはスターとスレーブの割当てがありますが、データの送受信方向とは無関係で、ビットクロック(BCK)を送出する側がマスターになります。
ただ、ESP-WROOM-02 の場合、BCKの生成に制約があるらしく、スピードが要求される場合はBCKを受信する側、つまりスレーブ側でないと高速で動けないようです(後述)
送信側がマスターで ESP-WROOM-02 側がスレーブ
というわけで、この構成が本命です。
まず、テストデータをI2S送信するPIC側のプログラムです。PICには手持ちにあった dsPIC33CK32MP202(100MHz)を使いましたが、SPI/I2Sペリフェラルを持つ同等のPIC24シリーズでも、同じようなコードになると思われます。
テストデータは単に0x000~0x3FFのカウント値ですが、Rchには最上位ビットを立てて、受信側で確認できるようにしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | // ●メインエントリ int main(void) { InitSystem(); Run(); } // ●システム初期化 static void InitSystem(void) { // メインクロック設定 Fcy:100MHz ( FOSC = (4 * M) / (N1 * N2 * N3) ) PLLFBDbits.PLLFBDIV = 100; // M (400~1600, Default:150 max:200) CLKDIVbits.PLLPRE = 1; // N1 PLLDIVbits.POST1DIV = 2; // N2 (1~7, must POST1DIVx >= POST2DIVx def:4) PLLDIVbits.POST2DIV = 1; // N3 (1~7, must POST1DIVx >= POST2DIVx def:1) // SPI/I2Sモジュールのピンアサイン RPOR1bits.RP35R = 0b000101; // RB3 ← SDO1 (DATA) RPOR2bits.RP36R = 0b000111; // RB4 ← SS1 (WS) RPOR2bits.RP37R = 0b000110; // RB5 ← SCK1 (BCK) } // ●実行ループ static void Run(void) { // SPIモジュール初期化(→ I2S JUSTFIED 16bit(32Fs)) SPI1STATLbits.SPIROV = 0; SPI1STATLbits.FRMERR = 0; SPI1CON1H = 0; SPI1CON1Hbits.AUDEN = 1; SPI1CON1L = 0; SPI1CON1Lbits.MSTEN = 1; SPI1CON1Lbits.CKP = 1; SPI1CON1Lbits.ENHBUF = 1; SPI1IMSKHbits.TXWIEN = 1; SPI1IMSKHbits.TXMSK = 4; // CLKの周波数 = FPB(Fcy:FOSC/2) / (2 * (SPI1BRGL + 1)) //SPI1BRGL = 49; // CLK = 1MHz SPI1BRGL = 2; // CLK = 16.666MHz // 送信開始 SPI1BUFL = 0x55AA; // ダミー値(Lch) SPI1CON1Lbits.SPIEN = 1; SPI1BUFL = 0xAA55; // ダミー値(Rch) // ダミー値を送信し続ける unsigned int i; for(i = 0; ; i++) { unsigned short datL = i % 1024; // Lch: 0x0000~0x03FF unsigned short datR = 0x8000 | datL; // Rch: 0x8000~0x83FF // 送信バッファが空になるまで待つ while(!SPI1STATLbits.SPITBE); SPI1BUFL = datL; SPI1BUFL = datR; } } |
次に、I2S受信する ESP-WROOM-02 側のプログラムです。
先に説明したとおり、I2Sドライバが含まれる最新のRTOS SDK上で動作します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | #include <stdio.h> #include "esp_system.h" #include "freertos/FreeRTOS.h" #include "freertos/queue.h" #include "driver/i2s.h" //I2Sレジスタに直接アクセスしたい場合はコメントインする //#include "esp8266/i2s_struct.h" //static i2s_struct_t* i2s_reg = (i2s_struct_t*)&I2S0; #define I2S_INDEX 0 static xQueueHandle event_queue; // ●メインエントリ void app_main() { printf("\nSDK version:%s\n\n", esp_get_idf_version()); // I2Sドライバを設定する i2s_config_t i2s_config = { .mode = I2S_MODE_SLAVE | I2S_MODE_RX, // スレーブモードでデータ受信する .sample_rate = 5000 * 1000, // とりあえず最大にしておけばOK?ぽい .bits_per_sample = 16, .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, .communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB, .dma_buf_count = 4, // 用意するDMA受信バッファ数 .dma_buf_len = 1024, // DMA受信バッファ長(32bitワード数) }; i2s_driver_install(I2S_INDEX, &i2s_config, 10, &event_queue); // I2SI(受信)ピンを有効化する i2s_pin_config_t pin_config = { .bck_o_en = false, .ws_o_en = false, .data_out_en = false, .bck_i_en = true, .ws_i_en = true, .data_in_en = true }; i2s_set_pin(I2S_INDEX, &pin_config); // 受信バッファを確保 int* read_buf = malloc(1024 * 4 * 8); // 受信開始 printf("\r\n******** I2S Receive test start. Current free memory = %dbyte. ********\r\n", esp_get_free_heap_size()); for(;;) { // I2Sドライバからのイベント待ち i2s_event_t event_data; xQueueReceive(event_queue, &event_data, portMAX_DELAY); if (event_data.type == I2S_EVENT_RX_DONE) { // 1バッファ受信完了イベント size_t read_bytes; i2s_read(I2S_INDEX, read_buf, 1024 * 4 * 8, &read_bytes, 1000); printf("read_buf[0] = 0x%x byte=%d\r\n", read_buf[0], read_bytes); printf("read_buf[1] = 0x%x\r\n", read_buf[1]); } else if (event_data.type == I2S_EVENT_DMA_ERROR) { // DMAエラー発生イベント printf("!!!!!!!!!!!!!! I2S_EVENT_DMA_ERROR !!!!!!!!!!!!\r\n"); } } } |
この例では、1024✕8ごとにLchとRchの受信データを表示して確認します。
検証結果
PIC側から16Mbps(16bit/32Fs(0.5MSPS))で送信したデータを、正しく受信していることがログや波形でも確認できました。
16Mbps(16bit/32Fs(0.5MSPS))のI2S波形
BCK:16.7MHz
DATA
反射による乱れが見えていますが、BCKの立ち上がりで確実にDATAをサンプルできる状態であることが分かります。
1Mbps程度なら余裕ですね。
しかし!ドライバのコード内からログ出しするなどして調査した結果、残念なことに受信はできてもそれを処理する余裕がないことが判明。よって、このレートでWiFi送信できる可能性は無いことが、この時点で分かったのでした。。。
ん~、こんなんじゃ、いくらかハイスペックである ESP32-WROOM-32 を使っても、多分ムリだと思います(というくらい余裕がない…)
送信側がスレーブで ESP-WROOM-02 側がマスター
今更もういいかとも思ったんですが・・・
前途した ESP-WROOM-02 には BCKの生成に制約がある?説の話です。
ESP-WROOM-02 をマスター側にして16Mbps受信してみましたが、全然ダメでした。
1 2 3 4 5 6 7 8 9 10 11 12 | i2s_config_t i2s_config = { .mode = I2S_MODE_MASTER | I2S_MODE_RX, // マスターモードでデータ受信する .sample_rate = 5000 * 1000, // 受信時にも関係する? てか、そもそも5MSが最大ぽい .bits_per_sample = 16, .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, .communication_format = I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB, .dma_buf_count = 4, // 用意するDMA受信バッファ数 .dma_buf_len = 1024, // DMA受信バッファ長(32bitワード数) }; |
じゃあ送信ならどうなのかと、送信側に設定してI2Sの出力をオシロで観測すると、正しいデータが出ていません。なぜか、片チャンネルあたり8Bitになってしまいます。
SDKに付属しているI2Cのマスター送信サンプル(examples\peripherals\i2s)では、サンプルレートは36Kになっていますが、それだと正しく出力されることが確認できました。でも48Kにしただけで途端にダメになりました。何よそれって感じです。
ドライバ(i2s.c)のコードを見てみると、I2Sレジスタにクロック分周比を設定する箇所が次の様になっているんですが…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // Calculate the frequency division corresponding to the bit rate uint32_t scaled_base_freq = I2S_BASE_CLK / 32; float delta_best = scaled_base_freq; for (uint8_t i = 1; i < 64; i++) { for (uint8_t j = i; j < 64; j++) { float new_delta = abs(((float)scaled_base_freq / i / j) - rate); if (new_delta < delta_best) { delta_best = new_delta; bck_div = i; mclk_div = j; } } } |
指定されたレートに最も近くなるようにクロックを設定しようとしていますが、これってなんかおかしくないですかね?
レートはともかくフレーム周期が意図した通りにならない場合がありそうです。
また、I2Sレジスタへのクロック分周設定と、実際に出力される周波数の関係が、ここのフォーラム に書かれているんですが・・・
BCLK = CLK_I2S / I2S_BCK_DIV_NUM
WS = BCLK/ 2 / (16 + I2S_BITS_MOD)
Note that I2S_CLKM_DIV_NUM must be >5 for I2S data
サンプルコードにある36Kという低いレートならばこの通りになるんですが、高いレートをセットすると、この関係が崩れてしまうんですよね。
で、ドライバのソースを直接変更してレジスタへの設定を色々試してみましたが、結局、高レートだと正しい出力を得ることができず、これ以上探求することは諦めました。
ESP-WROOM-02 でI2Sループバック
ESP-WROOM-02 のI2S出力を入力側へ接続して、単体での送受信(ループバック)をやってみました。送信側をマスターとします。
DATA信号がUSBシリアルのTxDと競合するため抵抗を挿入しています。実行中はPC側から何か送信するわけではないんですが、フラッシュ書き込みの時のためです。
ソフト的には、普通は送受信処理を別々のタスクで実行することを考えると思いますが、DMAでバッファリングしてもらえるため、単一のタスク内で送信した後に受信するやり方でも、バッファオーバーフローしない限り大丈夫です。
そもそもRTOSでは、Windowsみたいにタイムスライスされるわけじゃないですからね。
なお、こちらでもBCKの制約により高レートではうまくいきませんでした。
WiFi送信速度の検証
16MbpsのレートではI2Sの受信だけで手一杯ということが既に判明していますが、WiFi送信のパフォーマンスについても今後のために検証しておくことにしました。
今回は、ESP-WROOM-02 がサーバーとなり、TCP送信するケースについて検証しています。後にUDPでもやっていますが、受信はやっていません。
送信プログラムは、シンプルにC言語のソケットを使いました。WiFi APへの接続後、クライアントの接続をacceptしたら、ダミーデータを送信します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | #include <stdio.h> #include <string.h> #include <sys/socket.h> #include "freertos/FreeRTOS.h" #include "freertos/event_groups.h" #include "esp_system.h" #include "esp_wifi.h" #include "esp_event_loop.h" #include "esp_log.h" #include "nvs_flash.h" #define WIFI_SSID CONFIG_WIFI_SSID #define WIFI_PASS CONFIG_WIFI_PASSWORD #define SERVER_PORT 5678 static const int WIFI_CONNECTED_BIT = BIT0; static const int WIFI_DISCONNECTED_BIT = BIT1; static const int WIFI_STOPPED_BIT = BIT2; static const char *TAG = "I2S Test"; #define LOG_FMT(x) "%s: " x, __func__ #define delay(x) vTaskDelay((x) / portTICK_RATE_MS) static void wifi_init_and_connect(); static esp_err_t event_handler(void *ctx, system_event_t *event); static void server(); static EventGroupHandle_t wifi_event_group; // ●メインエントリ void app_main(void) { printf("\nSDK version:%s\n\n", esp_get_idf_version()); // NVS初期化 ESP_ERROR_CHECK(nvs_flash_init()); // WiFi初期化と接続開始 wifi_init_and_connect(); // 接続待ち xEventGroupWaitBits(wifi_event_group, WIFI_CONNECTED_BIT, true, false, portMAX_DELAY); // サーバー処理(クライアントへ送信) server(); // 切断 ESP_ERROR_CHECK(esp_wifi_disconnect()); // 切断待ち xEventGroupWaitBits(wifi_event_group, WIFI_DISCONNECTED_BIT, true, false, portMAX_DELAY); // 停止 ESP_ERROR_CHECK(esp_wifi_stop()); // 停止待ち xEventGroupWaitBits(wifi_event_group, WIFI_STOPPED_BIT, true, false, portMAX_DELAY); printf("\n********** exit **********\n\n"); } // ●WiFi初期化と接続開始(Stationモード) static void wifi_init_and_connect() { tcpip_adapter_init(); wifi_event_group = xEventGroupCreate(); ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL)); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); wifi_config_t wifi_config; strcpy((char*)&wifi_config.sta.ssid, WIFI_SSID); strcpy((char*)&wifi_config.sta.password, WIFI_PASS); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start()); ESP_LOGI(TAG, "connect to ap SSID: %s password: %s", WIFI_SSID, WIFI_PASS); } // ●WiFiイベントハンドラ static esp_err_t event_handler(void *ctx, system_event_t *event) { switch(event->event_id) { case SYSTEM_EVENT_STA_START: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START:"); esp_wifi_connect(); break; case SYSTEM_EVENT_STA_CONNECTED: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_CONNECTED:"); break; case SYSTEM_EVENT_STA_GOT_IP: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP: %s", ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip)); xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT); break; case SYSTEM_EVENT_STA_LOST_IP: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_LOST_IP:"); break; case SYSTEM_EVENT_STA_DISCONNECTED: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED:"); xEventGroupClearBits(wifi_event_group, WIFI_CONNECTED_BIT); xEventGroupSetBits(wifi_event_group, WIFI_DISCONNECTED_BIT); break; case SYSTEM_EVENT_STA_STOP: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_STOP:"); xEventGroupSetBits(wifi_event_group, WIFI_STOPPED_BIT); break; default: ESP_LOGI(TAG, "SYSTEM_EVENT_????: event_id = %d", event->event_id); break; } return ESP_OK; } // ●サーバー処理 static void server() { // ソケット作成 int listener_fd = socket(PF_INET, SOCK_STREAM, 0); if (listener_fd < 0) { ESP_LOGE(TAG, LOG_FMT("error in socket (%d)"), errno); return; } // バインド struct sockaddr_in serv_addr; serv_addr.sin_family = PF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(SERVER_PORT); int ret = bind(listener_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); if (ret < 0) { ESP_LOGE(TAG, LOG_FMT("error in bind (%d)"), errno); close(listener_fd); return; } // リッスン開始 ret = listen(listener_fd, 1); if (ret < 0) { ESP_LOGE(TAG, LOG_FMT("error in listen (%d)"), errno); close(listener_fd); return; } // 接続待ち ESP_LOGI(TAG, "Waiting for connection... PORT = %d\n", SERVER_PORT); fd_set read_set; FD_ZERO(&read_set); FD_SET(listener_fd, &read_set); ret = select(listener_fd + 1, &read_set, NULL, NULL, NULL); if (ret < 0) { ESP_LOGE(TAG, LOG_FMT("error in select (%d)"), errno); close(listener_fd); return; } // 接続受け入れ struct sockaddr_in addr_from; socklen_t addr_from_len = sizeof(addr_from); int client_fd = accept(listener_fd, (struct sockaddr*)&addr_from, &addr_from_len); if (client_fd < 0) { ESP_LOGW(TAG, LOG_FMT("error in accept (%d)"), errno); close(listener_fd); return; } ESP_LOGI(TAG, "Accepted connection from %s, port=%d\n", inet_ntoa(addr_from.sin_addr), ntohs(addr_from.sin_port)); // データ送信 #define SEND_SIZE (1024 * 16) char* buf = malloc(SEND_SIZE); for(int i = 0; i < 1000; i++) { ret = write(client_fd, buf, SEND_SIZE); if (ret != SEND_SIZE) { ESP_LOGW(TAG, LOG_FMT("error in write (%d)"), errno); break; } } free(buf); close(client_fd); close(listener_fd); } |
受信側はPC。VisualStudioでMFCを使ったコードです。
開始ボタンが押されたら受信スレッドを立ち上げ、ESP-WROOM-02 へ接続、ダミーデータを受信して、パフォーマンス情報をメインウィンドウに表示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | void CRecvTesterDlg::ThreadProc() { UINT64 dwStart; UINT64 dwTotal; BYTE buf[1024]; delete m_pSocket; m_pSocket = new CSocket(); if (m_pSocket->Create()) { if (m_pSocket->Connect(_T("192.168.1.105"), 5678)) { dwStart = ::GetTickCount(); dwTotal = 0; while(!m_bBreak) { int nRecv = m_pSocket->Receive(buf, sizeof(buf)); if (nRecv <= 0) break; dwTotal += nRecv; CString strTotal; strTotal.Format(_T("%llu bytes"), dwTotal); m_EditInfo1.SendMessage(WM_SETTEXT, 0, (LPARAM)((LPCTSTR)strTotal)); UINT64 dwTime = ::GetTickCount() - dwStart; if (dwTime > 0) { CString strSpeed; strSpeed.Format(_T("%llu byte/sec"), dwTotal * 1000 / dwTime); m_EditInfo2.SendMessage(WM_SETTEXT, 0, (LPARAM)((LPCTSTR)strSpeed)); } } } m_pSocket->Close(); } } |
検証結果
当方の無線LAN環境では、
ESP-WROOM-02 → (IEEE802.11 b/g/n) → WiFI AP → (IEEE802.11 ac) → PC
になるんですが、これで1.6~4Mbps(200~500KByte/Sec)という結果でした。
ん~アップロードが不利なのは分かっていましたが、予想してたより遅いですね。
ESP-WROOM-02 をAPのすぐそばに置いてみましたが、さほど変わりませんでした。
一体、b/g/n のどれでつながっているんでしょうか・・・
UDPで試してみたところ、速度は若干上がりますがロストしてしまうデータが結構ありました。いくらUDPだからってこんなにパケット消失したっけ?てな感じです。
結論
I2Sの16Mbps受信自体は問題なくできましたが、他の処理をする余裕が無いです。
そして、WiFiの上りが結構遅い。WiFiのパフォーマンスを上げるための施策があるのかも知れませんが、いずれにせよ、I2Sで受信してそのままWiFiで送信するという処理を、16Mbpsでこなすのは無理という収穫があったので、今回はこれで終了です。