LILYGOのT-Internet-COMでESP32でLAN接続

概要

上記でRJ45でLAN接続できるESP32ボードを調べましたが、書き込み機が不要で一番安そうだったT-Internet-COMを入手して、LAN接続をいろいろ実験してみました。

製品

https://s.click.aliexpress.com/e/_DC4duI5

上記の製品で送料込みの3千円以下で購入できると思います。他にもいろいろなボードがあるのですが書き込みきが別途必要だったり、技適が怪しかったりと使いにくいものが多いです。ただし、国内での販売はないと思いますので入手性は悪いです。

ATOMIC W5500搭載PoEベース
ATOMシリーズ向けに設計されたW5500搭載のイーサネットコントローラです。別電源不要で、PoEハブ/スイッチから直接給電でき、使用環境を効率的に簡素化できます。

入手性を考えると上記の「ATOMIC W5500搭載PoEベース」に「ATOM Lite」を組み合わせたものが利用しやすいと思います。

ただ、合計で4,466円ぐらいとちょっと高くなります。これはLANケーブル一本で通信と給電の両方可能なPoEに対応しているためだと思われます。Core用のLANモジュールは2千円ぐらいなので、PoE対応で千円ぐらい価格があがります。

上記が届いた製品になります。

中身はピンヘッダと本体ボードのみになります。

本体です。右下に本当はLTEモジュールなどを接続するコネクタがあります。今回はLAN接続可能なESP32として利用します。

このボードはPSRAM搭載の技適付きESP32-WROVER-Eモジュールを採用しているので安心して利用することができます。

逆側の写真です。

裏側です。

Ethernetへの接続

GitHub - Xinyuan-LilyGO/T-Internet-COM
Contribute to Xinyuan-LilyGO/T-Internet-COM development by creating an account on GitHub.

上記に公式の情報がまとまっていました。

#include <ETH.h>
#include <HTTPClient.h>

#define MY_ETH_ADDR 0
#define MY_ETH_POWER_PIN 4
#define MY_ETH_MDC_PIN 23
#define MY_ETH_MDIO_PIN 18
#define MY_ETH_TYPE ETH_PHY_LAN8720
#define MY_ETH_CLK_MODE ETH_CLOCK_GPIO0_OUT

static bool eth_connected = false;

void WiFiEvent(WiFiEvent_t event) {
  switch (event) {
    case ARDUINO_EVENT_ETH_START:
      Serial.println("ETH Started");
      //set eth hostname here
      ETH.setHostname("esp32-ethernet");
      break;

    case ARDUINO_EVENT_ETH_CONNECTED:
      Serial.println("ETH Connected");
      break;

    case ARDUINO_EVENT_ETH_GOT_IP:
      Serial.print("ETH MAC: ");
      Serial.print(ETH.macAddress());
      Serial.print(", IPv4: ");
      Serial.print(ETH.localIP());
      if (ETH.fullDuplex()) {
        Serial.print(", FULL_DUPLEX");
      }
      Serial.print(", ");
      Serial.print(ETH.linkSpeed());
      Serial.println("Mbps");
      eth_connected = true;
      break;

    case ARDUINO_EVENT_ETH_DISCONNECTED:
      Serial.println("ETH Disconnected");
      eth_connected = false;
      break;

    case ARDUINO_EVENT_ETH_STOP:
      Serial.println("ETH Stopped");
      eth_connected = false;
      break;

    default:
      break;
  }
}

void testClient(const char* url) {
  WiFiClient client;
  HTTPClient http;

  Serial.printf("[HTTP] GET %s\n", url);
  if (http.begin(client, url)) {
    int httpCode = http.GET();
    if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
      String payload = http.getString();
      Serial.println(payload);
    } else {
      Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
    }
    http.end();
  } else {
    Serial.printf("[HTTP] Unable to connect\n");
  }

  Serial.println("closing connection\n");
  client.stop();
}

void setup() {
  Serial.begin(115200);
  WiFi.onEvent(WiFiEvent);
  ETH.begin(MY_ETH_ADDR, MY_ETH_POWER_PIN, MY_ETH_MDC_PIN, MY_ETH_MDIO_PIN, MY_ETH_TYPE, MY_ETH_CLK_MODE);
}

void loop() {
  if (eth_connected) {
    testClient("http://httpbin.org/ip");
  }
  delay(10000);
}

上記のコードで接続できます。

#define MY_ETH_ADDR 0
#define MY_ETH_POWER_PIN 4
#define MY_ETH_MDC_PIN 23
#define MY_ETH_MDIO_PIN 18
#define MY_ETH_TYPE ETH_PHY_LAN8720
#define MY_ETH_CLK_MODE ETH_CLOCK_GPIO0_OUT

接続に必要なパラメータを先頭で宣言しています。ボードによってはArduino IDEでボード選択をすると自動的に宣言されて、なにもパラメーターを設定しなくてもETH.begin()で動くようになっていますが、日本で入手性のよいボードはあまりないと思います。

void WiFiEvent(WiFiEvent_t event) {
  switch (event) {
    case ARDUINO_EVENT_ETH_START:
      Serial.println("ETH Started");
      //set eth hostname here
      ETH.setHostname("esp32-ethernet");
      break;

    case ARDUINO_EVENT_ETH_CONNECTED:
      Serial.println("ETH Connected");
      break;

    case ARDUINO_EVENT_ETH_GOT_IP:
      Serial.print("ETH MAC: ");
      Serial.print(ETH.macAddress());
      Serial.print(", IPv4: ");
      Serial.print(ETH.localIP());
      if (ETH.fullDuplex()) {
        Serial.print(", FULL_DUPLEX");
      }
      Serial.print(", ");
      Serial.print(ETH.linkSpeed());
      Serial.println("Mbps");
      eth_connected = true;
      break;

    case ARDUINO_EVENT_ETH_DISCONNECTED:
      Serial.println("ETH Disconnected");
      eth_connected = false;
      break;

    case ARDUINO_EVENT_ETH_STOP:
      Serial.println("ETH Stopped");
      eth_connected = false;
      break;

    default:
      break;
  }
}

上記はイベントからIPアドレスなどを表示しているだけですのであまり必要ではありません。一応接続と切断でフラグを更新していますので、未接続のときには送信しない設定にはなっています。

void testClient(const char* url) {
  WiFiClient client;
  HTTPClient http;

  Serial.printf("[HTTP] GET %s\n", url);
  if (http.begin(client, url)) {
    int httpCode = http.GET();
    if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
      String payload = http.getString();
      Serial.println(payload);
    } else {
      Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
    }
    http.end();
  } else {
    Serial.printf("[HTTP] Unable to connect\n");
  }

  Serial.println("closing connection\n");
  client.stop();
}

上記は単にHTTPで通信をして、結果を表示するロジックです。

void setup() {
  Serial.begin(115200);
  WiFi.onEvent(WiFiEvent);
  ETH.begin(MY_ETH_ADDR, MY_ETH_POWER_PIN, MY_ETH_MDC_PIN, MY_ETH_MDIO_PIN, MY_ETH_TYPE, MY_ETH_CLK_MODE);
}

setupはシリアルと、イベントのコールバック登録、ETHの開始です。

void loop() {
  if (eth_connected) {
    testClient("http://httpbin.org/ip");
  }
  delay(10000);
}

loopはEthが接続されている場合には接続元IPを表示するページを取得しています。

Wi-FiとEthernetの同時利用

#include <ETH.h>
#include <HTTPClient.h>

#define MY_ETH_ADDR 0
#define MY_ETH_POWER_PIN 4
#define MY_ETH_MDC_PIN 23
#define MY_ETH_MDIO_PIN 18
#define MY_ETH_TYPE ETH_PHY_LAN8720
#define MY_ETH_CLK_MODE ETH_CLOCK_GPIO0_OUT

static bool eth_connected = false;

void WiFiEvent(WiFiEvent_t event) {
  switch (event) {
    case ARDUINO_EVENT_ETH_START:
      Serial.println("ETH Started");
      //set eth hostname here
      ETH.setHostname("esp32-ethernet");
      break;

    case ARDUINO_EVENT_ETH_CONNECTED:
      Serial.println("ETH Connected");
      break;

    case ARDUINO_EVENT_ETH_GOT_IP:
      Serial.print("ETH MAC: ");
      Serial.print(ETH.macAddress());
      Serial.print(", IPv4: ");
      Serial.print(ETH.localIP());
      if (ETH.fullDuplex()) {
        Serial.print(", FULL_DUPLEX");
      }
      Serial.print(", ");
      Serial.print(ETH.linkSpeed());
      Serial.println("Mbps");
      eth_connected = true;
      break;

    case ARDUINO_EVENT_ETH_DISCONNECTED:
      Serial.println("ETH Disconnected");
      eth_connected = false;
      break;

    case ARDUINO_EVENT_ETH_STOP:
      Serial.println("ETH Stopped");
      eth_connected = false;
      break;

    default:
      break;
  }
}

void testClient(const char* url) {
  WiFiClient client;
  HTTPClient http;

  Serial.printf("[HTTP] GET %s\n", url);
  if (http.begin(client, url)) {
    int httpCode = http.GET();
    if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
      String payload = http.getString();
      Serial.println(payload);
    } else {
      Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
    }
    http.end();
  } else {
    Serial.printf("[HTTP] Unable to connect\n");
  }

  Serial.println("closing connection\n");
  client.stop();
}

void setup() {
  Serial.begin(115200);
  WiFi.onEvent(WiFiEvent);
  ETH.begin(MY_ETH_ADDR, MY_ETH_POWER_PIN, MY_ETH_MDC_PIN, MY_ETH_MDIO_PIN, MY_ETH_TYPE, MY_ETH_CLK_MODE);

  WiFi.begin();
  while (!WiFi.isConnected()) {
    delay(1000);
  }
  Serial.print("WIFI MAC: ");
  Serial.print(WiFi.macAddress());
  Serial.print(", IPv4: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  testClient("http://httpbin.org/ip");
  delay(10000);
}

ほぼ変わっていませんが、Wi-FiとEthernetを同時に接続してみました。

void setup() {
  Serial.begin(115200);
  WiFi.onEvent(WiFiEvent);
  ETH.begin(MY_ETH_ADDR, MY_ETH_POWER_PIN, MY_ETH_MDC_PIN, MY_ETH_MDIO_PIN, MY_ETH_TYPE, MY_ETH_CLK_MODE);

  WiFi.begin();
  while (!WiFi.isConnected()) {
    delay(1000);
  }
  Serial.print("WIFI MAC: ");
  Serial.print(WiFi.macAddress());
  Serial.print(", IPv4: ");
  Serial.println(WiFi.localIP());
}

setupにWi-Fiへの接続を追加しただけになります。このときEthernetとWi-Fiで同じネットワークに接続すると区別が付きませんので、Wi-Fiは携帯電話のテザリングなどを利用して別のグローバルIPになるようにすると実験がしやすいと思います。

Wi-FiEthernetGIP
接続接続Wi-FiのGIP
接続未接続Wi-FiのGIP
未接続接続EthernetのGIP
未接続未接続通信エラー

上記の結果となりました。携帯でテザリングのオンオフをしたり、LANケーブルの抜き出しをしても自動的に再接続してくれています。ただし、Wi-Fiの優先順位が高いのが意外でした。

接続方法route_prio
ESP_NETIF_INHERENT_DEFAULT_WIFI_STA100
ESP_NETIF_INHERENT_DEFAULT_ETH50
ESP_NETIF_INHERENT_DEFAULT_PPP20
ESP_NETIF_INHERENT_DEFAULT_SLIP16
ESP_NETIF_INHERENT_DEFAULT_OPENTHREAD15
ESP_NETIF_INHERENT_DEFAULT_WIFI_AP10

調べたところ、上記の優先順位になっていました。route_prioが大きい接続から順にためして通信をしています。一番高いのはWi-FiのステーションモードですのでWi-Fiアクセスポイントに接続している場合には基本的にはWi-Fi経由での通信になります。

次のETHがEthernetで、ダイアルアップなどのPPP、シリアルでTCP/IPの通信をするためのSLIP、ホームIoTなどで利用されるThreadプロトコルのオープンソース実装であるOpenThread、ESP32がアクセスポイントになるWi-Fi APモードの順で送信するようでした。

受信する場合には接続IFのIPアドレスでバインドしておけば使い分けができるみたいですが、未検証です。

インターフェースの使い分け

#include <ETH.h>
#include <HTTPClient.h>
#include <lwip/netif.h>

#define MY_ETH_ADDR 0
#define MY_ETH_POWER_PIN 4
#define MY_ETH_MDC_PIN 23
#define MY_ETH_MDIO_PIN 18
#define MY_ETH_TYPE ETH_PHY_LAN8720
#define MY_ETH_CLK_MODE ETH_CLOCK_GPIO0_OUT

static bool eth_connected = false;

void WiFiEvent(WiFiEvent_t event) {
  switch (event) {
    case ARDUINO_EVENT_ETH_START:
      Serial.println("ETH Started");
      //set eth hostname here
      ETH.setHostname("esp32-ethernet");
      break;

    case ARDUINO_EVENT_ETH_CONNECTED:
      Serial.println("ETH Connected");
      break;

    case ARDUINO_EVENT_ETH_GOT_IP:
      Serial.print("ETH MAC: ");
      Serial.print(ETH.macAddress());
      Serial.print(", IPv4: ");
      Serial.print(ETH.localIP());
      if (ETH.fullDuplex()) {
        Serial.print(", FULL_DUPLEX");
      }
      Serial.print(", ");
      Serial.print(ETH.linkSpeed());
      Serial.println("Mbps");
      eth_connected = true;
      break;

    case ARDUINO_EVENT_ETH_DISCONNECTED:
      Serial.println("ETH Disconnected");
      eth_connected = false;
      break;

    case ARDUINO_EVENT_ETH_STOP:
      Serial.println("ETH Stopped");
      eth_connected = false;
      break;

    default:
      break;
  }
}

void testClient(const char* url) {
  WiFiClient client;
  HTTPClient http;

  Serial.printf("[HTTP] GET %s\n", url);
  if (http.begin(client, url)) {
    int httpCode = http.GET();
    if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_MOVED_PERMANENTLY) {
      String payload = http.getString();
      Serial.println(payload);
    } else {
      Serial.printf("[HTTP] GET... failed, error: %s\n", http.errorToString(httpCode).c_str());
    }
    http.end();
  } else {
    Serial.printf("[HTTP] Unable to connect\n");
  }

  Serial.println("closing connection\n");
  client.stop();
}

void setup() {
  Serial.begin(115200);
  WiFi.onEvent(WiFiEvent);
  ETH.begin(MY_ETH_ADDR, MY_ETH_POWER_PIN, MY_ETH_MDC_PIN, MY_ETH_MDIO_PIN, MY_ETH_TYPE, MY_ETH_CLK_MODE);

  WiFi.begin();
  while (!WiFi.isConnected()) {
    delay(1000);
  }
  Serial.print("WIFI MAC: ");
  Serial.print(WiFi.macAddress());
  Serial.print(", IPv4: ");
  Serial.println(WiFi.localIP());
}

void loop() {
  if (netif_default) {
    Serial.printf("Default Interface: %c%c%d (" IPSTR "/" IPSTR " gateway " IPSTR ")\n",
                  netif_default->name[0], netif_default->name[1], netif_default->num,
                  IP2STR(&netif_default->ip_addr.u_addr.ip4),
                  IP2STR(&netif_default->netmask.u_addr.ip4),
                  IP2STR(&netif_default->gw.u_addr.ip4));
  } else {
    Serial.println("No default interface\n");
  }

  for (struct netif* pri = netif_list; pri != NULL; pri = pri->next) {
    netif_set_default(pri);
    Serial.printf("Set Interface: %c%c%d (" IPSTR "/" IPSTR " gateway " IPSTR ")\n",
                  pri->name[0], pri->name[1], pri->num,
                  IP2STR(&pri->ip_addr.u_addr.ip4),
                  IP2STR(&pri->netmask.u_addr.ip4),
                  IP2STR(&pri->gw.u_addr.ip4));
    testClient("http://httpbin.org/ip");
  }

  delay(10000);
}

自動設定の優先順位ではなく、自分で送信に利用したいIFを指定する方法を調べてみました。netif_set_default()で指定が可能なようです。

  if (netif_default) {
    Serial.printf("Default Interface: %c%c%d (" IPSTR "/" IPSTR " gateway " IPSTR ")\n",
                  netif_default->name[0], netif_default->name[1], netif_default->num,
                  IP2STR(&netif_default->ip_addr.u_addr.ip4),
                  IP2STR(&netif_default->netmask.u_addr.ip4),
                  IP2STR(&netif_default->gw.u_addr.ip4));
  } else {
    Serial.println("No default interface\n");
  }

netif_defaultに現在指定されているIFが設定されています。

  for (struct netif* pri = netif_list; pri != NULL; pri = pri->next) {
    netif_set_default(pri);
    Serial.printf("Set Interface: %c%c%d (" IPSTR "/" IPSTR " gateway " IPSTR ")\n",
                  pri->name[0], pri->name[1], pri->num,
                  IP2STR(&pri->ip_addr.u_addr.ip4),
                  IP2STR(&pri->netmask.u_addr.ip4),
                  IP2STR(&pri->gw.u_addr.ip4));
    testClient("http://httpbin.org/ip");
  }

netif_listにIFの一覧がリスト構造で保存されています。リストは優先順位が高いものから入っていました。

Default Interface: st2 (192.168.61.19/255.255.255.0 gateway 192.168.61.80)

実行結果ですが、デフォルトは上記のように出力されます。ただし、最後に設定したIFがデフォルトになりますので、本当のデフォルトではなく現在選択されているIFとなります。

Set Interface: st2 (192.168.61.19/255.255.255.0 gateway 192.168.61.80)
[HTTP] GET http://httpbin.org/ip
{
  "origin": "携帯電話のGIP"
}

closing connection

Set Interface: en1 (192.168.1.134/255.255.252.0 gateway 192.168.1.1)
[HTTP] GET http://httpbin.org/ip
{
  "origin": "Wi-Fi環境のGIP"
}

closing connection

Set Interface: lo0 (127.0.0.1/255.0.0.0 gateway 127.0.0.1)
[HTTP] GET http://httpbin.org/ip
[HTTP] GET... failed, error: connection refused
closing connection

上記がIFの一覧と、変更してアクセス元グローバルIPを表示した結果となります。ちゃんと使い分けができているのですが、最後にlo0という、ローカルIFがありました。もちろん外部には接続できないのですが、IFが無い状態にはならないようですね。

まとめ

本来は上記のようにMini PCIeのLTEモジュールを接続して、UARTで通信をするためのボードなのですがLANだけ使っても素直で安心して使えるボードだと思います。少ないですがGPIOも何個か外にでていますので組み込みなどにも使いやすそうです。

用途としてはESP32で作ったセンサーなどからESP-NOWなどを受信して、Ethernet経由でMTQQサーバーにデータを送信するようなブリッジ的なものに適していそうです。

コメント