Keeping the lights on - mhightower83/Arduino-ESP8266-misc GitHub Wiki

WIP: Keeping the lights on

GPIO Pin, output state persistence across soft reboots

No code is perfect, sooner or later perhaps due to some unforeseen event, our program will crash. We may not be able to prevent a crash; however, we can improve on how we deal with a crash. In this area, I focus on the situation often found in IoT switches, where the relay turns off and back on as the device reboots. In this case, after the crash, the ESP8266 Core returns the system to a fresh boot state, before running setup(). This is where the relay gets turned off. Then via setup(), the IoT code would decide the relay should be on.

Looking deeper at the Arduino ESP8266 Core startup. At boot, it sets all GPIO pins to INPUT via user_init()->init()->initPins()->resetPins(). For some set of reset reasons and some set of the GPIO pins, the pin's configuration/state/status does not change and does not need to be reprogrammed. For these cases, we could skip them in resetPins(). And have an intelligent sketch that does likewise.

The set of reset reasons that this works for are REASON_EXCEPTION_RST, REASON_SOFT_WDT_RST, and REASON_SOFT_RESTART. The set of GPIO pins I think this will work with are 0, 2, 3, 4, 5, 12, 13 14, and 15. While pin 2 might work on soft reset it would not be a wise choice to drive a relay since at Power On boot ROM/SDK writes to that port.

New Method

The solution relies on resetPins() being a weak link. By providing a replacement function, we can selectively skip calls to pinMode(). Thus allowing the pin to keep its previous state before the restart.

More specifically if the reset reason is one that preserved the GPIO pin's state, skip calls to pinMode() from reinitializing the GPIO pins of interest.

#include <user_interface.h>
#define RELAY_PIN 12

// exempt pins are pins that hold their state/status across soft reboots.
//uint32_t const exempt_pin_mask = BIT(0) | BIT(3) | BIT(4) | BIT(5) |
//                                 BIT(12) | BIT(13) | BIT(14) | BIT(15);
uint32_t constexpr exempt_pin_mask = BIT(LED_BUILTIN) | BIT(RELAY_PIN);

inline bool is_gpio_pin_exempt(int pin) {
  return (exempt_pin_mask & BIT(pin)) ? true : false;
}

// Pin state was preserved for REASON_EXCEPTION_RST, REASON_SOFT_WDT_RST, and REASON_SOFT_RESTART
extern struct rst_info resetInfo;

inline bool is_gpio_persistent(void) {
  return REASON_EXCEPTION_RST <= resetInfo.reason &&
         REASON_SOFT_RESTART  >= resetInfo.reason;
}

// Replacement for weak resetPin()
extern "C" void resetPins() {
  for (int pin = 0; pin <= 16; ++pin) {
    if (is_gpio_persistent() && is_gpio_pin_exempt(pin))
      continue;

    if (!isFlashInterfacePin(pin))
      pinMode(pin, INPUT);
  }
}

void setup() {
  // Filter on reset reasons that require GPIO pins
  // to be reprogrammed.
  if ( !is_gpio_persistent() ) {
    pinMode( LED_BUILTIN, OUTPUT );
    pinMode( RELAY_PIN, OUTPUT );
  }
  //  ...
}

void loop() {
  // ...
}
Expand for Obsolete method

Obsolete method

This is for older versions of Arduino ESP8266 Core, before resetPins() was available as a weak link. During user_init() if the reset reason is one that preserved the GPIO pin's state, block calls to pinMode() from reinitializing the GPIO pins of interest. We can easily do this because pinMode() is a weak link to __pinMode() And, initPins() does a sequential sweep calling pinMode() allowing us to set up a run once like scenario.

Example:

extern struct rst_info resetInfo;
extern "C" void __pinMode( uint8_t pin, uint8_t mode );

// exempt pins are pins that hold their state/status across soft reboots.
//uint32_t const exempt_pin_mask = BIT(0) | BIT(3) | BIT(4) | BIT(5) |
//                                 BIT(12) | BIT(13) | BIT(14) | BIT(15);
uint32_t const exempt_pin_mask = BIT(LED_BUILTIN) | BIT(RELAY_PIN);

inline bool is_gpio_pin_exempt(int pin) {
  return (exempt_pin_mask & BIT(pin)) ? true : false;
}

inline bool is_gpio_persistent(void) {
  return REASON_EXCEPTION_RST <= resetInfo.reason &&
         REASON_SOFT_RESTART  >= resetInfo.reason;
}

extern "C" void pinMode( uint8_t pin, uint8_t mode ) {
  static bool in_initPins = true;
  if ( in_initPins && is_gpio_persistent() ) {
    if ( 16 == pin )
      in_initPins = false;
      
    if ( is_gpio_pin_exempt(pin) )
      return;
  }

  __pinMode( pin, mode );
}

void setup() {
  // Filter on reset reasons that require GPIO pins
  // to be reprogrammed.
  if ( !is_gpio_persistent() ) {
    pinMode( LED_BUILTIN, OUTPUT );
    pinMode( RELAY_PIN, OUTPUT );
  }
  ...
}

This PDF page list GPIO state for various reset causes; however does not elaborate.

TODO: More research on which pins are left alone during various boots. Need a table of which will wiggle due to the boot process. Currently, I think pins 4, 5, 12, 13, 14 are safe from this.

Internal Pullup Resistors

Internal pull-up resistors are between 30K and 100K ohms. (ref)

Descriptions of GPIO Pins identifying pull-up and pull-down options here.

Related Post

Hey all, I'm wondering if there's any documentation on what the GPIO pins do during startup and following a hardware/software reset? ...

GPIOs 4 and 5 are the only ones that are always high impedance. All others do have internal pull-ups or are even driven low/high during boot.

GPIOs 3, 12, 13 and 14 pulled HIGH during boot. Their actual state does not influence the boot process.

GPIOs 0, 1, 2 and 15 are pulled HIGH during boot and also driven LOW for short periods. The device will not boot if 0, 1 or 2 is driven LOW during start-up.

GPIO 16 is driven HIGH during boot, don't short to GND.

ref

More Pin Options

here

⚠️ **GitHub.com Fallback** ⚠️