From b4afa6d3f77937f5f18f401016d3065a5b8e175f Mon Sep 17 00:00:00 2001 From: torlando-tech Date: Wed, 4 Mar 2026 09:29:47 -0500 Subject: [PATCH] Fix SD card SPI init: use FSPI before Display claims HSPI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SD card was unresponsive (MISO stuck 0xFF) because Display's HSPI peripheral had already claimed the GPIO pins via the matrix, preventing FSPI from routing MISO. Fix by initializing SD card BEFORE Display, using the global SPI (FSPI) instance — matching LilyGo's reference code. - Move SD card init before display init in boot sequence - Use global SPI (FSPI) instead of Display's SPIClass(HSPI) - Lower SPI frequency to 800kHz matching LilyGo example - Drive all CS lines (display, LoRa, SD) high before SD init - Add MISO=38 to Display's SPI.begin for post-init bus sharing - Add Display::get_spi() accessor for future shared use Co-Authored-By: Claude Opus 4.6 --- lib/tdeck_ui/Hardware/TDeck/Display.cpp | 4 +++- lib/tdeck_ui/Hardware/TDeck/Display.h | 3 +++ lib/tdeck_ui/Hardware/TDeck/SDAccess.cpp | 17 +++++++------- lib/tdeck_ui/Hardware/TDeck/SDAccess.h | 9 ++++--- src/main.cpp | 30 ++++++++++++++---------- 5 files changed, 36 insertions(+), 27 deletions(-) diff --git a/lib/tdeck_ui/Hardware/TDeck/Display.cpp b/lib/tdeck_ui/Hardware/TDeck/Display.cpp index c338b5e5..9091ea01 100644 --- a/lib/tdeck_ui/Hardware/TDeck/Display.cpp +++ b/lib/tdeck_ui/Hardware/TDeck/Display.cpp @@ -88,7 +88,9 @@ bool Display::init_hardware_only() { // Initialize SPI _spi = new SPIClass(HSPI); - _spi->begin(Pin::DISPLAY_SCK, -1, Pin::DISPLAY_MOSI, Pin::DISPLAY_CS); + // Include MISO (pin 38) even though display is write-only — SD card and + // LoRa share this SPI instance and need MISO for read operations. + _spi->begin(Pin::DISPLAY_SCK, Radio::SPI_MISO, Pin::DISPLAY_MOSI, Pin::DISPLAY_CS); // Configure CS and DC pins pinMode(Pin::DISPLAY_CS, OUTPUT); diff --git a/lib/tdeck_ui/Hardware/TDeck/Display.h b/lib/tdeck_ui/Hardware/TDeck/Display.h index 8aeafa45..9dd0265e 100644 --- a/lib/tdeck_ui/Hardware/TDeck/Display.h +++ b/lib/tdeck_ui/Hardware/TDeck/Display.h @@ -36,6 +36,9 @@ public: */ static void set_spi_mutex(SemaphoreHandle_t mutex); + /** Get the SPI instance (for sharing with SD card) */ + static SPIClass* get_spi() { return _spi; } + /** * Initialize display and LVGL * @return true if initialization successful diff --git a/lib/tdeck_ui/Hardware/TDeck/SDAccess.cpp b/lib/tdeck_ui/Hardware/TDeck/SDAccess.cpp index bc00c93c..d3c6a64d 100644 --- a/lib/tdeck_ui/Hardware/TDeck/SDAccess.cpp +++ b/lib/tdeck_ui/Hardware/TDeck/SDAccess.cpp @@ -13,7 +13,6 @@ namespace Hardware { namespace TDeck { SemaphoreHandle_t SDAccess::_spi_mutex = nullptr; -SPIClass* SDAccess::_sd_spi = nullptr; bool SDAccess::_ready = false; bool SDAccess::init(SemaphoreHandle_t mutex) { @@ -25,22 +24,24 @@ bool SDAccess::init(SemaphoreHandle_t mutex) { _spi_mutex = mutex; - // Drive SD CS high before init to avoid bus contention + // Drive ALL SPI CS lines high to deselect other devices on the bus. + pinMode(Pin::DISPLAY_CS, OUTPUT); + digitalWrite(Pin::DISPLAY_CS, HIGH); + pinMode(Radio::LORA_CS, OUTPUT); + digitalWrite(Radio::LORA_CS, HIGH); pinMode(SDCard::CS, OUTPUT); digitalWrite(SDCard::CS, HIGH); - // Acquire mutex for SD.begin() since it reconfigures SPI + // Acquire mutex for SD.begin() if (xSemaphoreTake(_spi_mutex, pdMS_TO_TICKS(2000)) != pdTRUE) { Serial.println("[SDAccess] Failed to acquire SPI mutex for init"); return false; } - // Create SPIClass on HSPI — attaches to same hardware peripheral - // as display and LoRa, but SD.begin() needs its own instance - _sd_spi = new SPIClass(HSPI); - _sd_spi->begin(Pin::DISPLAY_SCK, Radio::SPI_MISO, Pin::DISPLAY_MOSI, SDCard::CS); + // SD card init: use global SPI (FSPI) before Display claims HSPI + SPI.begin(Pin::DISPLAY_SCK, Radio::SPI_MISO, Pin::DISPLAY_MOSI); - bool ok = SD.begin(SDCard::CS, *_sd_spi, SD_SPI_FREQ); + bool ok = SD.begin(SDCard::CS, SPI, SD_SPI_FREQ); xSemaphoreGive(_spi_mutex); diff --git a/lib/tdeck_ui/Hardware/TDeck/SDAccess.h b/lib/tdeck_ui/Hardware/TDeck/SDAccess.h index 1852c1bf..d9dd97dd 100644 --- a/lib/tdeck_ui/Hardware/TDeck/SDAccess.h +++ b/lib/tdeck_ui/Hardware/TDeck/SDAccess.h @@ -29,9 +29,9 @@ namespace TDeck { class SDAccess { public: /** - * Initialize SD card on the shared HSPI bus. - * Creates its own SPIClass(HSPI) attached to the same hardware - * peripheral, then calls SD.begin() with that instance. + * Initialize SD card on the shared SPI bus. + * Must be called BEFORE Display::init() to avoid SPI peripheral conflicts. + * Uses the global SPI (FSPI) instance matching LilyGo's reference code. * * @param mutex Shared SPI bus mutex (created in main.cpp) * @return true if SD card mounted successfully @@ -66,10 +66,9 @@ public: private: static SemaphoreHandle_t _spi_mutex; - static SPIClass* _sd_spi; static bool _ready; - static constexpr uint32_t SD_SPI_FREQ = 20000000; // 20MHz for SD card + static constexpr uint32_t SD_SPI_FREQ = 800000; // 800kHz (matches LilyGo example) }; } // namespace TDeck diff --git a/src/main.cpp b/src/main.cpp index 65120523..bbdfa67f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1277,12 +1277,24 @@ void setup() { } } - // Create shared SPI bus mutex (display, LoRa, SD card all share HSPI) + // Create shared SPI bus mutex (display, LoRa, SD card all share SPI) SemaphoreHandle_t spi_mutex = xSemaphoreCreateMutex(); if (!spi_mutex) { ERROR("Failed to create SPI bus mutex!"); } + // Try SD card FIRST, before display claims pins — matches LilyGo init order. + // Uses global SPI (FSPI) with no competing peripheral on the bus. + BOOT_PROFILE_START("sd_card"); + if (spi_mutex) { + if (Hardware::TDeck::SDAccess::init(spi_mutex)) { + INFO("SD card initialized on shared SPI bus"); + } else { + INFO("SD card not available (no card inserted?)"); + } + } + BOOT_PROFILE_END("sd_card"); + // Set SPI mutex on Display before init (null-safe if mutex creation failed) Hardware::TDeck::Display::set_spi_mutex(spi_mutex); @@ -1301,20 +1313,12 @@ void setup() { setup_reticulum(); BOOT_PROFILE_END("reticulum"); - // Initialize SD card on shared SPI bus (after display + LoRa) - BOOT_PROFILE_START("sd_card"); - if (spi_mutex) { - if (Hardware::TDeck::SDAccess::init(spi_mutex)) { - INFO("SD card initialized on shared SPI bus"); - // Initialize SD logging now that SDAccess is ready - if (Hardware::TDeck::SDLogger::init()) { - INFO("SD card logging active"); - } - } else { - INFO("SD card not available (no card inserted?)"); + // Initialize SD logging (SDAccess already initialized above) + if (Hardware::TDeck::SDAccess::is_ready()) { + if (Hardware::TDeck::SDLogger::init()) { + INFO("SD card logging active"); } } - BOOT_PROFILE_END("sd_card"); // Initialize LXMF BOOT_PROFILE_START("lxmf");