interruptesp8266watchdogsleep-modewakeup

Light Sleep with GPIO Wakeup for ESP8266


I am trying to achieve a light sleep on ESP8266 with a GPIO wakeup. Everything is working fine except that if after wakeup the GPIO stays in a state opposite to the WAKEUP trigger level then the ESP crashes with a HW WDT reset. Need some help in the same.

What am I trying to achieve: I have a ESP hooked up to a flow meter. The flow meter sends HIGH/LOW pulses when it turns. What I need is to be able to light sleep the ESP and wake up it when the flow starts again. For this I am connecting the flow meter output to the WAKE PIN on the ESP , so that the ESP wakes up when the flow starts. As I dont know if the flow meter output will be on LOW or HIGH when the sleep starts, I read the GPIO and then decide if to configure the WAKEUP trigger as GPIO_PIN_INTR_LOLEVEL or GPIO_PIN_INTR_HILEVEL

What's the issue: If the ESP sleeps while GPIO is LOW then it has to wake up on a HIGH signal, which it does but if the flow meter stops at a HIGH signal then the ESP crashes after around 7 secs. This happens irrespective of how much time has elapsed since the wakeup. If for any reason the GPIO (connected to flow meter) will stop and remain at HIGH , the ESP will crash within 7 secs. The same applies if it sleeps on GPIO being HIGH, it then wakes up on a LOW and will crash with hardware Watchdog timer reset if the GPIO comes to LOW at any time. I read on a blog that the light sleep wakeup will work only on a pulsed trigger so if that's the case, this makes sense but I am still not able to accept the fact that this GPIO cannot be at a particular state at any time in the program after wakeup.

What have I tried: I tried a lot of things before I realized that this is the reason for crash so I wont go into those but will restrict it to this reason alone.

output on crash

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

Code with all unrelated things stripped out:

#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();

  }

Solution

  • Got the solution on this via another forum. The key is to call the api gpio_pin_wakeup_disable(); after waking up to disable the GPIO wake function. The code works fine with this. Fully working code for reference:

    #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 *******************
    volatile 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();
    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
        gpio_pin_wakeup_disable();  // Disable wakeup functionality for the GPIO pin
     }
    
    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();
    
      }