I’m rewriting an ESP8266 program to run on an ESP32. It reads (and logs) sensor data, connects to a wifi for ntp synchronisation and there also is an LCD to show current sensor readings and statuses. I’m planning on running the ESP32 on batteries, so the LCD is activated by pushing a button.
Connecting to the wifi (or even a backup wifi), syncing with ntp and some sensor readings all have timeouts in the seconds range, so I use an interrupt to detect when the button is pushed. The interrupt service routine then switches on the LCD.
Although this concept worked on an ESP8266 (beginners luck?), pushing the button on an ESP32 results in a “Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1).” So, is the ESP32 that much more strict than the ESP8266 when it comes to ISR interruption times?
A simplified code example (including button debouncing) reproducing the error:
#include <LiquidCrystal_I2C.h>
#define MILLIS_MAX 4294967295 // = (2^32 - 1)
#define PIN_BUTTON 32
#define DISPLAY_I2C_ADDR 0x3F
#define DISPLAY_WIDTH 20
#define DISPLAY_HEIGHT 4
const unsigned short debounceDelay = 50; // [ms]
const unsigned short displayTime = 2000; // [ms]
// volatile since manipulated by an ISR
volatile unsigned long currentMillis;
volatile unsigned long lastDebounceMillis; // the last time the interrupt was triggered
volatile unsigned long millisAtButtonPushed;
volatile boolean LCDOn;
LiquidCrystal_I2C lcd( DISPLAY_I2C_ADDR, DISPLAY_WIDTH, DISPLAY_HEIGHT );
unsigned long getMillisDelta( unsigned long millisStart, unsigned long millisEnd ) {
if( millisEnd < millisStart ) return MILLIS_MAX - millisStart + millisEnd + 1;
else return millisEnd - millisStart;
}
void IRAM_ATTR isr_falling_edge();
void IRAM_ATTR isr_rising_edge();
void IRAM_ATTR isr_falling_edge() {
currentMillis = millis();
if( getMillisDelta(lastDebounceMillis,currentMillis) > debounceDelay ) {
millisAtButtonPushed = currentMillis;
LCDOn = true;
lcd.backlight();
lcd.display();
attachInterrupt( PIN_BUTTON, isr_rising_edge, RISING );
}
}
void IRAM_ATTR isr_rising_edge() {
currentMillis = millis();
if( getMillisDelta(lastDebounceMillis,currentMillis) > debounceDelay ) {
lastDebounceMillis = currentMillis;
attachInterrupt( PIN_BUTTON, isr_falling_edge, FALLING );
}
}
void setup() {
Serial.begin( 115200 );
lcd.begin( DISPLAY_WIDTH, DISPLAY_HEIGHT );
lcd.init();
lcd.backlight();
pinMode( PIN_BUTTON, INPUT_PULLUP );
attachInterrupt( PIN_BUTTON, isr_falling_edge, FALLING );
delay(500);
lcd.noDisplay();
lcd.noBacklight();
LCDOn = false;
}
void loop() {
currentMillis = millis();
if( LCDOn ) {
Serial.println( "LCD should be on..." );
if( getMillisDelta(millisAtButtonPushed,currentMillis) > displayTime ) {
lcd.noDisplay();
lcd.noBacklight();
LCDOn = false;
}
}
else Serial.println( "LCD should be off..." );
}
I’m well aware of the requirement to have ISR’s as short as possible (I’m not writing to Serial nor to the LCD). But it is also clear that the problem is caused by both lcd.backlight() and lcd.display(), since they cause the same timeout on their own and commenting out both statements eliminates the occurrence of the problem.
Plan B is to rewrite the code: set a flag in the ISR, and pick up its changed state in the next execution of the loop. Most of the time this will work and the LCD will activate almost immediately after the button is pushed, but since a loop can sometimes take several seconds (while timing out on wifi, ntp-sync or sensor readings), this will then result in an LCD which doesn’t seem to react to the button being pushed.
Before implementing plan B (or even plan C: checking the flag set by the ISR multiple times during the execution of the loop), I’m hoping for another, more elegant (correct?) way to get the desired result: activating an LCD the moment a button is pushed, without having to hope for ‘very short execution times of the loop’. Or is the ESP32 just much more strict when it comes to timeouts for interrupts than the ESP8266, and there’s no way around this...
Any ideas on how to get out of this, or is there just plan B?
Thanks in advance!