-
Notifications
You must be signed in to change notification settings - Fork 0
Keeping the lights on
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.
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
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 pull-up resistors are between 30K and 100K ohms. (ref)
Descriptions of GPIO Pins identifying pull-up and pull-down options here.
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.
- Keeping the Lights On - how to manage GPIO state across reboots and crashes
- Boot fails when SPI Bus used
- GPIO Drive Strength and Ringing
- LDO Regulators WIP
- ESP8266 Power Considerations This is only a rough outline, needs a lot of development.
- Upgrading Flash Chips, QIO, and DIO
- Dodgy Extra 2K of DRAM or CONT - WIP
- WDTracks - Print last call before WDT
- 5V Tolerant I/O?
Arduino IDE specific
Misc.
- Exception Causes
- ESP8266 will not boot
- Stacks sys and cont
- WIP Boot ROM and SDK Notes
- Multi-segment Boot ROM Loader, notes
- Cache_Read_Enable How to turn off and on instruction cache execution.