ESP32使用ESP_NOW时受loop中delay阻塞,如何实现异步接收?
ESP-NOW本身就原生支持和ESPAsyncWebServer一致的事件驱动异步接收能力,你遇到的接收延迟完全是delay()阻塞函数导致的问题,解决方案如下:
1. 问题本质
delay()是完全阻塞内核的函数,调用期间除了最高优先级硬件中断外,所有用户态的事件回调(包括ESP-NOW接收回调、ESPAsyncWebServer的请求回调)都会被推迟到阻塞结束后才能执行,所以才会出现数据接收延迟。
另外你对millis()时间比对的担心是完全不必要的:单次时间差判断仅为一次整数运算,开销可以忽略不计,loop空转时ESP32会自动进入低功耗空闲状态,根本不会长期占用资源。
2. 实现ESP-NOW异步接收
ESP-NOW的接收逻辑不需要在loop中轮询,直接注册接收回调即可,数据到达时会自动触发回调执行,和ESPAsyncWebServer的请求回调逻辑完全一致:
// 第一步:定义和发送端格式匹配的接收数据结构体 typedef struct { int light_switch; int pump_switch; } EspnowRecvData; // 第二步:定义接收回调函数,收到数据时自动触发 void onEspnowRecv(const uint8_t *macAddr, const uint8_t *data, int dataLen) { EspnowRecvData recvBuf; memcpy(&recvBuf, data, sizeof(recvBuf)); // 直接在回调内更新全局变量即可,不需要轮询 light_switch = recvBuf.light_switch; pump_switch = recvBuf.pump_switch; } // 第三步:setup中初始化ESP-NOW后注册回调 void setup() { // 省略WiFi、GPIO、ESP-NOW基础初始化代码 esp_now_register_recv_cb(onEspnowRecv); }
3. 改造loop去掉阻塞延迟
将原有delay()替换为非阻塞的时间比对逻辑,确保内核不会被长时间挂起,回调可以随时触发:
// 全局变量,记录上次温度检测的时间戳 unsigned long lastTempCheck = 0; // 温度检测间隔5000ms const long CHECK_INTERVAL = 5000; void loop() { if (WiFi.status() != WL_CONNECTED) { Serial.println("[ ERR ] Wifi not connected. Exiting program"); delay(9999); exit(0); } // 仅到间隔时间才执行一次检测逻辑 unsigned long currentMillis = millis(); if (currentMillis - lastTempCheck >= CHECK_INTERVAL) { lastTempCheck = currentMillis; float temp_check = dht.readTemperature(); if (temp_check == 0) { delay(1000); temp_check = dht.readTemperature(); } if (temp_check > 32.0 && *cooling_switch_p == false) { Serial.println("[ INF ] Too high temperature, switch on a cooling system"); digitalWrite(COOLING_RELAY, LOW); *cooling_switch_p = true; } else if (temp_check < 30.0 && *cooling_switch_p == true) { Serial.println("[ INF ] Normal temperature, switch off a cooling system"); digitalWrite(COOLING_RELAY, HIGH); *cooling_switch_p = false; } Serial.print("[ DBG ] Light Switch: "); Serial.println(String(light_switch)); Serial.println(""); Serial.print("[ DBG ] Pump Switch: "); Serial.println(String(pump_switch)); Serial.println(""); } }
4. 进阶优化方案(可选)
如果不想在loop里做时间比对,可以直接用ESP32自带的FreeRTOS任务调度:把温度控制逻辑单独放到独立任务中,用RTOS的vTaskDelay()做延迟,该延迟只会挂起当前任务,不会阻塞整个内核,ESP-NOW和WebServer的回调都能正常执行,loop可以完全留空:
// 温度控制任务函数 void tempControlTask(void *param) { for(;;) { // 原有温度检测、继电器控制、串口打印逻辑 float temp_check = dht.readTemperature(); if (temp_check == 0) { vTaskDelay(pdMS_TO_TICKS(1000)); temp_check = dht.readTemperature(); } // 省略温度判断逻辑,和原有代码一致 // 延迟5s,仅挂起当前任务 vTaskDelay(pdMS_TO_TICKS(5000)); } } void setup() { // 其他初始化逻辑 xTaskCreate(tempControlTask, "TempCtrl", 2048, NULL, 1, NULL); } void loop() { // 无需任何代码 }
内容的提问来源于stack exchange,提问作者user14603676
相关产品推荐
相关产品推荐

