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复位。
软件修复步骤
- 唤醒后立即禁用GPIO唤醒中断:在唤醒后的第一时间关闭之前配置的GPIO唤醒功能,避免持续触发中断请求;
- 恢复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
相关产品推荐
相关产品推荐

