Skip to main content

Section 03 Debugging TF card Interface

The ESP32-S3 has a built‑in SD/MMC host controller (SDHOST) that supports TF Memory, SDIO, MMC/eMMC, and CE‑ATA devices, and can operate in 1‑bit, 4‑bit, or 8‑bit bus modes.

On this product, the ESP32-S3-Touch-LCD-7, we have integrated a TF card slot that uses the SPI communication interface, which consumes fewer GPIO pins. In the following tutorial, we will learn how to connect and call the TF card, allowing users to quickly verify the TF card functionality.

ESP32-S3-Touch-LCD-7 TF demo 2

Hardware Design

In this product, we use the ESP32-S3 chip to control the TF card via the SPI interface. Pins IO11, IO12, and IO13 are defined as MOSI, SCK, and MISO respectively for connecting to the TF card. Additionally, pins IO8 and IO9 are defined as I2C SDA and SCL to communicate with the CH422G expansion chip, and the expanded EXIO4 pin serves as the TF card chip select (SDCS).

The following is a simplified circuit diagram of the above design. For a more detailed schematic, please refer to the schematic.

ESP32-S3-Touch-LCD-7 TF demo 1

Required Components

  • ESP32-S3-Touch-LCD-7 x1
  • USB cable Type‑A male to Type‑C male x1
  • TF card x1
    ESP32-S3-Touch-LCD-7 TF demo 13
tip

This experiment requires a TF card (not included in the kit, please purchase separately). Before use, insert the TF card into a card reader, connect it to a computer, and format the TF card to FAT32.

Hardware Connection

To upload the code to the ESP32-S3, connect the USB port of the ESP32-S3-Touch-LCD-7 to the computer using a Type‑C to Type‑A cable, and insert the formatted TF card into the TF card slot:

ESP32-S3-Touch-LCD-7 TF demo 3

Demo

Please download the example package from the following address: ESP32-S3-Touch-LCD-7 Demo (if already downloaded, simply open the corresponding folder). After extracting the example package, open the corresponding 03_SD_Test folder.

ESP32-S3-Touch-LCD-7 TF demo 4

Double-click to open 03_SD_Test.ino:

ESP32-S3-Touch-LCD-7 TF demo 5

Before uploading the program, select the board model "Waveshare ESP32-S3-Touch-LCD-7" and the corresponding port:

ESP32-S3-Touch-LCD-7 TF demo 6

Set the board parameters; under Flash Size, select 8MB (64Mb).

ESP32-S3-Touch-LCD-7 TF demo 7

Then click the Upload button in the upper left corner and wait for the upload to complete.

ESP32-S3-Touch-LCD-7 TF demo 8

Running Results

After the code is uploaded, open the Serial Monitor from the Tools menu. Restart the program and you will see the printed output. The code will sequentially create a file, read a file, edit a file, rename a file, and perform other operations. It will also output file transfer speed results and TF card capacity information.

ESP32-S3-Touch-LCD-7 TF demo 9

If you reconnect the TF card to the computer using a card reader, you will see two files: foo.txt and test.txt. Opening foo.txt reveals the text "Hello World", indicating that the above operations have been executed successfully.

ESP32-S3-Touch-LCD-7 TF demo 10

tip
  • If you encounter library‑related errors when downloading the program, check that the Arduino IDE sketchbook location correctly points to the ESP32-S3-Touch-LCD-7-Demo\Arduino directory.

  • After downloading the program using the USB port, if you see no output in the Serial Monitor, enable "USB CDC On Boot" in the Tools menu, then re‑download the program. You should then see output on the USB port.

    ESP32-S3-Touch-LCD-7 TF demo 12

Code Review

This example consists of the main test file SD_Test.ino and the library files waveshare_sd_card.cpp and waveshare_sd_card.h. Below we explain the key parts of each.

1. Pin Definitions and Function Declarations in waveshare_sd_card.h

#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include <esp_io_expander.hpp>

Includes the headers for the FS filesystem library, the TF library, the SPI library (all built into ESP32 Arduino), and the esp_io_expander library needed for the CH422G chip.

#define TP_RST 1 // Touch screen reset pin
#define LCD_BL 2 // LCD backlight pinout
#define LCD_RST 3 // LCD reset pin
#define SD_CS 4 // TF card select pin
#define USB_SEL 5 // USB select pin

Defines the hardware functions corresponding to CH422G expansion pins 1‑5. These five expansion pins control various peripherals such as the LCD, TF card, and USB.

info
  • When your main controller (e.g., ESP32, STM32) runs out of IOs, you can use an expander chip like the CH422G to obtain more input/output pins without using many pins. For example, if you need multiple chip‑select (CS) pins for several TF card modules, sensors, or a display, you can use the CH422G's IOs for "chip‑select control".
  • CH422G is an I2C‑interface 8‑channel GPIO expander plus 4 open‑drain outputs (OC) from Nanjing Qinheng (WCH). It allows you to control extra digital I/O pins using just the I2C bus (SCL/SDA). In this product, we use six expansion pins (IO1~IO6).

#define EXAMPLE_I2C_ADDR (ESP_IO_EXPANDER_I2C_CH422G_ADDRESS)
#define EXAMPLE_I2C_SDA_PIN 8 // I2C data line pins
#define EXAMPLE_I2C_SCL_PIN 9 // I2C clock line pin

These lines define the I2C communication pins of the ESP32: GPIO8 as SDA and GPIO9 as SCL. Also, the preprocessor directive #define renames ESP_IO_EXPANDER_I2C_CH422G_ADDRESS to EXAMPLE_I2C_ADDR for readability and ease of maintenance.

info

ESP_IO_EXPANDER_I2C_CH422G_ADDRESSis a pre‑defined constant in the library for the CH422G chip’s I2C address. It can be found in esp_io_expander_ch422g.h file inside the ESP32-S3-Touch-LCD-7-Demo\Arduino\libraries\ESP32_IO_Expander\src\port folder.

#define SD_MOSI 11 // TF card master output slave input pin
#define SD_CLK 12 // TF card clock pin
#define SD_MISO 13 // TF card master input slave output pin
#define SD_SS -1 // TF card select pin (not used)

These lines define the SPI pins used for communication between the ESP32-S3 and the TF card. GPIO11 is MOSI, GPIO12 is CLK, and GPIO13 is MISO. Because the TF card’s CS pin is controlled by the expansion chip (not directly by the ESP32), SD_SS is set to -1, indicating it is not used.

void listDir(fs::FS &fs, const char * dirname, uint8_t levels);
void createDir(fs::FS &fs, const char * path);
void removeDir(fs::FS &fs, const char * path);
void writeFile(fs::FS &fs, const char * path, const char * message);
void appendFile(fs::FS &fs, const char * path, const char * message);
void readFile(fs::FS &fs, const char * path);
void deleteFile(fs::FS &fs, const char * path);
void renameFile(fs::FS &fs, const char * path1, const char * path2);
void testFileIO(fs::FS &fs, const char * path);

These are the common basic function declarations for ESP32 file system (FS) operations. These functions allow you to perform file operations (create, read, delete, rename, etc.) on files in a TF card or SPIFFS filesystem, just like on a computer. The actual function implementations are provided in waveshare_sd_card.cpp. These functions are also examples from the ESP32 TF library – think of them as "file manager" commands on a PC:

FunctionAnalogyPurpose
listDir()List folder contentsLists all files and subdirectories under a given directory
createDir()Create new folderCreates a new directory
removeDir()Delete folderDeletes a directory
writeFile()Create & write fileWrites content to a file (overwrites existing)
appendFile()Append contentAppends data to the end of an existing file
readFile()Open file & viewReads and prints the content of a file
deleteFile()Delete fileDeletes a specified file
renameFile()Rename fileChanges a file's name
testFileIO()Performance testTests read/write speed or stability

2. Main function in SD_Test.ino

First, include the header and define global variables:

#include "waveshare_sd_card.h"
esp_expander::CH422G *expander = NULL;

Include the encapsulated driver library waveshare_sd_card.h. Use esp_expander::CH422G *expander = NULL to define a pointer to a CH422G object, which will be dynamically created later with new.

Then enters the setup() initialization, which executes only once on power-up or reset.

Serial.begin(115200);
Serial.println("Initialize IO expander");

Open the serial debug output to facilitate viewing the operation logs.

expander = new esp_expander::CH422G(EXAMPLE_I2C_SCL_PIN, EXAMPLE_I2C_SDA_PIN, EXAMPLE_I2C_ADDR);
expander->init();
expander->begin();

Creates a new CH422G object, specifying the I2C clock line (SCL), data line (SDA), and I2C address. Calls init() and begin() to complete initialization.

Serial.println("Set the IO0-7 pin to output mode.");
expander->enableAllIO_Output();

Prints a debug message and configures all expansion pins as outputs.

expander->digitalWrite(TP_RST , HIGH);
expander->digitalWrite(LCD_RST , HIGH);
expander->digitalWrite(LCD_BL , HIGH);

expander->digitalWrite(SD_CS, LOW);
// TF card chip‑select pin: when LOW, the TF card is selected and accessible via the SPI bus.

expander->digitalWrite(LCD_BL , LOW);
// Temporarily turn off the backlight to save power or for debugging

expander->digitalWrite(USB_SEL, LOW);
// When USB_SEL = HIGH, the FSUSB42UMX chip is enabled, and GPIO19/20 are used as CAN_TX/CAN_RX. // When USB_SEL = LOW, FSUSB42UMX is disabled, and USB can be used normally.

Initializes the output levels of expansion pins IO1‑IO6.

SPI.setHwCs(false);
SPI.begin(SD_CLK, SD_MISO, SD_MOSI, SD_SS);

SPI.setHwCs(false) disables hardware automatic chip‑select. SPI.begin()* sets up the four SPI lines; the pins are defined in waveshare_sd_card.h (Here, SD_SS is actually controlled by the CH422G, but a placeholder parameter still needs to be specified for the SPI).

if (!SD.begin(SD_SS)) {
Serial.println("Card Mount Failed"); // TF card mounting failed
return;
}

SD.begin() mounts the TF card filesystem. It:

  • Initializes the SPI interface
  • Sends initialization commands to the TF card
  • Detects the presence of the card
  • Mounts the FAT filesystem

uint8_t cardType = SD.cardType();

if (cardType == CARD_NONE) {
Serial.println("No TF card attached"); // No TF card connected
return;
}

Serial.print("TF Card Type: "); // TF card type
if (cardType == CARD_MMC) {
Serial.println("MMC");
} else if (cardType == CARD_SD) {
Serial.println("SDSC");
} else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
}else{
Serial.println("UNKNOWN"); // Unknown Type
}

SD.cardType() detects the TF card type and stores it in cardType. The if statement checks whether the TF card was successfully connected; if not, it prints a debug message and exits. Otherwise, it prints the card type. Common types:

  • CARD_MMC: old MMC cards
  • CARD_SD: standard capacity SD
  • CARD_SDHC: high‑capacity SDHC
  • CARD_NONE: no card detected
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("TF Card Size: %lluMB\n", cardSize); // TF card size

Gets the TF card capacity and prints it.

After the above steps, we can test the TF card filesystem functionality:

listDir(SD, "/", 0);
createDir(SD, "/mydir");
listDir(SD, "/", 0);
removeDir(SD, "/mydir");
listDir(SD, "/", 2);
writeFile(SD, "/hello.txt", "Hello ");
appendFile(SD, "/hello.txt", "World!\n");
readFile(SD, "/hello.txt");
deleteFile(SD, "/foo.txt");
renameFile(SD, "/hello.txt", "/foo.txt");
readFile(SD, "/foo.txt");
testFileIO(SD, "/test.txt");

These lines will sequentially:

  • List the root directory
  • Create a folder /mydir
  • Delete the newly created /mydir
  • Write "Hello World" to /hello.txt
  • Read the file /hello.txt
  • Rename hello.txt to foo.txt
  • Test large file read/write speed All these functions are implemented in waveshare_sd_card.h / .cpp and are wrappers around FS class functions.
Serial.printf("Total space: %lluMB\n", SD.totalBytes() / (1024 * 1024)); // Total space
Serial.printf("Used space: %lluMB\n", SD.usedBytes() / (1024 * 1024)); // Used space

Then output the total capacity and used space of the TF card.

// Main Loop
void loop() {

}

This example does not require repeated operations, so void loop() is left empty.