1. ESP-RPC 与 ESP-NOW:两类截然不同的乐鑫无线通信范式

在嵌入式无线通信领域,“ESP-NOW”与“ESP-RPC”常被初学者混淆。但二者在协议栈层级、应用场景、系统资源占用及工程实现逻辑上存在本质差异。本节不讨论视频中误读的“ESP RTC”,而是基于字幕实际内容与乐鑫官方技术文档,厘清 ESP-NOW 的真实定位,并明确其与音视频实时通信(RTC)方案的技术边界。

ESP-NOW 是乐鑫在 ESP-IDF 框架中提供的 链路层直连通信机制 ,运行于 IEEE 802.11 MAC 层之上,绕过 TCP/IP 协议栈与 Wi-Fi 关联过程。它不依赖 AP,不建立 IP 连接,不分配 IP 地址,不经过 DHCP 或 DNS 流程。其核心目标是实现 极低功耗、超低延迟、高可靠性的点对点或一对多单向/双向数据报文传输 ,典型应用于传感器网络、遥控器、工业 IO 点同步等场景。

而字幕中反复提及的“ESP RTC”实为口误或命名偏差。乐鑫官方并未发布名为“ESP RTC”的独立 SDK。实际对应的是 ESP-ADF(Audio Development Framework) + ESP-IDF 中的 SIP 栈组件 + 自研音视频编解码与网络传输模块 所构成的端到端音视频通信解决方案,其底层依赖 Wi-Fi STA 模式下的完整 TCP/IP 协议栈、RTP/RTCP 流媒体传输、SIP 信令控制及硬件加速的 MJPEG/H.264 编解码能力。该方案运行于 ESP32-S3-DevKitC-1(即字幕所称“ESP32-S3 COVE-2”开发板)之上,需启用双核 FreeRTOS、DMA 音频通道、摄像头接口(DVP 或 MIPI)、LCD 显示控制器及 SDIO 高速外设总线。

二者不可混用,亦无法通过简单配置将 ESP-NOW 升级为音视频流通道。试图在 ESP-NOW 上承载 MJPEG 视频帧或 PCM 音频流,将直接导致以下工程失败:
- ESP-NOW 单包最大有效载荷仅 250 字节(含 MAC 头),而 480P MJPEG 帧平均大小在 15–30 KB 区间,需拆分为 60–120 个碎片包;
- 无重传机制与拥塞控制,任意一包丢失即造成整帧解码失败,视觉表现为严重马赛克或卡顿;
- 无时间戳与序列号同步机制,无法保障音视频 PTS/DTS 对齐,导致唇音不同步;
- 无 QoS 分类与队列管理,音频小包与视频大包在射频信道上竞争,语音延时不可控。

因此,理解 ESP-NOW 的设计初衷与能力边界,是构建可靠无线系统的前提。下文将完全聚焦于 ESP-NOW 的工程实践,剥离所有音视频相关干扰项,还原其作为轻量级无线数据链路的本质。

2. ESP-NOW 协议栈定位与硬件依赖关系

ESP-NOW 并非独立协议,而是乐鑫对 IEEE 802.11 协议族中 Action Frame(动作帧)机制的定制化封装 。其物理层(PHY)与介质访问控制层(MAC)完全复用 ESP32 系列 SoC 的 Wi-Fi 射频前端与基带处理器,无需额外硬件资源。这意味着:

  • 所有支持 Wi-Fi 功能的 ESP32 芯片(ESP32, ESP32-S2, ESP32-S3, ESP32-C3, ESP32-C6)均原生支持 ESP-NOW;
  • 启用 ESP-NOW 不影响 Wi-Fi STA/AP 模式共存——同一设备可同时作为 Wi-Fi 客户端接入路由器,并使用 ESP-NOW 与其他节点直连通信;
  • 射频性能参数(如发射功率、接收灵敏度、信道选择)完全继承自 Wi-Fi PHY,可通过 esp_wifi_set_max_tx_power() esp_wifi_set_channel() 统一配置;
  • 无专用加密协处理器,AES-128 加密由软件库( mbedtls_aes_crypt_ecb )或 ESP32-S3/S2 内置的 AES 硬件加速引擎完成,取决于 IDF 版本与配置选项。

从软件架构看,ESP-NOW 位于 ESP-IDF 协议栈的 Wi-Fi Driver 层之上、TCP/IP Stack 层之下 ,如下图所示(文字描述):

Application Layer
│
├── ESP-NOW API (esp_now_send, esp_now_register_recv_cb)
│
▼
ESP-NOW Protocol Engine (esp_now.c)
│  ├─ Encryption/Decryption (AES-128-ECB)
│  ├─ Peer Management (peer list, key binding)
│  └─ Retry & ACK Logic (configurable)
│
▼
Wi-Fi Driver (wifi_driver.c)
│  ├─ 802.11 Action Frame Construction
│  ├─ RF Channel Synchronization
│  └─ MAC Address Filtering
│
▼
Wi-Fi PHY / RF Hardware

关键点在于:ESP-NOW 不感知 IP 地址、端口号、TCP 序列号或 UDP 校验和。它只处理 MAC 地址(6 字节)、数据净荷(≤250 字节)与可选的 16 字节 AES 密钥 。每个发送操作对应一个 IEEE 802.11 Action Frame,其帧结构如下(精简版):

字段 长度(字节) 说明
Frame Control 2 Type=00(Management),Subtype=1000(Action)
Duration 2 RTS/CTS 保护时长,通常设为 0x0000
Destination Address 6 接收方 MAC 地址(Peer MAC)
Source Address 6 发送方 MAC 地址(自动填充)
BSSID 6 通常与 Source Address 相同(Ad-hoc 模式)
Sequence Control 2 自动递增,用于重传检测
Category 1 0x04(Self-protected Action)
Action 1 0x00(Spectrum Management),实际由乐鑫私有解析
Vendor Specific 可变 乐鑫私有 TLV 结构:包含版本号、加密标志、数据长度、净荷

此帧由 Wi-Fi 驱动直接注入射频链路,跳过 Beacon、Probe Request/Response、Association 等 Wi-Fi 管理帧流程。因此,ESP-NOW 设备启动后无需“连接 Wi-Fi 网络”,只需调用 esp_now_init() 初始化驱动并注册回调函数,即可进入通信就绪状态。

3. ESP-NOW 工程初始化与对等节点注册

ESP-NOW 的初始化流程必须严格遵循时序约束,任何步骤缺失或顺序错误都将导致 ESP_ERR_ESPNOW_NOT_INIT ESP_ERR_ESPNOW_ARG 错误。完整流程如下:

3.1 基础环境准备

app_main() 中执行前,需确保:
- nvs_flash_init() 已完成,用于持久化存储配对密钥(若启用加密);
- tcpip_adapter_init() 已调用,即使不使用 IP 协议栈,Wi-Fi 驱动仍依赖 TCP/IP 适配器的事件循环机制;
- esp_netif_init() 已初始化,为 Wi-Fi 驱动提供网络接口抽象;
- esp_event_loop_create_default() 已创建默认事件循环,用于接收 ESP-NOW 事件(如发送完成、接收中断)。

void app_main(void)
{
    // 1. 初始化 NVS 存储(必需)
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    // 2. 初始化 TCP/IP 适配器与事件循环(必需)
    tcpip_adapter_init();
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    // 3. 初始化 Wi-Fi 驱动(必需,即使不启用 Wi-Fi)
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
}

3.2 ESP-NOW 初始化与角色配置

esp_now_init() 必须在 esp_wifi_set_mode() 之后调用,且 Wi-Fi 模式可设为 WIFI_MODE_NULL (纯 ESP-NOW 模式)或 WIFI_MODE_STA (STA+ESP-NOW 共存模式)。推荐使用后者,便于后续 OTA 升级或调试:

// 设置 Wi-Fi 模式为 STA(即使不连接 AP)
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
// 初始化 ESP-NOW
ESP_ERROR_CHECK(esp_now_init());
// 注册发送完成回调(可选但强烈推荐)
esp_now_register_send_cb(on_data_sent);
// 注册接收回调(必需)
esp_now_register_recv_cb(on_data_received);

on_data_sent 回调原型为 void on_data_sent(const uint8_t *mac_addr, esp_now_send_status_t status) ,其中 status 取值为 ESP_NOW_SEND_SUCCESS ESP_NOW_SEND_FAIL ,用于判断是否需要重传。 on_data_received 原型为 void on_data_received(const uint8_t *mac_addr, const uint8_t *data, int len) data 指向接收到的净荷缓冲区, len 为实际字节数(≤250)。

3.3 对等节点(Peer)注册

ESP-NOW 要求通信双方预先注册对方的 MAC 地址。注册方式分为两种:

方式一:静态注册(推荐用于固定拓扑)

适用于已知所有节点 MAC 地址的场景(如工厂预烧录)。调用 esp_now_peer_info_t 结构体显式配置:

esp_now_peer_info_t peer;
memset(&peer, 0, sizeof(peer));
// 设置目标节点 MAC 地址(示例:24:0A:C4:12:34:56)
uint8_t peer_mac[6] = {0x24, 0x0a, 0xc4, 0x12, 0x34, 0x56};
memcpy(peer.peer_addr, peer_mac, 6);
// 设置加密类型:ESP_NOW_ENCRYPTED 或 ESP_NOW_UNENCRYPTED
peer.channel = 0; // 信道号(1–14),0 表示使用当前 Wi-Fi 信道
peer.ifidx = ESP_IF_WIFI_STA; // 绑定到 STA 接口
peer.encrypt = ESP_NOW_UNENCRYPTED; // 无加密(调试阶段)

esp_err_t add_stat = esp_now_add_peer(&peer);
if (add_stat != ESP_OK) {
    printf("Failed to add peer: %s\n", esp_err_to_name(add_stat));
    return;
}
方式二:动态配对(适用于现场组网)

通过广播“配对请求帧”实现自动发现。发送方构造一个含自身 MAC 与设备 ID 的明文帧,接收方收到后解析并调用 esp_now_add_peer() 注册该地址。此过程需自行定义帧格式与超时重传逻辑,典型结构如下:

typedef struct {
    uint8_t magic[4];   // "ENOW"
    uint8_t role;       // 0x01=Master, 0x02=Slave
    uint8_t mac[6];     // 发送方 MAC
    uint32_t timestamp; // Unix 时间戳,用于防重放
} __attribute__((packed)) pairing_req_t;

pairing_req_t req = {.magic = {0x45, 0x4e, 0x4f, 0x57}, .role = 0x01};
memcpy(req.mac, esp_mac, 6);
req.timestamp = time(NULL);

// 向广播地址发送(FF:FF:FF:FF:FF:FF)
esp_now_send(broadcast_mac, (uint8_t*)&req, sizeof(req));

接收方在 on_data_received 中识别 magic 字段,校验 timestamp 有效性后,提取 mac 并注册为新 peer。注意:动态配对需关闭加密(因密钥尚未协商),且应限制配对窗口期(如 60 秒),避免非法设备接入。

4. 数据传输机制与可靠性增强策略

ESP-NOW 默认采用“尽力而为”(Best-Effort)传输模型,无内置 ACK 重传。但 esp_now_send() 函数返回值与回调状态提供了构建可靠传输的基础。工程实践中,必须主动实现以下三类增强机制:

4.1 发送确认与重传控制

esp_now_send() 返回 ESP_OK 仅表示数据已提交至 Wi-Fi 驱动队列,并不保证送达。真正可靠的送达信号来自 on_data_sent 回调中的 status 参数。标准重传逻辑如下:

#define MAX_RETRY 3
#define RETRY_DELAY_MS 20

typedef struct {
    uint8_t dst_mac[6];
    uint8_t *data;
    uint16_t len;
    uint8_t retry_count;
} send_task_t;

void send_with_retry(const uint8_t *mac, const uint8_t *data, uint16_t len)
{
    send_task_t *task = malloc(sizeof(send_task_t));
    memcpy(task->dst_mac, mac, 6);
    task->data = malloc(len);
    memcpy(task->data, data, len);
    task->len = len;
    task->retry_count = 0;

    _send_task(task);
}

static void _send_task(send_task_t *task)
{
    esp_err_t ret = esp_now_send(task->dst_mac, task->data, task->len);
    if (ret != ESP_OK) {
        printf("Send failed: %s\n", esp_err_to_name(ret));
        free(task->data);
        free(task);
        return;
    }
}

void on_data_sent(const uint8_t *mac_addr, esp_now_send_status_t status)
{
    send_task_t *task = find_pending_task(mac_addr); // 需自行实现任务查找
    if (!task) return;

    if (status == ESP_NOW_SEND_SUCCESS) {
        printf("Packet delivered to " MACSTR "\n", MAC2STR(mac_addr));
        free(task->data);
        free(task);
    } else if (task->retry_count < MAX_RETRY) {
        task->retry_count++;
        vTaskDelay(RETRY_DELAY_MS / portTICK_PERIOD_MS);
        _send_task(task);
    } else {
        printf("Max retry reached for " MACSTR "\n", MAC2STR(mac_addr));
        free(task->data);
        free(task);
    }
}

该机制将应用层发送与底层重试解耦,避免阻塞主任务。重试间隔 RETRY_DELAY_MS 需根据信道负载调整:在密集部署环境中(>10 节点),建议设为 50–100 ms 以降低冲突概率。

4.2 数据分片与重组(突破 250 字节限制)

当需传输大于 250 字节的数据(如固件块、图像元数据)时,必须实现应用层分片。分片协议需包含:总包数、当前序号、数据偏移、校验和。推荐采用如下轻量格式:

字段 长度(字节) 说明
Header Magic 2 0xA5A5
Total Fragments 1 总分片数(1–255)
Fragment Index 1 当前分片索引(0–254)
Data Offset 2 当前分片在原始数据中的起始偏移(BE)
CRC16 2 整个分片数据的 CRC16-CCITT
Payload ≤242 净荷(250 - 8)

接收方维护一个 fragment_buffer_t 结构体,按 Fragment Index 存储各分片,当 Fragment Index == Total Fragments - 1 且所有中间索引均已到达时,执行重组。关键点在于:
- 使用环形缓冲区管理未完成的重组会话,避免内存泄漏;
- 为每个会话设置超时定时器(如 5 秒),超时则丢弃已接收分片;
- Data Offset 字段允许非连续接收(如网络抖动导致分片乱序),提升鲁棒性。

4.3 信道优化与抗干扰策略

ESP-NOW 默认使用 Wi-Fi 当前信道,但在多 AP 环境中易受 Wi-Fi 流量干扰。可通过以下方式优化:

  • 固定信道 :在 esp_now_init() 前调用 esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE) 强制锁定信道 1(2.412 GHz),避开拥挤的信道 6/11;
  • 动态信道选择 :启动时扫描周围 Wi-Fi AP 数量,选择 RSSI 最弱的信道(需调用 esp_wifi_scan_start() 获取结果);
  • 功率控制 :在空旷环境降低发射功率( esp_wifi_set_max_tx_power(4) ,单位 0.25 dBm),减少同频干扰;
  • 速率控制 :强制使用最低基础速率(如 1 Mbps),提升接收灵敏度,代价是吞吐量下降。

这些配置需在 esp_now_init() 之前完成,因为 ESP-NOW 初始化会读取当前 Wi-Fi 配置并固化信道绑定。

5. 安全机制:AES-128 加密的工程实现

ESP-NOW 支持 AES-128-ECB 加密,但其安全性存在固有局限:ECB 模式不隐藏数据模式,相同明文块始终生成相同密文块。因此, 绝不应将 AES-128-ECB 用于传输敏感业务数据(如密码、密钥) 。其合理用途仅限于:
- 防止邻居节点窃听控制指令(如“打开继电器”、“上报温度”);
- 满足某些行业合规性要求(如 CE/FCC 认证中对无线指令的最低加密要求)。

5.1 加密密钥管理

密钥必须通过安全渠道预置,严禁硬编码在固件中。推荐方案:
- NVS 分区存储 :在 nvs_flash_init() 后,从专用 NVS 命名空间读取密钥;
- 外部安全芯片 :通过 I²C 读取 ATECC608A 的密钥槽;
- JTAG 烧录时注入 :使用 esptool.py --chip esp32s3 write_flash 将密钥写入特定 Flash 地址。

密钥长度严格为 16 字节。若使用字符串密钥(如 "MySecretKey123456" ),需进行 SHA256 哈希并取前 16 字节:

uint8_t key[16];
SHA256_CTX ctx;
sha256_init(&ctx);
sha256_update(&ctx, (const uint8_t*)"MySecretKey123456", 16);
sha256_final(&ctx, key); // key[0..15] 即为有效密钥

5.2 Peer 级别加密启用

加密必须在 esp_now_add_peer() 时指定,且 每个 peer 可配置独立密钥 。这是 ESP-NOW 安全模型的核心优势——支持一对一对称密钥,避免全局密钥泄露风险:

esp_now_peer_info_t peer;
memcpy(peer.peer_addr, target_mac, 6);
peer.channel = 1;
peer.ifidx = ESP_IF_WIFI_STA;
peer.encrypt = ESP_NOW_ENCRYPTED;
// 设置 16 字节密钥
memcpy(peer.lmk, key, 16); // lmk = Local Master Key

esp_err_t add_ret = esp_now_add_peer(&peer);

一旦启用加密,所有发往该 peer 的数据帧均被 AES-128-ECB 加密,接收方使用相同密钥自动解密。注意:密钥变更需重新 esp_now_del_peer() esp_now_add_peer() ,无法热更新。

5.3 安全实践警示

  • 禁用广播通信加密 :广播地址(FF:FF:FF:FF:FF:FF)不支持加密,任何启用加密的 peer 均无法接收广播帧;
  • 避免密钥复用 :切勿在多个设备间共享同一密钥,否则单点破解即全网沦陷;
  • 定期轮换密钥 :在支持 OTA 的系统中,设计密钥轮换机制,每次固件升级同步更新密钥;
  • 监控密钥泄露 :在生产固件中加入密钥使用统计,异常高频访问触发告警。

6. 实际项目调试经验与典型故障排查

在数十个 ESP-NOW 商业项目中,以下问题出现频率最高,其根本原因与解决路径值得记录:

6.1 “发送成功但对方收不到” —— 信道与模式错配

现象: on_data_sent 回调返回 ESP_NOW_SEND_SUCCESS ,但接收方 on_data_received 完全无响应。

根因分析:
- 发送方 Wi-Fi 模式为 WIFI_MODE_STA 但未连接 AP,接收方却设为 WIFI_MODE_AP ,导致信道不一致(STA 模式默认信道 1,AP 模式默认信道 6);
- 一方使用 esp_wifi_set_channel(6, ...) 锁定信道,另一方未同步,形成“鸡同鸭讲”。

验证方法:
- 在双方代码中插入 wifi_second_chan_t sec; uint8_t primary; esp_wifi_get_channel(&primary, &sec); printf("Channel: %d\n", primary); ,确认输出一致;
- 使用手机 Wi-Fi 分析仪 App(如 NetAnalyzer)扫描 2.4 GHz 频段,观察两设备是否在同一信道发射。

解决方案:
- 统一强制设置信道: esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE); esp_now_init() 前执行;
- 确保双方 Wi-Fi 模式兼容:均设为 WIFI_MODE_STA 或均设为 WIFI_MODE_NULL

6.2 “接收回调频繁触发但数据乱码” —— 内存越界与回调重入

现象: on_data_received 被高频调用, data 指针指向非法地址, len 值异常(如 65535)。

根因分析:
- on_data_received 运行在 Wi-Fi 中断上下文(IRAM),若在回调中调用 printf malloc vTaskDelay 等非 IRAM 安全函数,将导致堆栈溢出或内存破坏;
- 应用层未对 data 缓冲区做长度检查,直接 memcpy 到固定大小数组,引发栈溢出。

解决方案:
- 回调内仅做最小化操作 :复制 data 到预分配的 DMA 安全区,然后 xQueueSendFromISR() 通知任务处理;
- 严格校验 len if (len > 0 && len <= 250) { /* safe copy */ } else { return; }
- 使用 CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=y 编译选项 ,启用内存保护单元(MPU)捕获越界访问。

6.3 “批量发送时丢包率陡增” —— 驱动队列溢出

现象:连续调用 esp_now_send() 10 次以上,后半部分全部返回 ESP_ERR_ESPNOW_NOT_READY

根因分析:
- ESP-NOW 内部发送队列深度为 10(IDF v4.4),超出即拒绝新请求;
- 未等待前序发送完成回调即发起新请求,队列迅速填满。

解决方案:
- 实现发送队列管理器,维护待发任务链表,仅在 on_data_sent 成功后取出下一个任务;
- 或采用阻塞式发送: while (esp_now_send(...) != ESP_OK) { vTaskDelay(1); } ,但会阻塞任务调度,不推荐。

6.4 “加密启用后通信完全中断” —— 密钥与 peer 状态不一致

现象:启用 ESP_NOW_ENCRYPTED 后, esp_now_send() 返回 ESP_ERR_ESPNOW_ARG ,且 esp_now_get_peer() 查询显示 peer 状态为 ESP_NOW_PEER_INVALID

根因分析:
- esp_now_add_peer() 后未检查返回值,密钥长度错误(非 16 字节)导致添加失败,但程序继续执行;
- 接收方未正确注册发送方 MAC,或注册时 encrypt 字段设为 ESP_NOW_UNENCRYPTED ,而发送方启用加密。

解决方案:
- 所有 esp_now_add_peer() 调用后必须校验返回值 ,失败则 ESP_LOGE 并重启初始化流程;
- 使用 esp_now_fetch_peer_list() 获取当前所有 peer 列表,人工比对 MAC 与加密状态;
- 在 on_data_received 中打印 esp_now_is_peer_exist(mac_addr) 结果,确认接收方已注册该 peer。

7. 工程进阶:ESP-NOW 与 FreeRTOS 任务协同设计

在复杂应用中,ESP-NOW 往往只是整个系统的一环。例如,在电池供电的无线传感器节点中,需协调:低功耗休眠、定时采样、ESP-NOW 发送、OTA 升级、LED 指示等任务。此时,必须设计合理的任务优先级与同步机制。

7.1 任务划分原则

  • Wi-Fi 任务(高优先级,uxPriority=10) :处理 on_data_sent / on_data_received 回调,执行最小化数据搬运,通过队列/信号量通知其他任务;
  • 应用任务(中优先级,uxPriority=5) :执行传感器读取、数据处理、业务逻辑,从队列接收 ESP-NOW 数据并解析;
  • 低功耗管理任务(中优先级,uxPriority=5) :监控电量、控制休眠唤醒,需在休眠前确保 Wi-Fi 驱动处于空闲状态;
  • OTA 任务(高优先级,uxPriority=12) :接收固件包,需临时暂停 ESP-NOW 发送以释放内存。

7.2 同步原语选择

  • 队列(Queue) :用于传递接收到的 ESP-NOW 数据包。创建 xQueueHandle rx_queue = xQueueCreate(10, sizeof(rx_packet_t)); rx_packet_t 包含 mac[6] data[250] len 字段;
  • 二值信号量(Binary Semaphore) :用于通知应用任务“有新数据待处理”,避免队列满时丢包;
  • 事件组(Event Group) :用于组合多个状态,如 BIT0=WiFiReady , BIT1=SensorDataReady , BIT2=OTAInProgress ,应用任务 xEventGroupWaitBits() 等待复合条件。

7.3 低功耗协同示例

ESP32-S3 支持 Modem-sleep 与 Light-sleep。在 Light-sleep 下,Wi-Fi MAC 层停止工作,ESP-NOW 不可用。因此,必须在进入 Light-sleep 前完成所有发送,并确保无未决接收:

void enter_light_sleep(void)
{
    // 1. 等待所有发送完成
    while (is_sending()) {
        vTaskDelay(10 / portTICK_PERIOD_MS);
    }
    // 2. 清空接收队列
    rx_packet_t pkt;
    while (xQueueReceive(rx_queue, &pkt, 0) == pdTRUE) {
        process_rx_packet(&pkt);
    }
    // 3. 进入休眠
    esp_sleep_enable_timer_wakeup(30 * 1000000); // 30秒后唤醒
    esp_light_sleep_start();
}

此设计确保了低功耗与通信可靠性的平衡,是电池类产品的标配实践。

我在实际门磁传感器项目中曾因忽略第 2 步,导致休眠后遗留未处理的 ESP-NOW 报文,唤醒时 on_data_received 回调被积压触发,最终耗尽内存崩溃。踩过这次坑之后,所有低功耗设计都强制加入“通信清零”检查。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐