概要
ちょっと前に、ESP-NOWを使って複数の ESP-WROOM-02+温度センサーからの測定データを1台のESP32で受け、そのデータを ESP-WROOM-32(以下、ESP32)からインターネット上のサーバーに送ることができた、という話を書いた( ESP8266とESP32 温度測定にESP-NOWを使ってみる )。
ESP-NOWのスレーブとして動作するESP32側は、データ受信都度ごとにWiFiルーターに接続してからサーバーに送信するようにしていたので、WiFi.begin() して接続し送信が完了するまでの3~4秒の間はESP-NOWでの受信ができない時間帯になってしまった。この不感時間帯を短くするため、有線LANで接続しっぱなしにできないものかなと思っていた。もしかして有線LANでの送信中にESP-NOWが無効になるとしても、3秒ってことはないだろうということで。
ちょっと物色してみたところ、ArduinoとSPI接続することでEthernetを利用するためのモジュール(シールド)がいろいろあることが分かり、そのうちの一つのUSR-ES1 という製品を入手してESP32につないでみることにした。このモジュールには、Wiznet社のW5500 というデバイスが載っている。
結果として、標準的に結線しArduino IDEの「ライブラリの管理」からEthernet2 ライブラリ(1.0.4)を導入するだけで、ESP32からもきわめて容易に有線LANを利用できることが分かった。今回は転送レートを調べたり、高速化を試みたお話。
回路
基本的には、モジュールの各ピンをESP32のVSPI用のピンに接続するだけである。
秋月電子のAE-ESP-WROOM-32を使い、電源やリセットおよびブートのためのスイッチはブレッドボード上に用意した。ESP32とUSR-ES1間の接続は、SPI用の4本とハードウェアリセットのための1本で済む。
SPI接続のこと
ESP-WROOM-32には、3種類のSPI (内部フラッシュ用SPI, HSPI, VSPI) があって、今回のような単純な周辺接続の際には(機能する限りにおいて)空いているGPIOピンを使い、SPIライブラリにそれを教えればよい。ただ、SPIライブラリ内で、
SPIClass SPI(VSPI);
なんて宣言されているから、このSPIインスタンスを前提としたArduino用のライブラリを使い、なおかつライブラリを書き換えないで使いたい場合、ESP32のGPIOピンを以下のように読み換えて結線する。
- GPIO19 = SPI MISO
- GPIO23 = SPI MOSI
- GPIO18 = SPI SCLK
- GPIO5 = SPI SS
今回使ったEthernet2ライブラリ ( https://github.com/adafruit/Ethernet2 ) では、SS (Slave Select) の初期値が10 (つまりGPIO10) となっているが、スケッチで使いたいSSピンを init(); というメソッドで指定することができるので、特にライブラリに手をいれなくても接続できた。
HSPIを使うなら
もしもVSPIを別の周辺との接続に使っているなら、HSPIまたは空いているGPIOを組み合せて使うことになるが、HSPIを使うなら以下のように読み換える。
- GPIO12 = SPI MISO
- GPIO13 = SPI MOSI
- GPIO14 = SPI SCLK
- GPIO15 = SPI SS
そしてライブラリのソースから、SPI.begin(); とやっている箇所を探して以下のように書き換え、HSPIを使うことを教えてやる。
1 2 |
// SPI.begin(); SPI.begin(14, 12, 13, 15); |
さらに、ライブラリに対してSS用のピンが指定できるならば、GPIO15を使う旨を設定する。
GPIO15はESP32のブートモードを判別するためのストラッピングピンの一つなので内部でプルアップされているが、SPI SSとしての利用なら問題は起きないだろう。
電源のこと
USR-ES1のV33ピンにはケーブル未接続で60mA、1000BASE-Tのスイッチングハブにつながったケーブルを挿しただけで100mA程度、後述する転送実験中は120mA程度流れていた。いずれもテスター読みで過渡的な電流はより大きいと思われるが、データシートによると200mA以上の電源が必要と書いてある。
ESP32でWiFiを使うときには1Aは欲しいから、今回の構成ならば+3.3Vで1.5A流せる電源が欲しい。まだ確定していないのだが、今回は定電圧レギュレータ NJM2396F33に小型のヒートシンクを付けて使うことにした。
もしもESP32-DevKitCを使うならば、USR-ES1専用に(むろん、GNDは共通で)500mA程度流せる+3.3Vの定電圧レギュレータを用意した方がいいだろう。
GPIO2には注意
USR-ES1のRESETピンとの接続に、当初はGPIO2を使おうとしていた。なんとなく、番号の若い順に埋めていきたいから。ただ、このように接続した場合、GPIO0につないだSW1を押したままEN-SW(リセット)を押し、EN-SWを離してもダウンロードブートモードにならなくて焦った。データシートを調べてみると、GPIO2もストラッピングピンの1つで、ダウンロードブートモードとするためにはブート時にGPIO2 == L としておく必要があるとのこと。
GPIO2はESP32内部でプルダウンされているため、未接続ならばブート時にLとなるのだが、USR-ES1のRESETピンはモジュール内部で10KΩでプルアップされているようだ。そのためGPIO2につないでしまうとGPIO2 == H となってダウンロードブートモードにならなかったようである。では、GPIO0 == L && GPIO2 == Hのときどうなるのかは、データシートに明記されておらず不明。悔しいので(?) GPIO2にはエラーインジケーター用のLEDを接続することにした。
転送レートの測定
USR-ES1とESP32を組合せたとき、どの程度の転送レートなのかを調べるためのプログラムを書いてみた。おそらくHTTPで使うことになると思うので、ビットレートではなくTCP接続したときの転送バイト数を調べることにした。
測定は以下のような接続で行った。
1 2 3 4 5 |
PC (Win10) -----+ | ESP32(USR-ES1) -+- Switching HUB(1000BASE-T) --- WiFi Router | Note PC(Win 7) -+ |
デスクトップPC、ESP32(USR-ES1)、ノートPCは同じスイッチングハブに接続している。そして、そのハブは別のハブを介してWiFiルーターにも接続している。以下のような接続で転送速度を得てみた。
- デスクトップPC (有線送信)- ノートPC(受信)
Windows Socket APIを使ったプログラムを使った。送受信兼用で、受信側ノートPCではすべて同じプログラムを使用。デスクトップPCからの送信は参考データを得るために実施。 - ESP32(有線送信) – ノートPC(受信)
Ethernet2ライブラリのEtherClientクラスを使ったスケッチを使用し、ノートPCのWindowsプログラムで受ける。 - ESP32(無線送信) – ノートPC(受信)
これも参考用だが、典型的なWIFI_STAのスケッチを使った。WiFiルーター経由でノートPCに到達する。USR-ES1からLANケーブルを抜いて実施した。
調査のために、ある程度まとまったデータを送信したり受信したりするプログラムをWindows用とESP32用に作った。Windowsプログラムの送信側およびESP32用スケッチは、受信側のIPアドレスに指定のポートでTCP接続し、2048バイト長のバッファ単位に指定のバイト数送り終わるまで繰り返して送信する。
受信側は2048バイト受信するごとに内容を確認しつつ、やはり指定のバイト数に達するまで受信し続ける。指定バイト数の受信が終了すると1秒間あたりの受信バイト数を表示し、接続待ち (accept()) に戻る。
WindowsPCからの送信では100MBytes、ESP32からの送信では10MBytes転送した。バッファサイズを2048バイトとしたのは、Ethernet2ライブラリでのW5500の送受信バッファサイズの既定値が2048バイトなため。
なお受信側のノートPCでは、「セキュリティが強化されたWindowsファイアウォール」を無効にして実施した。なんでこんな名前にしちゃったんだろ。
調査結果
1. デスクトップPC(有線送信) – ノートPC
転送レートは平均約106.6MBytes/sec.。参考データを得るためと、久しぶりに書いたwinsockアプリケーションのテストのために実施した。同じ1000BASE-Tのスイッチングハブにつないでいるので、これくらいは出るだろう。
2. ESP32(有線送信) – ノートPC
転送レートは平均約0.45MBytes/sec.。UARTを使ったシリアル通信よりは格段に高速だが、LANを使っていることを考えるとちょっと物足りない。
2. ESP32(無線送信) – ノートPC
転送レートは平均約1.23MBytes/sec.。参考データとして、ESP32のWiFiを使ったスケッチを使って実行してみた。何か工夫すれば高速化できるのかもしれないが、今回のスケッチではこのような数字になった。
USR-ES1の高速化
Ethernet2ライブラリに手を入れて、以下のような高速化手法を試してみた。
- SPIクロックの周波数を大きくする
W5500Classではクロックが8MHzになっているので、より大きな値にしてみる。 - ライブラリ内でのSPIの使い方を変えてみる
W5500Class::write(); では、送信バッファの中身を1バイトずつSPI.transfer() しているのでバースト転送に書き換えてみる。
クロック周波数の変更
Ethernet2ライブラリ(1.0.4版)内のutility/w5500.cpp の25行目に、
SPISettings wiznet_SPI_settings(8000000, MSBFIRST, SPI_MODE0);
と書いてあるので、この8000000を24000000に変更し、ESP32 + USR-ES1用テストスケッチをビルドし直して実行したところ以下のようになった。
1 2 3 4 5 6 7 8 9 10 11 |
tart 1 : send 10485760 bytes completed. 15817 msec. 0.66 MBytes/sec start 2 : send 10485760 bytes completed. 15201 msec. 0.69 MBytes/sec start 3 : send 10485760 bytes completed. 15171 msec. 0.69 MBytes/sec start 4 : send 10485760 bytes completed. 15162 msec. 0.69 MBytes/sec start 5 : send 10485760 bytes completed. 15180 msec. 0.69 MBytes/sec start 6 : send 10485760 bytes completed. 15152 msec. 0.69 MBytes/sec start 7 : send 10485760 bytes completed. 15159 msec. 0.69 MBytes/sec start 8 : send 10485760 bytes completed. 15168 msec. 0.69 MBytes/sec start 9 : send 10485760 bytes completed. 15148 msec. 0.69 MBytes/sec start 10 : send 10485760 bytes completed. 15177 msec. 0.69 MBytes/sec average = 0.69 MBytes/sec |
平均転送レートは0.69MBytes/sec.となり、周波数変更前の0.45から50%高速化した。
現在のところ安定して動作するのは24MHzまでで、例えば32000000とするとclient.write() から帰って来なくなった。ブレッドボードでの配線が理由なのか別に理由があるのかは不明。
SPIバースト転送(的な)
周波数は24MHzのままで、w5500.cppの118行目あたりにある、write() メソッドの中身を以下のように変更してみた。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
uint16_t W5500Class::write(uint16_t _addr, uint8_t _cb, const uint8_t *_buf, uint16_t _len) { SPI.beginTransaction(wiznet_SPI_settings); setSS(); SPI.transfer(_addr >> 8); SPI.transfer(_addr & 0xFF); SPI.transfer(_cb); #if 0 for (uint16_t i=0; i<_len; i++){ SPI.transfer(_buf[i]); } #else SPI.writeBytes((uint8_t*)_buf, _len); #endif resetSS(); SPI.endTransaction(); return _len; } |
1バイトずつ送るのではなく、送信バッファ自体と中身のバイト数をSPIに渡す。そのあとどうするかはSPIライブラリにお任せするという方法。
ビルドして実行したところ以下のようになった。
1 2 3 4 5 6 7 8 9 10 11 |
start 1 : send 10485760 bytes completed. 7805 msec. 1.34 MBytes/sec start 2 : send 10485760 bytes completed. 7202 msec. 1.46 MBytes/sec start 3 : send 10485760 bytes completed. 7205 msec. 1.46 MBytes/sec start 4 : send 10485760 bytes completed. 7204 msec. 1.46 MBytes/sec start 5 : send 10485760 bytes completed. 7205 msec. 1.46 MBytes/sec start 6 : send 10485760 bytes completed. 7199 msec. 1.46 MBytes/sec start 7 : send 10485760 bytes completed. 7204 msec. 1.46 MBytes/sec start 8 : send 10485760 bytes completed. 7271 msec. 1.44 MBytes/sec start 9 : send 10485760 bytes completed. 7204 msec. 1.46 MBytes/sec start 10 : send 10485760 bytes completed. 7202 msec. 1.46 MBytes/sec average = 1.44 MBytes/sec |
平均転送レートは1.44MBytes/sec.となり、周波数変更前の0.45MBpsの3.2倍になった。受信側プログラムもエラーにならないので、送信バッファの内容は受信側に正しく渡されていると判断した。
きょうのまとめ
- 複数のESP-NOW端末(?)からのデータをインターネット上のサーバーに送るためのゲートウェイ的な機能を実現するため、有線LANモジュールを使ってみた。結果は良好なので、ESP-NOWのスレーブ側に組み込み、LANケーブルで外部接続用ルーターに直結する予定。
- USR-ES1のコントロール用にArduinoのEthernet2ライブラリをそのまま使えたので話が早くて助かった。ESP32への対応を確認したことにもなる。さらに数行の変更で転送レートも3倍以上となり、そこそこの転送量に対しも実用的な数字になった。
- 問題は電源である。まだ電源回路自体も仮組みの状態なこともあるだろうが、ESP32で有線LAN と WiFiを同時に使おうとすると、3V3への電圧が2.5V以下になってしばしばBrownout detector が動いてしまう。今回WiFiを使ったスケッチを動かすときには、USR-ES1からLANケーブルを抜いた。ESP-NOWと組み合わせる際にはそうもいかないので、さらなる精進が必要なようである。