4LD + rotary hotfix: corrupted display on esp32

This s a temporary fix for display problems (corrupted display, sometimes crash). These problems exists since 0.14. as display updates are now running in a separate task.
The fix is still incomplete.

If nothing else works, you can use the 0.13.x behavior by compiling with `-D FLD_ESP32_NO_THREADS`

* fixing a few unsafe string operations and non-initialized strings
* fix max length for "two lines" names
* make sure that display methods are exiting early if display is not configured
* rotary_ALT : additional checks to ensure that drawing on the display does not overlap with redraw
* added some safeguards to prevent overlapping display redraw operations
--> Actually this should use semaphore/mutex instead of global booleans -> Still on my toDo list.
This commit is contained in:
Frank
2023-02-02 18:37:13 +01:00
parent b01893e8fe
commit e1f14628b0
4 changed files with 134 additions and 46 deletions

View File

@@ -9,6 +9,10 @@
#include <U8x8lib.h> // from https://github.com/olikraus/u8g2/
#include "4LD_wled_fonts.c"
#ifndef FLD_ESP32_NO_THREADS
#define FLD_ESP32_USE_THREADS // comment out to use 0.13.x behviour without parallel update task - slower, but more robust. May delay other tasks like LEDs or audioreactive!!
#endif
//
// Insired by the usermod_v2_four_line_display
//
@@ -103,7 +107,8 @@ class FourLineDisplayUsermod : public Usermod {
static FourLineDisplayUsermod *instance;
bool initDone = false;
volatile bool drawing = false;
volatile bool drawing = false; // true of overlay drawing is active
volatile bool reDrawing = false; // true if redraw ongoing (on esp32, this happens in a separate task)
char errorMessage[100] = ""; //WLEDMM: show error in um settings if occurred
@@ -119,7 +124,7 @@ class FourLineDisplayUsermod : public Usermod {
#endif
DisplayType type = FLD_TYPE; // display type
bool typeOK = true; //WLEDMM: instead of type == NULL and type=NULL
bool typeOK = false; //WLEDMM: instead of type == NULL and type=NULL. Intially false, as display was not initialized yet
bool flip = false; // flip display 180°
uint8_t contrast = 10; // screen contrast
uint8_t lineHeight = 1; // 1 row or 2 rows
@@ -187,6 +192,9 @@ class FourLineDisplayUsermod : public Usermod {
// some displays need this to properly apply contrast
void setVcomh(bool highContrast) {
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
if (!canDraw()) return; // don't interfere with ongoing draw
u8x8_t *u8x8_struct = u8x8->getU8x8();
u8x8_cad_StartTransfer(u8x8_struct);
u8x8_cad_SendCmd(u8x8_struct, 0x0db); //address of value
@@ -198,17 +206,17 @@ class FourLineDisplayUsermod : public Usermod {
* Wrappers for screen drawing
*/
void setFlipMode(uint8_t mode) {
if (!typeOK || !enabled) return;
u8x8->setFlipMode(mode);
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
if (canDraw()) u8x8->setFlipMode(mode);
}
void setContrast(uint8_t contrast) {
if (!typeOK || !enabled) return;
u8x8->setContrast(contrast);
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
if (canDraw()) u8x8->setContrast(contrast);
}
void drawString(uint8_t col, uint8_t row, const char *string, bool ignoreLH=false) {
if (!typeOK || !enabled) return;
u8x8->setFont(u8x8_font_chroma48medium8_r);
if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string);
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
u8x8->setFont(u8x8_font_chroma48medium8_r); // crashes randomly on ESP32
if (!ignoreLH && lineHeight==2) u8x8->draw1x2String(col, row, string); // crashes randomly on ESP32
else u8x8->drawString(col, row, string);
}
void draw2x2String(uint8_t col, uint8_t row, const char *string) {
@@ -233,10 +241,11 @@ class FourLineDisplayUsermod : public Usermod {
}
void clear() {
if (!typeOK || !enabled) return;
u8x8->clear();
if (nullptr == u8x8) return; // prevents some crashes
u8x8->clear(); // crashes randomly on ESP32
}
void setPowerSave(uint8_t save) {
if (!typeOK || !enabled) return;
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
u8x8->setPowerSave(save);
}
@@ -247,6 +256,7 @@ class FourLineDisplayUsermod : public Usermod {
}
void draw2x2GlyphIcons() {
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
if (lineHeight == 2) {
drawGlyph( 1, 0, 1, u8x8_4LineDisplay_WLED_icons_2x2, true); //brightness icon
drawGlyph( 5, 0, 2, u8x8_4LineDisplay_WLED_icons_2x2, true); //speed icon
@@ -274,7 +284,7 @@ class FourLineDisplayUsermod : public Usermod {
while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing
drawing = true;
char lineBuffer[LINE_BUFFER_SIZE];
char lineBuffer[LINE_BUFFER_SIZE] = { '\0' };
static byte lastSecond;
byte secondCurrent = second(localTime);
byte minuteCurrent = minute(localTime);
@@ -290,10 +300,10 @@ class FourLineDisplayUsermod : public Usermod {
}
if (knownHour != hourCurrent) {
// only update date when hour changes
sprintf_P(lineBuffer, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime));
snprintf_P(lineBuffer, LINE_BUFFER_SIZE, PSTR("%s %2d "), monthShortStr(month(localTime)), day(localTime));
draw2x2String(2, lineHeight==1 ? 0 : lineHeight, lineBuffer); // adjust for 8 line displays, draw month and day
}
sprintf_P(lineBuffer,PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hourCurrent), minuteCurrent);
snprintf_P(lineBuffer,LINE_BUFFER_SIZE, PSTR("%2d:%02d"), (useAMPM ? AmPmHour : hourCurrent), minuteCurrent);
draw2x2String(2, lineHeight*2, lineBuffer); //draw hour, min. blink ":" depending on odd/even seconds
if (useAMPM) drawString(12, lineHeight*2, (isitAM ? "AM" : "PM"), true); //draw am/pm if using 12 time
@@ -305,7 +315,7 @@ class FourLineDisplayUsermod : public Usermod {
if (showSeconds && secondCurrent != lastSecond) {
lastSecond = secondCurrent;
draw2x2String(6, lineHeight*2, secondCurrent%2 ? " " : ":");
sprintf_P(lineBuffer, PSTR("%02d"), secondCurrent);
snprintf_P(lineBuffer, LINE_BUFFER_SIZE, PSTR("%02d"), secondCurrent);
drawString(12, lineHeight*2+1, lineBuffer, true); // even with double sized rows print seconds in 1 line
}
drawing = false;
@@ -316,7 +326,7 @@ class FourLineDisplayUsermod : public Usermod {
// gets called once at boot. Do all initialization that doesn't depend on
// network here
void setup() {
if (!typeOK || !enabled) return;
if (!enabled) return; // typeOK = true will be set after successfull setup
bool isHW, isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64);
PinOwner po = PinOwner::UM_FourLineDisplay;
@@ -439,9 +449,16 @@ class FourLineDisplayUsermod : public Usermod {
}
lineHeight = u8x8->getRows() > 4 ? 2 : 1;
USER_PRINTLN(F("Starting display."));
if (isSPI) {
USER_PRINTLN(isHW ? F("Starting display (SPI HW).") : F("Starting display (SPI Soft)."));
} else {
USER_PRINTLN(isHW ? F("Starting display (I2C HW).") : F("Starting display (I2C Soft)."));
}
u8x8->setBusClock(ioFrequency); // can be used for SPI too
u8x8->begin();
typeOK = true;
drawing = false;
reDrawing = false;
setFlipMode(flip);
setVcomh(contrastFix);
setContrast(contrast); //Contrast setup will help to preserve OLED lifetime. In case OLED need to be brighter increase number up to 255
@@ -464,12 +481,17 @@ class FourLineDisplayUsermod : public Usermod {
* Da loop.
*/
void loop() {
#ifndef ARDUINO_ARCH_ESP32
if (!enabled || !typeOK || strip.isUpdating()) return;
#if !defined(ARDUINO_ARCH_ESP32) || !defined(FLD_ESP32_USE_THREADS)
static unsigned long lastRunTime = 0;
unsigned long now = millis();
if (!enabled || !typeOK || (strip.isUpdating() && (now - lastRunTime < 50))) return;
lastRunTime = now;
if (now < nextUpdate) return;
nextUpdate = now + ((displayTurnedOff && clockMode && showSeconds) ? 1000 : refreshRate);
reDrawing = true;
redraw(false);
reDrawing = false;
#endif
}
@@ -478,15 +500,47 @@ class FourLineDisplayUsermod : public Usermod {
lastRedraw = millis();
}
//function to to check if a redraw or overlay draw is active. Needed for UM Rotary, to avoid random/concurrent drawing
bool canDraw(void) {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) // only necessary on ESP32
if (drawing) return(false); // overlay draws someting
if (reDrawing) return(false); // redraw task draws something
#endif
return(true);
}
/**
* Redraw the screen (but only if things have changed
* or if forceRedraw).
*/
void redraw(bool forceRedraw) {
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
// use a wrapper onESP32, to ensure the functions is not running several times in parallel !
static bool doForceRedraw = false; // for delaying "force redraw"
if ((overlayUntil > 0) && (millis() >= overlayUntil)) {
forceRedraw = true; // Time to display the overlay has elapsed, force redraw needed
}
if (forceRedraw) doForceRedraw = true;
if (reDrawing) return; // redraw already active
if (drawing) return; // overlay draw active
reDrawing = true; // set redraw lock
if (doForceRedraw) forceRedraw = true;
redraw_core(forceRedraw);
if (overlayUntil == 0) doForceRedraw = false; // redraw was skipped if overlay is still visible
reDrawing = false; // reset activity flag, as redraw has too many early returns that don't take care of this
}
void redraw_core(bool forceRedraw) {
#endif
bool needRedraw = false;
unsigned long now = millis();
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
if (nullptr == u8x8) return; // prevent crash in case u8x8 is re-initialized (du to user changing setings)
if (!typeOK || !enabled) return;
if (overlayUntil > 0) {
if (now >= overlayUntil) {
// Time to display the overlay has elapsed.
@@ -575,6 +629,8 @@ class FourLineDisplayUsermod : public Usermod {
knownnightlight = nightlightActive;
wificonnected = interfacesInited;
while (drawing && millis()-now < 150) delay(8); // wait if someone else is drawing
// Do the actual drawing
// First row: Icons
draw2x2GlyphIcons();
@@ -598,7 +654,7 @@ class FourLineDisplayUsermod : public Usermod {
if (overlayUntil == 0) {
brightness100 = ((uint16_t)bri*100)/255;
char lineBuffer[4];
sprintf_P(lineBuffer, PSTR("%-3d"), brightness100);
snprintf_P(lineBuffer, 4, PSTR("%-3d"), brightness100);
drawString(1, lineHeight, lineBuffer);
//lastRedraw = millis();
}
@@ -609,7 +665,7 @@ class FourLineDisplayUsermod : public Usermod {
if (overlayUntil == 0) {
fxspeed100 = ((uint16_t)effectSpeed*100)/255;
char lineBuffer[4];
sprintf_P(lineBuffer, PSTR("%-3d"), fxspeed100);
snprintf_P(lineBuffer, 4, PSTR("%-3d"), fxspeed100);
drawString(5, lineHeight, lineBuffer);
//lastRedraw = millis();
}
@@ -620,7 +676,7 @@ class FourLineDisplayUsermod : public Usermod {
if (overlayUntil == 0) {
fxintensity100 = ((uint16_t)effectIntensity*100)/255;
char lineBuffer[4];
sprintf_P(lineBuffer, PSTR("%-3d"), fxintensity100);
snprintf_P(lineBuffer, 4, PSTR("%-3d"), fxintensity100);
drawString(9, lineHeight, lineBuffer);
//lastRedraw = millis();
}
@@ -654,10 +710,12 @@ class FourLineDisplayUsermod : public Usermod {
//Display the current effect or palette (desiredEntry)
// on the appropriate line (row).
void showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) {
char lineBuffer[MAX_JSON_CHARS];
char lineBuffer[MAX_JSON_CHARS] = { '\0' };
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
if (overlayUntil == 0) {
// Find the mode name in JSON
uint8_t printedChars = extractModeName(inputEffPal, qstring, lineBuffer, MAX_JSON_CHARS-1);
if (printedChars < 2) strcpy(lineBuffer, "invalid"); // catch possible error
if (lineBuffer[0]=='*' && lineBuffer[1]==' ') {
// remove "* " from dynamic palettes
for (byte i=2; i<=printedChars; i++) lineBuffer[i-2] = lineBuffer[i]; //include '\0'
@@ -668,8 +726,8 @@ class FourLineDisplayUsermod : public Usermod {
printedChars -= 5;
}
if (lineHeight == 2) { // use this code for 8 line display
char smallBuffer1[MAX_MODE_LINE_SPACE];
char smallBuffer2[MAX_MODE_LINE_SPACE];
char smallBuffer1[MAX_MODE_LINE_SPACE+1] = { '\0' };
char smallBuffer2[MAX_MODE_LINE_SPACE+1] = { '\0' };
uint8_t smallChars1 = 0;
uint8_t smallChars2 = 0;
if (printedChars < MAX_MODE_LINE_SPACE) { // use big font if the text fits
@@ -696,13 +754,15 @@ class FourLineDisplayUsermod : public Usermod {
}
while (smallChars1 < (MAX_MODE_LINE_SPACE-1)) smallBuffer1[smallChars1++]=' ';
smallBuffer1[smallChars1] = 0;
smallBuffer1[MAX_MODE_LINE_SPACE -1] = '\0'; // ensure the string ends where it should (while loop avove can overshoot by 1)
drawString(1, row*lineHeight, smallBuffer1, true);
while (smallChars2 < (MAX_MODE_LINE_SPACE-1)) smallBuffer2[smallChars2++]=' ';
smallBuffer2[smallChars2] = 0;
smallBuffer2[MAX_MODE_LINE_SPACE -1] = '\0'; // ensure the string ends where it should (while loop avove can overshoot by 1)
drawString(1, row*lineHeight+1, smallBuffer2, true);
}
} else { // use this code for 4 ling displays
char smallBuffer3[MAX_MODE_LINE_SPACE+1]; // uses 1x1 icon for mode/palette
char smallBuffer3[MAX_MODE_LINE_SPACE+1] = {'\0'}; // uses 1x1 icon for mode/palette
uint8_t smallChars3 = 0;
for (uint8_t i = 0; i < MAX_MODE_LINE_SPACE; i++) smallBuffer3[smallChars3++] = (i >= printedChars) ? ' ' : lineBuffer[i];
smallBuffer3[smallChars3] = 0;
@@ -739,8 +799,10 @@ class FourLineDisplayUsermod : public Usermod {
* Used in Rotary Encoder usermod.
*/
void overlay(const char* line1, long showHowLong, byte glyphType) {
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
unsigned long now = millis();
while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing
while ((reDrawing && overlayUntil == 0) && (millis()-now < 250)) delay(10); // wait if someone else is re-drawing
drawing = true;
// Turn the display back on
if (!wakeDisplay()) clear();
@@ -763,6 +825,7 @@ class FourLineDisplayUsermod : public Usermod {
* Clears the screen and prints.
*/
void overlayLogo(long showHowLong) {
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
unsigned long now = millis();
while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing
drawing = true;
@@ -824,8 +887,10 @@ class FourLineDisplayUsermod : public Usermod {
* Used in Auto Save usermod
*/
void overlay(const char* line1, const char* line2, long showHowLong) {
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
unsigned long now = millis();
while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing
while ((reDrawing && overlayUntil == 0) && (millis()-now < 250)) delay(10); // wait if someone else is re-drawing
drawing = true;
// Turn the display back on
if (!wakeDisplay()) clear();
@@ -845,6 +910,7 @@ class FourLineDisplayUsermod : public Usermod {
}
void networkOverlay(const char* line1, long showHowLong) {
if (!typeOK || !enabled) return; // WLEDMM make sure the display is initialized before we try to draw on it
unsigned long now = millis();
while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing
drawing = true;
@@ -982,7 +1048,7 @@ class FourLineDisplayUsermod : public Usermod {
#endif
#endif
void onUpdateBegin(bool init) {
#ifdef ARDUINO_ARCH_ESP32
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
if (init && Display_Task) {
vTaskSuspend(Display_Task); // update is about to begin, disable task to prevent crash
} else {
@@ -990,7 +1056,7 @@ class FourLineDisplayUsermod : public Usermod {
if (Display_Task)
vTaskResume(Display_Task);
else
xTaskCreatePinnedToCore(
xTaskCreateUniversal( // this is guaranteed to work on any ESP32 (single or dual core)
[](void * par) { // Function to implement the task
// see https://www.freertos.org/vtaskdelayuntil.html
const TickType_t xFrequency = REFRESH_RATE_MS * portTICK_PERIOD_MS / 2;
@@ -998,12 +1064,14 @@ class FourLineDisplayUsermod : public Usermod {
for(;;) {
delay(1); // DO NOT DELETE THIS LINE! It is needed to give the IDLE(0) task enough time and to keep the watchdog happy.
// taskYIELD(), yield(), vTaskDelay() and esp_task_wdt_feed() didn't seem to work.
vTaskDelayUntil(&xLastWakeTime, xFrequency); // release CPU, by doing nothing for REFRESH_RATE_MS millis
xLastWakeTime = xTaskGetTickCount(); // workaround for vTaskDelayUntil bug: it does not always keep the last time so we refresh it explicitly
FourLineDisplayUsermod::getInstance()->redraw(false);
vTaskDelayUntil(&xLastWakeTime, xFrequency); // release CPU, by doing nothing until next REFRESH_RATE_MS millis
}
},
"4LD", // Name of the task
3072, // Stack size in words
// 3072, // Stack size in words
4096, // bigger Stack size in words
NULL, // Task input parameter
1, // Priority of the task (not idle)
&Display_Task, // Task handle
@@ -1136,7 +1204,7 @@ class FourLineDisplayUsermod : public Usermod {
top[FPSTR(_flip)] = (bool) flip;
top[FPSTR(_contrast)] = contrast;
top[FPSTR(_contrastFix)] = (bool) contrastFix;
#ifndef ARDUINO_ARCH_ESP32
#if !defined(ARDUINO_ARCH_ESP32) || !defined(FLD_ESP32_USE_THREADS)
top[FPSTR(_refreshRate)] = refreshRate;
#endif
top[FPSTR(_screenTimeOut)] = screenTimeout/1000;
@@ -1172,7 +1240,7 @@ class FourLineDisplayUsermod : public Usermod {
for (byte i=0; i<5; i++) ioPin[i] = top["pin"][i] | ioPin[i];
flip = top[FPSTR(_flip)] | flip;
contrast = top[FPSTR(_contrast)] | contrast;
#ifndef ARDUINO_ARCH_ESP32
#if !defined(ARDUINO_ARCH_ESP32) || !defined(FLD_ESP32_USE_THREADS)
refreshRate = top[FPSTR(_refreshRate)] | refreshRate;
refreshRate = min(5000, max(250, (int)refreshRate));
#endif
@@ -1196,8 +1264,20 @@ class FourLineDisplayUsermod : public Usermod {
// changing parameters from settings page
bool pinsChanged = false;
for (byte i=0; i<5; i++) if (ioPin[i] != oldPin[i]) { pinsChanged = true; break; }
#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)
unsigned long now = millis();
while ((drawing || reDrawing) && millis()-now < 300) delay(10); // wait if someone else is drawing
#endif
drawing = false;
reDrawing = false;
if (pinsChanged || type!=newType) {
if (typeOK) delete u8x8;
if (typeOK) {
typeOK = false;
if (u8x8 != nullptr) delete u8x8;
u8x8 = nullptr;
USER_PRINTLN(F("Display terminated."));
}
PinOwner po = PinOwner::UM_FourLineDisplay;
bool isSPI = (type == SSD1306_SPI || type == SSD1306_SPI64);
if (isSPI) {
@@ -1213,10 +1293,12 @@ class FourLineDisplayUsermod : public Usermod {
setup();
needsRedraw |= true;
} else {
u8x8->setBusClock(ioFrequency); // can be used for SPI too
setVcomh(contrastFix);
setContrast(contrast);
setFlipMode(flip);
if (enabled && typeOK && (nullptr != u8x8)) { // WLEDMM ensure we have a valid, active driver
u8x8->setBusClock(ioFrequency); // can be used for SPI too
setVcomh(contrastFix);
setContrast(contrast);
setFlipMode(flip);
}
}
knownHour = 99;
if (needsRedraw && !wakeDisplay()) redraw(true);