diff --git a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h index a4e6291b..afc1bb63 100644 --- a/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h +++ b/usermods/usermod_v2_four_line_display_ALT/usermod_v2_four_line_display_ALT.h @@ -1,11 +1,16 @@ #pragma once #include "wled.h" +#undef U8X8_NO_HW_I2C // borrowed from WLEDMM: we do want I2C hardware drivers - if possible #include // 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 +// Inspired by the usermod_v2_four_line_display // // v2 usermod for using 128x32 or 128x64 i2c // OLED displays to provide a four line display @@ -88,9 +93,11 @@ typedef enum { class FourLineDisplayUsermod : public Usermod { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) public: FourLineDisplayUsermod() { if (!instance) instance = this; } static FourLineDisplayUsermod* getInstance(void) { return instance; } +#endif private: @@ -215,9 +222,7 @@ class FourLineDisplayUsermod : public Usermod { void loop(); //function to update lastredraw - void updateRedrawTime() { - lastRedraw = millis(); - } + inline void updateRedrawTime() { lastRedraw = millis(); } /** * Redraw the screen (but only if things have changed @@ -352,10 +357,13 @@ const char FourLineDisplayUsermod::_showSeconds[] PROGMEM = "showSeconds"; const char FourLineDisplayUsermod::_busClkFrequency[] PROGMEM = "i2c-freq-kHz"; const char FourLineDisplayUsermod::_contrastFix[] PROGMEM = "contrastFix"; +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) FourLineDisplayUsermod *FourLineDisplayUsermod::instance = nullptr; +#endif // some displays need this to properly apply contrast void FourLineDisplayUsermod::setVcomh(bool highContrast) { + if (type == NONE || !enabled) return; u8x8_t *u8x8_struct = u8x8->getU8x8(); u8x8_cad_StartTransfer(u8x8_struct); u8x8_cad_SendCmd(u8x8_struct, 0x0db); //address of value @@ -364,6 +372,7 @@ void FourLineDisplayUsermod::setVcomh(bool highContrast) { } void FourLineDisplayUsermod::startDisplay() { + if (type == NONE || !enabled) return; lineHeight = u8x8->getRows() > 4 ? 2 : 1; DEBUG_PRINTLN(F("Starting display.")); u8x8->setBusClock(ioFrequency); // can be used for SPI too @@ -590,7 +599,7 @@ void FourLineDisplayUsermod::connected() { * Da loop. */ void FourLineDisplayUsermod::loop() { -#ifndef ARDUINO_ARCH_ESP32 +#if !(defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS)) if (!enabled || strip.isUpdating()) return; unsigned long now = millis(); if (now < nextUpdate) return; @@ -716,6 +725,11 @@ void FourLineDisplayUsermod::redraw(bool forceRedraw) { } void FourLineDisplayUsermod::updateBrightness() { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif knownBrightness = bri; if (overlayUntil == 0) { lockRedraw = true; @@ -728,6 +742,11 @@ void FourLineDisplayUsermod::updateBrightness() { } void FourLineDisplayUsermod::updateSpeed() { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif knownEffectSpeed = effectSpeed; if (overlayUntil == 0) { lockRedraw = true; @@ -740,6 +759,11 @@ void FourLineDisplayUsermod::updateSpeed() { } void FourLineDisplayUsermod::updateIntensity() { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif knownEffectIntensity = effectIntensity; if (overlayUntil == 0) { lockRedraw = true; @@ -752,6 +776,11 @@ void FourLineDisplayUsermod::updateIntensity() { } void FourLineDisplayUsermod::drawStatusIcons() { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif uint8_t col = 15; uint8_t row = 0; lockRedraw = true; @@ -775,6 +804,11 @@ void FourLineDisplayUsermod::setMarkLine(byte newMarkLineNum, byte newMarkColNum //Draw the arrow for the current setting beiong changed void FourLineDisplayUsermod::drawArrow() { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif lockRedraw = true; if (markColNum != 255 && markLineNum !=255) drawGlyph(markColNum, markLineNum*lineHeight, 21, u8x8_4LineDisplay_WLED_icons_1x1); lockRedraw = false; @@ -783,6 +817,11 @@ void FourLineDisplayUsermod::drawArrow() { //Display the current effect or palette (desiredEntry) // on the appropriate line (row). void FourLineDisplayUsermod::showCurrentEffectOrPalette(int inputEffPal, const char *qstring, uint8_t row) { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) + unsigned long now = millis(); + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return; +#endif char lineBuffer[MAX_JSON_CHARS]; if (overlayUntil == 0) { lockRedraw = true; @@ -851,9 +890,11 @@ void FourLineDisplayUsermod::showCurrentEffectOrPalette(int inputEffPal, const c bool FourLineDisplayUsermod::wakeDisplay() { if (type == NONE || !enabled) return false; if (displayTurnedOff) { + #if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) unsigned long now = millis(); - while (drawing && millis()-now < 250) delay(1); // wait if someone else is drawing - if (drawing) return false; + while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing + if (drawing || lockRedraw) return false; + #endif lockRedraw = true; clear(); // Turn the display back on @@ -870,9 +911,11 @@ bool FourLineDisplayUsermod::wakeDisplay() { * Used in Rotary Encoder usermod. */ void FourLineDisplayUsermod::overlay(const char* line1, long showHowLong, byte glyphType) { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) unsigned long now = millis(); while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing - if (drawing) return; + if (drawing || lockRedraw) return; +#endif lockRedraw = true; // Turn the display back on if (!wakeDisplay()) clear(); @@ -895,9 +938,11 @@ void FourLineDisplayUsermod::overlay(const char* line1, long showHowLong, byte g * Clears the screen and prints. */ void FourLineDisplayUsermod::overlayLogo(long showHowLong) { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) unsigned long now = millis(); while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing - if (drawing) return; + if (drawing || lockRedraw) return; +#endif lockRedraw = true; // Turn the display back on if (!wakeDisplay()) clear(); @@ -957,9 +1002,11 @@ void FourLineDisplayUsermod::overlayLogo(long showHowLong) { * Used in Auto Save usermod */ void FourLineDisplayUsermod::overlay(const char* line1, const char* line2, long showHowLong) { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) unsigned long now = millis(); while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing - if (drawing) return; + if (drawing || lockRedraw) return; +#endif lockRedraw = true; // Turn the display back on if (!wakeDisplay()) clear(); @@ -979,9 +1026,11 @@ void FourLineDisplayUsermod::overlay(const char* line1, const char* line2, long } void FourLineDisplayUsermod::networkOverlay(const char* line1, long showHowLong) { +#if defined(ARDUINO_ARCH_ESP32) && defined(FLD_ESP32_USE_THREADS) unsigned long now = millis(); while (drawing && millis()-now < 125) delay(1); // wait if someone else is drawing - if (drawing) return; + if (drawing || lockRedraw) return; +#endif lockRedraw = true; String line; @@ -1098,7 +1147,7 @@ bool FourLineDisplayUsermod::handleButton(uint8_t b) { #define ARDUINO_RUNNING_CORE 1 #endif void FourLineDisplayUsermod::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 { diff --git a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h index a5506e77..b1674951 100644 --- a/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h +++ b/usermods/usermod_v2_rotary_encoder_ui_ALT/usermod_v2_rotary_encoder_ui_ALT.h @@ -57,6 +57,10 @@ #define PCF8574_ADDRESS 0x20 // some may start at 0x38 #endif +#ifndef PCF8574_INT_PIN + #define PCF8574_INT_PIN -1 // GPIO connected to INT pin on PCF8574 +#endif + // The last UI state, remove color and saturation option if display not active (too many options) #ifdef USERMOD_FOUR_LINE_DISPLAY #define LAST_UI_STATE 11 @@ -125,6 +129,21 @@ static int re_qstringCmp(const void *ap, const void *bp) { } +static volatile uint8_t pcfPortData = 0; // port expander port state +static volatile uint8_t addrPcf8574 = PCF8574_ADDRESS; // has to be accessible in ISR + +// Interrupt routine to read I2C rotary state +// if we are to use PCF8574 port expander we will need to rely on interrupts as polling I2C every 2ms +// is a waste of resources and causes 4LD to fail. +// in such case rely on ISR to read pin values and store them into static variable +static void IRAM_ATTR i2cReadingISR() { + Wire.requestFrom(addrPcf8574, 1U); + if (Wire.available()) { + pcfPortData = Wire.read(); + } +} + + class RotaryEncoderUIUsermod : public Usermod { private: @@ -186,7 +205,7 @@ class RotaryEncoderUIUsermod : public Usermod { bool enabled; bool usePcf8574; - uint8_t addrPcf8574; + int8_t pinIRQ; // strings to reduce flash memory usage (used more than twice) static const char _name[]; @@ -199,6 +218,7 @@ class RotaryEncoderUIUsermod : public Usermod { static const char _applyToAll[]; static const char _pcf8574[]; static const char _pcfAddress[]; + static const char _pcfINTpin[]; /** * readPin() - read rotary encoder pin value @@ -254,7 +274,7 @@ class RotaryEncoderUIUsermod : public Usermod { , initDone(false) , enabled(true) , usePcf8574(USE_PCF8574) - , addrPcf8574(PCF8574_ADDRESS) + , pinIRQ(PCF8574_INT_PIN) {} /* @@ -356,15 +376,7 @@ class RotaryEncoderUIUsermod : public Usermod { */ byte RotaryEncoderUIUsermod::readPin(uint8_t pin) { if (usePcf8574) { - byte _data = 0; - if (pin < 8) { - Wire.requestFrom(addrPcf8574, 1U); - if (Wire.available()) { - _data = Wire.read(); - _data = (_data>>pin) & 1; - } - } - return _data; + return (pcfPortData>>pin) & 1; } else { return digitalRead(pin); } @@ -460,14 +472,25 @@ void RotaryEncoderUIUsermod::setup() { DEBUG_PRINTLN(F("Usermod Rotary Encoder init.")); - if (!usePcf8574) { + if (usePcf8574) { + if ((i2c_sda == i2c_scl && i2c_sda == -1) || pinA<0 || pinB<0 || pinC<0) { + DEBUG_PRINTLN(F("I2C and/or PCF8574 pins unused, disabling.")); + enabled = false; + return; + } else { + if (pinIRQ >= 0 && pinManager.allocatePin(pinIRQ, false, PinOwner::UM_RotaryEncoderUI)) { + attachInterrupt(pinIRQ, i2cReadingISR, CHANGE); // RISING, FALLING, CHANGE, ONLOW, ONHIGH + DEBUG_PRINTLN(F("Interrupt attached.")); + } else { + DEBUG_PRINTLN(F("Unable to allocate interrupt pin, disabling.")); + pinIRQ = -1; + enabled = false; + return; + } + } + } else { PinManagerPinType pins[3] = { { pinA, false }, { pinB, false }, { pinC, false } }; if (!pinManager.allocateMultiplePins(pins, 3, PinOwner::UM_RotaryEncoderUI)) { - // BUG: configuring this usermod with conflicting pins - // will cause it to de-allocate pins it does not own - // (at second config) - // This is the exact type of bug solved by pinManager - // tracking the owner tags.... pinA = pinB = pinC = -1; enabled = false; return; @@ -479,12 +502,6 @@ void RotaryEncoderUIUsermod::setup() pinMode(pinA, USERMOD_ROTARY_ENCODER_GPIO); pinMode(pinB, USERMOD_ROTARY_ENCODER_GPIO); pinMode(pinC, USERMOD_ROTARY_ENCODER_GPIO); - } else { - if ((i2c_sda == i2c_scl && i2c_sda == -1) || pinA<0 || pinB<0 || pinC<0) { - DEBUG_PRINTLN(F("Pins unused, disabling.")); - enabled = false; - return; - } } loopTime = millis(); @@ -536,7 +553,7 @@ void RotaryEncoderUIUsermod::loop() currentEffectAndPaletteInitialized = false; } - if (currentTime >= (loopTime + 20)) // 20ms since last check of encoder = 50Hz + if (currentTime >= (loopTime + 2)) // 2ms since last check of encoder = 500Hz { bool buttonPressed = !readPin(pinC); //0=pressed, 1=released if (buttonPressed) { @@ -645,8 +662,6 @@ void RotaryEncoderUIUsermod::loop() } } Enc_A_prev = Enc_A; // Store value of A for next time - DEBUG_PRINTF("Inputs: A:%d B:%d SW:%d\n", (int)Enc_A, (int)Enc_B, (int)!buttonPressed); - loopTime = currentTime; // Updates loopTime } } @@ -1051,6 +1066,7 @@ void RotaryEncoderUIUsermod::addToConfig(JsonObject &root) { top[FPSTR(_applyToAll)] = applyToAll; top[FPSTR(_pcf8574)] = usePcf8574; top[FPSTR(_pcfAddress)] = addrPcf8574; + top[FPSTR(_pcfINTpin)] = pinIRQ; DEBUG_PRINTLN(F("Rotary Encoder config saved.")); } @@ -1074,6 +1090,7 @@ bool RotaryEncoderUIUsermod::readFromConfig(JsonObject &root) { int8_t newDTpin = top[FPSTR(_DT_pin)] | pinA; int8_t newCLKpin = top[FPSTR(_CLK_pin)] | pinB; int8_t newSWpin = top[FPSTR(_SW_pin)] | pinC; + int8_t newIRQpin = top[FPSTR(_pcfINTpin)] | pinIRQ; bool oldPcf8574 = usePcf8574; presetHigh = top[FPSTR(_presetHigh)] | presetHigh; @@ -1097,8 +1114,15 @@ bool RotaryEncoderUIUsermod::readFromConfig(JsonObject &root) { } else { DEBUG_PRINTLN(F(" config (re)loaded.")); // changing parameters from settings page - if (pinA!=newDTpin || pinB!=newCLKpin || pinC!=newSWpin) { - if (!oldPcf8574) { + if (pinA!=newDTpin || pinB!=newCLKpin || pinC!=newSWpin || pinIRQ!=newIRQpin) { + if (oldPcf8574) { + if (pinIRQ >= 0) { + detachInterrupt(pinIRQ); + pinManager.deallocatePin(pinIRQ, PinOwner::UM_RotaryEncoderUI); + DEBUG_PRINTLN(F("Deallocated old IRQ pin.")); + } + pinIRQ = newIRQpin; + } else { pinManager.deallocatePin(pinA, PinOwner::UM_RotaryEncoderUI); pinManager.deallocatePin(pinB, PinOwner::UM_RotaryEncoderUI); pinManager.deallocatePin(pinC, PinOwner::UM_RotaryEncoderUI); @@ -1115,7 +1139,7 @@ bool RotaryEncoderUIUsermod::readFromConfig(JsonObject &root) { } } // use "return !top["newestParameter"].isNull();" when updating Usermod with new features - return !top[FPSTR(_pcf8574)].isNull(); + return !top[FPSTR(_pcfINTpin)].isNull(); } @@ -1130,3 +1154,4 @@ const char RotaryEncoderUIUsermod::_presetLow[] PROGMEM = "preset-low"; const char RotaryEncoderUIUsermod::_applyToAll[] PROGMEM = "apply-2-all-seg"; const char RotaryEncoderUIUsermod::_pcf8574[] PROGMEM = "use-PCF8574"; const char RotaryEncoderUIUsermod::_pcfAddress[] PROGMEM = "PCF8574-address"; +const char RotaryEncoderUIUsermod::_pcfINTpin[] PROGMEM = "PCF8574-INT-pin";