概要
前回はピアまわりを調べてみましたが、今回は送信速度まわりを確認しました。送信サイズと送信間隔、送信速度を変えながら送信成功率を確認しています。
送信成功
ESP-NOWの場合にはピアに対して送信した場合には、受信したピアから受信成功のパケットを受け取っています。ブロードキャスト宛の送信だとこの受信確認はありません。
そして、受信が成功していても戻りの成功パケットが受信できない場合があるので、受信成功パケットが届かない場合に必ずしも受信側デバイスに届いていないわけではありません。
受信側スケッチ
#include "ESP32_NOW.h"
#include "WiFi.h"
#include "EspEasySerialCommand.h"
EspEasySerialCommand command(Serial);
#define ESPNOW_WIFI_CHANNEL 4 // 1 - 14
#define ESPNOW_ADVERTISING_STRING "ADVERTISING_ESP32_ESPNOW_STRING"
uint16_t receiveCount;
class MY_ESP_NOW_Peer : public ESP_NOW_Peer {
public:
MY_ESP_NOW_Peer(const uint8_t *mac_addr, uint8_t channel, wifi_interface_t iface, const uint8_t *lmk)
: ESP_NOW_Peer(mac_addr, channel, iface, lmk) {}
~MY_ESP_NOW_Peer() {
}
bool Begin() {
return add();
}
bool Send(const uint8_t *data, size_t len) {
return send(data, len);
}
void onReceive(const uint8_t *data, size_t len, bool broadcast) {
if (broadcast && memcmp(ESPNOW_ADVERTISING_STRING, data, len) == 0) {
// ADVERTISING Echo
this->Send((const uint8_t *)ESPNOW_ADVERTISING_STRING, strlen(ESPNOW_ADVERTISING_STRING) + 1);
} else {
// Communication
receiveCount++;
}
}
};
MY_ESP_NOW_Peer *espnow_peer_broadcast = nullptr;
MY_ESP_NOW_Peer *espnow_peer = nullptr;
void espnow_receive(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg) {
if ((espnow_peer == nullptr) && (memcmp(ESPNOW_ADVERTISING_STRING, data, len) == 0)) {
// Add peer
Serial.println("Add peer");
espnow_peer = new MY_ESP_NOW_Peer(info->src_addr, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA, NULL);
espnow_peer->Begin();
// ADVERTISING Echo
espnow_peer->Send((const uint8_t *)ESPNOW_ADVERTISING_STRING, strlen(ESPNOW_ADVERTISING_STRING) + 1);
Serial.printf("Peer Send[%s] : %s\n", ARDUINO_BOARD, ESPNOW_ADVERTISING_STRING);
}
Serial.printf("Non Peer Receive(%02X:%02X:%02X:%02X:%02X:%02X) : %s\n", info->src_addr[0], info->src_addr[1], info->src_addr[2], info->src_addr[3], info->src_addr[4], info->src_addr[5], data);
}
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println("ESP-NOW");
WiFi.mode(WIFI_STA);
WiFi.setChannel(ESPNOW_WIFI_CHANNEL);
while (!WiFi.STA.started()) {
delay(100);
}
if (!ESP_NOW.begin()) {
ESP.restart();
}
ESP_NOW.onNewPeer(espnow_receive, NULL);
espnow_peer_broadcast = new MY_ESP_NOW_Peer(ESP_NOW.BROADCAST_ADDR, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA, NULL);
espnow_peer_broadcast->Begin();
command.addCommand("CLEAR", [](EspEasySerialCommand::command_t) {
Serial.println("#CLEAR");
receiveCount = 0;
});
}
void loop() {
if (espnow_peer == nullptr) {
// ADVERTISING
espnow_peer_broadcast->Send((const uint8_t *)ESPNOW_ADVERTISING_STRING, strlen(ESPNOW_ADVERTISING_STRING) + 1);
Serial.printf("Broadcast Send[%s] : %s\n", ARDUINO_BOARD, ESPNOW_ADVERTISING_STRING);
} else {
Serial.printf("receiveCount = %d\n", receiveCount);
}
command.task();
delay(1000);
}
前回の単純なピアを組むスケッチをベースに組んであります。今回受信成功したらカウントアップする仕組みと、シリアル経由でカウントを0に戻すコードが入っています。
void onReceive(const uint8_t *data, size_t len, bool broadcast) {
if (broadcast && memcmp(ESPNOW_ADVERTISING_STRING, data, len) == 0) {
// ADVERTISING Echo
this->Send((const uint8_t *)ESPNOW_ADVERTISING_STRING, strlen(ESPNOW_ADVERTISING_STRING) + 1);
} else {
// Communication
receiveCount++;
}
}
コード的にはアドバタイズの処理以外はカウントアップしています。
command.addCommand("CLEAR", [](EspEasySerialCommand::command_t) {
Serial.println("#CLEAR");
receiveCount = 0;
});
あとはEspEasyUtilsライブラリのEspEasySerialCommandクラスを使って、シリアルコンソールからCLEARと送るとカウントを0に戻す処理を入れました。
void loop() {
if (espnow_peer == nullptr) {
// ADVERTISING
espnow_peer_broadcast->Send((const uint8_t *)ESPNOW_ADVERTISING_STRING, strlen(ESPNOW_ADVERTISING_STRING) + 1);
Serial.printf("Broadcast Send[%s] : %s\n", ARDUINO_BOARD, ESPNOW_ADVERTISING_STRING);
} else {
Serial.printf("receiveCount = %d\n", receiveCount);
}
command.task();
delay(1000);
}
あとはloopでアドバタイズの処理と、現在のカウント値を定期的に出力しています。
送信側スケッチ
#include "ESP32_NOW.h"
#include "WiFi.h"
#include "EspEasySerialCommand.h"
EspEasySerialCommand command(Serial);
#define ESPNOW_WIFI_CHANNEL 4 // 1 - 14
#define ESPNOW_ADVERTISING_STRING "ADVERTISING_ESP32_ESPNOW_STRING"
uint16_t successCount;
uint16_t failCount;
class MY_ESP_NOW_Peer : public ESP_NOW_Peer {
public:
MY_ESP_NOW_Peer(const uint8_t *mac_addr, uint8_t channel, wifi_interface_t iface, const uint8_t *lmk)
: ESP_NOW_Peer(mac_addr, channel, iface, lmk) {}
~MY_ESP_NOW_Peer() {
}
bool Begin() {
return add();
}
bool Send(const uint8_t *data, size_t len) {
return send(data, len);
}
void onReceive(const uint8_t *data, size_t len, bool broadcast) {
if (broadcast && memcmp(ESPNOW_ADVERTISING_STRING, data, len) == 0) {
// ADVERTISING Echo
this->Send((const uint8_t *)ESPNOW_ADVERTISING_STRING, strlen(ESPNOW_ADVERTISING_STRING) + 1);
} else {
// Communication
const uint8_t *mac = addr();
Serial.printf("Peer Receive(%02X:%02X:%02X:%02X:%02X:%02X) %s : %s\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], (broadcast ? "Broadcast" : "Unicast"), data);
}
}
void onSent(bool success) {
if (success) {
successCount++;
} else {
failCount++;
}
}
};
MY_ESP_NOW_Peer *espnow_peer_broadcast = nullptr;
MY_ESP_NOW_Peer *espnow_peer = nullptr;
void espnow_receive(const esp_now_recv_info_t *info, const uint8_t *data, int len, void *arg) {
if ((espnow_peer == nullptr) && (memcmp(ESPNOW_ADVERTISING_STRING, data, len) == 0)) {
// Add peer
Serial.println("Add peer");
espnow_peer = new MY_ESP_NOW_Peer(info->src_addr, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA, NULL);
espnow_peer->Begin();
// ADVERTISING Echo
espnow_peer->Send((const uint8_t *)ESPNOW_ADVERTISING_STRING, strlen(ESPNOW_ADVERTISING_STRING) + 1);
Serial.printf("Peer Send[%s] : %s\n", ARDUINO_BOARD, ESPNOW_ADVERTISING_STRING);
}
Serial.printf("Non Peer Receive(%02X:%02X:%02X:%02X:%02X:%02X) : %s\n", info->src_addr[0], info->src_addr[1], info->src_addr[2], info->src_addr[3], info->src_addr[4], info->src_addr[5], data);
}
void setup() {
Serial.begin(115200);
delay(2000);
WiFi.mode(WIFI_STA);
WiFi.setChannel(ESPNOW_WIFI_CHANNEL);
while (!WiFi.STA.started()) {
delay(100);
}
if (!ESP_NOW.begin()) {
ESP.restart();
}
ESP_NOW.onNewPeer(espnow_receive, NULL);
espnow_peer_broadcast = new MY_ESP_NOW_Peer(ESP_NOW.BROADCAST_ADDR, ESPNOW_WIFI_CHANNEL, WIFI_IF_STA, NULL);
espnow_peer_broadcast->Begin();
command.addCommand("SEND", [](EspEasySerialCommand::command_t command) {
uint16_t count = command.command2.toInt();
uint16_t size = command.command3.toInt();
uint16_t wait = command.command4.toInt();
Serial.printf("#SEND count:%d size:%d wait:%d\n", count, size, wait);
successCount = 0;
failCount = 0;
for (uint16_t i = 0; i < count; i++) {
uint8_t data[250] = {};
espnow_peer->Send(data, size);
delay(wait);
}
Serial.printf("successCount = %d, failCount = %d\n", successCount, failCount);
});
command.addCommand("RATE", [](EspEasySerialCommand::command_t command) {
uint8_t rate = command.command2.toInt();
Serial.printf("#RATE 0x%02x\n", rate);
esp_wifi_config_espnow_rate(WIFI_IF_STA, (wifi_phy_rate_t)rate);
});
}
void loop() {
if (espnow_peer == nullptr) {
// ADVERTISING
espnow_peer_broadcast->Send((const uint8_t *)ESPNOW_ADVERTISING_STRING, strlen(ESPNOW_ADVERTISING_STRING) + 1);
Serial.printf("Broadcast Send[%s] : %s\n", ARDUINO_BOARD, ESPNOW_ADVERTISING_STRING);
}
command.task();
delay(1000);
}
送信側も受信側と同じような感じに作ってあります。
command.addCommand("SEND", [](EspEasySerialCommand::command_t command) {
uint16_t count = command.command2.toInt();
uint16_t size = command.command3.toInt();
uint16_t wait = command.command4.toInt();
Serial.printf("#SEND count:%d size:%d wait:%d\n", count, size, wait);
successCount = 0;
failCount = 0;
for (uint16_t i = 0; i < count; i++) {
uint8_t data[250] = {};
espnow_peer->Send(data, size);
delay(wait);
}
Serial.printf("successCount = %d, failCount = %d\n", successCount, failCount);
});
ピアの登録が終わったら「SEND [送信回数] [送信サイズ] [待機ms]」でESP-NOWを連続送信します。このパラメータを変えながら実験をしています。
command.addCommand("RATE", [](EspEasySerialCommand::command_t command) {
uint8_t rate = command.command2.toInt();
Serial.printf("#RATE 0x%02x\n", rate);
esp_wifi_config_espnow_rate(WIFI_IF_STA, (wifi_phy_rate_t)rate);
});
送信速度は上記のコードで変更しています。esp_wifi_config_espnow_rateは非推奨なのですが、Wi-Fi6を使わない場合には利用しやすい関数です。本来はesp_now_set_peer_rate_configという関数があり、ピアごとに送信速度の変更とWi-Fi6関連と思われるオプション設定が可能です。ただ、新しい関数は細かいパラメーターについての解説がなかったので、古いesp_wifi_config_espnow_rateを使っていますのでご注意ください。
速度は上記にあるようにWIFI_PHY_RATE_1M_Lなどのenumを使って設定するのが正しいのですが、コマンドでは上記のページを見ながら内部の数値を直接渡して設定しています。
esp_wifi_config_espnow_rate(WIFI_IF_STA, WIFI_PHY_RATE_54M);
通常は上記の指定となります。
上記で少し古いバージョンですが、速度を変更した実験をしているので合わせて参考にしてください。基本的に送信時間などはまわりの電波状況によって変わります。混雑しているチャンネルで他の電波と同じタイミングで送信をしようとすると待機する時間などが発生しますのでどのチャンネルを利用するかも重要な用途となります。
デフォルト設定での送信成功率
rate | count | size | wait | success | receive |
---|---|---|---|---|---|
WIFI_PHY_RATE_1M_L | 1000 | 1 | 0 | 27 | 59 |
WIFI_PHY_RATE_1M_L | 1000 | 1 | 1 | 550 | 582 |
WIFI_PHY_RATE_1M_L | 1000 | 1 | 2 | 998 | 1000 |
WIFI_PHY_RATE_1M_L | 1000 | 1 | 3 | 999 | 1000 |
WIFI_PHY_RATE_1M_L | 1000 | 1 | 4 | 1000 | 1000 |
デフォルトは1Mbpsでの通信になります。1バイトのデータを1000回送信しており、waitが送信間隔で、successが送信側が送信成功を受信した回数、receiveが受信側が受信した回数となります。
waitが2msのときには受信側はほぼ100%受信していますが、送信側の成功は少し取りこぼしています。基本的に1Mbpsの通信速度の場合には片道2ms程度の時間がかかるので、受信側は2ms以上の送信間隔である必要があり、送信側は往復で4ms程度の送信間隔でないと取りこぼす危険があります。
実際のところまわりの電波状況などで取りこぼしは発生しますので送信側の成功が戻るまで送信するのか、ストリーミングとして受信側が受け取っていると思われる送信間隔で送るのかは要件によると思います。
rate | count | size | wait | success | receive |
---|---|---|---|---|---|
WIFI_PHY_RATE_1M_L | 1000 | 100 | 0 | 16 | 48 |
WIFI_PHY_RATE_1M_L | 1000 | 100 | 1 | 370 | 402 |
WIFI_PHY_RATE_1M_L | 1000 | 100 | 2 | 638 | 669 |
WIFI_PHY_RATE_1M_L | 1000 | 100 | 3 | 998 | 1000 |
WIFI_PHY_RATE_1M_L | 1000 | 100 | 4 | 999 | 1000 |
WIFI_PHY_RATE_1M_L | 1000 | 100 | 5 | 1000 | 1000 |
送信データを1バイトから100バイトに増やしました。送信時間が増えますので片道3msぐらいかかっていそうでした。
rate | count | size | wait | success | receive |
---|---|---|---|---|---|
WIFI_PHY_RATE_1M_L | 1000 | 250 | 0 | 9 | 41 |
WIFI_PHY_RATE_1M_L | 1000 | 250 | 1 | 289 | 321 |
WIFI_PHY_RATE_1M_L | 1000 | 250 | 2 | 567 | 599 |
WIFI_PHY_RATE_1M_L | 1000 | 250 | 3 | 847 | 878 |
WIFI_PHY_RATE_1M_L | 1000 | 250 | 4 | 999 | 1000 |
WIFI_PHY_RATE_1M_L | 1000 | 250 | 5 | 999 | 1000 |
WIFI_PHY_RATE_1M_L | 1000 | 250 | 6 | 1000 | 1000 |
最大長である250バイトにしてみました。送信側が250バイトだと4ms、成功パケットは短いので2msぐらいで戻って来ているようです。
送信速度を変更してみる
rate | count | size | wait | success | receive |
---|---|---|---|---|---|
WIFI_PHY_RATE_1M_L | 1000 | 250 | 2 | 524 | 556 |
WIFI_PHY_RATE_2M_L | 1000 | 250 | 2 | 737 | 767 |
WIFI_PHY_RATE_2M_S | 1000 | 250 | 2 | 706 | 736 |
WIFI_PHY_RATE_5M_L | 1000 | 250 | 2 | 984 | 985 |
WIFI_PHY_RATE_5M_S | 1000 | 250 | 2 | 998 | 1000 |
WIFI_PHY_RATE_6M | 1000 | 250 | 2 | 998 | 1000 |
WIFI_PHY_RATE_9M | 1000 | 250 | 2 | 999 | 1000 |
WIFI_PHY_RATE_11M_L | 1000 | 250 | 2 | 982 | 1000 |
WIFI_PHY_RATE_11M_S | 1000 | 250 | 2 | 999 | 1000 |
WIFI_PHY_RATE_12M | 1000 | 250 | 2 | 999 | 1000 |
WIFI_PHY_RATE_18M | 1000 | 250 | 2 | 997 | 1000 |
WIFI_PHY_RATE_24M | 1000 | 250 | 2 | 996 | 1000 |
WIFI_PHY_RATE_36M | 1000 | 250 | 2 | 999 | 1000 |
WIFI_PHY_RATE_48M | 1000 | 250 | 2 | 998 | 1000 |
WIFI_PHY_RATE_54M | 1000 | 250 | 2 | 999 | 1000 |
最大長の250バイトで2msの送信間隔で送信速度を変更しながら実験してみました。LとSがついているものがありますが、これはパケットのヘッダー長が短いSと長いLがある送信速度になります。基本的にはSの短いほうが早いです。
250バイトだと1Mbpsだと送信に4ms程度かかっていましたが、5Mbpsだと倍の2ms程度になりました。プロトコル的に最初に1Mbpsでヘッダーを送信して、ペイロード部分のみ高速になるのでデータ長が長くないと送信速度の恩恵は少なくなります。
全体の傾向として受信側は2msの送信間隔でもある受信成功しているのですが、送信成功パケットは最速の54Mbpsにしても安定しませんでした。
rate | count | size | wait | success | receive |
---|---|---|---|---|---|
WIFI_PHY_RATE_1M_L | 1000 | 1 | 1 | 594 | 626 |
WIFI_PHY_RATE_2M_L | 1000 | 1 | 1 | 787 | 819 |
WIFI_PHY_RATE_2M_S | 1000 | 1 | 1 | 974 | 977 |
WIFI_PHY_RATE_5M_L | 1000 | 1 | 1 | 972 | 975 |
WIFI_PHY_RATE_5M_S | 1000 | 1 | 1 | 979 | 999 |
WIFI_PHY_RATE_6M | 1000 | 1 | 1 | 995 | 1000 |
WIFI_PHY_RATE_9M | 1000 | 1 | 1 | 997 | 1000 |
WIFI_PHY_RATE_11M_L | 1000 | 1 | 1 | 831 | 861 |
WIFI_PHY_RATE_11M_S | 1000 | 1 | 1 | 992 | 1000 |
WIFI_PHY_RATE_12M | 1000 | 1 | 1 | 980 | 1000 |
WIFI_PHY_RATE_18M | 1000 | 1 | 1 | 988 | 1000 |
WIFI_PHY_RATE_24M | 1000 | 1 | 1 | 995 | 1000 |
WIFI_PHY_RATE_36M | 1000 | 1 | 1 | 992 | 1000 |
WIFI_PHY_RATE_48M | 1000 | 1 | 1 | 997 | 1000 |
WIFI_PHY_RATE_54M | 1000 | 1 | 1 | 996 | 1000 |
送信データを1バイトに減らして、送信間隔も1msになっています。11M_Lはヘッダーが長いのでパフォーマンス的には悪いですが、データ長が少ない場合にはがんばれば1ms送信間隔でもそれなりの通信ができそうです。
まとめ
送信速度は早い方がよい結果でしたが、消費電力や電波が届く範囲などで影響がでる可能性があります。今回は1度だけの計測だったり、2台のボードが隣接している場所で実験しているなどあまり厳密な実験ではありません。全体的な傾向のみ確認をして、実利用では距離などを考慮してパラメータの調整をするのがよいと思います。
今回の実験だと受信側は速度設定が必要なく、どの速度でも受取が可能でした。そのためスループット的にはなるべく通信が少ないチャンネルで送信速度をあげて通信するのが有効そうでした。
コメント