ESP-IDF 中 ESP-NOW 协议实战指南:帧格式、安全机制与 Wi-Fi 无连接通信开发
2026/9/18 15:52:15 网站建设 项目流程

ESP-IDF 中 ESP-NOW 协议实战指南:帧格式、安全机制与 Wi-Fi 无连接通信开发

【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf

ESP-NOW 是乐鑫(Espressif)定义的一种无连接 Wi-Fi 通信协议,它无需建立 TCP/IP 连接,将应用程序数据封装在供应商动作帧(Vendor-Specific Action Frame)中直接从一个 Wi-Fi 设备传输到另一个 Wi-Fi 设备,广泛应用于智能照明、远程控制、传感器等物联网场景。本文基于 ESP-IDF 官方文档与 esp_now.h 源码,系统讲解 ESP-NOW 的帧格式、CCMP 安全机制、生命周期管理、配对与收发流程,并结合 espnow 示例工程 给出可直接落地的开发方案,帮助你快速掌握在 ESP32 系列芯片上使用 ESP-NOW 进行点对点通信的完整技能。

概述:ESP-NOW 是什么

ESP-NOW 是乐鑫公司定义的无连接 Wi-Fi 通信协议。在 ESP-NOW 中,应用程序数据被封装在各个供应商动作帧中,然后在无连接的情况下,从一个 Wi-Fi 设备传输到另一个 Wi-Fi 设备。这意味着通信双方不需要先完成 Wi-Fi 连接、DHCP 或 TCP/IP 握手,只要在同一个信道上即可直接收发数据,因此具有极低的建立延迟极小的协议开销

CTR 与 CBC-MAC 协议(CCMP,Counter Mode with CBC-MAC)可用来保护动作帧的安全,其具体实现参考 IEEE Std. 802.11-2012。目前 ESP-NOW 广泛应用于智能照明、远程控制、传感器等领域。

帧格式:供应商动作帧与特定供应商元素

ESP-NOW 使用供应商动作帧传输数据,默认比特率为 1 Mbps。理解其帧结构是进行协议分析、抓包调试和兼容性判断的基础。

协议版本与数据长度上限

目前 ESP-NOW 支持两个版本:v1.0v2.0,对应的最大数据包长度定义在 esp_now.h:

版本最大数据长度宏定义说明
v1.0250 字节ESP_NOW_MAX_DATA_LEN(即ESP_NOW_MAX_IE_DATA_LEN单次传输的数据长度上限
v2.01470 字节ESP_NOW_MAX_DATA_LEN_V2单次传输的数据长度上限

版本间的兼容规则如下:

  • v2.0 设备可以接收来自 v2.0 和 v1.0 设备的数据包;
  • v1.0 设备只能接收来自 v1.0 设备的数据包;
  • 不过,v1.0 设备也可以接收长度不超过 250(ESP_NOW_MAX_IE_DATA_LEN)字节的 v2.0 数据包;如果长度超过此值,v1.0 设备要么只接收前 250 字节,要么直接丢弃该数据包。

具体行为请参考对应 IDF 版本中的文档。应用程序可以通过esp_now_get_version()查询当前固件支持的 ESP-NOW 版本。

供应商动作帧格式

供应商动作帧的整体格式如下:

----------------------------------------------------------------------------- | MAC 报头 | 分类代码 | 组织标识符 | 随机值 | 供应商特定内容 | FCS | ----------------------------------------------------------------------------- 24 字节 1 字节 3 字节 4 字节 7-x 字节 4 字节

各字段含义:

  • 分类代码(Category Code):用于指示各个供应商的类别,例如 127;
  • 组织标识符(Organization Identifier,OUI):包含一个唯一标识符(比如0x18fe34),为乐鑫指定的 MAC 地址的前三个字节。可通过esp_now_set_user_oui()/esp_now_get_user_oui()自定义或查询 OUI,传NULL时使用默认值0x18fe34
  • 随机值(Random Value):用于防止重放攻击;
  • 供应商特定内容(Vendor Specific Content):包含若干个(大于等于 1)特定供应商元素字段。对于 v2.0 版本,x = 1512 (1470 + 6*7);对于 v1.0 版本,x = 257 (250 + 7)
  • FCS(Frame Check Sequence):帧校验序列。

特定供应商元素格式

特定供应商元素(Vendor-Specific Element)的帧格式因版本而异:

ESP-NOW v1.0: --------------------------------------------------------------------------- | 元素 ID | 长度 | 组织标识符 | 类型 | 保留 | 版本 | 正文 | --------------------------------------------------------------------------- 7~4 比特| 3~0 比特 1 字节 1 字节 3 字节 1 字节 1 字节 0-250 字节 ESP-NOW v2.0: ------------------------------------------------------------------------------------- | 元素 ID | 长度 | 组织标识符 | 类型 | 保留 |更多数据 | 版本 | 正文 | ------------------------------------------------------------------------------------- 7~5 比特 | 1 比特 | 3~0 比特 1 字节 1 字节 3 字节 1 字节 1 字节 0-250 字节

字段说明:

  • 元素 ID(Element ID):用于指示特定于供应商的元素;
  • 长度(Length):组织标识符、类型、版本和正文的总长度,最大值为 255;
  • 组织标识符:唯一标识符(比如0x18fe34),为乐鑫指定的 MAC 地址的前三个字节;
  • 类型(Type):设置为 4,代表 ESP-NOW;
  • 版本(Version):设置为 ESP-NOW 的版本(v1.0 或 v2.0);
  • 正文(Body):包含实际要发送的 ESP-NOW 数据,最长 250 字节(元素级长度上限)。

v2.0 在保留字段中新增了“更多数据”(More Data)位(1 比特),用于支持超过单元素长度上限的更大数据包传输(1470 字节)。

MAC 报头的无连接特性

由于 ESP-NOW 是无连接的,其 MAC 报头与标准帧略有不同:

  • FrameControl 字段的 FromDS 和 ToDS 位均为 0(即不经过 AP 转发,属于 IBSS 之外的直接传输);
  • 第一个地址字段用于配置目标地址(目的 MAC);
  • 第二个地址字段用于配置源地址(源 MAC);
  • 第三个地址字段用于配置广播地址0xff:0xff:0xff:0xff:0xff:0xff

这种地址排列使得 ESP-NOW 既能支持单播(目标地址为对端 MAC),也能支持广播(目标地址为全0xFF)。

安全:PMK 与 LMK 的两级密钥体系

ESP-NOW 采用 CCMP 方法保护供应商特定动作帧的安全(参考 IEEE Std. 802.11-2012)。Wi-Fi 设备维护一个初始主密钥(PMK,Primary Master Key)和若干本地主密钥(LMK,Local Master Key),每个配对设备拥有一个 LMK,两者长度均为16 字节(对应源码中的ESP_NOW_KEY_LEN)。

两级密钥的分工如下:

  • PMK用于使用 AES-128 算法加密 LMK。应用通过esp_now_set_pmk()设置 PMK;如果未设置,将使用默认 PMK;
  • LMK用于通过 CCMP 方法对供应商特定动作帧进行加密。如果未设置配对设备的 LMK,则动作帧不进行加密

需要注意:目前不支持加密组播(multicast)供应商特定动作帧。此外,从源码结构看,SoftAP 与 ESP-NOW 共享同一套加密硬件密钥资源,因此 esp_wifi.h 中提示max_connection参数会受CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM的影响。

初始化和反初始化

调用esp_now_init()初始化 ESP-NOW,调用esp_now_deinit()反初始化 ESP-NOW。API 的返回码定义在 esp_now.h,其中ESP_ERR_ESPNOW_NOT_INITESP_ERR_ESPNOW_ARGESP_ERR_ESPNOW_NO_MEM等错误码可用于排查调用失败原因。

关键时序要求:

  • ESP-NOW 数据必须在 Wi-Fi 启动后传输,因此建议在初始化 ESP-NOW之前启动 Wi-Fi,并在反初始化 ESP-NOW之后停止 Wi-Fi;
  • 当调用esp_now_deinit()时,配对设备的所有信息都将被删除

添加配对设备

在将数据发送到其他设备之前,请先调用esp_now_add_peer()将目标设备添加到配对设备列表中。如果启用了加密,则必须设置 LMK。

esp_now_peer_info_t结构体(定义于 esp_now.h)是配对的核心参数:

字段说明
peer_addr[6]对端 MAC 地址(Station 或 SoftAP 的 MAC 地址)
lmk[16]本地主密钥,用于加密数据
channel对端收发 ESP-NOW 数据的 Wi-Fi 信道;若为 0,则使用当前信道
ifidx对端使用的 Wi-Fi 接口(STA 或 AP)
encrypt该对端收发数据是否加密
priv对端私有数据指针

ESP-NOW 数据可以从Station 或 SoftAP 接口发送。确保在发送 ESP-NOW 数据之前已启用该接口。

信道规则(按芯片区分)

  • ESP32 / ESP32-C2 / ESP32-S2 / ESP32-S3 / ESP32-C3 / ESP32-C6:配对设备的信道范围是0 ~ 14。如果信道设置为 0,数据将在当前信道上发送;否则,必须使用本地设备所在的信道;
  • ESP32-C5:2.4G 频段配对设备的信道范围是1 ~ 14;5G 频段配对设备的信道范围是[36, 40, 44, 48, 52, 56, 60, 64, 100, 112, 116, 120, 124, 128, 132, 136, 140, 144, 149, 153, 157, 161, 165, 169, 173, 177]。如果信道设置为 0,数据将在当前信道上发送;否则,必须使用本地设备所在的信道。

接收端是否必须添加配对设备

对于接收设备,调用esp_now_add_peer()不是必需的

  • 如果没有添加配对设备,只能接收广播包不加密的单播包
  • 如果需要接收加密的单播包,则必须添加配对设备并设置相同的 LMK。

配对设备数量限制

  • ESP32-C2:配对设备的最大数量是20,其中加密设备的数量不超过4,默认值是2。可通过 Wi-Fi menuconfig 中的CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM修改加密设备的数量(该配置项定义于 esp_wifi/Kconfig);
  • ESP32 / ESP32-S2 / ESP32-S3 / ESP32-C3 / ESP32-C6 / ESP32-C5:配对设备的最大数量是20,其中加密设备的数量不超过17,默认值是7。同样可通过CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM修改。

源码中的ESP_NOW_MAX_TOTAL_PEER_NUM(20)与ESP_NOW_MAX_ENCRYPT_PEER_NUM(6)提供了编译期默认约束,esp_now_get_peer_num()可查询当前已有的配对总数与加密对端数。

发送 ESP-NOW 数据

调用esp_now_send()发送 ESP-NOW 数据,调用esp_now_register_send_cb()注册发送回调函数。发送回调的原型为:

typedef void (*esp_now_send_cb_t)(const esp_now_send_info_t *tx_info, esp_now_send_status_t status);

发送结果的判定与重传策略:

  • 如果 MAC 层成功接收到数据,回调将返回ESP_NOW_SEND_SUCCESS事件;否则返回ESP_NOW_SEND_FAIL
  • ESP-NOW 数据发送失败可能有几种原因,比如目标设备不存在、设备的信道不相同、动作帧在传输过程中丢失等;
  • 应用层并不一定总能接收到数据。如果需要,应用层可在接收 ESP-NOW 数据时发回一个应答(ACK)数据;如果接收 ACK 数据超时,则重新传输 ESP-NOW 数据;
  • 可以为 ESP-NOW 数据设置序列号,从而在接收端删除重复的数据。

esp_now_send()的三种发送目标(见 esp_now.h 的注意事项):

  • peer_addr非 NULL:向 MAC 地址匹配的配对设备发送;
  • peer_addr为 NULL:向配对列表中的所有设备发送;
  • 广播地址0xFF:0xFF:0xFF:0xFF:0xFF:0xFF:广播发送。

大批量发送的注意事项

如果有大量 ESP-NOW 数据要发送,调用esp_now_send()时需注意:

  1. 单次发送的数据不能超过最大数据包长(v1.0 为 250 字节,v2.0 为 1470 字节);
  2. 两个 ESP-NOW 数据包的发送间隔太短可能导致回调函数返回混乱,因此建议在等到上一次回调函数返回 ACK 后再发送下一个 ESP-NOW 数据
  3. 发送回调函数从高优先级的 Wi-Fi 任务中运行,不要在回调函数中执行冗长的操作。相反,应将必要的数据发布到队列,交给优先级较低的任务处理。

esp_now_send()可能的返回错误包括ESP_ERR_ESPNOW_NO_MEM(此时可延迟片刻再发送下一条)、ESP_ERR_ESPNOW_NOT_FOUND(对端不在列表中)、ESP_ERR_ESPNOW_IF(当前 Wi-Fi 接口与对端不匹配)、ESP_ERR_ESPNOW_CHAN(当前 Wi-Fi 信道与对端不匹配)等。

接收 ESP-NOW 数据

调用esp_now_register_recv_cb()注册接收回调函数。接收回调原型为:

typedef void (*esp_now_recv_cb_t)(const esp_now_recv_info_t *esp_now_info, const uint8_t *data, int data_len);

esp_now_recv_info_t中包含源地址src_addr、目的地址des_addr以及接收控制信息rx_ctrl。由于广播与加密单播在接收路径上可能并存,可以通过检查des_addr是否为广播地址来区分(示例工程中即通过IS_BROADCAST_ADDR(des_addr)判断)。

接收回调函数同样在Wi-Fi 任务中运行,因此:

  • 不要在回调函数中执行冗长的操作;
  • 将必要的数据发布到队列,交给优先级较低的任务处理;
  • 注意esp_now_info是局部变量,只能在回调函数内部使用。

配置 ESP-NOW 速率

调用esp_now_set_peer_rate_config()配置每个 peer 的 ESP-NOW 速率(对应源码中的esp_now_rate_config_t,即wifi_tx_rate_config_t)。使用约束:

  • 请确保在配置速率之前添加 peer;
  • 此 API 应在esp_wifi_start()esp_now_add_peer()之后调用。

在示例工程的example_wifi_init()中,若启用 Long Range 选项(CONFIG_ESPNOW_ENABLE_LONG_RANGE),会通过esp_wifi_set_protocol()追加WIFI_PROTOCOL_LR,此时 ESP32 设备将以512Kbps 或 256Kbps 的 PHY 速率发送数据,从而在两端之间实现更远距离的传输。

配置 ESP-NOW 功耗参数

当且仅当目标芯片配置为STA 模式时,允许其进行休眠。进行休眠时,调用esp_now_set_wake_window()为 ESP-NOW 收包配置 Window:

  • 参数window表示每个间隔内芯片保持唤醒的毫秒数,范围 0 ~ 65535;
  • 默认情况下 Window 为最大值,将允许一直收包;
  • 该配置在连接状态下可工作;当启用了ESP_WIFI_STA_DISCONNECTED_PM_ENABLE时,在断开状态下也可工作(见 esp_now.h 的注释)。

如果对 ESP-NOW 进行功耗管理,还需要调用esp_wifi_connectionless_module_set_wake_interval()(声明于 esp_wifi.h),用于设置无连接模块的唤醒间隔。更完整的信息可参考 ESP-IDF 文档中的“非连接模块功耗管理”(connectionless-module-power-save)章节。

应用示例:基于 espnow 示例工程的完整开发流程

examples/wifi/espnow 演示了如何在 ESP32 系列芯片上使用 Wi-Fi 的 ESPNOW 功能,完整覆盖了本文前述的启动 Wi-Fi、初始化 ESP-NOW、注册发送/接收回调、添加对端信息以及在两台设备之间收发数据的全部步骤。工程至少需要两台 ESP 设备

示例的工作流程

依据 README.md 与 espnow_example_main.c,示例的交互流程如下:

  1. 设备 1先发送state=0的广播 ESP-NOW 数据,以便获知对方 MAC 地址;
  2. 设备 2收到state=0的广播数据后,将设备 1 加入配对列表,并开始发送state=1的广播数据;
  3. 设备 1收到state=1的广播数据后,比较本地 magic 数与数据中的 magic 数:若本地 magic 更大,则停止广播,开始向设备 2 发送单播 ESP-NOW 数据;
  4. 设备 2收到单播数据后,也停止广播。

实际工程中,如果已知对方 MAC 地址,则不必先通过广播交互,直接将对端加入配对列表并收发单播数据即可。

示例中的数据可靠性设计

示例在 ESP-NOW 原始数据之上额外封装了typestateseq_num(序列号)、crc(CRC16 校验)和magic等“附加字段”(见 espnow_example.h)。这些字段并非 ESP-NOW 协议所必需,仅用于让示例可靠运行;但官方明确建议实际产品在 ESP-NOW 数据中加入类似扩展,以提升数据的安全性与可靠性:

  • seq_num用于识别并去重(对应文档中“设置序列号删除重复数据”的建议);
  • crc使用esp_crc16_le()计算校验,用于检测数据损坏;
  • magic用于在广播握手阶段裁决哪台设备负责发起单播。

回调与低优先级任务解耦

示例中,发送/接收回调只负责把事件投递到 FreeRTOS 队列s_example_espnow_queue,真正的数据处理(解析、应答、发送下一条)在低优先级的example_espnow_task中完成。发送端严格遵循“等上一次发送回调返回后再发下一条”的策略,并通过send_param->delay控制两条数据之间的间隔,这正是文档中“不要连续快速发送、避免回调混乱”建议的工程化落地。

配置项说明(menuconfig)

进入工程目录后执行idf.py menuconfig,在Example Configuration菜单下可配置(定义见 Kconfig.projbuild):

配置项默认值取值范围说明
ESPNOW_WIFI_MODEStationStation / SoftAPWi-Fi 工作模式
ESPNOW_PMKpmk123456789012316 字节字符串主密钥,收发双方必须一致
ESPNOW_LMKlmk123456789012316 字节字符串本地主密钥,收发双方必须一致
ESPNOW_CHANNEL10 ~ 14收发 ESP-NOW 数据的信道,双方必须相同
ESPNOW_SEND_COUNT1001 ~ 65535单播数据发送总次数
ESPNOW_SEND_DELAY10000 ~ 65535两条数据间的发送间隔(ms)
ESPNOW_SEND_LEN1010 ~ 1470发送数据长度(字节)
ESPNOW_ENABLE_LONG_RANGE关闭开/关启用后 PHY 速率为 512Kbps 或 256Kbps,可远距离传输
ESPNOW_ENABLE_POWER_SAVE关闭开/关启用 ESP-NOW 功耗管理(仅 Station 模式)
ESPNOW_WAKE_WINDOW500 ~ 65535唤醒窗口(ms)
ESPNOW_WAKE_INTERVAL1001 ~ 65535唤醒间隔(ms)

要点:PMK 与 LMK 在收发双方必须设置为相同值;发送与接收设备必须处于同一信道

构建、烧录与运行

idf.py set-target esp32 # 按实际芯片选择目标 idf.py menuconfig # 按上文配置项进行设置 idf.py -p PORT flash monitor # 构建、烧录并打开串口监视器

(退出串口监视器:按Ctrl-]。)

接收设备控制台输出示例:

I (898) espnow_example: WiFi started I (898) ESPNOW: espnow [version: 1.0] init I (5908) espnow_example: Start sending broadcast data I (52138) espnow_example: Receive 0th broadcast data from: 30:ae:a4:0c:34:ec, len: 200 I (54168) espnow_example: Receive 0th unicast data from: 30:ae:a4:0c:34:ec, len: 200

常见问题排查

  • 无法收到对端数据:可能原因是对端不在同一信道,或 PMK / LMK 配置不一致;
  • Station 模式接收问题:在真实应用中,如果接收设备仅为 Station 模式且已连接 AP,应禁用 modem sleep,否则可能无法收到来自其他设备的 ESP-NOW 数据;
  • 加密单播收不到:接收端必须添加对端并设置相同的 LMK;
  • 发送失败:检查esp_now_send()返回码,特别是ESP_ERR_ESPNOW_IF(接口不匹配)与ESP_ERR_ESPNOW_CHAN(信道不匹配)。

其他常用 API 速查

除本文重点讲解的 API 外,esp_now.h 还提供以下常用接口:

函数作用
esp_now_del_peer()从配对列表删除对端
esp_now_mod_peer()修改对端信息(如切换加密、改信道)
esp_now_get_peer()/esp_now_fetch_peer()查询 / 遍历配对列表
esp_now_is_peer_exist()判断对端是否已在列表中
esp_now_get_peer_num()获取当前配对总数与加密对端数
esp_now_unregister_recv_cb()/esp_now_unregister_send_cb()注销收发回调
esp_now_switch_channel_tx()/esp_now_remain_on_channel()切换到指定信道并发送数据 / 在目标信道停留指定时长
esp_now_get_version()查询当前 ESP-NOW 协议版本

小结

ESP-NOW 以供应商动作帧为载体,通过 PMK/LMK 两级密钥与 CCMP 加密提供安全能力,并以“无连接、免握手”的特性成为智能照明、远程控制、传感器等低延迟、低开销物联网场景的理想选择。开发时需重点把握:先启动 Wi-Fi 再初始化 ESP-NOW、发送前添加对端、收发回调中不做耗时操作、严格控制发送节奏与数据长度。结合 espnow 示例工程 的广播握手、CRC 校验、序列号去重与队列解耦设计,即可快速搭建出稳定可靠的 ESP-NOW 点对点通信应用。

【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询