diff --git a/include/config/ConfigManager.h b/include/config/ConfigManager.h index ce48975..035d63f 100644 --- a/include/config/ConfigManager.h +++ b/include/config/ConfigManager.h @@ -24,20 +24,17 @@ #include "config/SecureStorage.h" #include #include +#include // LCD configuration defaults for hellocubic lite -static constexpr bool LCD_ENABLE = true; static constexpr int16_t LCD_W = 240; static constexpr int16_t LCD_H = 240; static constexpr uint8_t LCD_ROTATION = 4; static constexpr int8_t LCD_MOSI_GPIO = 13; static constexpr int8_t LCD_SCK_GPIO = 14; -static constexpr int8_t LCD_CS_GPIO = 2; static constexpr int8_t LCD_DC_GPIO = 0; -static constexpr int8_t LCD_RST_GPIO = 15; -static constexpr bool LCD_CS_ACTIVE_HIGH = true; -static constexpr bool LCD_DC_CMD_HIGH = false; -static constexpr uint8_t LCD_SPI_MODE = 0; +static constexpr int8_t LCD_RST_GPIO = 2; +static constexpr uint8_t LCD_SPI_MODE = SPI_MODE3; static constexpr uint32_t LCD_SPI_HZ = 40000000; static constexpr int8_t LCD_BACKLIGHT_GPIO = 5; static constexpr bool LCD_BACKLIGHT_ACTIVE_LOW = true; @@ -50,63 +47,17 @@ class ConfigManager { void setWiFi(const char* newSsid, const char* newPassword); const char* getSSID() const; const char* getPassword() const; - bool getLCDEnable() const; - int16_t getLCDWidth() const; - int16_t getLCDHeight() const; uint8_t getLCDRotation() const; - int8_t getLCDMosiGpio() const; - int8_t getLCDSckGpio() const; - int8_t getLCDCsGpio() const; - int8_t getLCDDcGpio() const; - int8_t getLCDRstGpio() const; - bool getLCDCsActiveHigh() const; - bool getLCDDcCmdHigh() const; - uint8_t getLCDSpiMode() const; - bool getLCDKeepCsAsserted() const; uint32_t getLCDSpiHz() const; - int8_t getLCDBacklightGpio() const; - bool getLCDBacklightActiveLow() const; bool migrateWiFiToSecureStorage(String ssid, String password); public: - bool getLCDEnableSafe() const { return lcd_enable; } - int16_t getLCDWidthSafe() const { return (lcd_w > 0) ? lcd_w : LCD_W; } - int16_t getLCDHeightSafe() const { return (lcd_h > 0) ? lcd_h : LCD_H; } uint8_t getLCDRotationSafe() const { return lcd_rotation; } - int8_t getLCDMosiGpioSafe() const { return (lcd_mosi_gpio >= 0) ? lcd_mosi_gpio : LCD_MOSI_GPIO; } - int8_t getLCDSckGpioSafe() const { return (lcd_sck_gpio >= 0) ? lcd_sck_gpio : LCD_SCK_GPIO; } - int8_t getLCDCsGpioSafe() const { return (lcd_cs_gpio >= 0) ? lcd_cs_gpio : LCD_CS_GPIO; } - int8_t getLCDDcGpioSafe() const { return (lcd_dc_gpio >= 0) ? lcd_dc_gpio : LCD_DC_GPIO; } - int8_t getLCDRstGpioSafe() const { return (lcd_rst_gpio >= 0) ? lcd_rst_gpio : LCD_RST_GPIO; } - bool getLCDCsActiveHighSafe() const { return lcd_cs_active_high; } - bool getLCDDcCmdHighSafe() const { return lcd_dc_cmd_high; } - uint8_t getLCDSpiModeSafe() const { return lcd_spi_mode; } - bool getLCDKeepCsAssertedSafe() const { return lcd_keep_cs_asserted; } - uint32_t getLCDSpiHzSafe() const { return (lcd_spi_hz > 0) ? lcd_spi_hz : LCD_SPI_HZ; } - int8_t getLCDBacklightGpioSafe() const { - return (lcd_backlight_gpio >= 0) ? lcd_backlight_gpio : LCD_BACKLIGHT_GPIO; - } - bool getLCDBacklightActiveLowSafe() const { return lcd_backlight_active_low; } std::string ssid; std::string password; std::string filename; SecureStorage secure; - bool lcd_enable = true; - int16_t lcd_w = 240; - int16_t lcd_h = 240; uint8_t lcd_rotation = 4; - int8_t lcd_mosi_gpio = 13; - int8_t lcd_sck_gpio = 14; - int8_t lcd_cs_gpio = 2; - int8_t lcd_dc_gpio = 0; - int8_t lcd_rst_gpio = 15; - bool lcd_cs_active_high = true; - bool lcd_dc_cmd_high = false; - uint8_t lcd_spi_mode = 0; - bool lcd_keep_cs_asserted = true; - uint32_t lcd_spi_hz = 40000000; - int8_t lcd_backlight_gpio = 5; - bool lcd_backlight_active_low = true; }; #endif // CONFIG_MANAGER_H diff --git a/include/display/DisplayManager.h b/include/display/DisplayManager.h index 41bab7f..6da0af1 100644 --- a/include/display/DisplayManager.h +++ b/include/display/DisplayManager.h @@ -36,8 +36,6 @@ static constexpr int THREE_LINES_SPACE = 60; class DisplayManager { public: static void begin(); - static bool isReady(); - static void ensureInit(); static Arduino_GFX* getGfx(); static void drawStartup(String currentIP); static void drawTextWrapped(int16_t xPos, int16_t yPos, const String& text, uint8_t textSize, uint16_t fgColor, diff --git a/include/display/GeekMagicSPIBus.h b/include/display/GeekMagicSPIBus.h deleted file mode 100644 index a851c18..0000000 --- a/include/display/GeekMagicSPIBus.h +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -/* - * GeekMagic Open Firmware - * Copyright (C) 2026 Times-Z - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef HELLO_CUBIC_SPI_BUS_H -#define HELLO_CUBIC_SPI_BUS_H - -#include -#include - -/** - * @brief Determines whether the Chip Select (CS) line should remain asserted (active low) between SPI transactions - */ -static constexpr bool LCD_KEEP_CS_ASSERTED = true; - -/** - * @class GeekMagicSPIBus - * @brief Custom SPI bus class for GeekMagicSPIBus display - * - * This class extends Arduino_DataBus to provide SPI communication with custom Chip Select (CS) handling for the - * GeekMagicSPIBus display - */ -class GeekMagicSPIBus : public Arduino_DataBus { - public: - GeekMagicSPIBus(int8_t dataCmdPin, int8_t csPin, bool csActiveHigh, int32_t defaultSpeed, int8_t defaultDataMode); - - bool begin(int32_t speed = GFX_NOT_DEFINED, int8_t dataMode = GFX_NOT_DEFINED) override; - void beginWrite() override; - void endWrite() override; - - virtual ~GeekMagicSPIBus() {} - - void writeCommand(uint8_t c) override { _spi.writeCommand(c); } - void writeCommand16(uint16_t c) override { _spi.writeCommand16(c); } - void writeCommandBytes(uint8_t* data, uint32_t len) override { _spi.writeCommandBytes(data, len); } - void write(uint8_t d) override { _spi.write(d); } - void write16(uint16_t d) override { _spi.write16(d); } - void writeRepeat(uint16_t p, uint32_t len) override { _spi.writeRepeat(p, len); } - void writeBytes(uint8_t* data, uint32_t len) override { _spi.writeBytes(data, len); } - void writePixels(uint16_t* data, uint32_t len) override { _spi.writePixels(data, len); } - - private: - Arduino_HWSPI _spi; - int8_t _cs; - bool _csActiveHigh; - int32_t _defaultSpeed; - int8_t _defaultDataMode; -}; - -#endif diff --git a/readme.md b/readme.md index 75ad80c..2306cc6 100644 --- a/readme.md +++ b/readme.md @@ -70,7 +70,7 @@ - **Resolution**: 240x240 pixels - **Color Format**: RGB565 (16-bit color) - **Interface**: SPI (Serial Peripheral Interface) -- **SPI Speed**: up to 80 MHz (40 MHz is more stable) +- **SPI Speed**: 40 MHz (80 MHz is possible, but unstable and outside datasheet spec) - **Rotation**: Upside-down for cube display, normal for the small tv ### Pin wiring @@ -83,9 +83,9 @@ The display is connected to the ESP8266 using the following GPIO pins: | ------------- | -------- | ----------------------------------------------------- | | **MOSI** | GPIO 13 | SPI Master Out Slave In (data from ESP8266 to screen) | | **SCK** | GPIO 14 | SPI Clock | -| **CS** | GPIO 2 | Chip Select (Active HIGH) | +| **CS** | GND | Chip Select, tied permanently to GND | | **DC** | GPIO 0 | Data/Command select (LOW=command, HIGH=data) | -| **RST** | GPIO 15 | Reset pin | +| **RST** | GPIO 2 | Reset pin | | **Backlight** | GPIO 5 | Backlight control (Active LOW) |
@@ -96,9 +96,9 @@ The display is connected to the ESP8266 using the following GPIO pins: ### Important configuration details -**Chip select (CS) polarity**: This board uses **active-high** CS, which is non-standard. Most SPI displays use active-low CS. The CS pin must be driven HIGH to select the display +**Chip select (CS) polarity**: This board ties CS of the display permanently to GND. -**SPI mode**: SPI Mode 0 (CPOL=0, CPHA=0) +**SPI mode**: SPI Mode 3 (CPOL=1, CPHA=1) **Data/command pin**: LOW for commands, HIGH for data @@ -109,8 +109,8 @@ The display is connected to the ESP8266 using the following GPIO pins: ### Initialization sequence 1. **Backlight control**: GPIO 5 is set as output and driven LOW to turn on the backlight -2. **Hardware reset**: The RST pin (GPIO 15) is toggled to reset the ST7789 controller -3. **SPI bus setup**: Hardware SPI is initialized with 80 MHz clock speed and Mode 0 +2. **Hardware reset**: The RST pin (GPIO 2) is toggled to reset the ST7789 controller +3. **SPI bus setup**: Hardware SPI is initialized with 40 MHz clock speed and Mode 3 4. **Display controller init**: The ST7789 is configured using a vendor-specific initialization sequence that includes: - Sleep out (0x11) - Porch settings (0xB2) @@ -128,19 +128,15 @@ The display is connected to the ESP8266 using the following GPIO pins: The display uses **SPI** protocol for communication: -1. **Chip select**: CS is driven HIGH to select the display -2. **Command/data mode**: The DC pin indicates whether the data on MOSI is a command (DC=LOW) or pixel data (DC=HIGH) -3. **Data transfer**: Data is shifted out on the MOSI pin, synchronized with the SCK clock signal -4. **Chip deselect**: CS can be kept HIGH during continuous operations for better performance, or dropped LOW between operations +1. **Command/data mode**: The DC pin indicates whether the data on MOSI is a command (DC=LOW) or pixel data (DC=HIGH) +2. **Data transfer**: Data is shifted out on the MOSI pin, synchronized with the SCK clock signal ### Drawing to the screen The firmware uses the Arduino_GFX library with a custom ESP8266SPIWithCustomCS bus driver that handles the active-high CS polarity. Graphics operations: -1. **Begin write**: Assert CS (set HIGH) -2. **Write commands**: Set DC LOW, send command bytes via SPI -3. **Write data**: Set DC HIGH, send pixel data via SPI -4. **End write**: Optionally deassert CS (set LOW) - can be kept asserted for continuous operations +1. **Write commands**: Set DC LOW, send command bytes via SPI +2. **Write data**: Set DC HIGH, send pixel data via SPI ### Color format @@ -160,8 +156,7 @@ Example colors: ### Performance optimizations -- **High SPI speed**: 80 MHz clock for fast data transfer -- **CS kept asserted**: During continuous operations, CS stays HIGH to reduce overhead +- **High SPI speed**: 40 MHz clock for fast data transfer - **Hardware SPI**: Uses ESP8266's hardware SPI peripheral for efficient transfers - **Batch writes**: Multiple operations are batched between beginWrite/endWrite calls - **Direct frame buffer writes**: GIF frames are streamed directly to avoid intermediate buffering diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 9b42dbf..04210a0 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -65,22 +65,7 @@ auto ConfigManager::load() -> bool { String ssid = doc["wifi_ssid"] | ""; String password = doc["wifi_password"] | ""; - this->lcd_enable = doc["lcd_enable"] | lcd_enable; - this->lcd_w = doc["lcd_w"] | lcd_w; - this->lcd_h = doc["lcd_h"] | lcd_h; this->lcd_rotation = doc["lcd_rotation"] | lcd_rotation; - this->lcd_mosi_gpio = doc["lcd_mosi_gpio"] | lcd_mosi_gpio; - this->lcd_sck_gpio = doc["lcd_sck_gpio"] | lcd_sck_gpio; - this->lcd_cs_gpio = doc["lcd_cs_gpio"] | lcd_cs_gpio; - this->lcd_dc_gpio = doc["lcd_dc_gpio"] | lcd_dc_gpio; - this->lcd_rst_gpio = doc["lcd_rst_gpio"] | lcd_rst_gpio; - this->lcd_cs_active_high = doc["lcd_cs_active_high"] | lcd_cs_active_high; - this->lcd_dc_cmd_high = doc["lcd_dc_cmd_high"] | lcd_dc_cmd_high; - this->lcd_spi_mode = doc["lcd_spi_mode"] | lcd_spi_mode; - this->lcd_keep_cs_asserted = doc["lcd_keep_cs_asserted"] | lcd_keep_cs_asserted; - this->lcd_spi_hz = doc["lcd_spi_hz"] | lcd_spi_hz; - this->lcd_backlight_gpio = doc["lcd_backlight_gpio"] | lcd_backlight_gpio; - this->lcd_backlight_active_low = doc["lcd_backlight_active_low"] | lcd_backlight_active_low; String nvs_ssid = secure.get("wifi_ssid", ""); String nvs_password = secure.get("wifi_password", ""); @@ -118,25 +103,6 @@ auto ConfigManager::getSSID() const -> const char* { return ssid.c_str(); } */ auto ConfigManager::getPassword() const -> const char* { return password.c_str(); } -/** - * @brief Returns the current status of the LCD enable flag - * - * @return true if the LCD is enabled false otherwise - */ -auto ConfigManager::getLCDEnable() const -> bool { return lcd_enable; } - -/** - * @brief Retrieves the LCD width - * - * @return The width of the LCD in pixels - */ -auto ConfigManager::getLCDWidth() const -> int16_t { return lcd_w; } -/** - * @brief Retrieves the LCD height - * - * @return The height of the LCD in pixels - */ -auto ConfigManager::getLCDHeight() const -> int16_t { return lcd_h; } /** * @brief Retrieves the LCD rotation setting * @@ -144,89 +110,6 @@ auto ConfigManager::getLCDHeight() const -> int16_t { return lcd_h; } */ auto ConfigManager::getLCDRotation() const -> uint8_t { return lcd_rotation; } -/** - * @brief Retrieves the GPIO pin number for LCD MOSI - * - * @return The GPIO pin number for LCD MOSI - */ -auto ConfigManager::getLCDMosiGpio() const -> int8_t { return lcd_mosi_gpio; } - -/** - * @brief Retrieves the GPIO pin number for LCD SCK - * - * @return The GPIO pin number for LCD SCK - */ -auto ConfigManager::getLCDSckGpio() const -> int8_t { return lcd_sck_gpio; } - -/** - * @brief Retrieves the GPIO pin number for LCD CS - * - * @return The GPIO pin number for LCD CS - */ -auto ConfigManager::getLCDCsGpio() const -> int8_t { return lcd_cs_gpio; } - -/** - * @brief Retrieves the GPIO pin number for LCD DC - * - * @return The GPIO pin number for LCD DC - */ -auto ConfigManager::getLCDDcGpio() const -> int8_t { return lcd_dc_gpio; } - -/** - * @brief Retrieves the GPIO pin number for LCD RST - * - * @return The GPIO pin number for LCD RST - */ -auto ConfigManager::getLCDRstGpio() const -> int8_t { return lcd_rst_gpio; } - -/** - * @brief Returns whether the LCD CS pin is active high - * - * @return true if the LCD CS pin is active high false otherwise - */ -auto ConfigManager::getLCDCsActiveHigh() const -> bool { return lcd_cs_active_high; } -/** - * @brief Returns whether the LCD DC pin is command high - * - * @return true if the LCD DC pin is command high false otherwise - */ -auto ConfigManager::getLCDDcCmdHigh() const -> bool { return lcd_dc_cmd_high; } - -/** - * @brief Retrieves the LCD SPI mode - * - * @return The SPI mode of the LCD - */ -auto ConfigManager::getLCDSpiMode() const -> uint8_t { return lcd_spi_mode; } - -/** - * @brief Returns whether the LCD CS pin is kept asserted - * - * @return true if the LCD CS pin is kept asserted false otherwise - */ -auto ConfigManager::getLCDKeepCsAsserted() const -> bool { return lcd_keep_cs_asserted; } - -/** - * @brief Retrieves the SPI clock frequency for the LCD - * - * @return The SPI clock frequency in Hz - */ -auto ConfigManager::getLCDSpiHz() const -> uint32_t { return lcd_spi_hz; } - -/** - * @brief Retrieves the GPIO pin number for the LCD backlight - * - * @return The GPIO pin number for the LCD backlight - */ -auto ConfigManager::getLCDBacklightGpio() const -> int8_t { return lcd_backlight_gpio; } - -/** - * @brief Returns whether the LCD backlight pin is active low - * - * @return true if the LCD backlight pin is active low false otherwise - */ -auto ConfigManager::getLCDBacklightActiveLow() const -> bool { return lcd_backlight_active_low; } - /** * @brief Set WiFi credentials in memory * @param newSsid The SSID diff --git a/src/display/DisplayManager.cpp b/src/display/DisplayManager.cpp index 0ade253..7530ae1 100644 --- a/src/display/DisplayManager.cpp +++ b/src/display/DisplayManager.cpp @@ -21,10 +21,10 @@ #include #include #include +#include #include "project_version.h" #include "display/DisplayManager.h" -#include "display/GeekMagicSPIBus.h" #include "config/ConfigManager.h" #include "display/Gif.h" @@ -32,14 +32,10 @@ static Gif s_gif; extern ConfigManager configManager; -static Arduino_DataBus* g_lcdBus = nullptr; -static Arduino_GFX* g_lcd = nullptr; -static bool g_lcdReady = false; -static bool g_lcdInitializing = false; -static uint32_t g_lcdInitAttempts = 0; -static uint32_t g_lcdInitLastMs = 0; -static bool g_lcdInitOk = false; -static constexpr uint32_t LCD_HARDWARE_RESET_DELAY_MS = 100; +static Arduino_HWSPI g_lcdBus = Arduino_HWSPI(LCD_DC_GPIO, -1, &SPI, true); +static Arduino_ST7789 g_lcd = Arduino_ST7789(&g_lcdBus, -1, 0, true, LCD_W, LCD_H); + +static constexpr uint32_t LCD_HARDWARE_RESET_DELAY_MS = 120; static constexpr uint32_t LCD_BEGIN_DELAY_MS = 10; static constexpr int16_t DISPLAY_PADDING = 10; static constexpr int16_t DISPLAY_INFO_Y = 100; @@ -147,6 +143,7 @@ static constexpr uint8_t ST7789_SLEEP_DELAY_MS = 120; static constexpr uint8_t ST7789_SLEEP_OUT = 0x11; static constexpr uint8_t ST7789_PORCH = 0xB2; static constexpr uint8_t ST7789_PORCH_SETTINGS = 0x1F; +static constexpr uint8_t ST7789_SW_RESET = 0x01; static constexpr uint8_t ST7789_TEARING_EFFECT = 0x35; static constexpr uint8_t ST7789_MEMORY_ACCESS_CONTROL = 0x36; @@ -169,6 +166,7 @@ static constexpr uint8_t ST7789_GAMMA_CTRL = 0xE4; static constexpr uint8_t ST7789_INVERSION_ON = 0x21; static constexpr uint8_t ST7789_DISPLAY_ON = 0x29; +static constexpr uint8_t ST7789_NORMAL_DISPLAY_MODE = 0x13; // Porch parameters used in sequence static constexpr uint8_t ST7789_PORCH_PARAM_HS = 0x1F; @@ -209,7 +207,7 @@ static constexpr uint8_t ST7789_ADDR_END_LOW = 0xEF; * * @return Pointer to the Arduino_GFX instance */ -auto DisplayManager::getGfx() -> Arduino_GFX* { return g_lcd; } +auto DisplayManager::getGfx() -> Arduino_GFX* { return &g_lcd; } /** * @brief Turn the LCD backlight on @@ -217,14 +215,8 @@ auto DisplayManager::getGfx() -> Arduino_GFX* { return g_lcd; } * @return void */ static inline void lcdBacklightOn() { - int8_t gpio = configManager.getLCDBacklightGpioSafe(); - if (gpio < 0) { - Logger::warn("No backlight GPIO defined", "DisplayManager"); - return; - } - - pinMode((uint8_t)gpio, OUTPUT); - digitalWrite((uint8_t)gpio, configManager.getLCDBacklightActiveLowSafe() ? LOW : HIGH); + pinMode((uint8_t)LCD_BACKLIGHT_GPIO, OUTPUT); + digitalWrite((uint8_t)LCD_BACKLIGHT_GPIO, LCD_BACKLIGHT_ACTIVE_LOW ? LOW : HIGH); } /** @@ -232,30 +224,14 @@ static inline void lcdBacklightOn() { * * @return void */ -static inline void ST7789_WriteCommand(uint8_t cmd) { - if (g_lcdBus == nullptr) { - Logger::error("No data bus for LCD", "DisplayManager"); - - return; - } - - g_lcdBus->writeCommand(cmd); -} +static inline void ST7789_WriteCommand(uint8_t cmd) { g_lcdBus.writeCommand(cmd); } /** * @brief Write a single data byte to the ST7789 via the data bus * * @return void */ -static inline void ST7789_WriteData(uint8_t data) { - if (g_lcdBus == nullptr) { - Logger::error("No data bus for LCD", "DisplayManager"); - - return; - }; - - g_lcdBus->write(data); -} +static inline void ST7789_WriteData(uint8_t data) { g_lcdBus.write(data); } /** * @brief Run a vendor-specific initialization sequence for the ST7789 panel @@ -283,17 +259,10 @@ static inline void ST7789_WriteData(uint8_t data) { * @return void */ static void lcdRunVendorInit() { - if (g_lcdBus == nullptr) { - Logger::error("No data bus for LCD", "DisplayManager"); - - return; - }; - - g_lcdBus->beginWrite(); + g_lcdBus.beginWrite(); ST7789_WriteCommand(ST7789_SLEEP_OUT); delay(ST7789_SLEEP_DELAY_MS); - yield(); ST7789_WriteCommand(ST7789_PORCH); ST7789_WriteData(ST7789_PORCH_PARAM_HS); @@ -301,103 +270,81 @@ static void lcdRunVendorInit() { ST7789_WriteData(ST7789_PORCH_PARAM_DUMMY); ST7789_WriteData(ST7789_PORCH_PARAM_HBP); ST7789_WriteData(ST7789_PORCH_PARAM_VBP); - yield(); ST7789_WriteCommand(ST7789_TEARING_EFFECT); ST7789_WriteData(ST7789_TEARING_PARAM_OFF); - yield(); ST7789_WriteCommand(ST7789_MEMORY_ACCESS_CONTROL); ST7789_WriteData(ST7789_MADCTL_PARAM_DEFAULT); - yield(); ST7789_WriteCommand(ST7789_COLORMODE); ST7789_WriteData(ST7789_COLORMODE_RGB565); - yield(); ST7789_WriteCommand(ST7789_POWER_B7); ST7789_WriteData(ST7789_B7_PARAM_DEFAULT); - yield(); ST7789_WriteCommand(ST7789_POWER_BB); ST7789_WriteData(ST7789_BB_PARAM_VOLTAGE); - yield(); ST7789_WriteCommand(ST7789_POWER_C0); ST7789_WriteData(ST7789_C0_PARAM_1); - yield(); ST7789_WriteCommand(ST7789_POWER_C2); ST7789_WriteData(ST7789_C2_PARAM_1); - yield(); ST7789_WriteCommand(ST7789_POWER_C3); ST7789_WriteData(ST7789_C3_PARAM_1); - yield(); ST7789_WriteCommand(ST7789_POWER_C4); ST7789_WriteData(ST7789_C4_PARAM_1); - yield(); ST7789_WriteCommand(ST7789_POWER_C6); ST7789_WriteData(ST7789_C6_PARAM_1); - yield(); ST7789_WriteCommand(ST7789_POWER_D6); ST7789_WriteData(ST7789_D6_PARAM_1); - yield(); ST7789_WriteCommand(ST7789_POWER_D0); ST7789_WriteData(ST7789_D0_PARAM_1); ST7789_WriteData(ST7789_D0_PARAM_2); - yield(); ST7789_WriteCommand(ST7789_POWER_D6); ST7789_WriteData(ST7789_D6_PARAM_1); - yield(); ST7789_WriteCommand(ST7789_GAMMA_POS); for (uint8_t v : ST7789_GAMMA_POS_DATA) { ST7789_WriteData(v); } - yield(); ST7789_WriteCommand(ST7789_GAMMA_NEG); for (uint8_t v : ST7789_GAMMA_NEG_DATA) { ST7789_WriteData(v); } - yield(); ST7789_WriteCommand(ST7789_GAMMA_CTRL); for (uint8_t v : ST7789_GAMMA_CTRL_DATA) { ST7789_WriteData(v); } - yield(); ST7789_WriteCommand(ST7789_INVERSION_ON); - yield(); ST7789_WriteCommand(ST7789_DISPLAY_ON); - yield(); ST7789_WriteCommand(ST7789_CASET); ST7789_WriteData(ST7789_ADDR_START_HIGH); ST7789_WriteData(ST7789_ADDR_START_LOW); ST7789_WriteData(ST7789_ADDR_END_HIGH); ST7789_WriteData(ST7789_ADDR_END_LOW); - yield(); ST7789_WriteCommand(ST7789_RASET); ST7789_WriteData(ST7789_ADDR_START_HIGH); ST7789_WriteData(ST7789_ADDR_START_LOW); ST7789_WriteData(ST7789_ADDR_END_HIGH); ST7789_WriteData(ST7789_ADDR_END_LOW); - yield(); ST7789_WriteCommand(ST7789_RAMWR); - yield(); - g_lcdBus->endWrite(); + g_lcdBus.endWrite(); } /** @@ -408,18 +355,12 @@ static void lcdRunVendorInit() { * @return void */ static void lcdHardReset() { - int8_t rst_gpio = configManager.getLCDRstGpioSafe(); - if (rst_gpio < 0) { - Logger::warn("No reset GPIO defined", "DisplayManager"); - return; - } - - pinMode((uint8_t)rst_gpio, OUTPUT); - digitalWrite((uint8_t)rst_gpio, HIGH); + pinMode((uint8_t)LCD_RST_GPIO, OUTPUT); + digitalWrite((uint8_t)LCD_RST_GPIO, HIGH); delay(LCD_HARDWARE_RESET_DELAY_MS); - digitalWrite((uint8_t)rst_gpio, LOW); + digitalWrite((uint8_t)LCD_RST_GPIO, LOW); delay(LCD_HARDWARE_RESET_DELAY_MS); - digitalWrite((uint8_t)rst_gpio, HIGH); + digitalWrite((uint8_t)LCD_RST_GPIO, HIGH); delay(LCD_HARDWARE_RESET_DELAY_MS); } @@ -429,66 +370,26 @@ static void lcdHardReset() { * @return void */ static void lcdEnsureInit() { - if (!configManager.getLCDEnableSafe() || g_lcdReady || g_lcdInitializing) { - return; - }; - - g_lcdInitializing = true; - g_lcdInitAttempts++; - g_lcdInitLastMs = millis(); - g_lcdInitOk = false; - Logger::info("Initialization started", "DisplayManager"); lcdBacklightOn(); - lcdHardReset(); - - if (g_lcd != nullptr) { - delete static_cast(g_lcd); - g_lcd = nullptr; - } - if (g_lcdBus != nullptr) { - delete static_cast(g_lcdBus); - g_lcdBus = nullptr; - } - SPI.begin(); - - int8_t dc_gpio = configManager.getLCDDcGpioSafe(); - int8_t cs_gpio = configManager.getLCDCsGpioSafe(); - bool cs_active_high = configManager.getLCDCsActiveHighSafe(); - uint32_t spi_hz = configManager.getLCDSpiHzSafe(); - uint8_t spi_mode = configManager.getLCDSpiModeSafe(); uint8_t rotation = configManager.getLCDRotationSafe(); - int16_t lcd_w = configManager.getLCDWidthSafe(); - int16_t lcd_h = configManager.getLCDHeightSafe(); - - g_lcdBus = new GeekMagicSPIBus(dc_gpio, cs_gpio, cs_active_high, (int32_t)spi_hz, (int8_t)spi_mode); - g_lcd = new Arduino_ST7789(g_lcdBus, -1, rotation, true, lcd_w, lcd_h); - - g_lcdBus->begin((int32_t)spi_hz, (int8_t)spi_mode); - - g_lcd->begin(); - delay(LCD_BEGIN_DELAY_MS); + // SPI mode 3 is required. This toggles the pin from LOW to HIGH after reset, which my guess + // is after reset "initializes" the SPI interface of the display, as CS is tied to GND? + // ...strange that SPI_MODE0 will not work as the IC doesn't care about CLK's polarity + g_lcdBus.begin((int32_t)LCD_SPI_HZ, (int8_t)LCD_SPI_MODE); lcdHardReset(); - g_lcdBus->begin((int32_t)spi_hz, (int8_t)spi_mode); - lcdRunVendorInit(); + delay(LCD_BEGIN_DELAY_MS); - g_lcd->setRotation(rotation); - - g_lcdReady = true; - g_lcdInitializing = false; - g_lcdInitOk = true; + g_lcd.setRotation(rotation); - Logger::info( - ("Pointers g_lcd=" + String((uintptr_t)g_lcd, HEX) + " g_lcdBus=" + String((uintptr_t)g_lcdBus, HEX)).c_str(), - "DisplayManager"); - Logger::info(("Width=" + String(g_lcd->width()) + " height=" + String(g_lcd->height())).c_str(), "DisplayManager"); + Logger::info(("Width=" + String(g_lcd.width()) + " height=" + String(g_lcd.height())).c_str(), "DisplayManager"); - g_lcd->fillScreen(LCD_BLACK); - g_lcd->setTextColor(LCD_WHITE, LCD_BLACK); + g_lcd.fillScreen(LCD_BLACK); + g_lcd.setTextColor(LCD_WHITE, LCD_BLACK); Logger::info("Initialization completed", "DisplayManager"); } @@ -579,8 +480,8 @@ static auto lcdWrapTextToBuffer(const String& text, int maxCharsPerLine, int max */ static void lcdDrawTextWrapped(int16_t startX, int16_t startY, const String& text, uint8_t textSize, uint16_t fgColor, uint16_t bgColor, bool clearBg) { - const auto screenW = static_cast(g_lcd->width()); - const auto screenH = static_cast(g_lcd->height()); + const auto screenW = static_cast(g_lcd.width()); + const auto screenH = static_cast(g_lcd.height()); if (startX < 0) { startX = 0; @@ -621,16 +522,16 @@ static void lcdDrawTextWrapped(int16_t startX, int16_t startY, const String& tex if (clearBg) { const auto heightPixels = static_cast(static_cast(lineCount) * static_cast(charH)); - g_lcd->fillRect(startX, startY, static_cast(screenW - startX), static_cast(heightPixels), - bgColor); + g_lcd.fillRect(startX, startY, static_cast(screenW - startX), static_cast(heightPixels), + bgColor); } - g_lcd->setTextSize(textSize); - g_lcd->setTextColor(fgColor, bgColor); + g_lcd.setTextSize(textSize); + g_lcd.setTextColor(fgColor, bgColor); for (int li = 0; li < lineCount; ++li) { - g_lcd->setCursor(startX, static_cast(startY + li * charH)); - g_lcd->print(lines[li].data()); + g_lcd.setCursor(startX, static_cast(startY + li * charH)); + g_lcd.print(lines[li].data()); } } @@ -643,35 +544,22 @@ static void lcdDrawTextWrapped(int16_t startX, int16_t startY, const String& tex */ auto DisplayManager::begin() -> void { lcdEnsureInit(); } -/** - * @brief Check if the display is ready for drawing - * - * @return true if ready false otherwise - */ -auto DisplayManager::isReady() -> bool { return g_lcdReady && g_lcd != nullptr && g_lcdInitOk; } - /** * @brief Draw the startup screen on the LCD * * @return void */ auto DisplayManager::drawStartup(String currentIP) -> void { - if (!DisplayManager::isReady()) { - Logger::warn("Display not ready", "DisplayManager"); - - return; - } - int constexpr rgbDelayMs = 1000; - g_lcd->fillScreen(LCD_RED); + g_lcd.fillScreen(LCD_RED); delay(rgbDelayMs); - g_lcd->fillScreen(LCD_GREEN); + g_lcd.fillScreen(LCD_GREEN); delay(rgbDelayMs); - g_lcd->fillScreen(LCD_BLUE); + g_lcd.fillScreen(LCD_BLUE); delay(rgbDelayMs); - g_lcd->fillScreen(LCD_BLACK); + g_lcd.fillScreen(LCD_BLACK); int constexpr titleY = 10; int constexpr fontSize = 2; @@ -687,9 +575,9 @@ auto DisplayManager::drawStartup(String currentIP) -> void { const int16_t gap = 20; const int16_t boxY = titleY + (THREE_LINES_SPACE * 2) + ONE_LINE_SPACE; - g_lcd->fillRect(DISPLAY_PADDING, boxY, box, box, LCD_RED); - g_lcd->fillRect((int16_t)(DISPLAY_PADDING + box + gap), boxY, box, box, LCD_GREEN); - g_lcd->fillRect((int16_t)(DISPLAY_PADDING + (box + gap) * 2), boxY, box, box, LCD_BLUE); + g_lcd.fillRect(DISPLAY_PADDING, boxY, box, box, LCD_RED); + g_lcd.fillRect((int16_t)(DISPLAY_PADDING + box + gap), boxY, box, box, LCD_GREEN); + g_lcd.fillRect((int16_t)(DISPLAY_PADDING + (box + gap) * 2), boxY, box, box, LCD_BLUE); yield(); @@ -726,22 +614,18 @@ void DisplayManager::drawTextWrapped(int16_t xPos, int16_t yPos, const String& t */ void DisplayManager::drawLoadingBar(float progress, int yPos, int barWidth, int barHeight, uint16_t fgColor, uint16_t bgColor) { - if ((g_lcd == nullptr) || (!g_lcdReady)) { - return; - } - - auto barXPos = (static_cast(configManager.getLCDWidthSafe()) - static_cast(barWidth)) / 2; + auto barXPos = (static_cast(LCD_W) - static_cast(barWidth)) / 2; auto barXPos16 = static_cast(barXPos); auto yPos16 = static_cast(yPos); auto barWidth16 = static_cast(barWidth); auto barHeight16 = static_cast(barHeight); - g_lcd->fillRect(barXPos16, yPos16, barWidth16, barHeight16, bgColor); + g_lcd.fillRect(barXPos16, yPos16, barWidth16, barHeight16, bgColor); auto fillWidthF = static_cast(barWidth) * progress; auto fillWidth16 = static_cast(fillWidthF); if (fillWidth16 > 0) { - g_lcd->fillRect(barXPos16, yPos16, fillWidth16, barHeight16, fgColor); + g_lcd.fillRect(barXPos16, yPos16, fillWidth16, barHeight16, fgColor); } yield(); @@ -817,8 +701,4 @@ auto DisplayManager::update() -> void { s_gif.update(); } * * @return void */ -auto DisplayManager::clearScreen() -> void { - if (g_lcdReady && g_lcd != nullptr) { - g_lcd->fillScreen(LCD_BLACK); - } -} +auto DisplayManager::clearScreen() -> void { g_lcd.fillScreen(LCD_BLACK); } diff --git a/src/display/GeekMagicSPIBus.cpp b/src/display/GeekMagicSPIBus.cpp deleted file mode 100644 index 4575e72..0000000 --- a/src/display/GeekMagicSPIBus.cpp +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -/* - * GeekMagic Open Firmware - * Copyright (C) 2026 Times-Z - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "display/GeekMagicSPIBus.h" - -/** - * @brief Construct a new Geek Magic SPI Bus:: Geek Magic SPI Bus object - * - * @param dc Data/Command pin - * @param cs Chip Select pin - * @param csActiveHigh Whether CS is active high - * @param defaultSpeed Default SPI speed - * @param defaultDataMode Default SPI data mode - */ -GeekMagicSPIBus::GeekMagicSPIBus(int8_t dataCmdPin, int8_t csPin, bool csActiveHigh, int32_t defaultSpeed, - int8_t defaultDataMode) - : _spi(dataCmdPin, GFX_NOT_DEFINED, &SPI, true), - _cs(csPin), - _csActiveHigh(csActiveHigh), - _defaultSpeed(defaultSpeed), - _defaultDataMode(defaultDataMode) {} - -/** - * @brief Initializes the SPI bus with the specified speed and data mode - * @param speed SPI speed in Hz - * @param dataMode SPI data mode - * - * @return true if initialization is successful false otherwise - */ -auto GeekMagicSPIBus::begin(int32_t speed, int8_t dataMode) -> bool { - if (speed == GFX_NOT_DEFINED) { - speed = _defaultSpeed; - } - if (dataMode == GFX_NOT_DEFINED) { - dataMode = _defaultDataMode; - } - - if (_cs != GFX_NOT_DEFINED) { - pinMode((uint8_t)_cs, OUTPUT); - digitalWrite((uint8_t)_cs, _csActiveHigh ? LOW : HIGH); - } - - return _spi.begin(speed, dataMode); -} - -/** - * @brief Begins an SPI write transaction - * - * @return void - */ -auto GeekMagicSPIBus::beginWrite() -> void { - if (_cs != GFX_NOT_DEFINED) { - digitalWrite((uint8_t)_cs, _csActiveHigh ? HIGH : LOW); - } - _spi.beginWrite(); -} - -/** - * @brief Ends an SPI write transaction - * - * @return void - */ -auto GeekMagicSPIBus::endWrite() -> void { - _spi.endWrite(); - - if (LCD_KEEP_CS_ASSERTED) { - return; - } - - if (_cs != GFX_NOT_DEFINED) { - digitalWrite((uint8_t)_cs, _csActiveHigh ? LOW : HIGH); - } -} diff --git a/src/display/Gif.cpp b/src/display/Gif.cpp index 7730c63..4d7b819 100644 --- a/src/display/Gif.cpp +++ b/src/display/Gif.cpp @@ -188,10 +188,6 @@ auto Gif::gifSeekFile(GIFFILE* pFile, int32_t iPosition) -> int32_t { */ auto Gif::gifDraw(GIFDRAW* pDraw) -> void // NOLINT(readability-function-cognitive-complexity) { - if (!DisplayManager::isReady()) { - return; - } - auto* gfx = DisplayManager::getGfx(); if (gfx == nullptr) { return; @@ -447,10 +443,6 @@ auto Gif::gifDraw(GIFDRAW* pDraw) -> void // NOLINT(readability-function-cognit * @return true if playback started successfully false otherwise */ auto Gif::playOne(const String& path) -> bool { - if (!DisplayManager::isReady()) { - return false; - } - if (m_gif == nullptr) { if (!begin()) { return false; @@ -578,10 +570,6 @@ auto Gif::playAllFromLittleFS() -> bool { begin(); } - if (!DisplayManager::isReady()) { - return false; - } - if (!LittleFS.begin()) { return false; } diff --git a/src/main.cpp b/src/main.cpp index e0bd663..82850ab 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -85,13 +85,13 @@ void setup() { Serial.println(""); Logger::info(("GeekMagic Open Firmware " + String(PROJECT_VER_STR)).c_str()); + DisplayManager::begin(); + constexpr int TOTAL_STEPS = 6; int step = 0; if (!LittleFS.begin()) { - if (DisplayManager::isReady()) { - DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); - } + DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); Logger::error("Failed to mount LittleFS"); return; } @@ -111,28 +111,24 @@ void setup() { } step++; - DisplayManager::begin(); - if (DisplayManager::isReady()) { - DisplayManager::drawTextWrapped(LOADING_BAR_TEXT_X, LOADING_BAR_TEXT_Y, "Starting...", 2, LCD_WHITE, LCD_BLACK, - true); - DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); - } + DisplayManager::drawTextWrapped(LOADING_BAR_TEXT_X, LOADING_BAR_TEXT_Y, "Starting...", 2, LCD_WHITE, LCD_BLACK, + true); + DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); step++; wifiManager = new WiFiManager(configManager.getSSID(), configManager.getPassword(), AP_SSID, AP_PASSWORD); wifiManager->begin(); - if (DisplayManager::isReady()) { - DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); - } + + DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); + step++; webserver = new Webserver(); webserver->begin(); // capture initial free heap after core subsystems initialized initial_free_heap = ESP.getFreeHeap(); // NOLINT(readability-static-accessed-through-instance) - if (DisplayManager::isReady()) { - DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); - } + + DisplayManager::drawLoadingBar((float)step / TOTAL_STEPS, LOADING_BAR_Y); step++; registerApiEndpoints(webserver); @@ -151,13 +147,15 @@ void setup() { webserver->registerStaticDir("/web/css", "/css", "text/css"); webserver->registerStaticDir("/web/js", "/js", "application/javascript"); - if (DisplayManager::isReady()) { - DisplayManager::drawLoadingBar(1.0F, LOADING_BAR_Y); - } + DisplayManager::drawLoadingBar(1.0F, LOADING_BAR_Y); delay(LOADING_DELAY_MS); DisplayManager::drawStartup(wifiManager->getIP().toString()); + + // enable watchdog before going to loop() + // 2 seconds should be way more than the main loop needs to do stuff + EspClass::wdtEnable(WDTO_2S); } void loop() { @@ -183,4 +181,6 @@ void loop() { snprintf(msgBuf, sizeof(msgBuf), "Free heap: %s (initial: %s)", freeBuf, initBuf); Logger::info(msgBuf); } + + EspClass::wdtFeed(); // kick watchdog } diff --git a/src/web/Api.cpp b/src/web/Api.cpp index 840409d..b9c08e9 100644 --- a/src/web/Api.cpp +++ b/src/web/Api.cpp @@ -688,12 +688,10 @@ static void otaHandleStart(HTTPUpload& upload, int mode) { otaCancelRequested = false; otaTotal = static_cast(upload.contentLength); - if (DisplayManager::isReady()) { - DisplayManager::clearScreen(); - DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Uploading...", 2, LCD_WHITE, LCD_BLACK, - true); - DisplayManager::drawLoadingBar(0.0F, OTA_LOADING_Y_OFFSET); - } + DisplayManager::clearScreen(); + DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Uploading...", 2, LCD_WHITE, LCD_BLACK, + true); + DisplayManager::drawLoadingBar(0.0F, OTA_LOADING_Y_OFFSET); int constexpr security_space = 0x1000; u_int constexpr bin_mask = 0xFFFFF000; @@ -729,11 +727,9 @@ static void otaHandleWrite(HTTPUpload& upload) { otaInProgress = false; Logger::warn("OTA canceled by user", "API::OTA"); - if (DisplayManager::isReady()) { - DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Canceled", 2, LCD_WHITE, - LCD_BLACK, true); - DisplayManager::drawLoadingBar(0.0F, OTA_LOADING_Y_OFFSET); - } + DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Canceled", 2, LCD_WHITE, LCD_BLACK, + true); + DisplayManager::drawLoadingBar(0.0F, OTA_LOADING_Y_OFFSET); return; } @@ -746,14 +742,12 @@ static void otaHandleWrite(HTTPUpload& upload) { otaSize += upload.currentSize; - if (DisplayManager::isReady()) { - float progress = 0.0F; - if (otaTotal > 0) { - progress = static_cast(otaSize) / static_cast(otaTotal); - } - - DisplayManager::drawLoadingBar(progress, OTA_LOADING_Y_OFFSET); + float progress = 0.0F; + if (otaTotal > 0) { + progress = static_cast(otaSize) / static_cast(otaTotal); } + + DisplayManager::drawLoadingBar(progress, OTA_LOADING_Y_OFFSET); } } @@ -776,11 +770,9 @@ static void otaHandleEnd(HTTPUpload& /*upload*/, int mode) { otaStatus = String("Update OK (") + String(otaSize) + " bytes)"; Logger::info(otaStatus.c_str(), "API::OTA"); - if (DisplayManager::isReady()) { - DisplayManager::drawLoadingBar(1.0F, OTA_LOADING_Y_OFFSET); - DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Success!", 2, LCD_WHITE, - LCD_BLACK, true); - } + DisplayManager::drawLoadingBar(1.0F, OTA_LOADING_Y_OFFSET); + DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Success!", 2, LCD_WHITE, LCD_BLACK, + true); } else { otaError = true; otaStatus = Update.getErrorString(); @@ -802,8 +794,6 @@ static void otaHandleAborted(HTTPUpload& /*upload*/) { otaInProgress = false; otaCancelRequested = false; - if (DisplayManager::isReady()) { - DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Aborted", 2, LCD_WHITE, LCD_BLACK, true); - DisplayManager::drawLoadingBar(0.0F, OTA_LOADING_Y_OFFSET); - } + DisplayManager::drawTextWrapped(OTA_TEXT_X_OFFSET, OTA_TEXT_Y_OFFSET, "Aborted", 2, LCD_WHITE, LCD_BLACK, true); + DisplayManager::drawLoadingBar(0.0F, OTA_LOADING_Y_OFFSET); }