ESP32 UNO - TFT LCD Touch Display SPI
Want a bright color screen on your ESP32 Uno? Connect an SPI TFT LCD to the ESP32 Uno form board and draw shapes, text, pictures and touch buttons with the DIYables_TFT_SPI library. The guide fits ILI9341, ILI9488 and ST7789 panels from 1.3 to 3.5 inch, with or without the XPT2046 touch layer.
By the end, you can:
- Wire an SPI TFT display and its touch chip to the ESP32 Uno
- Draw shapes, colors, text and numbers with Adafruit GFX calls
- Show a bitmap from flash memory or from a micro SD card
- Use a custom font and a custom SPI bus
- Read, calibrate and use touch input for drawing and buttons

Good to know: the ESP32 Uno works at 3.3V logic, and these display modules accept 3.3V signals. So the signal wires go straight from the board to the display, with no level converter.
Other option: a TFT touch shield plugs onto the Uno headers and needs no jumper wires. It uses a parallel interface, which moves pixels faster than SPI. Read ESP32 Uno - TFT LCD Touch Screen Display if you want that path.
Components You Need
| 1 | × | ESP32 Uno-form Board | |
| 1 | × | USB Cable Type-A to Type-C (for USB-A PC) | |
| 1 | × | USB Cable Type-C to Type-C (for USB-C PC) | |
| 1 | × | SPI TFT display module | |
| 1 | × | Jumper Wires |
Or you can buy the following kits:
| 1 | × | DIYables Sensor Kit (18 sensors/displays) |
Additionally, some of these links are for products from our own brand, DIYables .
SPI TFT Display Basics
By the end of this part, you know what the display chip does and which driver to pick.
An SPI TFT module is a color LCD with a small driver chip on the back. The ESP32 Uno sends drawing commands to that chip over SPI. The DIYables_TFT_SPI library supports three chips, and it builds on Adafruit GFX, so all the usual shape and text functions work.
| Driver Chip | Color Depth | Max SPI Speed |
|---|---|---|
| ILI9341 | 16-bit RGB565 | 40 MHz |
| ILI9488 | 18-bit RGB666 | 24 MHz |
| ST7789 | 16-bit RGB565 | 40 MHz |
Buying tip: if you do not own a display yet, take one with the ST7789 chip. It is easy to find and runs at the full 40 MHz.
Pinout
Most SPI TFT modules print the same pin names, so the tables below match almost every board.
Display pins
| Pin | Function |
|---|---|
| VCC | Power supply |
| GND | Ground |
| CS | Chip select, pulled low to talk to the display |
| DC / RS | Data or command select, high for pixels and low for commands |
| RST | Hardware reset, optional (tie to 3.3V if unused) |
| MOSI / SDI / SDA | SPI data from the board to the display |
| SCK / CLK | SPI clock |
| MISO / SDO | SPI data from the display to the board, optional for display-only use |
| LED / BL / BLK | Backlight power, 3.3V or a PWM pin for dimming |
SD card pins (only if you read files from the card)
| Pin | Function |
|---|---|
| SD_CS / TF_CS | SD card chip select |
| MOSI / SDI | Data from the board to the card |
| SCK / CLK | SPI clock |
| MISO / SDO | Data from the card to the board |
Touch pins (only on touch versions)
| Pin | Function |
|---|---|
| T_CS | Touch chip select |
| T_CLK | SPI clock |
| T_DIN | Data from the board to the touch chip |
| T_DO | Data from the touch chip to the board |
| T_IRQ | Touch interrupt, optional |
※ NOTE THAT:
Some non-touch modules still show the T_CS, T_CLK, T_DIN, T_DO and T_IRQ pins. They do nothing there, because the touch chip is not soldered on. The maker reused the touch PCB layout for both versions.

ESP32 Uno Pinout
Every Uno header pin on the ESP32 Uno form board sits on one GPIO, and your sketch uses that GPIO number. Find D2, D3, D9 to D13 in the picture before you pick up a jumper wire.

How to Connect the Display
By the end of this part, the display (and the touch chip, if you have one) is wired to the ESP32 Uno.
※ NOTE THAT:
The SDO (MISO) wire only carries data from the display back to the board. None of the sketches here read from the display, so you can leave it unconnected. On modules where the touch chip shares the bus, a connected SDO wire can even disturb touch, so skipping it is the safer choice.
Without Touch
MOSI goes to D11 (GPIO23), SCK to D13 (GPIO18) and MISO to D12 (GPIO19). CS, DC and RST can use any free pin. The sketches use D10 (GPIO5), D3 (GPIO25) and D9 (GPIO13).
Display
| TFT Pin | ESP32 Uno Pin | Description |
|---|---|---|
| VCC | 3.3V | Power supply |
| GND | GND | Ground |
| CS | D10 (GPIO5) | Chip select |
| DC / RS | D3 (GPIO25) | Data or command select |
| RST | D9 (GPIO13) | Reset (optional) |
| MOSI / SDI | D11 (GPIO23) | Hardware SPI MOSI |
| SCK | D13 (GPIO18) | Hardware SPI clock |
| MISO / SDO | D12 (GPIO19) | Hardware SPI MISO (optional) |
| LED / BL | 3.3V | Backlight power |
SD card (only if you use the card)
| SD Pin | ESP32 Uno Pin | Description |
|---|---|---|
| SD_CS / TF_CS | D4 (GPIO17) | SD card chip select |
| MOSI / SDI | D11 (GPIO23) | Shared with the display MOSI |
| SCK / CLK | D13 (GPIO18) | Shared with the display SCK |
| MISO / SDO | D12 (GPIO19) | Shared with the display MISO |

This image is created using Fritzing. Click to enlarge image
※ NOTE THAT:
The wiring diagram may not show the correct pins. On the ESP32 Uno, DC goes to D3 (GPIO25), not D8 as the picture may show. The diagram may also show the touch wires on other pins (D7 and D6). On the ESP32 Uno, T_CS goes to D2 (GPIO26) and T_IRQ is not connected. We will update the diagram soon. Until then, follow the wiring table and the code on this page, not the picture.
With Touch
The XPT2046 touch chip joins the same SPI wires. It only needs its own chip select line.
Display - wire it exactly as in the table above.
Touch controller
| Touch Pin | ESP32 Uno Pin | Description |
|---|---|---|
| T_CS | D2 (GPIO26) | Touch chip select |
| T_IRQ | not connected | Touch interrupt (optional, the sketches pass -1) |
| T_DIN | D11 (GPIO23) | Shared with the display MOSI |
| T_CLK | D13 (GPIO18) | Shared with the display SCK |
| T_DO | D12 (GPIO19) | Shared with the display MISO |

This image is created using Fritzing. Click to enlarge image
One SPI bus or two?
The ESP32 Uno has more than one SPI peripheral, but this tutorial puts the display, the SD card and the touch chip on one bus (D11, D13, D12). Each device has its own chip select, so only one talks at a time. The library drives both the display and the XPT2046 through one API, so you need no extra touch library.
Install the Library
By the end of this part, the DIYables_TFT_SPI library is ready in Arduino IDE.
- Connect the ESP32 Uno to your computer with a USB Type-C cable.
- In Arduino IDE, select the board ESP32 Dev Module and the right COM port.
- Click the Libraries icon in the left sidebar.
- Search for "DIYables_TFT_SPI" and find the entry by DIYables.
- Click Install. Accept the Adafruit GFX Library dependency when Arduino IDE asks.
- Search for DIYables TFT SPI created by DIYables.io and click the Install button.
Sketch Skeleton
Every example below starts from this frame. It shows the three pin lines and the one driver line you may need to change.
Give the constructor the real pixel size of your panel. One chip is sold in many panel sizes.
| Code Part | What It Does |
|---|---|
| TFT_CS_PIN TFT_DC_PIN TFT_RST_PIN | GPIO numbers of the three control wires |
| DIYables_ST7789_SPI | Driver class, swap it for the ILI9341 or ILI9488 class if needed |
| (240, 320, ...) | Width and height of the panel in pixels |
| begin() | Starts the SPI bus and the display chip |
| setRotation(1) | Turns the picture to landscape |
Upload and Test Any Example
Use these steps for every example in this guide.
- First time with this board? Set it up with ESP32 Uno - Getting Started before you continue.
- Build the circuit by following the wiring tables above.
- Plug in the USB Type-C cable between the ESP32 Uno and your computer.
- Select the board in Arduino IDE: pick ESP32 Dev Module and the COM port of the board (CH340).
- Copy the example code, check the driver line and the panel size, and click Upload.
- Watch the screen. The picture appears a second or two after the upload ends.
- Pro tip: if the colors look inverted or the picture is mirrored, you picked the wrong driver class. Try the other two lines one at a time.
Draw Shapes
The DrawShapes sketch paints circles, triangles, rectangles, rounded rectangles, diamonds and lines on a white screen, then redraws them every ten seconds.
Run It
- Wire the display to the ESP32 Uno.
- Upload the code with the steps in the section above.
- A grid of colored shapes appears.
Method Cheat Sheet
| Method | What It Does | Example |
|---|---|---|
| begin() | Starts the display, call once in setup() | TFT_display.begin(); |
| setRotation(r) | Turns the picture, 0 is portrait and 1 is landscape | TFT_display.setRotation(1); |
| fillScreen(color) | Clears the screen with one color | TFT_display.fillScreen(WHITE); |
| colorRGB(r,g,b) | Builds a 16-bit RGB565 color from three 8-bit values | colorRGB(0, 128, 255) |
| fillCircle(x,y,r,color) | Draws a solid circle | TFT_display.fillCircle(80, 80, 40, RED); |
| fillRect(x,y,w,h,color) | Draws a solid rectangle | TFT_display.fillRect(10, 10, 60, 40, BLUE); |
| drawRoundRect(x,y,w,h,r,color) | Draws a rounded rectangle outline | TFT_display.drawRoundRect(5,5,100,50,10,GREEN); |
Show Text and Number
The ShowTextAndNumber sketch prints a temperature and a humidity value in big magenta letters.
Run It
- Upload the code.
- Two text lines show on the screen.
Method Cheat Sheet
| Method | What It Does | Example |
|---|---|---|
| setTextColor(color) | Sets the text color | TFT_display.setTextColor(BLACK); |
| setTextSize(size) | Scales the text, size 1 is 6x8 pixels | TFT_display.setTextSize(2); |
| setCursor(x, y) | Moves the text cursor | TFT_display.setCursor(10, 30); |
| print(value) | Prints a string or number at the cursor | TFT_display.print(12.5); |
| println(value) | Prints, then moves to the next line | TFT_display.println(25.4); |
Draw Image
The DrawImage sketch shows an RGB565 picture that lives in flash memory. The pixels sit in bitmap.h as a const uint16_t array marked PROGMEM, so they stay out of RAM.
Copy the header file into the sketch folder before you compile. Arduino IDE picks it up with #include "bitmap.h".
Run It
- Save bitmap.h next to the .ino file.
- Upload the code.
- The picture shows in the middle of the screen and flips to inverted colors every two seconds.
Method Cheat Sheet
| Method | What It Does | Example |
|---|---|---|
| drawRGBBitmap(x,y,bitmap,w,h) | Draws an RGB565 PROGMEM picture from pixel (x, y) | TFT_display.drawRGBBitmap(0, 0, myBitmap, 240, 320); |
| fillScreen(color) | Clears the screen before drawing | TFT_display.fillScreen(BLACK); |
| invertDisplay(on) | Swaps all colors on or off | TFT_display.invertDisplay(true); |
Draw Image from SD Card
The DrawImageSDcard sketch opens a 24-bit BMP file named diyables.bmp on a micro SD card and draws it pixel by pixel in small chunks. The full picture is never held in RAM.
Put the SD module on the same SPI wires as the display (D11, D13, D12) and give the card its own chip select on D4 (GPIO17).
Run It
- Wire the SD card module as in the SD table above.
- Copy a 24-bit BMP file called diyables.bmp to the root of the card.
- Upload the code and open the Serial Monitor at 9600 baud.
- The picture appears on the display.
Method Cheat Sheet
| Method | What It Does | Example |
|---|---|---|
| SD.begin(cs) | Starts the SD card on the given chip select GPIO | SD.begin(17); |
| SD.open(name) | Opens a file on the card | SD.open("diyables.bmp"); |
| drawPixel(x, y, color) | Draws a single pixel | TFT_display.drawPixel(10, 10, WHITE); |
Use an External Font
The UseExternalFont sketch prints the same two lines with a bold Adafruit GFX font instead of the built-in 5x7 font. One call to setFont() switches the font.
Run It
- Upload the code.
- The text looks smoother than the default font.
Method Cheat Sheet
| Method | What It Does | Example |
|---|---|---|
| setFont(&FontName) | Picks a GFX font, pass NULL to go back to the built-in font | TFT_display.setFont(&FreeSans12pt7b); |
| setCursor(x, y) | Sets the text baseline position | TFT_display.setCursor(10, 40); |
| setTextColor(color) | Sets the text color | TFT_display.setTextColor(WHITE); |
| print(text) | Prints text with the active font | TFT_display.print("Hello!"); |
Touch Get Point
The TouchGetPoint sketch reads the finger position and prints it in the Serial Monitor. It also puts a red dot where you press.
Touch wiring from the table above: T_CS to D2 (GPIO26), T_CLK to D13, T_DIN to D11, T_DO to D12. The sketch passes -1 as the interrupt pin, so T_IRQ stays free.
Run It
- Wire the touch chip to the ESP32 Uno.
- Upload the code and open the Serial Monitor at 9600 baud.
- Press the screen and read the coordinates:
Method Cheat Sheet
| Method | What It Does | Example |
|---|---|---|
| initTouchSPI(cs, irq) | Starts the XPT2046 on the shared SPI bus, use -1 when T_IRQ is unused | TFT_display.initTouchSPI(26, -1); |
| setTouchCalibration(minX,maxX,minY,maxY) | Maps raw chip values to screen pixels | TFT_display.setTouchCalibration(300, 3700, 300, 3700); |
| getTouch(x, y) | Gives the touch position, returns true while pressed | TFT_display.getTouch(x, y) |
Touch Draw
The TouchDraw sketch is a tiny paint program. Each touch sample leaves a small red dot, so dragging your finger draws a line.
Run It
- Wire the touch chip as in the Touch Get Point part.
- Upload the code.
- Drag a finger over the screen to paint.
Method Cheat Sheet
| Method | What It Does | Example |
|---|---|---|
| initTouchSPI(cs, irq) | Starts the XPT2046 on the shared bus | TFT_display.initTouchSPI(26, -1); |
| setTouchCalibration(minX,maxX,minY,maxY) | Maps raw values to pixels, take the numbers from the TouchCalibration sketch | TFT_display.setTouchCalibration(200, 3800, 300, 3700); |
| setTouchInvertX(invert) / setTouchInvertY(invert) | Flips an axis that is mirrored on your panel, call it BEFORE setTouchCalibration() | TFT_display.setTouchInvertY(true); |
| getTouch(x, y) | Gives the calibrated touch position | if (TFT_display.getTouch(x, y)) { } |
| fillCircle(x, y, r, color) | Draws the dot under your finger | TFT_display.fillCircle(x, y, 3, RED); |
Touch Calibration
The TouchCalibration sketch finds the raw range of your own touch panel. It shows a blinking dot in each corner. Press and hold each dot, and the Serial Monitor prints four numbers for setTouchCalibration().
Run It
- Wire the touch chip as before.
- Upload the code and open the Serial Monitor at 9600 baud.
- Hold each blinking dot until you see Captured, in the order top-left, top-right, bottom-right, bottom-left.
- Copy the final line into your touch sketches. The output looks like this:
Method Cheat Sheet
| Method | What It Does | Example |
|---|---|---|
| initTouchSPI(cs, irq) | Starts the touch chip | TFT_display.initTouchSPI(26, -1); |
| readTouchRaw(x, y, z) | Reads raw ADC values without calibration | TFT_display.readTouchRaw(x, y, z); |
| setTouchCalibration(minX,maxX,minY,maxY) | Stores the numbers so getTouch() maps to pixels | TFT_display.setTouchCalibration(395, 3742, 361, 3688); |
| setTouchInvertX(invert) / setTouchInvertY(invert) | Flips a mirrored axis, call it BEFORE you calibrate so the numbers match your panel | TFT_display.setTouchInvertY(true); |
Custom SPI Bus
The CustomSPI sketch passes your own SPIClass pointer to the display constructor. Do this when the default bus is busy with another device, or when you want a second bus.
The ESP32 Uno default SPI object is VSPI, and its pins are already D11, D12 and D13, so the plain &SPI works with no remap. The sketch also holds a commented HSPI option that you can route to the same header pins.
Run It
- Wire the display as in the main wiring table.
- Upload the code.
- Three color bars and the text Custom SPI bus OK appear.
Method Cheat Sheet
| Method | What It Does | Example |
|---|---|---|
| DIYables_ST7789_SPI(w,h,cs,dc,rst,spi) | Constructor that takes a pointer to any SPIClass, the default is &SPI | DIYables_ST7789_SPI tft(240, 320, 5, 12, 13, &SPI); |
| begin() | Starts the display on the chosen bus | TFT_display.begin(); |
ESP32 Uno vs UNO R4 for This Project
When you move this display project from the UNO R4 to the ESP32 Uno, the wiring stays on the same headers and only the pin numbers in code change.
| Item | Arduino UNO R4 | ESP32 Uno |
|---|---|---|
| Logic voltage | 5V | 3.3V |
| TFT CS pin in code | 10 (D10) | 5 (D10 is GPIO5) |
| TFT DC pin in code | 8 (D8) | 25 (D3 is GPIO25) |
| TFT RST pin in code | 9 (D9) | 13 (D9 is GPIO13) |
| Touch CS pin in code | 2 (D2) | 26 (D2 is GPIO26) |
| SD CS pin in code | 4 (D4) | 17 (D4 is GPIO17) |
| Arduino IDE board | Arduino UNO R4 Minima or WiFi | ESP32 Dev Module |
The library and all drawing calls stay the same. The DC wire moves from D8 to D3, because D8 (GPIO12) is a boot strapping pin on the ESP32 Uno. Do not feed a 5V signal into any ESP32 Uno pin.
Go Wireless: Show Messages on Your Display from a Phone
By the end of this part, you know how WiFi or Bluetooth can feed text to the display.
The ESP32 Uno has WiFi and Bluetooth built in. A phone or a web page can send text, and your sketch prints it on the TFT with the same print() calls used above. That turns the display into a small wireless notice board.
Try These Next
- Show a live sensor value in big letters with the external font.
- Combine Touch Button and Draw Shapes to build a menu with several buttons.
- Add a second button beside the first one and give it its own color change.
- Log touch points to the SD card.
Platform Support
The DIYables_TFT_SPI library uses only standard Arduino calls (SPI, digitalWrite, millis), so it runs on the ESP32 Uno and on many other boards.
Troubleshooting
| Problem | Possible Cause | Solution |
|---|---|---|
| Black or white screen | CS, DC, MOSI or SCK wire is loose | Check the four wires against the wiring table. A missing CS wire leaves the display idle |
| Wrong colors or garbage | Wrong driver class in the sketch | Keep only one constructor line active and match it to your chip |
| Shifted or clipped picture | Width and height do not fit the panel | Set TFT_WIDTH and TFT_HEIGHT to the real pixel size |
| Touch does nothing | Touch chip not wired or wrong calibration | Check T_CS on D2 (GPIO26), then run TouchCalibration and use its numbers |
| Touch is mirrored | Axis flipped on this panel batch | Call setTouchInvertX(true) or setTouchInvertY(true) before setTouchCalibration() |
| No COM port | CH340 driver missing or charge-only cable | Install the CH340 driver and use a data USB cable |
| Failed to connect or Waiting for download | Board is not in upload mode | Hold the BOOT button while the upload starts |
| SD card initialization failed | Wrong SD_CS pin or bad card format | Use D4 (GPIO17), format the card as FAT32 and check the 3.3V supply |
| Display and touch fight each other | SDO (MISO) wire disturbs the touch bus | Leave the SDO wire unconnected |
FAQ
Which ESP32 Uno pins does the SPI TFT display use?
MOSI uses D11 (GPIO23), SCK uses D13 (GPIO18) and MISO uses D12 (GPIO19). The sketches use D10 (GPIO5) for CS, D3 (GPIO25) for DC and D9 (GPIO13) for RST. In code, always write the GPIO numbers.
Do I need a level converter between the ESP32 Uno and the TFT?
No. The ESP32 Uno works at 3.3V and the TFT chip expects 3.3V, so connect the signal wires directly. Just never send a 5V signal into an ESP32 Uno pin.
Which driver chip should I choose for a new display?
Pick ST7789 if you can. It is widely sold, runs at 40 MHz and is easy to set up. ILI9341 is also fine, and ILI9488 is slower because it sends 18 bits per pixel.
How do I know which driver my display has?
Look at the seller page or at the print on the module. If the picture shows wrong colors or noise, switch the constructor to another driver class and upload again.
Can I use the same code as on the UNO R4?
Almost. The library calls are the same. Just the pin values differ, since code on the ESP32 Uno takes GPIO numbers. D10 becomes 5, D8 becomes 25 (the DC wire moves to D3), D9 becomes 13, D2 becomes 26 and D4 becomes 17.
Can I use an 8-bit parallel TFT shield on the ESP32 Uno instead?
A parallel shield is faster, but it uses many pins and is made for classic Uno pin functions. The SPI module in this tutorial needs few wires and works well. Check the shield pin list before you try a parallel one.
How do I fix a touch screen that is mirrored or off target?
Run the TouchCalibration sketch and paste its four numbers into setTouchCalibration(). If an axis is still flipped, call setTouchInvertX(true) or setTouchInvertY(true) before the calibration call.
Can I show the display text from a phone with the ESP32 Uno?
Yes. The ESP32 Uno has WiFi and Bluetooth, so a web app or a Bluetooth app can send text and your sketch prints it with the same TFT calls. See the Go Wireless part above for links.
What if my project needs more pins than the Uno headers give?
The ESP32 Uno has a few extra holes near the power header: GPIO32 and GPIO33 (I/O, PWM, analog on ADC1, so they also work while WiFi is on) and GPIO15 (I/O, PWM, analog on ADC2). Solder headers into the holes first, then use the plain GPIO number in your sketch. For this project, GPIO32 or GPIO33 can serve as a spare chip select, for example for a second device. Two warnings: (1) GPIO15 and GPIO0 (the IO0 pin at the unused Uno NC position) are boot strapping pins, so a wrong connection can stop the board from booting or uploading code. Use GPIO32 and GPIO33 first. (2) Never use the six holes under the digital header (SD0 to SD3, CMD, CLK = GPIO6 to GPIO11). They are wired to the flash chip, and using them crashes the board.