ESP-IDFを使ってみる

Ethernet通信(EMAC)


ESP32はWiFiを内蔵しているのでEthernetは余り使い道がないかもしれませんが、
EMAC (Ethernet Media Access Controller) が搭載されていて、
MII または RMII インターフェイスによる有線 LAN モジュールの接続が可能です。

安価に入手できるRMII (Reduced Media Independent Interface)のモジュールとして
「LAN8720 ETH Board」が有ります。Aliで$2以下で入手できますます。

ただ、このモジュールを使うためには、モジュールの基盤に少し改造が必要になります。
改造といっても1本ジャンパーを追加するだけです。
こ ちらのページに写真が掲載されていますが、NCのピンとClock Oscillatorのclock-enableを接続するだけです。
これにより、外部からのトリガー(GPIO17のON/OFF)でモジュールのEnable/Disableを切り替えることができます。



ESP32とLAN8720 Ethernetボードとの接続もこ ちらのページに公開されています。
ESP32開発ボードの中には、GPIO00のポートがピンに出ていないボードが有りますが、
GPIO00が使える開発ボードが必要になります。
GPIO17にPullDown抵抗(初期値をLOWにする)、GPIO00にPullUp抵抗(初期値をHIGHにする)が必要と書かれていま す。
GPIO17をPullDownしておくと、LAN8720のClock OscillatorはDisableになります。
GPIO00はBootStrapでファーム起動時にはPullUpしておく必要が有ります。
これによりファームウェアは正常に起動し、起動後にGPIO17をONにして、Clock OscillatorをEnableにすることができます。



こ ちらにEthernet経由でIPアドレスを取得するサンプルが公開されています。
LAN8720を使う場合、menuconfigで以下の設定を行います。
ESP-IDF Version4.1から、Ethernet関連のメニュー項目が大幅に変わっています。


esp-idf Version4.3ではSPIとRMIIの設定が混在していましたが、Version4.4で整理されました。
GPIO17でClock Oscillatorのclock-enableを有効にします。


ファームをビルドして実行すると以下の様に、EthernetのLink Upが成功して、DHCPからIPアドレスを取得します。
これ以降はlwIPによるEthernet通信ができるようになります。


Linuxマシンからpingを打つと応答が有ります。




こ ちらのサンプルをEthernetを使って動かしてみました。


Ethernet Link Upが成功して、DHCPから192.168.10.120のアドレスを取得し、
SNTPサーバーとの時刻同期に成功しています。




こ ちら
にESP32をWiFiのリピータとして使用するサンプルが公開されています。
READMEを見ると分かりますが、ESP32がWiFiリピーターとなります。
このサンプルコードはESP-IDF V5.1までは、こ こに有りますが、V5.2からはこ ちらに移動しました。
LAN8720を使う場合、menuconfigで以下の設定を行います。


以下のSSIDがリピーターのSSIDとなります。


ビルドして実行すると以下の表示になります。


もう1台、ESP32を準備してこ ちらのサンプルを書き込みます。
menuconfigで接続先としてリピーターとなっているESP32を指定します。




ビルドして実行するとESP32をリピーターとして、NTPによる時刻合わせを行います。
WiFiリピーターとなっているESP32には以下の様に表示されます。


長時間連続してこのリピーターを使い続けると、時々ネットワークを見失います。
ESP-IDF Ver4.1になってものすごく安定しました。
Linuxマシンを使用してWiFiのパフォーマンスを調べてみました。
使用したLinuxマシンはOrangePi-PCで、MT7601UのUSB-WiFiとspeedtestツールを使いました。
どちらも親機にはAterm PA-WG2600HSを使っています。
こちらがNECのAterm WR8165N(300Mbps)をリピーターとして、親機に接続している状態でのパフォーマンスです。
Downloadの最大値は40Mbit/sぐらいです。
orangepi@orangepipc:~$ speedtest --simple
Ping: 35.01 ms
Download: 19.07 Mbit/s
Upload: 5.17 Mbit/s
orangepi@orangepipc:~$ speedtest --simple
Ping: 45.535 ms
Download: 39.95 Mbit/s
Upload: 6.71 Mbit/s
orangepi@orangepipc:~$ speedtest --simple
Ping: 38.972 ms
Download: 31.21 Mbit/s
Upload: 6.82 Mbit/s

こちらがesp32+LAN8720経由で、親機に接続したときのパフォーマンスです。
Downloadの最大値は21Mbit/sぐらいです。
スピードではかなわないですが、安定性の点では市販のWiFiルーター並みに安定して使えます。
orangepi@orangepipc:~$ speedtest --simple
Ping: 37.113 ms
Download: 21.10 Mbit/s
Upload: 7.06 Mbit/s
orangepi@orangepipc:~$ speedtest --simple
Ping: 34.573 ms
Download: 18.97 Mbit/s
Upload: 6.23 Mbit/s
orangepi@orangepipc:~$ speedtest --simple
Ping: 42.316 ms
Download: 15.46 Mbit/s
Upload: 6.59 Mbit/s

ESP-IDF V5.1からEthernetの初期化が驚くほど簡単になりました。
こちらがESP-IDF V5.0までのEthernetの初期化コードです。
PHYの種類ごとに初期化するコードが並んでいます。
static void initialize_ethernet(void)
{
    ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler, NULL));
    eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
    eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
    phy_config.phy_addr = CONFIG_EXAMPLE_ETH_PHY_ADDR;
    phy_config.reset_gpio_num = CONFIG_EXAMPLE_ETH_PHY_RST_GPIO;
#if CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET
    eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG();
    esp32_emac_config.smi_mdc_gpio_num = CONFIG_EXAMPLE_ETH_MDC_GPIO;
    esp32_emac_config.smi_mdio_gpio_num = CONFIG_EXAMPLE_ETH_MDIO_GPIO;
    esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
#if CONFIG_EXAMPLE_ETH_PHY_IP101
    esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config);
#elif CONFIG_EXAMPLE_ETH_PHY_RTL8201
    esp_eth_phy_t *phy = esp_eth_phy_new_rtl8201(&phy_config);
#elif CONFIG_EXAMPLE_ETH_PHY_LAN87XX
    esp_eth_phy_t *phy = esp_eth_phy_new_lan87xx(&phy_config);
#elif CONFIG_EXAMPLE_ETH_PHY_DP83848
    esp_eth_phy_t *phy = esp_eth_phy_new_dp83848(&phy_config);
#elif CONFIG_EXAMPLE_ETH_PHY_KSZ80XX
    esp_eth_phy_t *phy = esp_eth_phy_new_ksz80xx(&phy_config);
#endif
#elif CONFIG_ETH_USE_SPI_ETHERNET
    gpio_install_isr_service(0);
    spi_bus_config_t buscfg = {
        .miso_io_num = CONFIG_EXAMPLE_ETH_SPI_MISO_GPIO,
        .mosi_io_num = CONFIG_EXAMPLE_ETH_SPI_MOSI_GPIO,
        .sclk_io_num = CONFIG_EXAMPLE_ETH_SPI_SCLK_GPIO,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
    };
    ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ETH_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));

    spi_device_interface_config_t spi_devcfg = {
        .mode = 0,
        .clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
        .spics_io_num = CONFIG_EXAMPLE_ETH_SPI_CS_GPIO,
        .queue_size = 20
    };
#if CONFIG_EXAMPLE_USE_KSZ8851SNL
    eth_ksz8851snl_config_t ksz8851snl_config = ETH_KSZ8851SNL_DEFAULT_CONFIG(CONFIG_EXAMPLE_ETH_SPI_HOST, &spi_devcfg);
    ksz8851snl_config.int_gpio_num = CONFIG_EXAMPLE_ETH_SPI_INT_GPIO;
    esp_eth_mac_t *mac = esp_eth_mac_new_ksz8851snl(&ksz8851snl_config, &mac_config);
    esp_eth_phy_t *phy = esp_eth_phy_new_ksz8851snl(&phy_config);
#elif CONFIG_EXAMPLE_USE_DM9051
    eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(CONFIG_EXAMPLE_ETH_SPI_HOST, &spi_devcfg);
    dm9051_config.int_gpio_num = CONFIG_EXAMPLE_ETH_SPI_INT_GPIO;
    esp_eth_mac_t *mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config);
    esp_eth_phy_t *phy = esp_eth_phy_new_dm9051(&phy_config);
#elif CONFIG_EXAMPLE_USE_W5500
    eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(CONFIG_EXAMPLE_ETH_SPI_HOST, &spi_devcfg);
    w5500_config.int_gpio_num = CONFIG_EXAMPLE_ETH_SPI_INT_GPIO;
    esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
    esp_eth_phy_t *phy = esp_eth_phy_new_w5500(&phy_config);
#endif
#endif // CONFIG_ETH_USE_SPI_ETHERNET
    esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy);
    config.stack_input = pkt_eth2wifi;
    ESP_ERROR_CHECK(esp_eth_driver_install(&config, &s_eth_handle));
#if !CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET
    /* The SPI Ethernet module might doesn't have a burned factory MAC address, we cat to set it manually.
       02:00:00 is a Locally Administered OUI range so should not be used except when testing on a LAN under your control.
    */
    ESP_ERROR_CHECK(esp_eth_ioctl(s_eth_handle, ETH_CMD_S_MAC_ADDR, (uint8_t[]) {
        0x02, 0x00, 0x00, 0x12, 0x34, 0x56
    }));
#endif
    bool eth_promiscuous = true;
    esp_eth_ioctl(s_eth_handle, ETH_CMD_S_PROMISCUOUS, &eth_promiscuous);
    esp_eth_start(s_eth_handle);
}

こちらがESP-IDF V5.1からのEthernetの初期化コードです。
なんかものすごく簡単になっています。
static void initialize_ethernet(void)
{
    uint8_t eth_port_cnt = 0;
    esp_eth_handle_t *eth_handles;
    ESP_ERROR_CHECK(example_eth_init(&eth_handles, &eth_port_cnt));
    if (eth_port_cnt > 1) {
        ESP_LOGW(TAG, "multiple Ethernet devices detected, the first initialized is to be used!");
    }
    s_eth_handle = eth_handles[0];
    free(eth_handles);
    ESP_ERROR_CHECK(esp_eth_update_input_path(s_eth_handle, pkt_eth2wifi, NULL));
    bool eth_promiscuous = true;
    ESP_ERROR_CHECK(esp_eth_ioctl(s_eth_handle, ETH_CMD_S_PROMISCUOUS, &eth_promiscuous));
    ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler, NULL));
    ESP_ERROR_CHECK(esp_eth_start(s_eth_handle));
}



esp-idfのVersion4.3あたりから、RMIIのサポートデバイスにKSZ8041が追加されました。
チップは簡単に入手できますが、これを使ったBreakOutモジュールは見たことが有りません。


esp-idfのVersion4.4あたりから、RMIIのサポートデバイスにKSZ8081が追加されました。
これもBreakOutモジュールは見たことが有りません。


esp-idfのVersion5.0あたりから、KSZ8041/8081がKSZ80xxに統合されました。




WT32-ETH01というESP32とLAN8720Aを統合した開発ボードがSeeedStudioから販売されています。
こ ちらがSeeedStudioの製品ページです。
WT32-S1と言うSeeedStudioの独自モジュールが使われています。
使われているMPUはESP32-D0WDQ5です。
データシートがこ ちらに、回路図がこ ちらに公開されています。
回路図を見るとGPIO16がClock OscillatorのEnable/Disableに使われています。
このボードには、USB-TTL変換チップが実装されていないので、ファーム書き込み時にはUSB-TTL変換モジュールが別途必 要になります。
また、ボード上にResetボタンが有りません。ENピンとGNDをケーブルで繋いで、離すとリセットします。
ファーム書き込み時には、GPIO0とGNDをワイヤーケーブルで接続し、リセットすると書き込みモードになります。
ファーム実行時には、GPIO0とGNDのワイヤーケーブルを外して、リセットするとファームを実行します。

以下の設定でこ ちらのサンプルが動きます。






WT32-ETH01を使ってWiFiとEthernetのTCP Socket通信のベンチマークを計ってみました。
1024バイトを1パケットとして、10MByteになるまで連続して送信しました。
こちらがWiFiのベンチマークです。
I (23861) TCP-CLIENT: All done. transed_bytes=10485760
I (23861) TCP-CLIENT: elapsed time[ms]:12340
I (23861) TCP-CLIENT: transferRate=0.833333[MB/Sec]

こちらがEthernetのベンチマークです。
WiFiに比べ2.5倍程度早いです。
I (7938) TCP-CLIENT: All done. transed_bytes=10485760
I (7938) TCP-CLIENT: elapsed time[ms]:4470
I (7938) TCP-CLIENT: transferRate=2.500000[MB/Sec]

続く...