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)でClock OscillatorのEnable/Disableを切り替えることができます。

ESP32とLAN8720 Ethernetボードとの接続もこ ちらのページに公開されています。
RMIインタフェースでは9本のGPIOを使いますが、使用できるGPIOは固定されていて変更することができません。
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をEthernetとWiFiのブリッジとして使用するサンプルが公開されています。
READMEを見ると分かりますが、ESP32がAPを提供し、Ethernet経由でInternetに接続します。
このサンプルコードは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の製品ページです。
開発元はどうもwireless-tag社の様で、こちらに製品 ページが有ります。
WT32-S1と言うwireless-tag社の独自モジュールが使われています。
使われているMPUはESP32-D0WDQ5です。
データシートがこ ちらに、回路図がこ ちらに公開されています。
回路図を見るとGPIO16がClock OscillatorのEnable/Disableに使われています。
このボードには、USB-TTL変換チップが実装されていないので、ファーム書き込み時にはUSB-TTL変換モジュールが別途必 要になります。
また、ボード上にResetボタンが有りません。ENピンとGNDをケーブルで繋いで、離すとリセットします。
ファーム書き込み時には、GPIO0とGNDをワイヤーケーブルで接続し、リセットすると書き込みモードになります。
ファーム実行時には、GPIO0とGNDのワイヤーケーブルを外して、リセットするとファームを実行します。

製品出荷時に、このようなファーム(2019年11月にビルドされたファーム)が書き込まれていて、
Ethernetケーブルを接続してリセットすると、直ぐにDHCP経由でIPアドレスを取得することができます。
I (29) boot: ESP-IDF 5d27367-dirty 2nd stage bootloader
I (29) boot: compile time 20:25:30
I (29) boot: Enabling RNG early entropy source...
I (34) boot: SPI Speed      : 40MHz
I (38) boot: SPI Mode       : DIO
I (42) boot: SPI Flash Size : 4MB
I (46) boot: Partition Table:
I (50) boot: ## Label            Usage          Type ST Offset   Length
I (57) boot:  0 nvs              WiFi data        01 02 0000d000 00004000
I (65) boot:  1 phy_init         RF data          01 01 00011000 00001000
I (72) boot:  2 factory          factory app      00 00 00020000 00200000
I (79) boot:  3 abup_fota        OTA data         01 00 00220000 00100000
I (87) boot: End of partition table
I (91) boot: Defaulting to factory image
I (96) ABUP: abup_bl_init_flash: app_offset = 0x20000, app_size = 0x200000
I (103) ABUP: abup_bl_init_flash: ota_offset = 0x220000, ota_size = 0x100000
I (111) ABUP: Delta file is not exist, exit.
I (116) esp_image: segment 0: paddr=0x00020020 vaddr=0x3f400020 size=0x3c6cc (247500) map
I (212) esp_image: segment 1: paddr=0x0005c6f4 vaddr=0x3ffbdb60 size=0x0391c ( 14620) load
I (218) esp_image: segment 2: paddr=0x00060018 vaddr=0x400d0018 size=0xeb954 (964948) map
I (557) esp_image: segment 3: paddr=0x0014b974 vaddr=0x3ffc147c size=0x00340 (   832) load
I (557) esp_image: segment 4: paddr=0x0014bcbc vaddr=0x40080000 size=0x00400 (  1024) load
I (564) esp_image: segment 5: paddr=0x0014c0c4 vaddr=0x40080400 size=0x175e8 ( 95720) load
I (626) boot: Loaded app from partition at offset 0x20000
I (626) boot: Disabling RNG early entropy source...
I (626) cpu_start: Pro cpu up.
I (630) cpu_start: Application information:
I (635) cpu_start: Project name:     ethernet_basic
I (640) cpu_start: App version:      5d27367-dirty
I (646) cpu_start: Compile time:     Nov 26 2019 20:24:44
I (652) cpu_start: ELF file SHA256:  b3b686af6ffc043e...
I (658) cpu_start: ESP-IDF:          5d27367-dirty
I (663) cpu_start: Starting app cpu, entry point is 0x4008113c
I (0) cpu_start: App cpu up.
I (674) heap_init: Initializing. RAM available for dynamic allocation:
I (681) heap_init: At 3FFAFF10 len 000000F0 (0 KiB): DRAM
I (687) heap_init: At 3FFB6388 len 00001C78 (7 KiB): DRAM
I (693) heap_init: At 3FFB9A20 len 00004108 (16 KiB): DRAM
I (699) heap_init: At 3FFBDB5C len 00000004 (0 KiB): DRAM
I (705) heap_init: At 3FFCFA48 len 000105B8 (65 KiB): DRAM
I (711) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (717) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (724) heap_init: At 400979E8 len 00008618 (33 KiB): IRAM
I (730) cpu_start: Pro cpu start user code
I (748) spi_flash: detected chip: generic
I (749) spi_flash: flash io: dio
I (749) cpu_start: Starting scheduler on PRO CPU.
I (0) cpu_start: Starting scheduler on APP CPU.
I (757) gpio: GPIO[16]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (767) gpio: GPIO[2]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (777) gpio: GPIO[32]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (787) eth_example: ~~~~~~ver:[1.2]~~~~abup_ota_flag = 1 ~~~~~
I (797) eth_example: ~~CFG_val = 1 ~~
I (807) eth_example: MAC:0c:b8:15:28:ae:f7
dhcp_type = 0x04
I (807) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (827) user_eth: eth_event_handler
I (827) user_eth: Ethernet Started
I (827) user_AT: user_AT_handler_task
I (6377) user_eth: eth_event_handler
I (6377) user_eth: Ethernet Link Up
I (7807) tcpip_adapter: eth ip: 192.168.0.202, mask: 255.255.255.0, gw: 192.168.0.1
I (7807) user_eth: Ethernet Got IP Address
I (7807) user_eth: ~~~~~~~~~~~
E (7807) event: system event loop not initialized via esp_event_loop_init
I (7807) user_eth: ETHIP:192.168.0.202
I (7817) user_eth: ETHMASK:255.255.255.0
I (7827) user_eth: ETHGW:192.168.0.1
I (7827) user_eth:

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






WT32-ETH01を使ってWiFiとEMAC 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]

こちらがLAN8720のベンチマークです。
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]



WT32-ETH01と同じメーカの製品に、WT32-ETH01-EVOが有ります。
こちらに製 品ページが有りますが、ESP32C3+DM9051の製品です。
ESP32C3にはEMACが搭載されていないので、SPI I/FのPHYが使われています。

続く...