You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ESP8266 GPIO唤醒浅睡眠异常:触发电平持续时触发硬件看门狗复位

ESP8266浅睡眠GPIO唤醒后硬件看门狗复位问题求助

需求说明

ESP8266与流量计相连,流量计转动时发送高低电平脉冲。需要实现浅睡眠模式,在流量再次启动时通过GPIO唤醒设备。由于无法确定睡眠开始时GPIO的电平状态,会先读取当前GPIO状态,再将唤醒触发配置为GPIO_PIN_INTR_LOLEVEL或GPIO_PIN_INTR_HILEVEL。

问题现象

  • 若睡眠时GPIO为低电平,被高电平唤醒后,若GPIO持续保持高电平,约7秒后ESP因硬件看门狗(HW WDT)复位崩溃;
  • 若睡眠时GPIO为高电平,被低电平唤醒后,若GPIO持续保持低电平,同样触发HW WDT复位;
  • 已知浅睡眠唤醒仅支持脉冲触发的说法,但无法接受唤醒后GPIO不能持续处于特定电平的限制。

已尝试方案

  • 唤醒后调用wifi_fpm_close(),无效;
  • 唤醒后再次睡眠1毫秒并配置其他未使用的GPIO,无效;
  • 关闭WiFi、ESP-NOW等功能,极简无WiFi代码仍存在问题;
  • 用按键替代流量计测试,按键持续处于与唤醒电平相反的状态时设备崩溃;
  • 考虑过搭建硬件电路仅传递单个脉冲,但希望先确认是否存在软件解决方案。

崩溃输出信息

19:15:39.318 >  ets Jan  8 2013,rst cause:4, boot mode:(3,7)
19:15:39.321 >
19:15:39.321 > wdt reset
19:15:39.321 > load 0x4010f000, len 3424, room 16
19:15:39.328 > tail 0
19:15:39.329 > chksum 0x2e
19:15:39.329 > load 0x3fff20b8, len 40, room 8
19:15:39.331 > tail 0
19:15:39.331 > chksum 0x2b
19:15:39.334 > csum 0x2b
19:15:39.334 > v00044c80
19:15:39.336 > ~ld

简化测试代码

#define DEBOUNCE_INTERVAL 10 //debouncing time in ms for interrupts
#define IDLE_TIME 15 //300 //idle time in sec beyond which the ESP goes to sleep , to be woken up only by a pulse from the meter
#define PULSE_PIN                4  // defines the pin to which the encoder is connected
#define WAKEUP_PIN               14  // defines the pin to which the wakeup signal is connected, in this case it is the same as the encoder pin 
// ************ HASH DEFINES *******************

#include <Arduino.h>
#include "secrets.h"
#include "Debugutils.h"
#include <ESP8266WiFi.h>
#include <user_interface.h> // for RTC memory functions

// ************ GLOBAL OBJECTS/VARIABLES *******************
unsigned int pulses = 0; //stores the no of pulses detected by the sensor, it is reset after SENSOR_UPDATE_INTERVAL
unsigned long last_sleep_time = millis();//stores the no of ms since a message was last published , used to sleep the ESP beyond a certain value
unsigned long idle_time = millis();
volatile unsigned long lastMicros;
// ************ GLOBAL OBJECTS/VARIABLES *******************

void IRAM_ATTR pulseHandler() {
  if((long)(micros() - lastMicros) >= DEBOUNCE_INTERVAL * 1000) {  //note DEBOUNCE_INTERVAL is in ms , so multiply by 1000 for microsec
    pulses += 1;
    lastMicros = micros();
  }
}

void light_sleep(){
    WiFi.mode(WIFI_OFF);  // you must turn the modem off; using disconnect won't work
    // extern os_timer_t* timer_list;
    // timer_list = nullptr;  // stop (but don't disable) the 4 OS timers    
    wifi_fpm_set_sleep_type(LIGHT_SLEEP_T);
    bool wakeup_pin_state = digitalRead(WAKEUP_PIN);
    gpio_pin_wakeup_enable(GPIO_ID_PIN(WAKEUP_PIN), wakeup_pin_state? GPIO_PIN_INTR_LOLEVEL:GPIO_PIN_INTR_HILEVEL);// only LOLEVEL or HILEVEL interrupts work, no edge, that's a CPU limitation
    Serial.printf("CPU going to sleep, pull WAKE_UP_PIN %s to wakeup",wakeup_pin_state?"LOW":"HIGH");Serial.flush();
    wifi_fpm_open();
    wifi_fpm_do_sleep(0xFFFFFFF);  // only 0xFFFFFFF, any other value and it won't disconnect the RTC timer
    delay(10);  // it goes to sleep during this delay() and waits for an interrupt
    Serial.println(F("Woke up!"));  // the interrupt callback hits before this is executed*/
    wifi_fpm_close();  // Disable force sleep mode
    //sometimes the above statement gets printed before actually sleeping off, doesnt happen all the time though
    //But the code works as expected, the ESP sleeps after printing Woke up
 }

void setup() {
  Serial.begin(115200);
  pinMode(WAKEUP_PIN, INPUT_PULLUP);//INPUT_PULLUP
  pinMode(PULSE_PIN, INPUT_PULLUP);
  attachInterrupt(PULSE_PIN, pulseHandler, RISING);

  Serial.println("Setup complete");
}

void loop() {
    idle_time = millis() - last_sleep_time;
    if(idle_time >= IDLE_TIME * 1000)
    {
      Serial.print("going to sleep after being idle for :"); Serial.print(idle_time/1000);Serial.println(" sec");Serial.flush();
      light_sleep();
      //now that we're awake , add the sleep time to the previous value
      last_sleep_time = millis();
    }
    yield();

  }

解决方案

问题核心原因:ESP8266浅睡眠的电平触发唤醒机制,在唤醒后如果触发电平持续存在,会不断触发唤醒中断,导致系统无法正常执行看门狗喂狗操作,最终触发HW WDT复位。

软件修复步骤

  1. 唤醒后立即禁用GPIO唤醒中断:在唤醒后的第一时间关闭之前配置的GPIO唤醒功能,避免持续触发中断请求;
  2. 恢复GPIO的正常功能:重新配置GPIO为普通输入或恢复脉冲检测的边缘触发中断。

修改后的light_sleep函数示例:

void light_sleep(){
    WiFi.mode(WIFI_OFF);  
    wifi_fpm_set_sleep_type(LIGHT_SLEEP_T);
    bool wakeup_pin_state = digitalRead(WAKEUP_PIN);
    gpio_pin_wakeup_enable(GPIO_ID_PIN(WAKEUP_PIN), wakeup_pin_state? GPIO_PIN_INTR_LOLEVEL:GPIO_PIN_INTR_HILEVEL);
    Serial.printf("CPU going to sleep, pull WAKE_UP_PIN %s to wakeup",wakeup_pin_state?"LOW":"HIGH");Serial.flush();
    wifi_fpm_open();
    wifi_fpm_do_sleep(0xFFFFFFF);  
    delay(10);  
    Serial.println(F("Woke up!"));  

    // 关键:唤醒后立即禁用GPIO唤醒中断
    gpio_pin_wakeup_disable();
    wifi_fpm_close();  

    // 恢复脉冲检测中断,确保流量计计数功能正常
    detachInterrupt(PULSE_PIN);
    attachInterrupt(PULSE_PIN, pulseHandler, RISING);
 }

额外说明

  • gpio_pin_wakeup_disable()会关闭所有GPIO的唤醒中断,这是必要步骤,避免持续触发中断占用系统资源;
  • 重新附加脉冲检测中断是为了恢复流量计的正常计数,避免唤醒后丢失脉冲信号;
  • 后续再次进入浅睡眠时,代码会重新读取GPIO状态并配置唤醒触发条件,不影响重复睡眠逻辑。

替代硬件方案(若软件方案无效)

  • 在流量计输出端增加RC滤波电路,将持续电平转换为短脉冲;
  • 使用单稳态触发器,确保唤醒信号仅为单次脉冲,避免持续触发。

内容的提问来源于stack exchange,提问作者Vijay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 07:17:09