From e5ba4885de573e4ededfcb7bb7e5608d5757e18f Mon Sep 17 00:00:00 2001 From: Jared Dohrman Date: Sun, 9 Aug 2026 17:36:20 +1000 Subject: [PATCH] fix(display): stop SH1106 boot crash when the boot-time probe fails The upstream merge switched SH1106Display::begin() to probe-then-init. On the T-Beam S3 Supreme the OLED bus can NAK a cold address probe, so begin() bailed before display.begin() ever ran, and UITask::begin()'s unconditional turnOn() then drove Adafruit_GrayOLED with a null bus device: bogus gpio_set_level(227) followed by a LoadProhibited boot loop. Restore the proven S3 Supreme bring-up order (Adafruit init with reset sequence first, then settle the bus and verify), and track begin() success so turnOn/turnOff/clear are safe no-ops when the display never initialised - a failed or absent display now boots headless instead of crash-looping. Co-Authored-By: Claude Fable 5 --- src/helpers/ui/SH1106Display.cpp | 14 ++++++++++++-- src/helpers/ui/SH1106Display.h | 3 ++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/helpers/ui/SH1106Display.cpp b/src/helpers/ui/SH1106Display.cpp index e7b15ebf..81809349 100644 --- a/src/helpers/ui/SH1106Display.cpp +++ b/src/helpers/ui/SH1106Display.cpp @@ -23,29 +23,39 @@ ColorVal UIColor::corp_blue = SH110X_WHITE; bool SH1106Display::begin() { #if defined(ESP32) && defined(TBEAM_SUPREME_SX1262) && defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) + // The S3 Supreme OLED bus NAKs cold address probes on some boards, so run the + // Adafruit init (with its reset sequence) first, then settle the bus before + // verifying the display responds. + _began = display.begin(DISPLAY_ADDRESS, true); Wire.begin(PIN_BOARD_SDA, PIN_BOARD_SCL); Wire.setClock(100000); -#endif + _began = _began && i2c_probe(Wire, DISPLAY_ADDRESS); +#else // Wire must already be initialised by board.begin() before this is called. // Boards with non-standard SH1106 addresses should define DISPLAY_ADDRESS // in their variant/platformio configuration. - return i2c_probe(Wire, DISPLAY_ADDRESS) && display.begin(DISPLAY_ADDRESS, true); + _began = i2c_probe(Wire, DISPLAY_ADDRESS) && display.begin(DISPLAY_ADDRESS, true); +#endif + return _began; } void SH1106Display::turnOn() { + if (!_began) return; // display.begin() never ran; oled_command would crash on a null bus device display.oled_command(SH110X_DISPLAYON); _isOn = true; } void SH1106Display::turnOff() { + if (!_began) return; display.oled_command(SH110X_DISPLAYOFF); _isOn = false; } void SH1106Display::clear() { + if (!_began) return; display.clearDisplay(); display.display(); } diff --git a/src/helpers/ui/SH1106Display.h b/src/helpers/ui/SH1106Display.h index 4e269d5e..417dab99 100644 --- a/src/helpers/ui/SH1106Display.h +++ b/src/helpers/ui/SH1106Display.h @@ -18,12 +18,13 @@ class SH1106Display : public DisplayDriver { Adafruit_SH1106G display; bool _isOn; + bool _began; uint8_t _color; bool i2c_probe(TwoWire &wire, uint8_t addr); public: - SH1106Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; } + SH1106Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; _began = false; } bool begin(); bool isOn() override { return _isOn; }