Conflict of TFT display and SD card SPI communication

This code sensor data display and store to SD card. below code is working. but there is problem. after uncomment the if condition inside the go_to_menu_mode() function. It shows SD card attached but Error opening file in Serial monitor. Is it possible? What is the reason for that?

#include <SD.h>
#include <SPI.h>
#include <TFT_eSPI.h>
#include <HardwareSerial.h>
#include "logo.h"

#define RE 2
#define DE 2
#define RO 13  // Modbus -> RX on GPIO13
#define DI 15  // Modbus -> TX on GPIO15
#define EN 25
#define PWR_EN 12
#define CS_SD 5            // Chip select pin for SD card
#define CS_TFT 4           // Chip select pin for display
#define BTN_1 32           // S2
#define BACK_BTN 33        // S3
#define DWN_BTN 35         // S4
#define OK_BTN 34          // S5
#define WIFI_BTN 39        // S6
#define PWR_AND_UP_BTN 36  // S7

#define powerOnTimeOut 1000
#define powerOffTimeOut 2000

bool btnPressed = 0;
unsigned long currentMillis = 0;
int btnDebounceDelay = 200;

#define TFT_GRAY 0x5AEB  // Define background colour

TFT_eSPI tft = TFT_eSPI();

HardwareSerial modSerial(2);  // Use UART2 which allows you to set custom pins to serial

// Define mod addresses
const byte nitro[] = { 0x01, 0x03, 0x00, 0x1e, 0x00, 0x01, 0xe4, 0x0c };
const byte phos[] = { 0x01, 0x03, 0x00, 0x1f, 0x00, 0x01, 0xb5, 0xcc };
const byte pota[] = { 0x01, 0x03, 0x00, 0x20, 0x00, 0x01, 0x85, 0xc0 };
const byte soil_ph[] = { 0x01, 0x03, 0x00, 0x06, 0x00, 0x01, 0x64, 0x0b };
const byte soil_moist[] = { 0x01, 0x03, 0x00, 0x12, 0x00, 0x01, 0x24, 0x0f };
const byte elec_conduct[] = { 0x01, 0x03, 0x00, 0x15, 0x00, 0x01, 0x95, 0xce };
const byte soil_temp[] = { 0x01, 0x03, 0x00, 0x13, 0x00, 0x01, 0x75, 0xcf };

byte values[11];

void setup() {
  pinMode(RE, OUTPUT);
  pinMode(DE, OUTPUT);
  pinMode(EN, OUTPUT);
  pinMode(PWR_AND_UP_BTN, INPUT);
  pinMode(BTN_1, INPUT);
  pinMode(BACK_BTN, INPUT);
  pinMode(DWN_BTN, INPUT);
  pinMode(OK_BTN, INPUT);
  pinMode(WIFI_BTN, INPUT);
  pinMode(PWR_EN, OUTPUT);
  pinMode(CS_TFT, OUTPUT);
  pinMode(CS_SD, OUTPUT);

  digitalWrite(EN, HIGH);
  digitalWrite(CS_TFT, HIGH);
  digitalWrite(CS_SD, HIGH);

  delay(powerOnTimeOut);
  digitalWrite(PWR_EN, HIGH);
  while (digitalRead(PWR_AND_UP_BTN))
    ;

  Serial.begin(115200);
  modSerial.begin(9600, SERIAL_8N1, RO, DI);

  SPI.begin();

  // Initialize TFT
  digitalWrite(CS_TFT, LOW);
  delay(100);
  tft.init();
  tft.setRotation(3);

  tft.fillScreen(TFT_WHITE);
  tft.setSwapBytes(true);
  tft.pushImage(60, 0, 200, 200, logo);
  delay(200);

  tft.setCursor(50, 220, 4);
  tft.setTextColor(TFT_YELLOW);
  tft.setFreeFont(&FreeSansBold12pt7b);
  auto_typing_text("ELZIAN AGRO EYE");
  tft.setFreeFont(NULL);
  delay(1000);
  digitalWrite(CS_TFT, HIGH);
  delay(100);

  // Initialize SD caed
  digitalWrite(CS_SD, LOW);
  delay(100);
  while (!SD.begin(CS_SD)) {
    Serial.println("SD card mount failed, retrying...");
    delay(1000);
  }
  digitalWrite(CS_SD, HIGH);
  delay(100);
}

void loop() {
  go_to_menu_modes();
}

// Handle menu
void go_to_menu_modes() {
  String menuOptions[] = { "Measuring", "Records", "Upload to Internet", "Wifi Settings" };  // Can add more options
  int currentMode = 0;
  int maxModes = sizeof(menuOptions) / sizeof(menuOptions[0]);

  while (true) {
    digitalWrite(CS_TFT, LOW);
    delay(100);

    clear_screen();
    print_header();

    tft.setCursor(130, 40, 4);
    tft.setTextColor(TFT_GREEN);
    tft.setFreeFont(&FreeSansBold12pt7b);
    tft.println("Menu");
    tft.setFreeFont(NULL);

    tft.setTextColor(TFT_BLUE);
    tft.setTextFont(4);

    // for (int i = 0; i < maxModes; i++) {
    //   if (i == currentMode) {
    //     tft.setTextColor(TFT_YELLOW);  // Highlight current mode
    //   } else {
    //     tft.setTextColor(TFT_BLUE);
    //   }
    //   tft.println("   " + String(i + 1) + ". " + menuOptions[i]);
    // }

    digitalWrite(CS_TFT, HIGH);
    delay(100);

    digitalWrite(CS_SD, LOW);
    delay(100);

    uint8_t cardType = SD.cardType();
    if (cardType == CARD_NONE) {
      Serial.println("No SD card attached");
    } else {
      Serial.println("SD card attached");
      if (SD.exists("/data.txt")) {
        Serial.println("File exists");
      } else {
        Serial.println("File does not exist, creating file...");
      }
      File file = SD.open("/data.txt", FILE_WRITE);
      if (file) {
        file.println("kavinda");
        file.close();
        Serial.println("data written to file");
      } else {
        Serial.println("Error opening file for writing");
      }
    }

    digitalWrite(CS_SD, HIGH);
    delay(100);

    delay(2000);
  }
}

Is the SD card full? Faulty? Have you checked it in PC/laptop?

Is this the full code? This seems like a problem where you are writing outside the bounds of an array somewhere else in the code, since there is no obvious problem with the if statement that is commented out.

The respective libraries take care of the CS_SD and CS_TFT lines. Only need to be set HIGH in setup.

Try the filename without the leading slash.

Yes, There is no any data in SD card.

What type SD card is it? How big? Format?

It is not full code. I will send it by another sample code. In here also same issue. after uncomment the if condition, It show "Error opening file".

#include <SD.h>
#include <SPI.h>
#include <TFT_eSPI.h>

#define CS_SD 5
#define CS_TFT 4

TFT_eSPI tft = TFT_eSPI();

void setup() {
  pinMode(CS_TFT, OUTPUT);
  pinMode(CS_SD, OUTPUT);

  digitalWrite(CS_TFT, HIGH);
  digitalWrite(CS_SD, HIGH);

  Serial.begin(115200);

  SPI.begin();

  // Initialize TFT
  digitalWrite(CS_TFT, LOW);
  delay(50);
  tft.init();
  tft.setRotation(3);
  digitalWrite(CS_TFT, HIGH);
  delay(50);

  // Initialize SD card
  digitalWrite(CS_SD, LOW);
  delay(50);
  while (!SD.begin(CS_SD)) {
    Serial.println("SD card mount failed, retrying...");
    delay(1000);  // Wait for 1 second before retrying
  }
  digitalWrite(CS_SD, HIGH);
  delay(50);
}

void loop() {
  digitalWrite(CS_TFT, LOW);
  delay(50);
  tft.fillScreen(TFT_GREEN);
  delay(1000);

  // if (true) {
  //   tft.setCursor(130, 40, 4);
  //   tft.setTextColor(TFT_GREEN);
  //   tft.println("Menu");
  // }

  tft.fillScreen(TFT_BLUE);
  digitalWrite(CS_TFT, HIGH);
  delay(50);

  digitalWrite(CS_SD, LOW);
  delay(50);

  uint8_t cardType = SD.cardType();
  if (cardType == CARD_NONE) {
    Serial.println("No SD card attached");
  } else {
    Serial.println("SD card attached");
    File file = SD.open("/data.txt", FILE_APPEND);
    if (file) {
      file.print("kavinda");
      file.print(", ");
      file.println("shammi");
      file.close();
      Serial.println("data written to file");
    } else {
      Serial.println("Error opening file for writing");
    }
  }

  digitalWrite(CS_SD, HIGH);
  delay(50);

  delay(2000);
}

It is empty card

OK, empty.
Size? 32 GB or smaller?
Format? FAT16 or FAT32?

This code working properly. But there is a problem. After uncomment the if condition in line 46, the serial monitor show "Error opening file". What is this happen.

#include <SD.h>
#include <SPI.h>
#include <TFT_eSPI.h>

#define CS_SD 5
#define CS_TFT 4

TFT_eSPI tft = TFT_eSPI();

void setup() {
  pinMode(CS_TFT, OUTPUT);
  pinMode(CS_SD, OUTPUT);

  digitalWrite(CS_TFT, HIGH);
  digitalWrite(CS_SD, HIGH);

  Serial.begin(115200);

  SPI.begin();

  // Initialize TFT
  digitalWrite(CS_TFT, LOW);
  delay(50);
  tft.init();
  tft.setRotation(3);
  digitalWrite(CS_TFT, HIGH);
  delay(50);

  // Initialize SD card
  digitalWrite(CS_SD, LOW);
  delay(50);
  while (!SD.begin(CS_SD)) {
    Serial.println("SD card mount failed, retrying...");
    delay(1000);  // Wait for 1 second before retrying
  }
  digitalWrite(CS_SD, HIGH);
  delay(50);
}

void loop() {
  digitalWrite(CS_TFT, LOW);
  delay(50);
  tft.fillScreen(TFT_GREEN);
  delay(1000);

  // if (true) {
  //   tft.setCursor(130, 40, 4);
  //   tft.setTextColor(TFT_GREEN);
  //   tft.println("Menu");
  // }

  tft.fillScreen(TFT_BLUE);
  digitalWrite(CS_TFT, HIGH);
  delay(50);

  digitalWrite(CS_SD, LOW);
  delay(50);

  uint8_t cardType = SD.cardType();
  if (cardType == CARD_NONE) {
    Serial.println("No SD card attached");
  } else {
    Serial.println("SD card attached");
    File file = SD.open("/data.txt", FILE_APPEND);
    if (file) {
      file.print("kavinda");
      file.print(", ");
      file.println("shammi");
      file.close();
      Serial.println("data written to file");
    } else {
      Serial.println("Error opening file for writing");
    }
  }

  digitalWrite(CS_SD, HIGH);
  delay(50);

  delay(2000);
}

the SD library manages the Chip Select (CS) pin for the SD card ➜ try not messing around yourself with the pin state. (you don't even need to set the CS pin as an OUTPUT).

If there is more than one SPI device on the bus. I suggest at least setting all SPI CS pins as INPUT_PULLUP. I use OUTPUT and HIGH.

I have merged your cross-posts @kavindaelzian.

Cross-posting is against the Arduino forum rules. The reason is that duplicate posts can waste the time of the people trying to help. Someone might spend a lot of time investigating and writing a detailed answer on one topic, without knowing that someone else already did the same in the other topic.

Repeated cross-posting can result in a suspension from the forum.

In the future, please only create one topic for each distinct subject matter. This is basic forum etiquette, as explained in the "How to get the best out of this forum" guide. It contains a lot of other useful information. Please read it.

Thanks in advance for your cooperation.

Can you identify if there is a particular line within the if statement that is causing the problem?

In the User_Setup.h file of the TFT_eSPI library, is the line #define USE_HSPI_PORT commented out? There seem to be a lot of issues about SD card usage involving that on the TFT_eSPI github page.

Forum Post Draft — Adafruit Forums

Title: SOLVED: ESP32-S3 Reverse TFT Feather + Adalogger FeatherWing — SD card writes fail silently (hardware fix)

Category: Adafruit Products / Feather


The Problem

If you stack an Adalogger FeatherWing (RTC + SD) on an ESP32-S3 Reverse TFT Feather (or the w.FL Antenna variant), SD card writes fail silently on Arduino core 3.x. Reads may work, but FILE_WRITE and FILE_APPEND produce zero-byte files or fail entirely. No error is reported — SD.begin() returns true, SD.open() returns a valid File object, but nothing is written.

The TFT display works fine. Sensors work fine. Everything looks normal except your data isn't being saved.

Root Cause

The ESP32-S3 Reverse TFT has its ST7789 TFT display hardwired internally to GPIO 35/36/37 (MOSI/SCK/MISO). These same GPIOs are exposed on the Feather stacking headers. When you stack the Adalogger, its SD card connects to the same pins through the headers.

On ESP32 Arduino core 2.0.x, both devices could share the SPI bus with CS arbitration. On core 3.x, something changed in the SPI peripheral GPIO routing that breaks this. The TFT driver takes ownership of the SPI GPIO routing in a way that prevents SD writes, even with proper CS management.

What Does NOT Work (save yourself days of debugging)

I tried all of these. None of them fix the problem:

  • Shared SPI with CS arbitration (deassert TFT_CS before SD access) — FAILS
  • Separate HSPI peripheral on the same physical pins (GPIO 35/36/37) — FAILS
  • sdReinit/sdRelease cycling (tear down and rebuild HSPI before/after each SD access) — SD works but TFT display hangs
  • Init order changes (SD.begin before tft.init, or vice versa) — FAILS
  • Lowering SPI clock speed — FAILS

The problem is hardware-level GPIO routing, not software timing or CS management.

The Fix: Hardware Mod (10 minutes, fully reversible)

Give the SD card its own SPI pins that the TFT doesn't touch. The ESP32-S3 GPIO matrix allows HSPI to be assigned to any available pins.

What You Need

  • 3 short jumper wires (any thin insulated wire)
  • Soldering iron
  • Pliers (to pull/bend header pins)

Steps

  1. Unstack the Adalogger from the ESP32-S3.

  2. Disconnect three SPI pins. On the ESP32-S3 board, bend or pull the male header pins for MO, MI, and SCK so they no longer enter the Adalogger's female sockets when stacked. These pins must NOT make contact. (This is reversible — straighten them back anytime.)

  3. Solder three jumper wires on the Adalogger's top nubs:

Adalogger MO  top nub --wire--> Adalogger A4 top nub  (routes to ESP32 GPIO 14)
Adalogger MI  top nub --wire--> Adalogger A5 top nub  (routes to ESP32 GPIO 8)
Adalogger SCK top nub --wire--> Adalogger A3 top nub  (routes to ESP32 GPIO 15)
  1. Restack the boards. CS (GPIO 10), RTC (I2C), power, and GND all pass through the stacking headers normally.

Important: Pin Mapping Warning

On the ESP32-S3 Reverse TFT Feather, the Arduino analog pin labels do NOT match GPIO numbers:

Board Label Actual GPIO
A0 GPIO 18
A1 GPIO 17
A2 GPIO 16
A3 GPIO 15
A4 GPIO 14
A5 GPIO 8

This is defined in pins_arduino.h for the adafruit_feather_esp32s3_reversetft variant. Always use GPIO numbers in code, never the A0-A5 aliases.

Working Code

#include <SPI.h>
#include <SD.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>

// SD card on HSPI — separate bus from TFT
#define SD_CS    10
#define SD_SCK   15   // A3 = GPIO 15
#define SD_MOSI  14   // A4 = GPIO 14
#define SD_MISO  8    // A5 = GPIO 8

SPIClass sdSPI(HSPI);
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

void setup() {
  Serial.begin(115200);
  delay(2000);

  // Power up I2C + TFT
  pinMode(TFT_I2C_POWER, OUTPUT);
  digitalWrite(TFT_I2C_POWER, HIGH);
  delay(100);
  pinMode(TFT_BACKLITE, OUTPUT);
  digitalWrite(TFT_BACKLITE, HIGH);

  // TFT on default FSPI (internal GPIO 35/36/37)
  tft.init(135, 240);
  tft.setRotation(3);
  tft.fillScreen(ST77XX_BLACK);
  tft.setTextSize(2);
  tft.setTextColor(ST77XX_WHITE);
  tft.setCursor(0, 10);
  tft.println("SD+TFT Test");
  Serial.println("TFT init OK");

  // SD on HSPI (rewired GPIO 14/8/15) — completely separate bus
  sdSPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
  if (!SD.begin(SD_CS, sdSPI, 4000000)) {
    Serial.println("SD FAIL");
    tft.setTextColor(ST77XX_RED);
    tft.println("SD FAIL");
    while(1);
  }
  Serial.println("SD OK");

  // Write test
  File f = SD.open("/test.txt", FILE_WRITE);   // NOTE: "/" prefix required!
  if (f) { f.println("Hello from SD!"); f.close(); Serial.println("WRITE OK"); }

  // Append test
  File f2 = SD.open("/test.txt", FILE_APPEND);
  if (f2) { f2.println("Append works too"); f2.close(); Serial.println("APPEND OK"); }

  // TFT update THEN SD write — the sequence that always failed before
  tft.fillScreen(ST77XX_BLACK);
  tft.setCursor(0, 10);
  tft.setTextColor(ST77XX_YELLOW);
  tft.println("Post-TFT write...");

  File f3 = SD.open("/test.txt", FILE_APPEND);
  if (f3) { f3.println("After TFT update"); f3.close(); Serial.println("POST-TFT WRITE OK"); }

  // Read back
  File f4 = SD.open("/test.txt", FILE_READ);
  if (f4) { while (f4.available()) Serial.write(f4.read()); f4.close(); }

  SD.remove("/test.txt");

  tft.fillScreen(ST77XX_BLACK);
  tft.setCursor(0, 10);
  tft.setTextColor(ST77XX_GREEN);
  tft.setTextSize(3);
  tft.println("ALL OK!");
}

void loop() {
  // TFT keeps updating — proves both buses work simultaneously
  static unsigned long last = 0;
  if (millis() - last > 1000) {
    last = millis();
    tft.setCursor(0, 60);
    tft.setTextSize(2);
    tft.setTextColor(ST77XX_CYAN, ST77XX_BLACK);
    tft.print("Up: "); tft.print(millis()/1000); tft.println("s  ");
  }
}

Additional ESP32 SD Gotchas

A few more things that bit me and aren't well documented:

  1. "/" prefix required for file creation. SD.open("test.txt", FILE_WRITE) silently fails. SD.open("/test.txt", FILE_WRITE) works. This is ESP32-specific — other Arduino platforms don't require it.

  2. FILE_WRITE truncates on ESP32. Unlike RP2040/AVR where FILE_WRITE appends, on ESP32 it truncates the file to zero and starts fresh. Use FILE_APPEND to add data to existing files.

  3. FILE_APPEND cannot create new files. If the file doesn't exist, FILE_APPEND fails silently. Create with FILE_WRITE first, then use FILE_APPEND for subsequent writes.

  4. Delete Arduino build cache after changing pin definitions. The IDE caches compiled headers. If you change #define values in a .h file, the old values may persist. Delete C:\Users\<you>\AppData\Local\Temp\arduino\sketches\ or use Sketch > Clean Build Folder.

Environment

  • Adafruit ESP32-S3 Reverse TFT Feather (w.FL Antenna variant, product #6303)
  • Adalogger FeatherWing (product #2922)
  • Arduino IDE 2.x
  • esp32 by Espressif board support package v3.3.7
  • Board selected: "Adafruit Feather ESP32-S3 Reverse TFT"

Hope this saves someone else a few days of debugging. Happy to answer questions.

Some SD cards are just not happy sharing the SPI bus.

On the ESP32S3 you can drive the SD card in MMC mode, that only needs 3 GPIOs.