ESP32でESP-NOWを使って通信してみよう

ESP-NOWのイメージ図
ESP-NOWのイメージ図
今回は、ESP32とM5StickC PLUSでESP-NOWを試してみました。 ESP-NOWとは、__ESP32やESP8266間で無線でデータを送受信できる通信方法__ です。無線LANのIEEE 802.11を使用して実現しているようですが、WiFiネットワークではないためアクセスポイントの必要がありません。 また、Bluetoothのようなセントラル・ペリフェラルといった複雑なペアリングの必要もないので簡単に開発できます。

▼ Bluetoothを使ったBLE通信はこちら。

はじめに

ESP32とESP32 PICOが内蔵されているM5StickC PLUSを使って、ESP-NOWを試してみました。ESP32を受信機として、M5StickC PLUSを送信機にしました。ESP-NOWを使って送信するには、受信機のMACアドレスが分かれば送信可能です。 正確には複数台に送るブロードキャスト方式もありますが、ここでは扱いません。 M5StickC PLUSのボタンを押すたびにESP32のLEDが点滅するプログラムを作ります。

つかうもの

ESP-NOWを使うには、ESP32やESP8266のチップが搭載されているマイコンボードを使用します。本記事では、こちらのESP32とM5StickC PLUSを使用しました。

ESP-NOWとは

ESP-NOWをもう少し詳しくみてみます。こちらにESP-NOWのドキュメントがありますので、目を通しておくと良いでしょう。 ESP-NOW-ESP32-—ESP-IDF Programming Guide latest documentation

ドキュメントを翻訳すると、ESP-NOWは次のように説明されてます。

ESP-NOWは、Espressifによって定義された一種のコネクションレス型Wi-Fi通信プロトコルです。 ESP-NOWでは、アプリケーションデータはベンダー固有のアクションフレームにカプセル化され、接続なしで1つのWi-Fiデバイスから別のWi-Fiデバイスに送信されます。 CBC-MACプロトコル(CCMP)を使用したCTRは、セキュリティのためにアクションフレームを保護するために使用されます。 ESP-NOWは、スマートライト、リモートコントロール、センサなどで広く使用されてます。

TCPなどのハンドシェイクがないためデータが到達したかどうかの確証は得られませんが、高速で省電力な通信を実現できるようです。

ドキュメントには関数がズラリと書かれてますが、単純な送受信だけならばほんのわずかな関数だけでESP-NOWを実現できます。

プログラム

ESP-NOWでLチカ
ESP-NOWでLチカ

それではESP-NOWを使ったメインプログラムの紹介です。 次のプログラムでは、送信機のM5StickC PLUSのAボタンが押されるとESP-Now通信を使ってESP32のMACアドレス宛へメッセージが投げられます。受信機のESP32でメッセージが受信されると、GPIO13につながったLEDが交互に点滅され動画のような動作を振る舞います。

受信側のプログラム(ESP32)

Serial.println(WiFi.macAddress()); を実行することで、ESP32のMACアドレスをシリアルモニタへ表示します。表示されたMACアドレスをメモして下さい。 ESP-NOWを使ってデータを受信するには、esp_now_init() で初期化をし、esp_now_register_recv_cb(onReceive) でデータが受信されたときに実行する関数を登録します。また、WiFiモードはWIFI_STAのステーションモード(子機モード)を指定します。

cpp
#include <esp_now.h>
#include <WiFi.h>

const int LED_PIN = 13;
bool ledState = false;

void toggleLed() {
    if (ledState) {
        ledState = false;
        digitalWrite(LED_PIN, LOW);
    }
    else {
        ledState = true;
        digitalWrite(LED_PIN, HIGH);
    }
}

void onReceive(const uint8_t* mac_addr, const uint8_t* data, int data_len) {
    char macStr[18];
    snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
        mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
    Serial.println();
    Serial.printf("Last Packet Recv from: %s\n", macStr);
    Serial.printf("Last Packet Recv Data(%d): ", data_len);
    for (int i = 0; i < data_len; i++) {
        Serial.print(data[i]);
        Serial.print(" ");
        if (data[i] == 222) {
            toggleLed();
        }
    }

}

void setup() {

    Serial.begin(115200);
    pinMode(LED_PIN, OUTPUT);
    Serial.println(WiFi.macAddress()); // このアドレスを送信側へ登録します

    WiFi.mode(WIFI_STA);
    WiFi.disconnect();
    if (esp_now_init() == ESP_OK) {
        Serial.println("ESP-Now Init Success");
    }
    esp_now_register_recv_cb(onReceive);
}

void loop() {
}

送信側のプログラム(M5StickC PLUS)

slaveAddressを、受信機のMACアドレスに書き換えて下さい。esp_now_peer_info_tで送信先の情報を登録してます。esp_now_register_send_cb(onSend);では送信が完了したときのイベントを登録できます。必要なければ省略可能です。 データを送信するには esp_now_send(slaveAddress, data, sizeof(data)); で送信が可能です。
cpp
#include <M5StickCPlus.h>
#include <esp_now.h>
#include <WiFi.h>

uint8_t slaveAddress[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; // 受信機のMACアドレスに書き換えます

void onSend(const uint8_t* mac_addr, esp_now_send_status_t status) {
    char macStr[18];
    snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X",
        mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);

    M5.Lcd.fillScreen(BLACK);
    M5.Lcd.setTextSize(2);
    M5.Lcd.setCursor(5, 10);
    M5.Lcd.println(macStr);
    M5.Lcd.println(status == ESP_NOW_SEND_SUCCESS ? "Success" : "Failed");
}

void setup() {
    M5.begin();
    M5.Lcd.fillScreen(BLACK);

    WiFi.mode(WIFI_STA);
    WiFi.disconnect();
    if (esp_now_init() == ESP_OK) {
        Serial.println("ESPNow Init Success");
    }

    esp_now_peer_info_t peerInfo;
    memcpy(peerInfo.peer_addr, slaveAddress, 6);
    peerInfo.channel = 0;
    peerInfo.encrypt = false;

    if (esp_now_add_peer(&peerInfo) != ESP_OK) {
        Serial.println("Failed to add peer");
        return;
    }

    esp_now_register_send_cb(onSend);
}

void loop() {
    M5.update();

    if (M5.BtnA.wasPressed()) {
        uint8_t data[2] = { 111, 222 }; // 送信データ
        esp_err_t result = esp_now_send(slaveAddress, data, sizeof(data));

        Serial.print("Send Status: ");
        switch (result)
        {
        case ESP_OK:
            Serial.println("Success");
            break;
        case ESP_ERR_ESPNOW_NOT_INIT:
            Serial.println("ESPNOW not Init.");
            break;
        case ESP_ERR_ESPNOW_ARG:
            Serial.println("Invalid Argument");
            break;
        case ESP_ERR_ESPNOW_INTERNAL:
            Serial.println("Internal Error");
            break;
        case ESP_ERR_ESPNOW_NO_MEM:
            Serial.println("ESP_ERR_ESPNOW_NO_MEM");
            break;
        case ESP_ERR_ESPNOW_NOT_FOUND:
            Serial.println("Peer not found.");
            break;

        default:
            Serial.println("Not sure what happened");
            break;
        }

    }
    delay(1);
}

まとめ

いかがだったでしょうか。うまくESP-NOWの通信ができましたでしょうか。 私は、ボタンを押したときのLEDの反応速度が有線と変わらないことに驚きました。ESP-NOW、これから大活躍しそうな予感です。 最初でもお伝えしましたが、ESP-NOWではルーターの必要がないこと、BLEのGATTのような複雑な記述がないことがとても良いですね。いままで敷居が高く感じてたアイデアも、ESP-NOWを使えばサクッと実現できそうです。

関連記事

最後までご覧いただきありがとうございます!

▼ 記事に関するご質問やお仕事のご相談は以下よりお願いいたします。
お問い合わせフォーム

ESP32搭載ボード
ESP32の書籍
Arduinoで人気の周辺パーツ
M5Stackのオススメ参考書
M5Stack製品
M5StickCで使えるHat
ESP32ボード
関連記事