### 前言
ESP-NOW 是乐鑫定义的一种无线通信协议,能够在无路由器的情况下直接、快速、低功耗地控制智能设备。它能够与 Wi-Fi 和 Bluetooth LE 共存,支持乐鑫 ESP8266、ESP32、ESP32-S 和 ESP32-C 等多系列 SoC。ESP-NOW 广泛应用于智能家电、远程控制和传感器等领域。
我们在使用ESP-NOW的时候,都非常想知道它的性能指标,例如带宽、延迟之类的。在查看espnow仓库源码的时候,正好发现了相关的测试代码,我们可以来看一下。
### 用于测试ESP-NOW性能指标的源码
在ESPNOW仓库源码里,可以找到测试相关的代码(components/esp-now/src/debug/src/commands/cmd_iperf.c)
```c
static void espnow_iperf_initiator_task(void *arg)
{
esp_err_t ret = ESP_OK;
espnow_iperf_data_t *iperf_data = ESP_CALLOC(1, g_iperf_cfg.packet_len);
iperf_data->type = IPERF_BANDWIDTH;
iperf_data->seq = 0;
int64_t start_time = esp_timer_get_time();
int64_t end_time = start_time + g_iperf_cfg.transmit_time * 1000 * 1000;
uint32_t total_count = 0;
if (!g_iperf_cfg.frame_head.broadcast) {
espnow_add_peer(g_iperf_cfg.addr, NULL);
}
ESP_LOGI(TAG, "[Responder MAC] Interval Transfer Frame_rate Bandwidth");
for (int64_t report_time = start_time + g_iperf_cfg.report_interval * 1000 * 1000, report_count = 0;
esp_timer_get_time() < end_time && !g_iperf_cfg.finish;) {
ret = espnow_send(g_iperf_cfg.type, g_iperf_cfg.addr, iperf_data,
g_iperf_cfg.packet_len, &g_iperf_cfg.frame_head, portMAX_DELAY);
ESP_ERROR_CONTINUE(ret != ESP_OK && ret != ESP_ERR_WIFI_TIMEOUT, "<%s> espnow_send", esp_err_to_name(ret));
iperf_data->seq++;
++total_count;
if (esp_timer_get_time() >= report_time) {
uint32_t report_time_s = (report_time - start_time) / (1000 * 1000);
double report_size = (iperf_data->seq - report_count) * g_iperf_cfg.packet_len / 1e6;
ESP_LOGI(TAG, "["MACSTR"]%2d-%2d sec%2.2f MBytes %0.2f Hz%0.2f Mbps",
MAC2STR(g_iperf_cfg.addr), report_time_s - g_iperf_cfg.report_interval, report_time_s,
report_size, (iperf_data->seq - report_count) * 1.0 / g_iperf_cfg.report_interval, report_size * 8 / g_iperf_cfg.report_interval);
report_time = esp_timer_get_time() + g_iperf_cfg.report_interval * 1000 * 1000;
report_count = iperf_data->seq;
}
}
iperf_data->type= IPERF_BANDWIDTH_STOP;
int retry_count = 5;
wifi_pkt_rx_ctrl_t rx_ctrl = {0};
uint32_t spend_time_ms = (esp_timer_get_time() - start_time) / 1000;
do {
ret = espnow_send(g_iperf_cfg.type, g_iperf_cfg.addr, iperf_data,
g_iperf_cfg.packet_len, &g_iperf_cfg.frame_head, portMAX_DELAY);
ESP_ERROR_CONTINUE(ret != ESP_OK, "<%s> espnow_send", esp_err_to_name(ret));
memset(iperf_data, 0, g_iperf_cfg.packet_len);
iperf_recv_data_t recv_data = { 0 };
if (g_iperf_queue && xQueueReceive(g_iperf_queue, &recv_data, pdMS_TO_TICKS(1000)) == pdPASS) {
ret = ESP_OK;
memcpy(iperf_data, recv_data.data, recv_data.size);
memcpy(g_iperf_cfg.addr, recv_data.src_addr, 6);
memcpy(&rx_ctrl, &recv_data.rx_ctrl, sizeof(wifi_pkt_rx_ctrl_t));
ESP_FREE(recv_data.data);
} else {
ret = ESP_FAIL;
}
} while (ret != ESP_OK && retry_count-- > 0 && iperf_data->type != IPERF_BANDWIDTH_STOP_ACK);
if (ret != ESP_OK) {
ESP_LOGW(TAG, "<%s> Receive responder response failed", esp_err_to_name(ret));
} else {
uint32_t write_count = iperf_data->seq > 0 ? iperf_data->seq - 1 : 0;
uint32_t lost_count= total_count - write_count;
double total_len = (total_count * g_iperf_cfg.packet_len) / 1e6;
if (total_count && write_count && spend_time_ms) {
ESP_LOGI(TAG, "initiator Report:");
ESP_LOGI(TAG, "[ ID] Interval Transfer Bandwidth Jitter Lost/Total DatagramsRSSIChannel");
ESP_LOGI(TAG, "[000] %2d-%2d sec %2.2f MBytes %0.2f Mbps %0.2f ms %d/%d (%0.2f%%) %d %d",
0, spend_time_ms / 1000, total_len, total_len * 8 * 1000 / spend_time_ms, spend_time_ms * 1.0 / write_count,
lost_count, total_count, lost_count * 100.0 / total_count, rx_ctrl.rssi, rx_ctrl.channel);
}
}
if (!g_iperf_cfg.frame_head.broadcast) {
espnow_del_peer(g_iperf_cfg.addr);
}
ESP_FREE(iperf_data);
g_iperf_cfg.finish = true;
espnow_set_config_for_data_type(ESPNOW_DATA_TYPE_RESERVED, 0, NULL);
if (g_iperf_queue) {
iperf_recv_data_t tmp_data ={ 0 };
while (xQueueReceive(g_iperf_queue, &tmp_data, 0)) {
ESP_FREE(tmp_data.data);
}
vQueueDelete(g_iperf_queue);
g_iperf_queue = NULL;
}
vTaskDelete(NULL);
}
```
这个函数的作用,是发起iperf测试任务,这个任务会向指定的地址发起测试,并打印相关的测试信息,例如传输数据的大小,传输带宽,信号强度等等内容。
-----------------------
光是一个设备跑这段代码也是没用的,我们需要另一个设备跑测试响应任务。测试响应任务也是在同一个文件里。
```c
static esp_err_t espnow_iperf_responder(uint8_t *src_addr, void *data,
size_t size, wifi_pkt_rx_ctrl_t *rx_ctrl)
{
ESP_PARAM_CHECK(src_addr);
ESP_PARAM_CHECK(data);
ESP_PARAM_CHECK(size);
ESP_PARAM_CHECK(rx_ctrl);
esp_err_t ret = ESP_OK;
espnow_iperf_data_t *iperf_data = (espnow_iperf_data_t *)data;
static int64_t start_time;
static uint32_t recv_count;
static int64_t report_time;
static uint32_t report_count;
memcpy(g_iperf_cfg.addr, src_addr, 6);
if (!g_iperf_cfg.finish) {
recv_count++;
if (iperf_data->seq == 0) {
recv_count = 0;
start_time= esp_timer_get_time();
report_time = start_time + g_iperf_cfg.report_interval * 1000 * 1000;
report_count = 0;
}
if (iperf_data->type == IPERF_BANDWIDTH && esp_timer_get_time() >= report_time) {
uint32_t report_time_s = (report_time - start_time) / (1000 * 1000);
double report_size = (recv_count - report_count) * size / 1e6;
ESP_LOGI(TAG, "["MACSTR"]%2d-%2d sec%2.2f MBytes%0.2f Mbps%d dbm",
MAC2STR(g_iperf_cfg.addr), report_time_s - g_iperf_cfg.report_interval, report_time_s,
report_size, report_size * 8 / g_iperf_cfg.report_interval, rx_ctrl->rssi);
report_time = esp_timer_get_time() + g_iperf_cfg.report_interval * 1000 * 1000;
report_count = recv_count;
} else if (iperf_data->type == IPERF_PING) {
ESP_LOGV(TAG, "Recv IPERF_PING, seq: %d, recv_count: %d", iperf_data->seq, recv_count);
iperf_data->type = IPERF_PING_ACK;
if (g_iperf_cfg.gpio_num >= 0) {
gpio_set_level(g_iperf_cfg.gpio_num, 0);
}
if (!g_iperf_cfg.frame_head.broadcast) {
espnow_add_peer(g_iperf_cfg.addr, NULL);
}
ret = espnow_send(g_iperf_cfg.type, g_iperf_cfg.addr, iperf_data,
size, &g_iperf_cfg.frame_head, portMAX_DELAY);
if (!g_iperf_cfg.frame_head.broadcast) {
espnow_del_peer(g_iperf_cfg.addr);
}
if (g_iperf_cfg.gpio_num >= 0) {
gpio_set_level(g_iperf_cfg.gpio_num, 1);
}
ESP_ERROR_RETURN(ret != ESP_OK, ret, "<%s> espnow_send", esp_err_to_name(ret));
} else if (iperf_data->type == IPERF_BANDWIDTH_STOP) {
uint32_t total_count = iperf_data->seq + 1;
uint32_t lost_count= total_count - recv_count;
double total_len = (total_count * size) / 1e6;
uint32_t spend_time_ms= (esp_timer_get_time() - start_time) / 1000;
ESP_LOGI(TAG, "[ ID] Interval Transfer Bandwidth Jitter Lost/Total Datagrams");
ESP_LOGI(TAG, "[000] %2d-%2d sec %2.2f MBytes %0.2f Mbps %0.2f ms %d/%d (%0.2f%%)",
0, spend_time_ms / 1000, total_len, total_len * 8 * 1000 / spend_time_ms, spend_time_ms * 1.0 / recv_count,
lost_count, total_count, lost_count * 100.0 / total_count);
iperf_data->seq = recv_count;
iperf_data->type = IPERF_BANDWIDTH_STOP_ACK;
ESP_LOGD(TAG, "iperf_data->seq: %d",iperf_data->seq);
espnow_frame_head_t frame_head = {
.filter_adjacent_channel = true,
};
espnow_add_peer(g_iperf_cfg.addr, NULL);
ret = espnow_send(g_iperf_cfg.type, g_iperf_cfg.addr, iperf_data,
sizeof(espnow_iperf_data_t), &frame_head, portMAX_DELAY);
ESP_ERROR_RETURN(ret != ESP_OK, ret, "<%s> espnow_send", esp_err_to_name(ret));
espnow_del_peer(g_iperf_cfg.addr);
}
}
return ESP_OK;
}
```
这个函数就是响应iperf测试的程序了。我们只需要在两个设备上按照调用方式修改好程序,就可以进行测试。以下是我的测试结果。
可以看到在1分钟时间内,总共发送了17888个数据包,丢了10个包,丢包率0.06%, 信号强度是-13左右(离的很近),带宽是0.51Mbps。
相对于wifi的带宽性能,espnow远距离模式的性能是比较弱的,但是考虑到ESPNOW不需要路由器,可以设备在未联网的时候,多设备互相发送数据,
并且这个速度也比常规串口115200的性能要强。那也不是不能接受了。
传输距离的话,我这边测试的情况大致如下,PCB天线的板子,基本上可以在穿1堵墙,距离20米内时,带宽维持在0.4Mbps,陶瓷天线的性能差一点,只能在10米内维持0.3Mbps的一个带宽。