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

ESP-NOWのイメージ図
ESP-NOWのイメージ図

今回は、ESP32とM5StickC PLUSでESP-NOWを試してみました。

ESP-NOWとは、ESP32やESP8266間で無線でデータを送受信できる通信方法です。無線LANのIEEE 802.11を使用して実現しているようですが、WiFiネットワークではないためアクセスポイントの必要がありません。また、Bluetoothのようなセントラル・ペリフェラルといった複雑なペアリングの必要もないので簡単に開発できます。ESP-NOWに興味のある方またはESP-NOWをやってみようという方は、この記事をぜひ参考になさってみて下さい。

▼ 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は次のように説明されています。

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のステーションモード(子機モード)を指定しておきます。

#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)); で送信が可能です。

#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を使えばサクッと実現できそうです。

記事に関するご質問などがあればTwitterへお返事ください。
この記事で紹介した商品
ESP32搭載ボード
ESP32の書籍
Arduinoで人気の周辺パーツ
関連記事