Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/ConfigHTML.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,12 +201,19 @@ const char CONFIG_HTML[] PROGMEM = R"rawliteral(
</label>
</div>
<div class="toggle-row">
<span class="toggle-label">TFT Display (ST7789 240x240)</span>
<span class="toggle-label">TFT Display (240x240)</span>
<label class="toggle-switch">
<input type="checkbox" id="tft_enabled" />
<span class="toggle-track"></span>
</label>
</div>
<div class="toggle-row" id="tft_driver_row" style="display:none">
<span class="toggle-label">TFT Driver</span>
<select id="tft_driver" style="padding:6px 10px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);font-size:0.95em">
<option value="st7789">ST7789 (square)</option>
<option value="gc9a01">GC9A01 (round)</option>
</select>
</div>
<div class="toggle-row">
<span class="toggle-label">NFC Reader</span>
<select id="nfc_reader" style="padding:6px 10px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);font-size:0.95em">
Expand Down Expand Up @@ -255,6 +262,11 @@ const char CONFIG_HTML[] PROGMEM = R"rawliteral(
document.getElementById('led_enabled').checked = !!cfg.led_enabled;
document.getElementById('keypad_enabled').checked = !!cfg.keypad_enabled;
document.getElementById('tft_enabled').checked = !!cfg.tft_enabled;
if (cfg.tft_driver) document.getElementById('tft_driver').value = cfg.tft_driver;
document.getElementById('tft_driver_row').style.display = cfg.tft_enabled ? '' : 'none';
document.getElementById('tft_enabled').addEventListener('change', function() {
document.getElementById('tft_driver_row').style.display = this.checked ? '' : 'none';
});
if (cfg.nfc_reader) document.getElementById('nfc_reader').value = cfg.nfc_reader;
maybeSetValue('moonraker_url', cfg.moonraker_url);
// Password placeholders
Expand Down Expand Up @@ -307,6 +319,7 @@ const char CONFIG_HTML[] PROGMEM = R"rawliteral(
led_enabled: document.getElementById('led_enabled').checked ? 1 : 0,
keypad_enabled: document.getElementById('keypad_enabled').checked ? 1 : 0,
tft_enabled: document.getElementById('tft_enabled').checked ? 1 : 0,
tft_driver: document.getElementById('tft_driver').value,
nfc_reader: document.getElementById('nfc_reader').value,
moonraker_url: document.getElementById('moonraker_url').value.trim(),
prusalink_on: document.getElementById('prusalink_on').checked ? 1 : 0,
Expand Down
11 changes: 11 additions & 0 deletions src/ConfigurationManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ static const char* NVS_KEY_LCD_ON = "lcd_on";
static const char* NVS_KEY_LED_ON = "led_on";
static const char* NVS_KEY_KEYPAD_ON = "keypad_on";
static const char* NVS_KEY_TFT_ON = "tft_on";
static const char* NVS_KEY_TFT_DRIVER = "tft_driver";
static const char* NVS_KEY_MOONRAKER_URL = "moonraker_url";
static const char* NVS_KEY_PRUSALINK_ON = "prusalink_on";
static const char* NVS_KEY_PRUSALINK_URL = "prusalink_url";
Expand Down Expand Up @@ -223,6 +224,10 @@ bool ConfigurationManager::loadFromNVS() {
_tftEnabled = prefs.getUChar(NVS_KEY_TFT_ON, _tftEnabled ? 1 : 0) != 0;
anyOverride = true;
}
if (prefs.isKey(NVS_KEY_TFT_DRIVER)) {
prefs.getString(NVS_KEY_TFT_DRIVER, _tftDriver, sizeof(_tftDriver));
anyOverride = true;
}
if (prefs.isKey(NVS_KEY_NFC_READER)) {
prefs.getString(NVS_KEY_NFC_READER, _nfcReader, sizeof(_nfcReader));
anyOverride = true;
Expand Down Expand Up @@ -314,6 +319,10 @@ bool ConfigurationManager::isTftEnabled() const {
return _tftEnabled;
}

const char* ConfigurationManager::getTftDriver() const {
return _tftDriver;
}

const char* ConfigurationManager::getMoonrakerURL() const {
return _moonrakerUrl;
}
Expand Down Expand Up @@ -344,6 +353,7 @@ void ConfigurationManager::getCurrentConfig(ConfigUpdate& out) const {
out.led_enabled = _ledEnabled ? 1 : 0;
out.keypad_enabled = _keypadEnabled ? 1 : 0;
out.tft_enabled = _tftEnabled ? 1 : 0;
strncpy(out.tft_driver, _tftDriver, sizeof(out.tft_driver) - 1);
strncpy(out.moonraker_url, _moonrakerUrl, sizeof(out.moonraker_url) - 1);
strncpy(out.nfc_reader, _nfcReader, sizeof(out.nfc_reader) - 1);
strncpy(out.hostname, _hostname, sizeof(out.hostname) - 1);
Expand Down Expand Up @@ -375,6 +385,7 @@ bool ConfigurationManager::saveToNVS(const ConfigUpdate& update) {
prefs.putUChar(NVS_KEY_LED_ON, update.led_enabled);
prefs.putUChar(NVS_KEY_KEYPAD_ON, update.keypad_enabled);
prefs.putUChar(NVS_KEY_TFT_ON, update.tft_enabled);
prefs.putString(NVS_KEY_TFT_DRIVER, update.tft_driver);
prefs.putString(NVS_KEY_MOONRAKER_URL, update.moonraker_url);
prefs.putBool(NVS_KEY_PRUSALINK_ON, update.prusalink_on != 0);
prefs.putString(NVS_KEY_PRUSALINK_URL, update.prusalink_url);
Expand Down
3 changes: 3 additions & 0 deletions src/ConfigurationManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ struct ConfigUpdate {
uint8_t led_enabled;
uint8_t keypad_enabled;
uint8_t tft_enabled;
char tft_driver[8]; // "st7789" or "gc9a01"
// Klipper / Moonraker
char moonraker_url[128];
// PrusaLink integration
Expand Down Expand Up @@ -79,6 +80,7 @@ class ConfigurationManager {
bool isLedEnabled() const;
bool isKeypadEnabled() const;
bool isTftEnabled() const;
const char* getTftDriver() const;

// Web config support
void getCurrentConfig(ConfigUpdate& out) const;
Expand Down Expand Up @@ -127,6 +129,7 @@ class ConfigurationManager {
bool _ledEnabled = false;
bool _keypadEnabled = false;
bool _tftEnabled = false;
char _tftDriver[8] = "st7789";

bool _initialized = false;
};
Expand Down
70 changes: 46 additions & 24 deletions src/TFTConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,32 @@
#include "BoardPins.h"

// ---------------------------------------------------------------------------
// LovyanGFX display config — one class per board, selected at compile time.
// LovyanGFX display config — runtime panel selection (ST7789 or GC9A01).
//
// Tested target: 240x240 ST7789 (common cheap round/square TFT).
// To use a different driver (ILI9341, GC9A01, etc.) change the _panel_instance
// type and adjust _width/_height below. Everything else stays the same.
// Both panels are 240x240 SPI. Same pins, same bus, different driver IC.
// The panel type is selected at construction via a string parameter
// read from NVS ("st7789" or "gc9a01").
//
// SPI bus used:
// SPI bus:
// WROOM — VSPI (bus 3): pins 22/23 freed from LCD when TFT replaces LCD
// S3-Zero — SPI2 (FSPI): pins 13/15/16 (PN5180 is on separate SPI instance)
// S3-Zero — SPI2 (FSPI): side header pins
// ---------------------------------------------------------------------------

enum class TFTDriver : uint8_t {
ST7789 = 0,
GC9A01 = 1
};

#if defined(BOARD_ESP32_S3)

class LGFX : public lgfx::LGFX_Device {
lgfx::Panel_ST7789 _panel_instance;
lgfx::Panel_ST7789 _panel_st7789;
lgfx::Panel_GC9A01 _panel_gc9a01;
lgfx::Bus_SPI _bus_instance;
lgfx::Light_PWM _light_instance;

public:
LGFX() {
LGFX(TFTDriver driver = TFTDriver::ST7789) {
// SPI bus
{
auto cfg = _bus_instance.config();
Expand All @@ -33,15 +39,23 @@ class LGFX : public lgfx::LGFX_Device {
cfg.freq_read = 16000000;
cfg.pin_sclk = PIN_TFT_SCLK;
cfg.pin_mosi = PIN_TFT_MOSI;
cfg.pin_miso = PIN_TFT_MISO; // -1 if not connected
cfg.pin_miso = PIN_TFT_MISO;
cfg.pin_dc = PIN_TFT_DC;
_bus_instance.config(cfg);
_panel_instance.setBus(&_bus_instance);
}

// Panel
// Select panel and configure
lgfx::Panel_Device* panel = nullptr;
if (driver == TFTDriver::GC9A01) {
panel = &_panel_gc9a01;
} else {
panel = &_panel_st7789;
}

panel->setBus(&_bus_instance);

{
auto cfg = _panel_instance.config();
auto cfg = panel->config();
cfg.pin_cs = PIN_TFT_CS;
cfg.pin_rst = PIN_TFT_RST;
cfg.pin_busy = -1;
Expand All @@ -55,11 +69,11 @@ class LGFX : public lgfx::LGFX_Device {
cfg.dummy_read_pixel = 8;
cfg.dummy_read_bits = 1;
cfg.readable = false;
cfg.invert = true; // ST7789 typically needs invert=true
cfg.invert = true;
cfg.rgb_order = false;
cfg.dlen_16bit = false;
cfg.bus_shared = false;
_panel_instance.config(cfg);
panel->config(cfg);
}

// Backlight
Expand All @@ -70,22 +84,23 @@ class LGFX : public lgfx::LGFX_Device {
cfg.freq = 44100;
cfg.pwm_channel = 7;
_light_instance.config(cfg);
_panel_instance.setLight(&_light_instance);
panel->setLight(&_light_instance);
}

setPanel(&_panel_instance);
setPanel(panel);
}
};

#else // WROOM

class LGFX : public lgfx::LGFX_Device {
lgfx::Panel_ST7789 _panel_instance;
lgfx::Panel_ST7789 _panel_st7789;
lgfx::Panel_GC9A01 _panel_gc9a01;
lgfx::Bus_SPI _bus_instance;
lgfx::Light_PWM _light_instance;

public:
LGFX() {
LGFX(TFTDriver driver = TFTDriver::ST7789) {
// SPI bus — VSPI. PN5180 uses HSPI via dedicated SPIClass instance.
{
auto cfg = _bus_instance.config();
Expand All @@ -98,12 +113,19 @@ class LGFX : public lgfx::LGFX_Device {
cfg.pin_miso = PIN_TFT_MISO;
cfg.pin_dc = PIN_TFT_DC;
_bus_instance.config(cfg);
_panel_instance.setBus(&_bus_instance);
}

// Panel
lgfx::Panel_Device* panel = nullptr;
if (driver == TFTDriver::GC9A01) {
panel = &_panel_gc9a01;
} else {
panel = &_panel_st7789;
}

panel->setBus(&_bus_instance);

{
auto cfg = _panel_instance.config();
auto cfg = panel->config();
cfg.pin_cs = PIN_TFT_CS;
cfg.pin_rst = PIN_TFT_RST;
cfg.pin_busy = -1;
Expand All @@ -121,7 +143,7 @@ class LGFX : public lgfx::LGFX_Device {
cfg.rgb_order = false;
cfg.dlen_16bit = false;
cfg.bus_shared = false;
_panel_instance.config(cfg);
panel->config(cfg);
}

// Backlight
Expand All @@ -132,10 +154,10 @@ class LGFX : public lgfx::LGFX_Device {
cfg.freq = 44100;
cfg.pwm_channel = 7;
_light_instance.config(cfg);
_panel_instance.setLight(&_light_instance);
panel->setLight(&_light_instance);
}

setPanel(&_panel_instance);
setPanel(panel);
}
};

Expand Down
12 changes: 7 additions & 5 deletions src/TFTManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ static const uint32_t COLOR_ACCENT = 0x4FC3F7;
// ---------------------------------------------------------------------------
// Constructor
// ---------------------------------------------------------------------------
TFTManager::TFTManager()
: _sprite(&_tft),
TFTManager::TFTManager(TFTDriver driver)
: _tft(driver),
_sprite(&_tft),
_messageQueue(nullptr),
_taskHandle(nullptr),
_screenTimeoutMs(DEFAULT_SCREEN_TIMEOUT_MS),
Expand All @@ -41,7 +42,8 @@ TFTManager::TFTManager()
_breathDirection(-1),
_lastBreathMs(0),
_isBreathing(false),
_breathColor(0xFFFFFF) {}
_breathColor(0xFFFFFF),
_driver(driver) {}

// ---------------------------------------------------------------------------
// begin / startTask
Expand Down Expand Up @@ -350,8 +352,8 @@ void TFTManager::renderSpoolScanned(const DisplaySpoolData& spool) {
// "SpoolSense" in header
_sprite.setTextColor(COLOR_ACCENT);
_sprite.setTextSize(1);
_sprite.setTextDatum(ML_DATUM);
_sprite.drawString("SpoolSense", 30, 14);
_sprite.setTextDatum(MC_DATUM);
_sprite.drawString("SpoolSense", _tft.width() / 2, 14);

// ---- Spool graphic ----
uint32_t fillColor = hexToRgb(spool.colorHex);
Expand Down
5 changes: 3 additions & 2 deletions src/TFTManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ struct TFTMessage {
// ---------------------------------------------------------------------------
class TFTManager : public DisplayI {
public:
TFTManager();
TFTManager(TFTDriver driver = TFTDriver::ST7789);

void begin();
void startTask();
Expand Down Expand Up @@ -96,7 +96,8 @@ class TFTManager : public DisplayI {
uint32_t dimColor(uint32_t color, uint8_t brightness); // for low-spool breathing

LGFX _tft;
LGFX_Sprite _sprite; // full-screen sprite for flicker-free rendering
LGFX_Sprite _sprite;
TFTDriver _driver;

QueueHandle_t _messageQueue;
TaskHandle_t _taskHandle;
Expand Down
2 changes: 2 additions & 0 deletions src/WebServerManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,7 @@ void WebServerManager::handleApiGetConfig() {
doc["nfc_reader"] = cfg.nfc_reader;
doc["hostname"] = cfg.hostname;
doc["tft_enabled"] = cfg.tft_enabled;
doc["tft_driver"] = cfg.tft_driver;
doc["ap_mode"] = _apMode;
if (_apMode) {
extern char g_apSSID[];
Expand Down Expand Up @@ -749,6 +750,7 @@ void WebServerManager::handleApiPostConfig() {
update.led_enabled = doc["led_enabled"] | (uint8_t)0;
update.keypad_enabled = doc["keypad_enabled"] | (uint8_t)0;
update.tft_enabled = doc["tft_enabled"] | (uint8_t)0;
strncpy(update.tft_driver, doc["tft_driver"] | "st7789", sizeof(update.tft_driver) - 1);
// TFT and LCD share GPIO 22/23 on WROOM — auto-disable LCD when TFT enabled
if (update.tft_enabled && update.lcd_enabled) {
update.lcd_enabled = 0;
Expand Down
Loading