乾電池駆動に適した ESP32 の WiFi 接続サンプルを紹介します.
はじめに
ESP32 で WiFi 接続するサンプルはすでにいろいろありますが,ESP32 の特長の 1 つである,低消費電力に着目した実用的なサンプルはまだ無いようです.そこで,消費電力の小ささを活かせるコードを紹介します.
無線 LAN に定期的に接続する使用形態の場合,消費電力≒無線LAN通信時間,となりますので接続時間の最小化を行っています.
ポイント
接続時間を短くするために工夫した点は以下のとおりです.最初の 4 つは,『ESP32 の無線 LAN 通信の電力削減』で紹介した内容になります.
- CPU 周波数
- CPU 周波数をデフォルトの 160Mhz から 240MHz に変更.sdkconfig にて設定.
- 切断処理
- 通信が終わったら WiFi を切断し,次回接続時にスムーズにつながるようにします.
wifi_stop()
関数にて処理. - IP アドレスチェック
- DHCP から通知された IP アドレスがすでに使われていないかチェックする処理を省略.sdkconfig にて設定.
- コンパイラオプション
- リリースオプションでコンパイルするようにします.sdkconfig にて設定.
- ビジーループ削除
- WiFi の接続待ちをビジーループではなくセマフォで行います.
wifi_connect()
関数にて処理.
また,実用性をあげるために下記の工夫も行いました.
- エラー時のリセット防止
- 独自のエラーチェックマクロ
ERROR_RETURN
を定義することで,エラーチェックを行いつつもリセットしないようにします.
コード
コード全体は github に上げてあります.
プログラム本体のコードを以下に転載します.なお,コンパイルするには,無線 LAN アクセスポイントの情報を記載した wifi_config.h を作成していただく必要があります.ソースのコメントを参照願います.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
#include "nvs_flash.h" #define LOG_LOCAL_LEVEL ESP_LOG_INFO #include "esp_log.h" #include "esp_event_loop.h" #include "esp_spi_flash.h" #include "esp_wifi.h" #include <string.h> #include "wifi_config.h" // wifi_config.h should define followings. // #define WIFI_SSID "XXXXXXXX" // WiFi SSID // #define WIFI_PASS "XXXXXXXX" // WiFi Password #define WIFI_HOSTNAME "ESP32-wifi_connect" // module's hostname #define WIFI_CONNECT_TIMEOUT 3 #define TAG "WIFI-SAMPLE" SemaphoreHandle_t wifi_conn_done = NULL; ////////////////////////////////////////////////////////////////////// // Error Handling static void _error_check_failed(esp_err_t rc, const char *file, int line, const char *function, const char *expression) { ets_printf("ESP_ERROR_CHECK failed: esp_err_t 0x%x", rc); #ifdef CONFIG_ESP_ERR_TO_NAME_LOOKUP ets_printf(" (%s)", esp_err_to_name(rc)); #endif //CONFIG_ESP_ERR_TO_NAME_LOOKUP ets_printf(" at 0x%08x\n", (intptr_t)__builtin_return_address(0) - 3); if (spi_flash_cache_enabled()) { // strings may be in flash cache ets_printf("file: \"%s\" line %d\nfunc: %s\nexpression: %s\n", file, line, function, expression); } } #define ERROR_RETURN(x, fail) do { \ esp_err_t __err_rc = (x); \ if (__err_rc != ESP_OK) { \ _error_check_failed(__err_rc, __FILE__, __LINE__, \ __ASSERT_FUNC, #x); \ return fail; \ } \ } while(0); ////////////////////////////////////////////////////////////////////// // Wifi Function static esp_err_t event_handler(void *ctx, system_event_t *event) { switch(event->event_id) { case SYSTEM_EVENT_STA_START: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START"); ERROR_RETURN(tcpip_adapter_set_hostname(TCPIP_ADAPTER_IF_STA, WIFI_HOSTNAME), ESP_FAIL); ERROR_RETURN(esp_wifi_connect(), ESP_FAIL); break; case SYSTEM_EVENT_STA_CONNECTED: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_CONNECTED"); break; case SYSTEM_EVENT_STA_GOT_IP: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP"); xSemaphoreGive(wifi_conn_done); break; case SYSTEM_EVENT_STA_DISCONNECTED: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED"); xSemaphoreGive(wifi_conn_done); break; case SYSTEM_EVENT_STA_STOP: ESP_LOGI(TAG, "SYSTEM_EVENT_STA_STOP"); break; default: break; } return ESP_OK; } static bool wifi_init() { esp_err_t ret; ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES) { ERROR_RETURN(nvs_flash_erase(), false); ret = nvs_flash_init(); } ERROR_RETURN(ret, false); tcpip_adapter_init(); ERROR_RETURN(esp_event_loop_init(event_handler, NULL), false); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ERROR_RETURN(esp_wifi_init(&cfg), false); ERROR_RETURN(esp_wifi_set_storage(WIFI_STORAGE_RAM), false); ERROR_RETURN(esp_wifi_set_mode(WIFI_MODE_STA), false); #ifdef WIFI_SSID wifi_config_t wifi_config = { .sta = { .ssid = WIFI_SSID, .password = WIFI_PASS, }, }; wifi_config_t wifi_config_cur; ERROR_RETURN(esp_wifi_get_config(WIFI_IF_STA, &wifi_config_cur), false); if (strcmp((const char *)wifi_config_cur.sta.ssid, (const char *)wifi_config.sta.ssid) || strcmp((const char *)wifi_config_cur.sta.password, (const char *)wifi_config.sta.password)) { ESP_LOGI(TAG, "SAVE WIFI CONFIG"); ERROR_RETURN(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config), false); } #endif return true; } static bool wifi_connect(wifi_ap_record_t *ap_info) { xSemaphoreTake(wifi_conn_done, portMAX_DELAY); ERROR_RETURN(esp_wifi_start(), false); if (xSemaphoreTake(wifi_conn_done, WIFI_CONNECT_TIMEOUT * 1000 / portTICK_RATE_MS) == pdTRUE) { ERROR_RETURN(esp_wifi_sta_get_ap_info(ap_info), false); return true; } else { ESP_LOGE(TAG, "WIFI CONNECT TIMECOUT"); return false; } } static bool wifi_stop() { esp_wifi_disconnect(); esp_wifi_stop(); return true; } ////////////////////////////////////////////////////////////////////// void app_main() { uint32_t sleep_sec = 60; wifi_ap_record_t ap_info; vSemaphoreCreateBinary(wifi_conn_done); esp_log_level_set("wifi", ESP_LOG_ERROR); uint32_t time_start = xTaskGetTickCount(); if (wifi_init() && wifi_connect(&ap_info)) { uint32_t connect_msec = (xTaskGetTickCount() - time_start) * portTICK_PERIOD_MS; ESP_LOGI(TAG, "CONN TIME: %d ms", connect_msec); vTaskDelay(100 / portTICK_RATE_MS); // Do something } wifi_stop(); ESP_LOGI(TAG, "Sleep %d sec", sleep_sec); vTaskDelay(20 / portTICK_RATE_MS); // wait 20ms for flush UART ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(sleep_sec * 1000000LL)); esp_deep_sleep_start(); } |
実行例
プログラムを実行すると,wifi_config.h に定義したアクセスポイントに 60 秒毎に接続を行います.
実行したときの通信の様子を以下に紹介します.接続開始(Probe Request)から IP アドレスの取得完了(DHCP ACK)まで 740ms しかかかっていません.
接続時間を気にしていないコードに比べると半分以下になっているはずです.
おまけ
上に掲載したパケットキャプチャの方法を紹介します.まず,Monitor Mode に対応した無線LANアダプタが必要になります.今だと RT5370 を使った下記のものがお値打ちでオススメです.
キャプチャは Linux の tshark で行います.Windows でも Network Monitor とか Npcap + Wireshark でやれないことは事は無いと思うのですが,パケットの欠損があって無線 LAN の decrypt がなかなか正常にできなかったので,途中であきらめました.
Ubuntu の場合,下記のようにすることで,60秒間キャプチャできます.生成された wifi.cap を Windows の Wireshark で開いてフィルタをかけてやれば上に掲載したような画面が表示されます.
1 2 3 4 |
$ sudo apt-get install tshark $ sudo ifconfig wlxc83a35dc7e96 up $ sudo iwconfig wlxc83a35dc7e96 channel 1 $ sudo tshark -I -i wlxc83a35dc7e96 -w wifi.cap -a duration:60 |
wlxc83a35dc7e96 は OS が名付けたデバイス名なので環境によっては適宜変更が必要かもしれません.
無線 LAN アダプタを差してから dmesg
を実行すると下記のような出力があるはずなので,ここから取得します.(最終行に着目)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
[1309696.490831] usb 2-1.5: new full-speed USB device number 13 using ehci-pci [1309696.601569] usb 2-1.5: New USB device found, idVendor=10c4, idProduct=ea60 [1309696.601573] usb 2-1.5: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [1309696.601575] usb 2-1.5: Product: CP2104 USB to UART Bridge Controller [1309696.601577] usb 2-1.5: Manufacturer: Silicon Labs [1309696.601579] usb 2-1.5: SerialNumber: 01472993 [1309696.602332] cp210x 2-1.5:1.0: cp210x converter detected [1309696.603523] usb 2-1.5: cp210x converter now attached to ttyUSB0 [1309700.331840] usb 2-1.6: new high-speed USB device number 14 using ehci-pci [1309700.457943] usb 2-1.6: New USB device found, idVendor=148f, idProduct=5370 [1309700.457946] usb 2-1.6: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [1309700.457949] usb 2-1.6: Product: 802.11 n WLAN [1309700.457951] usb 2-1.6: Manufacturer: Ralink [1309700.457953] usb 2-1.6: SerialNumber: 1.0 [1309700.547901] usb 2-1.6: reset high-speed USB device number 14 using ehci-pci [1309700.666801] ieee80211 phy2: rt2x00_set_rt: Info - RT chipset 5390, rev 0502 detected [1309700.695009] ieee80211 phy2: rt2x00_set_rf: Info - RF chipset 5370 detected [1309700.695486] ieee80211 phy2: Selected rate control algorithm 'minstrel_ht' [1309700.705121] rt2800usb 2-1.6:1.0 wlxc83a35dc7e96: renamed from wlan0 |
また,WPA2-PSKで暗号化されたパケットを復号する際に入力する Key は,Wireshark のこのページで生成できます.(処理は Javascript で記述されているので,キーが漏れる心配は無いです)
コメント