ESP-IDFを使ってみる

SoftAPモードとStationモード


こちら
にSoftAPモードのサンプルが公開されています。
ビルドするとmenuconfigが起動し、以下の画面が表示されます。
[Example Configuration]の項目でSoftAPモードの設定を行います。


SoftAPモードの時のSSIDとパスワードを指定します。


ビルドするとSoftAPモードで起動します。


app_mainを少し変更し、自分のMACアドレスを取得するコードと、vTaskList()を追加して裏で動いているタスクを確認してみま した。
void app_main()
{
    //Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG, "ESP_WIFI_MODE_AP");
    wifi_init_softap();

#if 1
    uint8_t sta_mac[6] = {0};
    uint8_t ap_mac[6] = {0};
    esp_wifi_get_mac(ESP_IF_WIFI_STA, sta_mac);
    esp_wifi_get_mac(ESP_IF_WIFI_AP, ap_mac);
    ESP_LOGI(pcTaskGetTaskName(0), "sta_mac:" MACSTR ,MAC2STR(sta_mac));
    ESP_LOGI(pcTaskGetTaskName(0), "ap_mac:" MACSTR ,MAC2STR(ap_mac));

    char buffer[512];
    vTaskList(buffer);
    ESP_LOGI(TAG,"\n%s",buffer);
#endif

vTaskList()を使うためにはmenuconfigを実行して、以下を有効にする必要が有ります。
menuconfigで定義を変更したときは、fullcleanを実行して全てのライブラリを再コンパイルする必要が有ります。


mainタスクは自分自身なので、システムタスクが10個、裏で動いていることが分かります。
また、Stationモード用のMACアドレスと、softAPモード用のMACアドレスは、別のアドレスであることが分かります。




こ ちらにStationモードのサンプルが公開されています。
ビルドするとmenuconfigが起動し、以下の画面が表示されます。
[Example Configuration]の項目でStationモードの設定を行います。


ここで指定するのはWiFiルーターのSSIDとパスワードです。


ビルドするとWiFiルーターに接続します。


こちらもapp_mainを少し変更し、自分のMACアドレスを取得するコードと、vTaskList()を追加して裏で動いているタスクを確認 してみました。
void app_main()
{
    //Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
      ESP_ERROR_CHECK(nvs_flash_erase());
      ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
    wifi_init_sta();

#if 1
    vTaskDelay(1000);
    uint8_t sta_mac[6] = {0};
    uint8_t ap_mac[6] = {0};
    esp_wifi_get_mac(ESP_IF_WIFI_STA, sta_mac);
    esp_wifi_get_mac(ESP_IF_WIFI_AP, ap_mac);
    ESP_LOGI(pcTaskGetTaskName(0), "sta_mac:" MACSTR ,MAC2STR(sta_mac));
    ESP_LOGI(pcTaskGetTaskName(0), "ap_mac:" MACSTR ,MAC2STR(ap_mac));

    char buffer[512];
    vTaskList(buffer);
    ESP_LOGI(TAG,"\n%s",buffer);
#endif
}

SoftAPモードの時と比べて、dportタスクが動いていません。
dportタスクはおそらくDHCPサーバータスクだと思います。




espressifの開発者に確認したら、ESP32チップは以下の4つのMACアドレスを持っていることが分かりました。
ステーション(STA)、アクセスポイント(AP)、BlueTooth(BT)、およびローカルエリアネットワーク(LAN)

それらのアドレス値は、以下の様に1ずつ増加します。
WIFI_STA MAC=base MAC
WIFI_AP MAC=base MAC+1
BT MAC=base MAC+2
LAN MAC=base MAC+3


STA MAC:xx-xx-xx-xx-xx-00
AP MAC:xx-xx-xx-xx-xx-01
BT MAC:xx-xx-xx-xx-xx-02
LAN MAC:xx-xx-xx-xx-xx-03

2つの無線LAN-I/Fと、1つのBlueTooth-I/Fと、1つの有線LAN-I/Fが内蔵されているWindowsパソコンと同じ構成 と言えます。
RaspberryPiでも1つの有線LANと、1つの無線LANと、1つのBlueToothしか持っていないので、ネットワーク に関しては贅沢なハード構成です。

STAモードのMACと、APモードのMACを別々に持っているので、STAモードを使った通信と、APモードを使った通信が同時に動きます。
ESP-IDFにはAPモード用のサンプルとSTAモード用のサンプルが含まれていますが、APSTAモードのサンプルが有りません。
APSTAで接続するときは少し手順が違います。
APSTAのサンプルコードをこちらで 公開しています。

こ ちらのサンプルコードでMACアドレスを確認することができます。
これはESP32の結果です。
Ethernetは同時に複数のI/Fを使うことが出来るので、少し変わった扱いとなっています。
I (338) BASE_MAC: Base MAC Address read from EFUSE BLK0
I (338) BASE_MAC: Using "0xc8, 0xc9, 0xa3, 0xcf, 0x10, 0xc4" as base MAC address
I (348) WIFI_STA MAC: 0xc8, 0xc9, 0xa3, 0xcf, 0x10, 0xc4
I (358) SoftAP MAC: 0xc8, 0xc9, 0xa3, 0xcf, 0x10, 0xc5
I (358) BT MAC: 0xc8, 0xc9, 0xa3, 0xcf, 0x10, 0xc6
I (368) Ethernet MAC: 0xc8, 0xc9, 0xa3, 0xcf, 0x10, 0xc7
I (378) Ethernet MAC: Overwrite Ethernet MAC
I (378) New Ethernet MAC: 0xc8, 0xc9, 0xa3, 0xcf, 0x10, 0xca

2019年に発表された、ESP32-S2チップにはBlueToothの機能が有りません。
これはESP32-S2の結果です。
ESP32-S2にはEMAC(Ethernet Media Access Controller)が搭載されていないので、
MII または RMII インターフェイスの有線 LAN モジュールは使えませんが、SPI インターフェイスの有線 LAN モジュールが使えるので、
Ethernet MACを持っています。
I (292) BASE_MAC: Base MAC Address read from EFUSE BLK0
I (302) BASE_MAC: Using "0x7c, 0xdf, 0xa1, 0x1, 0x69, 0xe4" as base MAC address
I (312) WIFI_STA MAC: 0x7c, 0xdf, 0xa1, 0x1, 0x69, 0xe4
I (312) SoftAP MAC: 0x7c, 0xdf, 0xa1, 0x1, 0x69, 0xe5
I (322) Ethernet MAC: 0x7e, 0xdf, 0xa1, 0x1, 0x69, 0xe5
I (322) Ethernet MAC: Overwrite Ethernet MAC
I (332) New Ethernet MAC: 0x7c, 0xdf, 0xa1, 0x1, 0x69, 0xea



ESP-IDFのV4.1から Network stack initialization のAPIが変わりました。
詳細はこ ちらに書かれています。

【原文】
Simply replace tcpip_adapter_init() with esp_netif_init().
Please note that the ESP-NETIF initialization API returns standard error code and the esp_netif_deinit() for un-initialization is available.
Also replace #include "tcpip_adapter.h" with #include "esp_netif.h".

【google君】
tcpip_adapter_init()をesp_netif_init()に置き換えるだけです。
ESP-NETIF初期化APIは標準エラーコードを返し、初期化を解除するためのesp_netif_deinit()が利用可能であることに 注意してください。
また、#include "tcpip_adapter.h"を#include "esp_netif.h"に置き換えます。

V4.4までは以下のコードで、V4.1とそれ以前のどちらでも動きます。
V5.0では完全にtcpip_adapter_init()は廃止され、コンパイルが通らなくなります。
void wifi_init_sta()
{
    s_wifi_event_group = xEventGroupCreate();

#if ESP_IDF_VERSION_MAJOR >= 4 && ESP_IDF_VERSION_MINOR >= 1
    ESP_LOGI(TAG,"ESP-IDF Ver4.1");
    ESP_ERROR_CHECK(esp_netif_init());

    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();
#else
    ESP_LOGI(TAG,"ESP-IDF Ver4.0");
    tcpip_adapter_init();

    ESP_ERROR_CHECK(esp_event_loop_create_default());
#endif

(後略)

また、WiFiイベントハンドラーのコードも変わっています。
こちらがV4.0までのイベントハンドラーです。
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;

/* The event group allows multiple bits for each event, but we only care about o
ne event
 * - are we connected to the AP with an IP? */
const int WIFI_CONNECTED_BIT = BIT0;

static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
            esp_wifi_connect();
            xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        }
        ESP_LOGI(TAG,"connect to the AP fail");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "got ip:%s",
                 ip4addr_ntoa(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

こちらがV4.1からのイベントハンドラーです。
所定の回数リトライしても接続できないときはWIFI_FAIL_BITを立てるようになりました。
これに伴いイベントの待ち方が変わっています。
詳細はこ ちらを参照してください。
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;

/* The event group allows multiple bits for each event, but we only care about two events:
 * - we are connected to the AP with an IP
 * - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT      BIT1

static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG,"connect to the AP fail");
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}



ESP-IDFのV4.1から Network stack initialization のAPIが変わり、tcpip_adapter から esp_netif になりました。
Simply replace tcpip_adapter_init() with esp_netif_init().
(tcpip_adapter_init()をesp_netif_init()に置き換えるだけ)
なんて書かれていますが、固定IPアドレスについては全く手順が変わっています。

こちらがtcpip_adapterの時の固定IPアドレスの設定です。
TCPIP_ADAPTER_IF_STAを使って、DHCPクライアントの停止と、IPアドレスの設定を行います。

DHCPクライアントを止めてしまうと、DNSサーバーのアドレスが分からなくなり、名前解決ができなくなります。
Googleは、アドレス8.8.8.8と8.8.4.4の2つのネームサーバー(Google Public DNS)を公開しています。
そこで、dns_setserver()を使ってこのネームサーバーを手動で登録しています。
固定IPアドレスとDHCPクライアントとDNSネームサーバーの関係はこちらに公 開されていました。
void wifi_init_sta()
{
    ESP_LOGI(TAG,"ESP-IDF tcpip_adapter");
    s_wifi_event_group = xEventGroupCreate();
    tcpip_adapter_init();

    ESP_ERROR_CHECK(esp_event_loop_create_default());

#if CONFIG_STATIC_IP

    //* Stop DHCP client */
    tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA);

    /* Set STATIC IP Address */
    tcpip_adapter_ip_info_t ipInfo;
    IP4_ADDR(&ipInfo.ip, 192, 168, 10, 100);
    IP4_ADDR(&ipInfo.gw, 192, 168, 10, 1);
    IP4_ADDR(&ipInfo.netmask, 255, 255, 255, 0);
    tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ipInfo);

    /*
    if we should not be using DHCP (for example we are using static IP addresses),
    then we need to instruct the ESP32 of the locations of the DNS servers manually.
    Google publicly makes available two name servers with the addresses of 8.8.8.8 and 8.8.4.4.
    */
    ip_addr_t d;
    d.type = IPADDR_TYPE_V4;
    d.u_addr.ip4.addr = 0x08080808; //8.8.8.8 Google Public DNS(Primary)
    dns_setserver(0, &d);
    d.u_addr.ip4.addr = 0x08080404; //8.8.4.4 Google Public DNS(Secondary)
    dns_setserver(1, &d);

#endif

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));

    wifi_config_t wifi_config = {
        .sta = {
            .ssid = EXAMPLE_ESP_WIFI_SSID,
            .password = EXAMPLE_ESP_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, "wifi_init_sta finished.");
    ESP_LOGI(TAG, "connect to ap SSID:%s password:%s",
             EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);

    // wait for IP_EVENT_STA_GOT_IP
    while(1) {
        /* Wait forever for WIFI_CONNECTED_BIT to be set within the event group.
           Clear the bits beforeexiting. */
        EventBits_t uxBits = xEventGroupWaitBits(s_wifi_event_group,
           WIFI_CONNECTED_BIT, /* The bits within the event group to waitfor. */
           pdTRUE,          /* WIFI_CONNECTED_BIT should be cleared before returning. */
           pdFALSE,          /* Don't waitfor both bits, either bit will do. */
           portMAX_DELAY);/* Wait forever. */
       if ( ( uxBits & WIFI_CONNECTED_BIT ) == WIFI_CONNECTED_BIT ){
           ESP_LOGI(TAG, "WIFI_CONNECTED_BIT");
           break;
       }
    }
    ESP_LOGI(TAG, "Got IP Address.");
}

esp_netifを使った固定IPアドレスのサンプルコードが、こ ちらに公開されています。
このサンプルでもDHCPを止めた後に、DNSを設定しています。



Stationモードでは、ルーターから払い出されるIPアドレスはリースされたアドレスなので、起動するタイミングでIPアドレスが変わる可能 性が有ります。
こ ちらにmDNSのサンプルが公開されています。
mDNSを使うとmDNSホスト名(hogehoge.local)による名前解決ができるようになります。

mDNSについては、github上で幾つかissuesが発生しています。
こちらに経 緯が公開されていますが、mDNSについては、沢山のパッチが当たっていることが分かります。
ESP-IDF V5からmDNSのコンポーネントは非標準となり、こ ちらのコンポーネントをダウンロードして使うように変更になりました。
これで、ESP-IDFのバージョンとは無関係に、コンポーネント独自にバージョンアップすることが可能になります。

mDNSのソースはこ ちらに公開されています。
ソースを見るとmDNSは以下の関数でタスクとして起動されていることが分かります。
static esp_err_t _mdns_service_task_start(void)
{
    if (!_mdns_service_semaphore) {
        _mdns_service_semaphore = xSemaphoreCreateMutex();
        if (!_mdns_service_semaphore) {
            return ESP_FAIL;
        }
    }
    MDNS_SERVICE_LOCK();
    if (_mdns_start_timer()) {
        MDNS_SERVICE_UNLOCK();
        return ESP_FAIL;
    }
    if (!_mdns_service_task_handle) {
        xTaskCreatePinnedToCore(_mdns_service_task, "mdns", MDNS_SERVICE_STACK_DEPTH, NULL, MDNS_TASK_PRIORITY,
                                (TaskHandle_t *const)(&_mdns_service_task_handle), MDNS_TASK_AFFINITY);
        if (!_mdns_service_task_handle) {
            _mdns_stop_timer();
            MDNS_SERVICE_UNLOCK();
            vSemaphoreDelete(_mdns_service_semaphore);
            _mdns_service_semaphore = NULL;
            return ESP_FAIL;
        }
    }
    MDNS_SERVICE_UNLOCK();
    return ESP_OK;
}

このタスクの優先度はデフォルトで1となっています。
高優先度のタスクが多数ある環境では、mDNSは正しく応答を返せない可能性が有ります。
mDNSのタスク優先度は、このメニューで変更することができます。


続く....