Welcome to this hands-on tutorial where we connect an SPI TFT display to an Arduino Nano ESP32. The Nano ESP32 is built around the ESP32-S3 chip, delivering dual-core processing, Wi-Fi, and Bluetooth Low Energy in the familiar Nano form factor with a USB-C connector. It operates at 3.3V, which matches the voltage level of most SPI TFT modules, making the wiring straightforward.
In this tutorial you will:
Wire a 3.3V SPI TFT display to the Arduino Nano ESP32.
Draw shapes and graphics using the Adafruit GFX functions.
Display text strings and numbers at different sizes.
Draw bitmap images from program memory (PROGMEM).
Draw bitmap images loaded from an SD card.
Render text with a custom external font.
Read raw touch coordinates from an XPT2046 touch controller.
Draw on screen by touch drag.
Create interactive touch buttons.
Calibrate the touch screen.
Use a secondary or custom SPI bus for the display.
This tutorial covers both touch and non-touch SPI TFT LCD displays. It works with 1.3, 1.54, 2.2, 2.4, 2.8, 3.2, and 3.5 inch panels driven by ILI9341, ILI9488, or ST7789 controller chips.
Disclosure: Some of the links provided in this section are Amazon affiliate links. We may receive a commission for any purchases made through these links at no additional cost to you. Additionally, some of these links are for products from our own brand, DIYables .
Overview of the SPI TFT Display
SPI TFT modules use a driver IC to manage pixel data and respond to drawing commands over a high-speed SPI link. Three drivers are supported:
ILI9341 - 16-bit RGB565 color, up to 40 MHz SPI.
ILI9488 - 18-bit RGB666 color over SPI, up to 24 MHz.
ST7789 - 16-bit RGB565 color, up to 40 MHz SPI.
Recommendation: If you have not yet purchased a display, we recommend the ST7789 driver. It is widely available, runs at full 40 MHz SPI speed, and is the most straightforward choice for new projects.
The library builds on Adafruit GFX, so circles, rectangles, text, custom fonts, and bitmaps are all ready to use.
Note: The Arduino Nano ESP32 uses 3.3V logic. Connect TFT VCC to the 3.3V pin only.
Pinout
Most SPI TFT LCD displays have the following pins:
Display pins:
Pin
Function
VCC
Power supply
GND
Ground
CS
Chip Select — pulled low to select the display on the SPI bus
DC / RS
Data / Command select — high for pixel data, low for commands
RST
Hardware reset — optional; tie to 3.3V if unused
MOSI / SDI / SDA
SPI data in (MCU → display)
SCK / CLK
SPI clock
MISO / SDO
SPI data out (display → MCU) — optional for display-only use
LED / BL / BLK
Backlight power — connect to 3.3V or a PWM pin for dimming
SD card pins (if your application needs to access the SD card):
Pin
Function
SD_CS / TF_CS
SD card Chip Select
MOSI / SDI
MOSI — data from MCU to SD card
SCK / CLK
SCK — SPI clock
MISO / SDO
MISO — data from SD card to MCU
For TFT displays that support touch, there are additional touch pins (if your application uses the touch function and the display supports it):
Pin
Function
T_CS
Touch controller Chip Select
T_CLK
SCK — SPI clock
T_DIN
MOSI — data from MCU to touch controller
T_DO
MISO — data from touch controller to MCU
T_IRQ
Touch interrupt — optional; signals when the screen is being touched
Note: Some non-touch display modules also expose T_CS, T_CLK, T_DIN, T_DO, and T_IRQ pins. These are non-functional on those boards — the touch controller IC is not populated. They appear because the PCB reuses the same layout as the touch-enabled version to reduce manufacturing variants.
Wiring Diagram
Without Touch
Connect MOSI to D11, SCK to D13, MISO to D12 on the Nano ESP32. CS, DC, and RST can be any available GPIO — D10, D9, D8 are used in the examples.
Display:
TFT Pin
Arduino Nano ESP32 Pin
Description
VCC
3.3V
Power supply (3.3V only)
GND
GND
Ground
CS
D10
Chip Select
DC / RS
D9
Data / Command select
RST
D8
Reset (optional)
MOSI / SDI
D11
Hardware SPI MOSI
SCK
D13
Hardware SPI clock
MISO / SDO
D12
Hardware SPI MISO (optional)
LED / BL
3.3V
Backlight power
SD card (if your application needs to access the SD card):
SD Pin
Arduino Nano ESP32 Pin
Description
SD_CS / TF_CS
any free GPIO
SD card Chip Select
MOSI / SDI
D11
Shared with display MOSI (D11)
SCK / CLK
D13
Shared with display SCK (D13)
MISO / SDO
D12
Shared with display MISO (D12)
This image is created using Fritzing. Click to enlarge image
With Touch
Connect the XPT2046 touch controller to the Arduino Nano ESP32 SPI bus, sharing D11, D13, and D12 with the display.
Display:
TFT Pin
Arduino Nano ESP32 Pin
Description
VCC
3.3V
Power supply (3.3V only)
GND
GND
Ground
CS
D10
Chip Select
DC / RS
D9
Data / Command select
RST
D8
Reset (optional)
MOSI / SDI
D11
Hardware SPI MOSI
SCK
D13
Hardware SPI clock
MISO / SDO
D12
Hardware SPI MISO (optional)
LED / BL
3.3V
Backlight power
Touch controller (if your application uses the touch function and the display supports it):
Touch Pin
Arduino Nano ESP32 Pin
Description
T_CS
D7
Touch Chip Select
T_IRQ
D6
Touch interrupt (optional)
T_DIN
D11
Shared with display MOSI (D11)
T_CLK
D13
Shared with display SCK (D13)
T_DO
D12
Shared with display MISO (D12)
This image is created using Fritzing. Click to enlarge image
If your MCU has two or more hardware SPI interfaces, you can assign each peripheral (display, SD card, touch controller) to its own dedicated SPI bus. If your MCU has only one hardware SPI interface, all three peripherals share the same three data lines (MOSI, SCK, MISO) — on the Nano ESP32 these are D11, D13, and D12. Each peripheral has its own CS pin, so only one is active at a time. The DIYables_TFT_SPI library manages both the display and the XPT2046 touch controller through a single API — no separate SPI library is needed for the touch side.
Library Setup
Plug the Arduino Nano ESP32 into your computer through its USB-C port.
Open Arduino IDE. Pick Arduino Nano ESP32 from the board list and select the right port.
Click the Libraries icon on the left sidebar.
Type "DIYables_TFT_SPI" in the search field. Find the DIYables entry.
Hit Install. Install all dependencies when prompted.
Search for DIYables TFTSPI created by DIYables.io and click the Install button.
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
Arduino Nano ESP32
Library Manager
DIYables TFT SPI
Type:
All
Topic:
All
DIYables TFT SPIby DIYables.io
Supports ILI9341 (240x320, 16-bit RGB565), ILI9488 (320x480, 18-bit RGB666), and ST7789 (240x320, 16-bit RGB565) TFT displays over SPI interface. Extends Adafruit GFX for full graphics support. Works with any Arduino-compatible board that has SPI.
More info
1.0.0
INSTALL
Newbiely.ino
···
1
voidsetup() {
Output
Serial Monitor
Ln 1, Col 1
Arduino Nano ESP32 on COM15
1
Starter Sketch
The minimum code needed to get going with the DIYables_TFT_SPI library:
#include <DIYables_TFT_SPI.h>#define TFT_CS_PIN D10#define TFT_DC_PIN D9#define TFT_RST_PIN D8// Uncomment the line that matches your driver chip:// DIYables_ILI9341_SPI TFT_display(240, 320, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);// DIYables_ILI9488_SPI TFT_display(320, 480, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);DIYables_ST7789_SPI TFT_display(240, 320, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);voidsetup() {TFT_display.begin();TFT_display.setRotation(1);}voidloop() {// drawing code here}
Tutorial - Draw Shapes
The DrawShapes example puts all the Adafruit GFX drawing primitives to work: circles, rectangles, triangles, and lines across the screen.
/* * This Arduino Nano ESP32 code was developed by newbiely.com * * This Arduino Nano ESP32 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-nano-esp32/arduino-nano-esp32-tft-lcd-touch-display-spi *//* Created by DIYables This example code is in the public domain Product page: https://diyables.io*/// =============================================// Single include brings in the base class plus all driver classes.// =============================================#include <DIYables_TFT_SPI.h>// =============================================// Wiring (Arduino Nano ESP32)// ---------------------------------------------// TFT module Arduino Nano ESP32// ------------ ---------------------------------// VCC -> 3.3V (NOT 5V!)// GND -> GND// CS -> D10 (TFT_CS_PIN)// RESET -> D8 (TFT_RST_PIN)// DC / RS -> D9 (TFT_DC_PIN)// SDI / MOSI -> D11 (hardware SPI MOSI)// SCK -> D13 (hardware SPI SCK)// LED -> 3.3V (or any GPIO via initBacklight)// SDO / MISO -> D12 (only needed when reading from display)// =============================================// =============================================// SPI pin definitions (adjust for your board)// =============================================#define TFT_CS_PIN D10#define TFT_DC_PIN D9#define TFT_RST_PIN D8// Panel resolution in native (portrait) orientation - change to match your module#define TFT_WIDTH 240#define TFT_HEIGHT 320// MOSI and SCK use default hardware SPI pins// =============================================// Create display object (uncomment matching driver)// =============================================// DIYables_ILI9341_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);// DIYables_ILI9488_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);DIYables_ST7789_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);#define BLACK DIYables_TFT_SPI::colorRGB(0, 0, 0)#define BLUE DIYables_TFT_SPI::colorRGB(0, 0, 255)#define RED DIYables_TFT_SPI::colorRGB(255, 0, 0)#define GREEN DIYables_TFT_SPI::colorRGB(0, 255, 0)#define ORANGE DIYables_TFT_SPI::colorRGB(255, 165, 0)#define PINK DIYables_TFT_SPI::colorRGB(255, 192, 203)#define VIOLET DIYables_TFT_SPI::colorRGB(148, 0, 211)#define TURQUOISE DIYables_TFT_SPI::colorRGB(64, 224, 208)#define WHITE DIYables_TFT_SPI::colorRGB(255, 255, 255)// Helper to draw a filled diamondvoid fillDiamond(int cx, int cy, int h, int v, uint16_t color) {int x0 = cx, y0 = cy - v;int x1 = cx + h, y1 = cy;int x2 = cx, y2 = cy + v;int x3 = cx - h, y3 = cy;TFT_display.fillTriangle(x0, y0, x1, y1, x2, y2, color);TFT_display.fillTriangle(x0, y0, x2, y2, x3, y3, color);}voidsetup() {TFT_display.begin();TFT_display.setRotation(1); // Landscape}voidloop() {TFT_display.fillScreen(WHITE);uint16_t w = TFT_display.width();uint16_t h = TFT_display.height();// Scale positions relative to screen size with better spacingint col1 = w / 8;int col2 = w * 3 / 8;int col3 = w * 5 / 8;int col4 = w * 7 / 8;int row1 = h / 4;int row2 = h / 2;int row3 = h * 3 / 4;// Outlined circleTFT_display.drawCircle(col1, row1, 30, RED);// Filled circleTFT_display.fillCircle(col2, row1, 30, RED);// Outlined triangleTFT_display.drawTriangle(col3 - 30, row1 + 25, col3 + 30, row1 + 25, col3, row1 - 25, BLUE);// Filled triangleTFT_display.fillTriangle(col4 - 30, row1 + 25, col4 + 30, row1 + 25, col4, row1 - 25, GREEN);// Outlined rectangleTFT_display.drawRect(col1 - 35, row2 - 20, 70, 40, ORANGE);// Filled rectangleTFT_display.fillRect(col2 - 35, row2 - 20, 70, 40, TURQUOISE);// Outlined round rectangleTFT_display.drawRoundRect(col3 - 35, row2 - 20, 70, 40, 10, VIOLET);// Filled round rectangleTFT_display.fillRoundRect(col4 - 35, row2 - 20, 70, 40, 10, PINK);// Outlined diamond (centered between col1 and col2)int diamond1_x = (col1 + col2) / 2;TFT_display.drawLine(diamond1_x, row3 - 30, diamond1_x + 25, row3, GREEN);TFT_display.drawLine(diamond1_x + 25, row3, diamond1_x, row3 + 30, GREEN);TFT_display.drawLine(diamond1_x, row3 + 30, diamond1_x - 25, row3, GREEN);TFT_display.drawLine(diamond1_x - 25, row3, diamond1_x, row3 - 30, GREEN);// Filled diamond (centered between col3 and col4)int diamond2_x = (col3 + col4) / 2; fillDiamond(diamond2_x, row3, 25, 30, BLUE);delay(10000);}
Hands-On
Wire the TFT module to the Nano ESP32 using the table above. Remember to use 3.3V for VCC.
Connect the Nano ESP32 to your computer via USB-C.
In Arduino IDE, choose the board and port, paste the code, and click Upload.
The display shows a rotating pattern of colored shapes.
The ShowTextAndNumber example prints strings and numbers using the built-in GFX text engine with adjustable size and color.
/* * This Arduino Nano ESP32 code was developed by newbiely.com * * This Arduino Nano ESP32 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-nano-esp32/arduino-nano-esp32-tft-lcd-touch-display-spi *//* Created by DIYables This example code is in the public domain Product page: https://diyables.io*/// =============================================// Single include brings in the base class plus all driver classes.// =============================================#include <DIYables_TFT_SPI.h>// =============================================// Wiring (Arduino Nano ESP32)// ---------------------------------------------// TFT module Arduino Nano ESP32// ------------ ---------------------------------// VCC -> 3.3V (NOT 5V!)// GND -> GND// CS -> D10 (TFT_CS_PIN)// RESET -> D8 (TFT_RST_PIN)// DC / RS -> D9 (TFT_DC_PIN)// SDI / MOSI -> D11 (hardware SPI MOSI)// SCK -> D13 (hardware SPI SCK)// LED -> 3.3V (or any GPIO via initBacklight)// SDO / MISO -> D12 (only needed when reading from display)// =============================================// =============================================// SPI pin definitions (adjust for your board)// =============================================#define TFT_CS_PIN D10#define TFT_DC_PIN D9#define TFT_RST_PIN D8// Panel resolution in native (portrait) orientation - change to match your module#define TFT_WIDTH 240#define TFT_HEIGHT 320// =============================================// Create display object (uncomment matching driver)// =============================================// DIYables_ILI9341_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);// DIYables_ILI9488_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);DIYables_ST7789_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);#define MAGENTA DIYables_TFT_SPI::colorRGB(255, 0, 255)#define WHITE DIYables_TFT_SPI::colorRGB(255, 255, 255)voidsetup() {Serial.begin(9600);Serial.println(F("TFT SPI Display - Show text and numbers"));TFT_display.begin();TFT_display.setRotation(1); // LandscapeTFT_display.fillScreen(WHITE);// Set text color and sizeTFT_display.setTextColor(MAGENTA);TFT_display.setTextSize(3);// Sample valuesfloat temperature = 23.5;float humidity = 78.6;// Display temperatureTFT_display.setCursor(20, 20);TFT_display.print("Temperature: ");TFT_display.print(temperature, 1);TFT_display.print(char(247));TFT_display.println("C");// Display humidityTFT_display.setCursor(20, 60);TFT_display.print("Humidity: ");TFT_display.print(humidity, 1);TFT_display.print("%");}voidloop() {}
Hands-On
Wire and upload as above.
The display prints several lines of text in different colors and sizes.
Method Reference
Method
Action
Syntax
setTextColor(color)
Sets the foreground color for text output.
TFT_display.setTextColor(WHITE);
setTextSize(size)
Scales text by an integer factor. Size 1 = 6×8 px.
TFT_display.setTextSize(2);
setCursor(x, y)
Moves the text cursor to pixel position (x, y).
TFT_display.setCursor(10, 20);
print(value)
Prints a string or number at the cursor position.
TFT_display.print("ESP32-S3!");
println(value)
Prints and moves the cursor to the next line.
TFT_display.println(42);
Tutorial - Draw Image
In this tutorial station you will load a full-color RGB565 bitmap image onto the display. The pixel data is compiled into the firmware as a PROGMEM array inside bitmap.h. On the ESP32-S3, PROGMEM data is stored in flash and mapped into the address space, so no SRAM is consumed by the image data itself. Add bitmap.h to the sketch folder before compiling.
/* * This Arduino Nano ESP32 code was developed by newbiely.com * * This Arduino Nano ESP32 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-nano-esp32/arduino-nano-esp32-tft-lcd-touch-display-spi *//* Created by DIYables This example code is in the public domain Product page: https://diyables.io*/// =============================================// Single include brings in the base class plus all driver classes.// =============================================#include <DIYables_TFT_SPI.h>#include"bitmap.h"// =============================================// Wiring (Arduino Nano ESP32)// ---------------------------------------------// TFT module Arduino Nano ESP32// ------------ ---------------------------------// VCC -> 3.3V (NOT 5V!)// GND -> GND// CS -> D10 (TFT_CS_PIN)// RESET -> D8 (TFT_RST_PIN)// DC / RS -> D9 (TFT_DC_PIN)// SDI / MOSI -> D11 (hardware SPI MOSI)// SCK -> D13 (hardware SPI SCK)// LED -> 3.3V (or any GPIO via initBacklight)// SDO / MISO -> D12 (only needed when reading from display)// =============================================// =============================================// SPI pin definitions (adjust for your board)// =============================================#define TFT_CS_PIN D10#define TFT_DC_PIN D9#define TFT_RST_PIN D8// Panel resolution in native (portrait) orientation - change to match your module#define TFT_WIDTH 240#define TFT_HEIGHT 320// =============================================// Create display object (uncomment matching driver)// =============================================// DIYables_ILI9341_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);// DIYables_ILI9488_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);DIYables_ST7789_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);#define WHITE DIYables_TFT_SPI::colorRGB(255, 255, 255)int img_width = 120;int img_height = 53;voidsetup() {Serial.begin(9600);Serial.println(F("TFT SPI Display - Draw Image"));TFT_display.begin();uint16_t SCREEN_WIDTH = TFT_display.width();uint16_t SCREEN_HEIGHT = TFT_display.height();int x = (SCREEN_WIDTH - img_width) / 2;int y = (SCREEN_HEIGHT - img_height) / 2;TFT_display.fillScreen(WHITE);TFT_display.drawRGBBitmap(x, y, myBitmap, img_width, img_height);}voidloop() {delay(2000);TFT_display.invertDisplay(true);delay(2000);TFT_display.invertDisplay(false);}
Hands-On
Place bitmap.h in the same folder as the sketch.
Wire the TFT module to the Nano ESP32. Use 3.3V for VCC.
Connect the Nano ESP32 to your computer via USB-C.
In Arduino IDE, choose the board and port, paste the code, and click Upload.
The display shows the bitmap image from program flash.
Method Reference
Method
Action
Syntax
drawRGBBitmap(x,y,bitmap,w,h)
Draws an RGB565 PROGMEM bitmap with its top-left corner at (x, y).
Clears the screen to one color before drawing the bitmap.
TFT_display.fillScreen(BLACK);
Tutorial - Draw Image SD Card
In this tutorial station you stream a raw RGB565 image file from a micro SD card directly to the display. The ESP32-S3 handles SD and display on the same SPI bus without issues thanks to its hardware SPI arbitration. Define the SD module's CS pin as SD_CS_PIN in the sketch.
Wire the SD module to the same SPI pins as the display (D11/D13/D12 on the Nano ESP32). Connect SD CS to your chosen pin.
/* * This Arduino Nano ESP32 code was developed by newbiely.com * * This Arduino Nano ESP32 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-nano-esp32/arduino-nano-esp32-tft-lcd-touch-display-spi *//* Created by DIYables This example code is in the public domain Product page: https://diyables.io*/// =============================================// Single include brings in the base class plus all driver classes.// =============================================#include <DIYables_TFT_SPI.h>#include <SD.h>// =============================================// Wiring (Arduino Nano ESP32)// =============================================// TFT + SD module pins// TFT + SD module Arduino Nano ESP32// ----------------------- ---------------------------------// VCC -> 3.3V (NOT 5V!)// GND -> GND// TFT CS -> D10 (TFT_CS_PIN)// TFT RESET -> D8 (TFT_RST_PIN)// TFT DC / RS -> D9 (TFT_DC_PIN)// SD CS -> D4 (SD_CS)// SDI / MOSI (shared) -> D11 (hardware SPI MOSI)// SDO / MISO (shared) -> D12 (hardware SPI MISO)// SCK (shared) -> D13 (hardware SPI SCK)// LED -> 3.3V (or any GPIO via initBacklight)// =============================================// =============================================// SPI pin definitions (adjust for your board)// =============================================#define TFT_CS_PIN D10#define TFT_DC_PIN D9#define TFT_RST_PIN D8// Panel resolution in native (portrait) orientation - change to match your module#define TFT_WIDTH 240#define TFT_HEIGHT 320#define SD_CS D4 // SD card chip select (must differ from TFT_CS_PIN)// =============================================// Create display object (uncomment matching driver)// =============================================// DIYables_ILI9341_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);// DIYables_ILI9488_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);DIYables_ST7789_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);#define WHITE DIYables_TFT_SPI::colorRGB(255, 255, 255)#define BUFFPIXEL 20File bmpFile;uint16_t SCREEN_WIDTH;uint16_t SCREEN_HEIGHT;// Helper functions to read BMP file headeruint16_t read16(File &f) {uint16_t result; result = f.read(); result |= (f.read() << 8);return result;}uint32_t read32(File &f) {uint32_t result; result = f.read(); result |= ((uint32_t)f.read() << 8); result |= ((uint32_t)f.read() << 16); result |= ((uint32_t)f.read() << 24);return result;}int32_t readS32(File &f) {int32_t result; result = f.read(); result |= ((uint32_t)f.read() << 8); result |= ((uint32_t)f.read() << 16); result |= ((uint32_t)f.read() << 24);return result;}bool getBMPDimensions(constchar *filename, uint32_t &w, uint32_t &h) {File f = SD.open(filename);if (!f) returnfalse;if (read16(f) != 0x4D42) { f.close(); returnfalse; } read32(f); // file size read32(f); // reserved read32(f); // image offset read32(f); // DIB header size w = read32(f);int32_t sh = readS32(f); h = (sh < 0) ? -sh : sh; f.close();returntrue;}void drawBMP(constchar *filename, int x, int y) { bmpFile = SD.open(filename);if (!bmpFile) {Serial.println("File not found");return; }if (read16(bmpFile) != 0x4D42) {Serial.println("Not a BMP file"); bmpFile.close();return; }uint32_t fileSize = read32(bmpFile); read32(bmpFile); // Reserveduint32_t imageOffset = read32(bmpFile);uint32_t dibHeaderSize = read32(bmpFile);uint32_t bmpWidth = read32(bmpFile);int32_t bmpHeight = readS32(bmpFile);bool topDown = false;if (bmpHeight < 0) { bmpHeight = -bmpHeight; topDown = true; }if (read16(bmpFile) != 1) {Serial.println("Invalid BMP file"); bmpFile.close();return; }uint16_t depth = read16(bmpFile);if (depth != 24) {Serial.println("Only 24-bit BMP is supported"); bmpFile.close();return; }if (read32(bmpFile) != 0) {Serial.println("Unsupported BMP compression"); bmpFile.close();return; } bmpFile.seek(imageOffset);uint8_t sdbuffer[3 * BUFFPIXEL];uint16_t color;uint32_t rowSize = (bmpWidth * 3 + 3) & ~3;if (x >= SCREEN_WIDTH || y >= SCREEN_HEIGHT) return;uint32_t maxRow = min((uint32_t)bmpHeight, (uint32_t)(SCREEN_HEIGHT - y));uint32_t maxCol = min(bmpWidth, (uint32_t)(SCREEN_WIDTH - x));for (uint32_t row = 0; row < maxRow; row++) {int32_t rowPos = topDown ? row : bmpHeight - 1 - row;uint32_t filePosition = imageOffset + rowPos * rowSize; bmpFile.seek(filePosition);for (uint32_t col = 0; col < maxCol; col += BUFFPIXEL) {uint32_t pixelsToRead = min((uint32_t)BUFFPIXEL, maxCol - col); bmpFile.read(sdbuffer, 3 * pixelsToRead);for (uint32_t i = 0; i < pixelsToRead; i++) {uint8_t b = sdbuffer[i * 3];uint8_t g = sdbuffer[i * 3 + 1];uint8_t r = sdbuffer[i * 3 + 2]; color = DIYables_TFT_SPI::colorRGB(r, g, b);if ((x + col + i) < SCREEN_WIDTH && (y + row) < SCREEN_HEIGHT) {TFT_display.drawPixel(x + col + i, y + row, color); } } } } bmpFile.close();Serial.println("BMP drawn");}voidsetup() {Serial.begin(9600);if (!SD.begin(SD_CS)) {Serial.println("SD card initialization failed!");return; }Serial.println("SD card initialized.");TFT_display.begin();TFT_display.setRotation(1); // Landscape SCREEN_WIDTH = TFT_display.width(); SCREEN_HEIGHT = TFT_display.height();TFT_display.fillScreen(WHITE);uint32_t imgWidth, imgHeight;if (getBMPDimensions("diyables.bmp", imgWidth, imgHeight)) {int x = (SCREEN_WIDTH - imgWidth) / 2;int y = (SCREEN_HEIGHT - imgHeight) / 2; drawBMP("diyables.bmp", x, y); } else {Serial.println("Failed to get BMP dimensions"); }}voidloop() {}
Hands-On
Wire the SD module to the Nano ESP32. Share MOSI (D11), SCK (D13), MISO (D12) with the display. Connect SD CS to the pin defined as SD_CS_PIN.
Copy a raw RGB565 binary image to the root of the SD card. Dimensions must match the panel.
Connect the Nano ESP32 to your computer via USB-C.
In Arduino IDE, choose board and port, paste the code, and click Upload.
The display renders the image streamed from the SD card.
Method Reference
Method
Action
Syntax
startWrite()
Opens a raw SPI write session, asserting the display CS.
TFT_display.startWrite();
setAddrWindow(x0,y0,x1,y1)
Sets the rectangular pixel region to receive data.
TFT_display.setAddrWindow(0, 0, 239, 319);
pushColors(buf, len)
Writes a buffer of RGB565 pixel values directly to the panel.
TFT_display.pushColors(buf, 512);
endWrite()
Closes the SPI session and releases the display CS.
TFT_display.endWrite();
Tutorial - Use External Font
In this tutorial station you replace the default raster font with a sharper Adafruit GFX-compatible custom font. The font descriptor is included as a header file. Switching fonts at runtime requires only one function call, and you can switch back to the built-in font just as easily.
/* * This Arduino Nano ESP32 code was developed by newbiely.com * * This Arduino Nano ESP32 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-nano-esp32/arduino-nano-esp32-tft-lcd-touch-display-spi *//* Created by DIYables This example code is in the public domain Product page: https://diyables.io*/// =============================================// Single include brings in the base class plus all driver classes.// =============================================#include <DIYables_TFT_SPI.h>#include <Fonts/FreeSansBold12pt7b.h>// =============================================// Wiring (Arduino Nano ESP32)// ---------------------------------------------// TFT module Arduino Nano ESP32// ------------ ---------------------------------// VCC -> 3.3V (NOT 5V!)// GND -> GND// CS -> D10 (TFT_CS_PIN)// RESET -> D8 (TFT_RST_PIN)// DC / RS -> D9 (TFT_DC_PIN)// SDI / MOSI -> D11 (hardware SPI MOSI)// SCK -> D13 (hardware SPI SCK)// LED -> 3.3V (or any GPIO via initBacklight)// SDO / MISO -> D12 (only needed when reading from display)// =============================================// =============================================// SPI pin definitions (adjust for your board)// =============================================#define TFT_CS_PIN D10#define TFT_DC_PIN D9#define TFT_RST_PIN D8// Panel resolution in native (portrait) orientation - change to match your module#define TFT_WIDTH 240#define TFT_HEIGHT 320// =============================================// Create display object (uncomment matching driver)// =============================================// DIYables_ILI9341_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);// DIYables_ILI9488_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);DIYables_ST7789_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);#define MAGENTA DIYables_TFT_SPI::colorRGB(255, 0, 255)#define WHITE DIYables_TFT_SPI::colorRGB(255, 255, 255)voidsetup() {Serial.begin(9600);Serial.println(F("TFT SPI Display - Use external font"));TFT_display.begin();TFT_display.setFont(&FreeSansBold12pt7b);TFT_display.setRotation(1); // LandscapeTFT_display.fillScreen(WHITE);TFT_display.setTextColor(MAGENTA);TFT_display.setTextSize(1);float temperature = 23.5;float humidity = 78.6;TFT_display.setCursor(20, 30);TFT_display.print("Temperature: ");TFT_display.print(temperature, 1);TFT_display.print(char(247));TFT_display.println("C");TFT_display.setCursor(20, 70);TFT_display.print("Humidity: ");TFT_display.print(humidity, 1);TFT_display.print("%");}voidloop() {}
Hands-On
Wire the TFT module to the Nano ESP32. Use 3.3V for VCC.
Connect the Nano ESP32 to your computer via USB-C.
In Arduino IDE, choose board and port, paste the code, and click Upload.
Observe the custom font rendering on screen. Compare it to the built-in font to see the improvement.
Method Reference
Method
Action
Syntax
setFont(&FontName)
Switches to a custom GFX-compatible font. Passing NULL restores the built-in 5×7 font.
TFT_display.setFont(&FreeSans12pt7b);
setCursor(x, y)
Positions the text cursor at the given pixel coordinate.
TFT_display.setCursor(10, 40);
setTextColor(color)
Sets the foreground color for all subsequent text output.
TFT_display.setTextColor(WHITE);
print(text)
Prints a string at the cursor position using the active font.
TFT_display.print("ESP32 Tutorial");
Tutorial - Touch Get Point
In this tutorial station you read raw ADC output from the XPT2046 touch controller connected to the Nano ESP32. Every touch point is printed to the Serial Monitor as raw X, Y, and pressure Z values. These numbers tell you the ADC range your panel produces — essential information before you apply calibration.
Wiring: share the SPI bus (D11/D13/D12) between the XPT2046 and the display. T_CS→D7, T_IRQ→D6. All signals are at 3.3V.
/* * This Arduino Nano ESP32 code was developed by newbiely.com * * This Arduino Nano ESP32 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-nano-esp32/arduino-nano-esp32-tft-lcd-touch-display-spi *//* Touch Get Point Example ----------------------- This example demonstrates how to read and display touch coordinates using a DIYables SPI TFT display with a 4-wire resistive touch panel. When you touch the screen, the sketch prints the mapped (screen) X and Y coordinates to the Serial Monitor and draws a red dot at the touched location. NOTE: Run the TouchCalibration example first and paste the calibration values into setTouchCalibration() below if the touch coordinates are inaccurate. Created by DIYables This example code is in the public domain Product page: https://diyables.io*/// =============================================// Single include brings in the base class plus all driver classes.// =============================================#include <DIYables_TFT_SPI.h>// =============================================// Wiring (Arduino Nano ESP32)// =============================================// TFT pins (always required)// TFT module Arduino Nano ESP32// ------------ ---------------------------------// VCC -> 3.3V (NOT 5V!)// GND -> GND// CS -> D10 (TFT_CS_PIN)// RESET -> D8 (TFT_RST_PIN)// DC / RS -> D9 (TFT_DC_PIN)// SDI / MOSI -> D11 (hardware SPI MOSI)// SCK -> D13 (hardware SPI SCK)// SDO / MISO -> D12 (only needed when reading from display)// LED -> 3.3V (or any GPIO via initBacklight)//// XPT2046 / ADS7843 SPI touch controller// (modules with pins: T_CS, T_CLK, T_DIN, T_DO, T_IRQ)// Touch pin Arduino Nano ESP32// ------------ ---------------------------------// T_CS -> D7 (TOUCH_CS_PIN)// T_IRQ -> D6 (TOUCH_IRQ_PIN, optional - use -1 to skip)// T_CLK -> D13 (shared with display SCK)// T_DIN -> D11 (shared with display MOSI)// T_DO -> D12 (shared with display MISO)// =============================================// =============================================// SPI pin definitions (adjust for your board)// =============================================#define TFT_CS_PIN D10#define TFT_DC_PIN D9#define TFT_RST_PIN D8// Panel resolution in native (portrait) orientation - change to match your module#define TFT_WIDTH 240#define TFT_HEIGHT 320// MOSI and SCK use default hardware SPI pins// =============================================// Touch pin definitions (XPT2046 SPI touch controller)// =============================================#define TOUCH_CS_PIN D7 // T_CS (any GPIO)#define TOUCH_IRQ_PIN D6 // T_IRQ (any GPIO, or -1 if not connected)// =============================================// =============================================// Calibration values.// Run the TouchCalibration example and update these if touch is inaccurate.// Typical raw ranges:// - XPT2046 : ~200..3900 (default below)// - 4-wire resistive : ~100..900// =============================================#define TOUCH_LEFT_X 300#define TOUCH_RIGHT_X 3700#define TOUCH_TOP_Y 300#define TOUCH_BOT_Y 3700// =============================================// Create display object (uncomment matching driver)// =============================================// DIYables_ILI9341_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);// DIYables_ILI9488_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);DIYables_ST7789_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);#define RED DIYables_TFT_SPI::colorRGB(255, 0, 0)#define WHITE DIYables_TFT_SPI::colorRGB(255, 255, 255)voidsetup() {Serial.begin(9600);TFT_display.begin();TFT_display.setRotation(0);TFT_display.fillScreen(WHITE);TFT_display.initTouchSPI(TOUCH_CS_PIN, TOUCH_IRQ_PIN);TFT_display.setTouchCalibration(TOUCH_LEFT_X, TOUCH_RIGHT_X, TOUCH_TOP_Y, TOUCH_BOT_Y);Serial.println("Touch the screen to see coordinates.");}voidloop() {int x, y;if (TFT_display.getTouch(x, y)) {Serial.print("Touch at: ");Serial.print(x);Serial.print(", ");Serial.println(y);TFT_display.fillCircle(x, y, 4, RED);delay(200); }}
Hands-On
Wire the XPT2046 to the Nano ESP32, sharing the SPI bus with the display. T_CS→D7, T_IRQ→D6.
Connect the Nano ESP32 to your computer via USB-C.
In Arduino IDE, choose board and port, paste the code, and click Upload.
Open the Serial Monitor at 9600 baud. Press the screen to see raw X, Y, and Z values appear.
Method Reference
Method
Action
Syntax
initTouchSPI(cs, irq)
Initializes the XPT2046 on the shared SPI bus. Pass -1 for irq if the interrupt pin is not connected.
TFT_display.initTouchSPI(7, 6);
readTouchRaw(x, y, z)
Reads raw ADC values from the controller, bypassing calibration. Returns true when pressed.
TFT_display.readTouchRaw(x, y, z);
Tutorial - Touch Draw
In this tutorial station you build a touch-paint application on the Nano ESP32. The XPT2046 supplies calibrated coordinates that are translated to pixel positions, and a small filled circle is drawn at each point. Drag a finger to create a continuous painted stroke.
/* * This Arduino Nano ESP32 code was developed by newbiely.com * * This Arduino Nano ESP32 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-nano-esp32/arduino-nano-esp32-tft-lcd-touch-display-spi *//* Touch Draw Lines Example ------------------------- Draws lines on the screen following the pen. - Touch and drag on the screen to draw. - Lift the pen to stop drawing. - Touch again to start a new line from the last point. NOTE: Run the TouchCalibration example and update setTouchCalibration() below if the touch coordinates are inaccurate. Created by DIYables This example code is in the public domain Product page: https://diyables.io*/// =============================================// Single include brings in the base class plus all driver classes.// =============================================#include <DIYables_TFT_SPI.h>// =============================================// Wiring (Arduino Nano ESP32)// =============================================// TFT pins (always required)// TFT module Arduino Nano ESP32// ------------ ---------------------------------// VCC -> 3.3V (NOT 5V!)// GND -> GND// CS -> D10 (TFT_CS_PIN)// RESET -> D8 (TFT_RST_PIN)// DC / RS -> D9 (TFT_DC_PIN)// SDI / MOSI -> D11 (hardware SPI MOSI)// SCK -> D13 (hardware SPI SCK)// SDO / MISO -> D12 (only needed when reading from display)// LED -> 3.3V (or any GPIO via initBacklight)//// XPT2046 / ADS7843 SPI touch controller// (modules with pins: T_CS, T_CLK, T_DIN, T_DO, T_IRQ)// Touch pin Arduino Nano ESP32// ------------ ---------------------------------// T_CS -> D7 (TOUCH_CS_PIN)// T_IRQ -> D6 (TOUCH_IRQ_PIN, optional - use -1 to skip)// T_CLK -> D13 (shared with display SCK)// T_DIN -> D11 (shared with display MOSI)// T_DO -> D12 (shared with display MISO)// =============================================// =============================================// SPI pin definitions (adjust for your board)// =============================================#define TFT_CS_PIN D10#define TFT_DC_PIN D9#define TFT_RST_PIN D8// Panel resolution in native (portrait) orientation - change to match your module#define TFT_WIDTH 240#define TFT_HEIGHT 320// =============================================// Touch pin definitions (XPT2046 SPI touch controller)// =============================================#define TOUCH_CS_PIN D7 // T_CS (any GPIO)#define TOUCH_IRQ_PIN D6 // T_IRQ (any GPIO, or -1 if not connected)// =============================================// =============================================// Calibration values.// Run the TouchCalibration example and update these if touch is inaccurate.// Typical raw ranges:// - XPT2046 : ~200..3900 (default below)// - 4-wire resistive : ~100..900// =============================================#define TOUCH_LEFT_X 300#define TOUCH_RIGHT_X 3700#define TOUCH_TOP_Y 300#define TOUCH_BOT_Y 3700// =============================================// Create display object (uncomment matching driver)// =============================================// DIYables_ILI9341_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);// DIYables_ILI9488_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);DIYables_ST7789_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);#define RED DIYables_TFT_SPI::colorRGB(255, 0, 0)#define WHITE DIYables_TFT_SPI::colorRGB(255, 255, 255)#define PEN_RADIUS 3voidsetup() {TFT_display.begin();TFT_display.setRotation(0);TFT_display.fillScreen(WHITE);TFT_display.initTouchSPI(TOUCH_CS_PIN, TOUCH_IRQ_PIN);TFT_display.setTouchCalibration(TOUCH_LEFT_X, TOUCH_RIGHT_X, TOUCH_TOP_Y, TOUCH_BOT_Y);}voidloop() {int x, y;if (TFT_display.getTouch(x, y)) {TFT_display.fillCircle(x, y, PEN_RADIUS, RED); }}
Hands-On
Wire the XPT2046 to the Nano ESP32 as described in the Touch Get Point station above.
Connect the Nano ESP32 to your computer via USB-C.
In Arduino IDE, choose board and port, paste the code, and click Upload.
Touch and drag a finger across the display to draw on screen.
Method Reference
Method
Action
Syntax
initTouchSPI(cs, irq)
Starts the XPT2046 on the shared SPI bus.
TFT_display.initTouchSPI(7, 6);
setTouchCalibration(minX,maxX,minY,maxY)
Maps raw ADC readings to screen pixel coordinates. Get the four values from the TouchCalibration station.
Returns calibrated touch coordinates in screen pixels. Returns true while the screen is pressed.
if (TFT_display.getTouch(x, y)) { ... }
fillCircle(x, y, r, color)
Draws a dot at the touch point, building up the painted stroke.
TFT_display.fillCircle(x, y, 3, RED);
Tutorial - Touch Button
In this tutorial station you construct interactive on-screen buttons that respond to finger taps. Each button is a colored rectangle, and touch coordinates are checked against each button's boundaries on every polling cycle. When a tap is detected inside a button, the button state changes and the action fires.
T_IRQ is optional here. Pass -1 as the irq argument to run in polling mode without the interrupt pin.
/* * This Arduino Nano ESP32 code was developed by newbiely.com * * This Arduino Nano ESP32 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-nano-esp32/arduino-nano-esp32-tft-lcd-touch-display-spi *//* Touch Button Press/Release Example ------------------------------------ This example shows how to detect press and release events on a rectangular button using a DIYables SPI TFT display with a 4-wire resistive touch panel. When you touch inside the button, it changes colour and shows "PRESSED". When you release, it returns to its original state. NOTE: Run the TouchCalibration example and update setTouchCalibration() below if the touch coordinates are inaccurate. Created by DIYables This example code is in the public domain Product page: https://diyables.io*/// =============================================// Single include brings in the base class plus all driver classes.// =============================================#include <DIYables_TFT_SPI.h>// =============================================// Wiring (Arduino Nano ESP32)// =============================================// TFT pins (always required)// TFT module Arduino Nano ESP32// ------------ ---------------------------------// VCC -> 3.3V (NOT 5V!)// GND -> GND// CS -> D10 (TFT_CS_PIN)// RESET -> D8 (TFT_RST_PIN)// DC / RS -> D9 (TFT_DC_PIN)// SDI / MOSI -> D11 (hardware SPI MOSI)// SCK -> D13 (hardware SPI SCK)// SDO / MISO -> D12 (only needed when reading from display)// LED -> 3.3V (or any GPIO via initBacklight)//// XPT2046 / ADS7843 SPI touch controller// (modules with pins: T_CS, T_CLK, T_DIN, T_DO, T_IRQ)// Touch pin Arduino Nano ESP32// ------------ ---------------------------------// T_CS -> D7 (TOUCH_CS_PIN)// T_IRQ -> -1 (TOUCH_IRQ_PIN, optional - use -1 to skip)// T_CLK -> D13 (shared with display SCK)// T_DIN -> D11 (shared with display MOSI)// T_DO -> D12 (shared with display MISO)// =============================================// =============================================// SPI pin definitions (adjust for your board)// =============================================#define TFT_CS_PIN D10#define TFT_DC_PIN D9#define TFT_RST_PIN D8// Panel resolution in native (portrait) orientation - change to match your module#define TFT_WIDTH 240#define TFT_HEIGHT 320// =============================================// Touch pin definitions (XPT2046 SPI touch controller)// =============================================#define TOUCH_CS_PIN D7 // T_CS (any GPIO)#define TOUCH_IRQ_PIN -1 // T_IRQ (any GPIO, or -1 if not connected)// =============================================// =============================================// Calibration values.// Run the TouchCalibration example and update these if touch is inaccurate.// Typical raw ranges:// - XPT2046 : ~200..3900 (default below)// - 4-wire resistive : ~100..900// =============================================#define TOUCH_LEFT_X 300#define TOUCH_RIGHT_X 3700#define TOUCH_TOP_Y 300#define TOUCH_BOT_Y 3700// =============================================// Create display object (uncomment matching driver)// =============================================// DIYables_ILI9341_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);// DIYables_ILI9488_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);DIYables_ST7789_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);#define BLACK DIYables_TFT_SPI::colorRGB( 0, 0, 0)#define WHITE DIYables_TFT_SPI::colorRGB(255, 255, 255)#define GRAY DIYables_TFT_SPI::colorRGB(128, 128, 128)#define RED DIYables_TFT_SPI::colorRGB(255, 0, 0)#define BUTTON_X 30#define BUTTON_Y 100#define BUTTON_W 180#define BUTTON_H 60#define DEBOUNCE_DELAY 50bool lastPressed = false;bool stablePressed = false;unsignedlong lastDebounceTime = 0;void drawButton(bool pressed) {uint16_t bg = pressed ? GRAY : RED;TFT_display.fillRect(BUTTON_X, BUTTON_Y, BUTTON_W, BUTTON_H, bg);TFT_display.drawRect(BUTTON_X, BUTTON_Y, BUTTON_W, BUTTON_H, BLACK);TFT_display.setTextColor(WHITE, bg);TFT_display.setTextSize(3);TFT_display.setCursor(BUTTON_X + 10, BUTTON_Y + 16);TFT_display.print(pressed ? "PRESSED" : " PRESS ");}voidsetup() {Serial.begin(9600);TFT_display.begin();TFT_display.setRotation(0);TFT_display.fillScreen(WHITE);TFT_display.initTouchSPI(TOUCH_CS_PIN, TOUCH_IRQ_PIN);TFT_display.setTouchCalibration(TOUCH_LEFT_X, TOUCH_RIGHT_X, TOUCH_TOP_Y, TOUCH_BOT_Y); drawButton(false);}voidloop() {int x, y;bool pressed = false;if (TFT_display.getTouch(x, y)) {if (x >= BUTTON_X && x < (BUTTON_X + BUTTON_W) && y >= BUTTON_Y && y < (BUTTON_Y + BUTTON_H)) { pressed = true; } }if (pressed != lastPressed) { lastDebounceTime = millis(); } lastPressed = pressed;if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {if (pressed != stablePressed) { stablePressed = pressed; drawButton(stablePressed);Serial.println(stablePressed ? "Button PRESSED" : "Button RELEASED"); } }}
Hands-On
Connect T_CS to D7. T_IRQ can be left unconnected.
Connect the Nano ESP32 to your computer via USB-C.
In Arduino IDE, choose board and port, paste the code, and click Upload.
Tap each button on the display. Watch it highlight and trigger its action.
Method Reference
Method
Action
Syntax
initTouchSPI(cs, irq)
Initializes the XPT2046. Pass -1 for irq to disable the interrupt pin.
TFT_display.initTouchSPI(7, -1);
setTouchCalibration(minX,maxX,minY,maxY)
Applies calibration so getTouch() returns accurate screen coordinates.
Gets the current calibrated touch position. Returns true while the screen is pressed.
if (TFT_display.getTouch(x, y)) { ... }
fillRect(x, y, w, h, color)
Renders a button as a solid filled rectangle.
TFT_display.fillRect(10, 10, 120, 60, BLUE);
Tutorial - Touch Calibration
In this tutorial station you calibrate the XPT2046 touch controller for your specific panel. Touch each corner of the screen when prompted and note the minimum and maximum X and Y values printed to the Serial Monitor. Those four numbers are the calibration constants to use in all other touch stations.
/* * This Arduino Nano ESP32 code was developed by newbiely.com * * This Arduino Nano ESP32 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-nano-esp32/arduino-nano-esp32-tft-lcd-touch-display-spi *//* Touch Screen Calibration Example --------------------------------- This example measures the raw touch coordinates at all four screen corners and prints ready-to-use calibration values to the Serial Monitor. It uses readTouchRaw() directly — it does NOT rely on getTouch() or any existing calibration values, so it works even when touch is completely broken. INSTRUCTIONS: 1. Upload this sketch to your board. 2. Open the Serial Monitor (Ctrl+Shift+M) and set baud rate to 9600. 3. The screen shows a blinking red dot in each corner, numbered 1-4: 1 = Top-left 2 = Top-right 3 = Bottom-right 4 = Bottom-left 4. Press and HOLD firmly on the blinking dot. Keep holding until the Serial Monitor prints "Captured!" for that corner. 5. Release, then wait for the next dot to appear and repeat. 6. After all 4 corners, the Serial Monitor prints the calibration values and a ready-to-use setTouchCalibration() call. Copy it into your sketch. NOTE: While waiting, the Serial Monitor continuously prints the live raw Z/X/Y readings so you can confirm that touch is being detected. Created by DIYables This example code is in the public domain Product page: https://diyables.io*/// =============================================// Single include brings in the base class plus all driver classes.// =============================================#include <DIYables_TFT_SPI.h>// =============================================// Wiring (Arduino Nano ESP32)// =============================================// TFT pins (always required)// TFT module Arduino Nano ESP32// ------------ ---------------------------------// VCC -> 3.3V (NOT 5V!)// GND -> GND// CS -> D10 (TFT_CS_PIN)// RESET -> D8 (TFT_RST_PIN)// DC / RS -> D9 (TFT_DC_PIN)// SDI / MOSI -> D11 (hardware SPI MOSI)// SCK -> D13 (hardware SPI SCK)// SDO / MISO -> D12 (only needed when reading from display)// LED -> 3.3V (or any GPIO via initBacklight)//// XPT2046 / ADS7843 SPI touch controller// (modules with pins: T_CS, T_CLK, T_DIN, T_DO, T_IRQ)// Touch pin Arduino Nano ESP32// ------------ ---------------------------------// T_CS -> D7 (TOUCH_CS_PIN)// T_IRQ -> D6 (TOUCH_IRQ_PIN, optional - use -1 to skip)// T_CLK -> D13 (shared with display SCK)// T_DIN -> D11 (shared with display MOSI)// T_DO -> D12 (shared with display MISO)// =============================================// =============================================// SPI pin definitions (adjust for your board)// =============================================#define TFT_CS_PIN D10#define TFT_DC_PIN D9#define TFT_RST_PIN D8// Panel resolution in native (portrait) orientation - change to match your module#define TFT_WIDTH 240#define TFT_HEIGHT 320// =============================================// Touch pin definitions (XPT2046 SPI touch controller)// =============================================#define TOUCH_CS_PIN D7 // T_CS (any GPIO)#define TOUCH_IRQ_PIN D6 // T_IRQ (any GPIO, or -1 if not connected)// =============================================// =============================================// Create display object (uncomment matching driver)// =============================================// DIYables_ILI9341_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);// DIYables_ILI9488_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);DIYables_ST7789_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);// Minimum pressure to count as a valid touch.#define TOUCH_Z_MIN 10// How many consecutive valid samples required before a corner is accepted.#define SAMPLES_NEEDED 10// Delay between samples (ms).#define SAMPLE_DELAY_MS 30#define DOT_RADIUS 12#define BLACK DIYables_TFT_SPI::colorRGB( 0, 0, 0)#define WHITE DIYables_TFT_SPI::colorRGB(255, 255, 255)#define RED DIYables_TFT_SPI::colorRGB(255, 0, 0)// Corner pixel positions - filled in setup() once display size is known.// Order: 0=top-left, 1=top-right, 2=bottom-right, 3=bottom-leftint cx[4], cy[4];// Captured averaged raw values per corner.int cap_x[4], cap_y[4];// -----------------------------------------------------------------------void drawDot(int corner, boolon) {uint16_t color = on ? RED : WHITE;TFT_display.fillCircle(cx[corner], cy[corner], DOT_RADIUS, color);TFT_display.setTextSize(2);TFT_display.setTextColor(BLACK, color);TFT_display.setCursor(cx[corner] - 6, cy[corner] - 8);TFT_display.print(corner + 1);}void captureCorner(int corner) {const char* names[] = { "Top-left", "Top-right", "Bottom-right", "Bottom-left" };Serial.println();Serial.print("Corner "); Serial.print(corner + 1);Serial.print(" ("); Serial.print(names[corner]); Serial.println(")");Serial.println(" Press and HOLD firmly on the blinking dot."); Serial.println(" Keep holding until you see 'Captured!'"); unsigned long lastBlink = 0; unsigned long lastPrint = 0; bool dotOn = false; int goodSamples = 0; long sumX = 0, sumY = 0; while (true) {// Blink the dotif (millis() - lastBlink > 400) { lastBlink = millis(); dotOn = !dotOn; drawDot(corner, dotOn); }int raw_x, raw_y, z;TFT_display.readTouchRaw(raw_x, raw_y, z);// Print live readings every 500 msif (millis() - lastPrint > 500) { lastPrint = millis();Serial.print(" Z="); Serial.print(z);Serial.print(" X="); Serial.print(raw_x);Serial.print(" Y="); Serial.println(raw_y); }if (z >= TOUCH_Z_MIN) { sumX += raw_x; sumY += raw_y; goodSamples++;if (goodSamples >= SAMPLES_NEEDED) { cap_x[corner] = sumX / goodSamples; cap_y[corner] = sumY / goodSamples;Serial.print(" Captured! raw_x="); Serial.print(cap_x[corner]);Serial.print(" raw_y="); Serial.println(cap_y[corner]); drawDot(corner, false);delay(500);return; } } else { goodSamples = 0; sumX = 0; sumY = 0; }delay(SAMPLE_DELAY_MS); }}// -----------------------------------------------------------------------voidsetup() {Serial.begin(9600);TFT_display.begin();TFT_display.setRotation(0); // Always calibrate in rotation 0TFT_display.fillScreen(WHITE);TFT_display.initTouchSPI(TOUCH_CS_PIN, TOUCH_IRQ_PIN);int w = TFT_display.width();int h = TFT_display.height();int m = DOT_RADIUS + 4; cx[0] = m; cy[0] = m; cx[1] = w - m; cy[1] = m; cx[2] = w - m; cy[2] = h - m; cx[3] = m; cy[3] = h - m;Serial.println("=== Touch Calibration ===");for (int i = 0; i < 4; i++) { captureCorner(i); }// Derive calibration values from the four cornersint min_x = (cap_x[0] + cap_x[3]) / 2; // left edgeint max_x = (cap_x[1] + cap_x[2]) / 2; // right edgeint min_y = (cap_y[0] + cap_y[1]) / 2; // top edgeint max_y = (cap_y[2] + cap_y[3]) / 2; // bottom edgeSerial.println();Serial.println("=== Calibration Results ===");Serial.print(" Left X (min_x): "); Serial.println(min_x);Serial.print(" Right X (max_x): "); Serial.println(max_x);Serial.print(" Top Y (min_y): "); Serial.println(min_y);Serial.print(" Bot Y (max_y): "); Serial.println(max_y);Serial.println();Serial.println("Copy this line into your sketch:");Serial.print(" TFT_display.setTouchCalibration(");Serial.print(min_x); Serial.print(", ");Serial.print(max_x); Serial.print(", ");Serial.print(min_y); Serial.print(", ");Serial.print(max_y); Serial.println(");");TFT_display.fillScreen(WHITE);TFT_display.setTextColor(BLACK);TFT_display.setTextSize(2);TFT_display.setCursor(10, 10);TFT_display.println("Done! Check");TFT_display.setCursor(10, 35);TFT_display.println("Serial Monitor");}voidloop() {}
Hands-On
Wire the XPT2046 to the Nano ESP32 as described in the Touch Get Point station.
Connect the Nano ESP32 to your computer via USB-C.
In Arduino IDE, choose board and port, paste the code, and click Upload.
Open the Serial Monitor at 9600 baud. Touch each corner as prompted.
Record the four printed values and use them in setTouchCalibration() in all other touch stations.
Method Reference
Method
Action
Syntax
initTouchSPI(cs, irq)
Initializes the XPT2046 controller.
TFT_display.initTouchSPI(7, 6);
readTouchRaw(x, y, z)
Reads raw ADC values to measure the calibration range.
TFT_display.readTouchRaw(x, y, z);
setTouchCalibration(minX,maxX,minY,maxY)
Stores calibration constants so getTouch() maps raw values to pixel coordinates.
In this tutorial station you route the display to a non-default SPI bus. The Nano ESP32 is built on the ESP32-S3, which supports multiple hardware SPI controllers. Passing a custom SPIClass instance to the constructor lets you assign the display to any available SPI peripheral — useful when the default bus is shared with a sensor that requires different SPI mode settings.
/* * This Arduino Nano ESP32 code was developed by newbiely.com * * This Arduino Nano ESP32 code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-nano-esp32/arduino-nano-esp32-tft-lcd-touch-display-spi *//* Created by DIYables This example code is in the public domain Product page: https://diyables.io This example demonstrates how to use a custom (non-default) SPI bus with the DIYables TFT SPI library. This is useful on boards that have multiple SPI interfaces, such as: - ESP32: HSPI / VSPI - Arduino Giga / Portenta: SPI1 - Raspberry Pi Pico: SPI1*/// =============================================// Single include brings in the base class plus all driver classes.// =============================================#include <DIYables_TFT_SPI.h>// =============================================// Wiring (Arduino Nano ESP32 - default SPI bus)// ---------------------------------------------// NOTE: The Arduino Nano ESP32 exposes one hardware SPI bus on the Nano// header pins (D11=MOSI, D13=SCK, D12=MISO). MY_SPI must remain &SPI// and the wiring uses the standard hardware SPI mapping below.//// TFT module Arduino Nano ESP32// ------------ ---------------------------------// VCC -> 3.3V (NOT 5V!)// GND -> GND// CS -> D10 (TFT_CS_PIN)// RESET -> D8 (TFT_RST_PIN)// DC / RS -> D9 (TFT_DC_PIN)// SDI / MOSI -> D11 (hardware SPI MOSI)// SCK -> D13 (hardware SPI SCK)// LED -> 3.3V (or any GPIO via initBacklight)// SDO / MISO -> D12 (only needed when reading from display)// =============================================// =============================================// SPI pin definitions (adjust for your board)// =============================================#define TFT_CS_PIN D10#define TFT_DC_PIN D9#define TFT_RST_PIN D8// Panel resolution in native (portrait) orientation - change to match your module#define TFT_WIDTH 240#define TFT_HEIGHT 320// =============================================// Select SPI bus// =============================================// --- Default SPI (D11/D13/D12 on Nano ESP32) ---#define MY_SPI &SPI// =============================================// Create display object with custom SPI bus// (uncomment matching driver)// =============================================// DIYables_ILI9341_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN, MY_SPI);// DIYables_ILI9488_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN, MY_SPI);DIYables_ST7789_SPI TFT_display(TFT_WIDTH, TFT_HEIGHT, TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN, MY_SPI);#define BLACK DIYables_TFT_SPI::colorRGB(0, 0, 0)#define WHITE DIYables_TFT_SPI::colorRGB(255, 255, 255)#define RED DIYables_TFT_SPI::colorRGB(255, 0, 0)#define GREEN DIYables_TFT_SPI::colorRGB(0, 255, 0)#define BLUE DIYables_TFT_SPI::colorRGB(0, 0, 255)voidsetup() {Serial.begin(9600);TFT_display.begin();TFT_display.setRotation(1); // LandscapeTFT_display.fillScreen(BLACK);uint16_t w = TFT_display.width();uint16_t h = TFT_display.height();// Draw a simple test patternTFT_display.fillRect(0, 0, w / 3, h, RED);TFT_display.fillRect(w / 3, 0, w / 3, h, GREEN);TFT_display.fillRect(w * 2 / 3, 0, w / 3, h, BLUE);TFT_display.setTextColor(WHITE);TFT_display.setTextSize(2);TFT_display.setCursor(10, h / 2 - 10);TFT_display.print("Custom SPI bus OK");}voidloop() {// Nothing to do}
Hands-On
Uncomment the SPIClass constructor in the sketch that matches the SPI bus you want to use and wire the display to the corresponding pins.
Connect the Nano ESP32 to your computer via USB-C.
In Arduino IDE, choose board and port, paste the code, and click Upload.
The display starts on the selected SPI bus and renders a color-bar pattern to confirm success.
Method Reference
Method
Action
Syntax
DIYables_ILI9341_SPI(w,h,cs,dc,rst,spi)
Constructor accepting an explicit SPIClass pointer as the final argument. Defaults to &SPI when omitted.
Please feel free to share the link of this tutorial. However, Please do not use our content on any other websites. We invested a lot of effort and time to create the content, please respect our work!