By the time I have got this cleaned up I might have found a use for it !
Still another challenge for these powerhouse ESP32 microcontrollers.
On the back is a slot for SD card, and I’ll use this page to document the how and possibilities.
An amusing aside: The SD card slot is tightly slipped into the “underside” of the board. I might use pin sockets when I install these, since soldering the board into a project makes removal/ replacement of the SD card impossible!
As they say in the classics: “Read the docs”.
So here is my definitive IO reference for the these ESP32 S2 T8 boards:
General declarations:
/// Screen #define TFT_CS 34 #define TFT_RST 38 #define TFT_DC 37 #define TFT_MOSI 35 #define TFT_SCLK 36 /// SD Card #define HAS_SDCARD #define SD_CS_PIN 10 #define SD_MOSI_PIN 11 #define SD_SCK_PIN 12 #define SD_MISO_PIN 13
In void setup():
pinMode(33, OUTPUT); digitalWrite(33, HIGH); // Turn on Backlight LED pinMode(14, OUTPUT); digitalWrite(14, HIGH); // Turn on SD card for battery use
Combining Screen and SD Card access
This example shows both working together.
The functions are built from the common libraries for SD Card.
Refer to these for further file handling functions.
Here, we:
- Access the SDcard
- Delete a file
- Create a file, put text in it.
- Read from the card
- Display the text from file
- Display a directory listing
- Get some stats from SDcard

/*
Example using Screen and SD Card together
Note turn on GPIO 14 high, needed to support battery operation.
*/
//Screen
#define TFT_CS 34
#define TFT_RST 38
#define TFT_DC 37
#define TFT_MOSI 35
#define TFT_SCLK 36
//SD Card
#define HAS_SDCARD
#define SD_CS_PIN 10
#define SD_MOSI_PIN 11
#define SD_SCK_PIN 12 //CLK
#define SD_MISO_PIN 13
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include "FS.h"
#include "SD.h"
#include "SPI.h"
SPIClass spi = SPIClass(HSPI);
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
void listDir(fs::FS &fs, const char * dirname, uint8_t levels) {
Serial.printf("Listing directory: %s\n", dirname);
File root = fs.open(dirname);
if (!root) {
Serial.println("Failed to open directory");
return;
}
if (!root.isDirectory()) {
Serial.println("Not a directory");
return;
}
tft.setCursor(0, 60);
tft.setTextSize(2);
tft.setTextColor(ST77XX_YELLOW);
File file = root.openNextFile();
while (file) {
if (file.isDirectory()) {
Serial.print(" DIR : ");
Serial.println(file.name());
if (levels) {
listDir(fs, file.path(), levels - 1);
}
} else {
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
tft.print(String(file.name()) + " / ");
}
file = root.openNextFile();
}
}
void readFile(fs::FS &fs, const char * path) {
Serial.printf("Reading file: %s\n", path);
File file = fs.open(path);
if (!file) {
Serial.println("Failed to open file for reading");
return;
}
Serial.print("Read from file: ");
tft.setCursor(0, 30);
tft.setTextColor(ST77XX_GREEN);
while (file.available()) {
char readByte = file.read();
// Serial.write(file.read());
Serial.print(readByte);
tft.print(readByte);
}
file.close();
}
void writeFile(fs::FS &fs, const char * path, const char * message) {
Serial.printf("Writing file: %s\n", path);
File file = fs.open(path, FILE_WRITE);
if (!file) {
Serial.println("Failed to open file for writing");
return;
}
if (file.print(message)) {
Serial.println("File written");
} else {
Serial.println("Write failed");
}
file.close();
}
void deleteFile(fs::FS &fs, const char * path) {
Serial.printf("Deleting file: %s\n", path);
if (fs.remove(path)) {
Serial.println("File deleted");
} else {
Serial.println("Delete failed");
}
}
void setup() {
Serial.begin(115200);
pinMode(33, OUTPUT);
digitalWrite(33, HIGH); // Turn on LED
pinMode(14, OUTPUT);
digitalWrite(14, HIGH); // Turn on SD card for battery use
tft.init(135, 240);
tft.setRotation(3);
spi.begin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN);
tft.fillScreen(ST77XX_BLACK);
tft.setTextSize(3);
tft.setCursor(0, 0);
tft.setTextColor(ST77XX_WHITE);
tft.print("ESP32 SD card");
if (!SD.begin(SD_CS_PIN, spi, 80000000)) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize);
deleteFile(SD, "/hello.txt");
writeFile(SD, "/hello.txt", "This is cool");
listDir(SD, "/", 0);
readFile(SD, "/hello.txt");
Serial.printf("Total space: %lluMB\n" + SD.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n" + SD.usedBytes() / (1024 * 1024));
}
void loop() {
}
STOP PRESS: The “official” page for this microcontroller points out the need to set GPIO14 High to allow battery operation.
(Found that out the hard way!)
More to follow…
Great Work, Will you publish the schematics ?
Don’t need one! It’s all on the Board:
https://www.banggood.com/LILYGO-TTGO-T8-ESP32-S2-V1_1-ST7789-1_14-Inch-LCD-Display-WIFI-Wireless-Module-Type-c-Connector-TF-Card-Slot-Development-Board-p-1862731.html?cur_warehouse=CN
… Unfortunately they have been out of stock for a while. I hope that they return.
Technically the same result wiring up the 4 SPI connecters from an SDcard module to another ESP32 with screen.
I’ll be trying this with the more popular, but lower spec board that lacks SDcard.
https://www.banggood.com/LILYGO-TTGO-T-Display-ESP32-CH9102F-WiFi-bluetooth-Module-1_14-Inch-LCD-Development-Board-p-1869556.html?rmmds=detail-left-hotproducts&cur_warehouse=CN&ID=6309998&trace_id=60651643493933762
Good luck.
Hi, thanks for sharing.
I’m curious about the manufacturer design decision to split the SPI bus for the display and SD card. Maybe you have an idea why? I’m trying to design my custom board and looking for a reference, got this but I think it taking unnecessary IO on the 2nd SPI bus while it can just be paralleled with a separate chip select pin. What do you think?
I think there may be reasons why this board is falling out of favour!