ESP8266 - TFT LCD Touch Display SPI
This is a practical, no-nonsense guide to connecting an SPI TFT display to an ESP8266 NodeMCU board. The ESP8266 is a compact Wi-Fi-enabled microcontroller that runs at 80-160 MHz and operates at 3.3V. Its hardware SPI bus sits on GPIO13 (MOSI), GPIO14 (SCK), and GPIO12 (MISO) - note these are NOT labeled D11/D13/D12 like Arduino boards.
Here is what we will cover:
- Identifying the correct SPI pins on the ESP8266 NodeMCU.
- Wiring a 3.3V SPI TFT display safely.
- Drawing shapes and graphics.
- Displaying text and numbers.
- Drawing bitmap images from program memory (PROGMEM).
- Drawing bitmap images loaded from an SD card.
- Rendering text with a custom external font.
- Reading raw touch coordinates from an XPT2046 controller.
- Drawing on screen by touch drag.
- Creating interactive touch buttons.
- Calibrating the touch screen.
- Using 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.

Hardware Preparation
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 .
The SPI TFT Display
SPI TFT modules use a dedicated driver IC to receive drawing commands and control the LCD pixels over a fast 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.
Drawing calls go through the Adafruit GFX API: shapes, text, fonts, and bitmaps are all available.
Note: The ESP8266 operates at 3.3V. Connect TFT VCC to the 3.3V pin. Do NOT apply 5V - it will damage the board.
Boot pin warning: GPIO0, GPIO2, and GPIO15 affect the boot mode on the ESP8266. Avoid using them for TFT CS, DC, or RST to prevent upload failures. GPIO4, GPIO5, and GPIO16 are safe choices.
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 D7 (GPIO13), SCK to D5 (GPIO14), MISO to D6 (GPIO12) on the ESP8266 NodeMCU. CS, DC, and RST can be any available boot-safe GPIO — D2 (GPIO4), D1 (GPIO5), D0 (GPIO16) are used in the examples.
Display:
| TFT Pin | ESP8266 NodeMCU Pin | GPIO Number | Description |
|---|---|---|---|
| VCC | 3V3 | - | Power supply (3.3V only) |
| GND | GND | - | Ground |
| CS | D2 | GPIO4 | Chip Select (boot-safe) |
| DC / RS | D1 | GPIO5 | Data / Command select (boot-safe) |
| RST | D0 | GPIO16 | Reset (boot-safe, optional) |
| MOSI / SDI | D7 | GPIO13 | Hardware SPI MOSI |
| SCK | D5 | GPIO14 | Hardware SPI clock |
| MISO / SDO | D6 | GPIO12 | Hardware SPI MISO (optional) |
| LED / BL | 3V3 | - | Backlight power |
SD card (if your application needs to access the SD card):
| SD Pin | ESP8266 NodeMCU Pin | GPIO Number | Description |
|---|---|---|---|
| SD_CS / TF_CS | D8 | GPIO15 | SD card Chip Select (boot-sensitive — see note below) |
| MOSI / SDI | D7 | GPIO13 | Shared with display MOSI (D7/GPIO13) |
| SCK / CLK | D5 | GPIO14 | Shared with display SCK (D5/GPIO14) |
| MISO / SDO | D6 | GPIO12 | Shared with display MISO (D6/GPIO12) |
⚠ Boot-pin note (SD_CS): GPIO15 (D8) must be LOW at boot. It is used for SD_CS in these examples. Standard SD modules have a pull-down resistor that satisfies this automatically. If uploads stop working after adding the SD module, disconnect the SD_CS wire and retry.

This image is created using Fritzing. Click to enlarge image
With Touch
Connect the XPT2046 touch controller to the ESP8266 NodeMCU SPI bus, sharing D7 (GPIO13), D5 (GPIO14), and D6 (GPIO12) with the display.
Display:
| TFT Pin | ESP8266 NodeMCU Pin | GPIO Number | Description |
|---|---|---|---|
| VCC | 3V3 | - | Power supply (3.3V only) |
| GND | GND | - | Ground |
| CS | D2 | GPIO4 | Chip Select (boot-safe) |
| DC / RS | D1 | GPIO5 | Data / Command select (boot-safe) |
| RST | D0 | GPIO16 | Reset (boot-safe, optional) |
| MOSI / SDI | D7 | GPIO13 | Hardware SPI MOSI |
| SCK | D5 | GPIO14 | Hardware SPI clock |
| MISO / SDO | D6 | GPIO12 | Hardware SPI MISO (optional) |
| LED / BL | 3V3 | - | Backlight power |
Touch controller (if your application uses the touch function and the display supports it):
| Touch Pin | ESP8266 NodeMCU Pin | GPIO Number | Description |
|---|---|---|---|
| T_CS | D3 | GPIO0 | Touch Chip Select (boot-sensitive — see note below) |
| T_IRQ | D4 | GPIO2 | Touch interrupt, optional (boot-sensitive — see note below) |
| T_DIN | D7 | GPIO13 | Shared with display MOSI (D7/GPIO13) |
| T_CLK | D5 | GPIO14 | Shared with display SCK (D5/GPIO14) |
| T_DO | D6 | GPIO12 | Shared with display MISO (D6/GPIO12) |
⚠ Boot-pin constraint note: The ESP8266 only has three boot-safe GPIOs that are not part of the hardware SPI bus: GPIO4 (D2), GPIO5 (D1), and GPIO16 (D0). These are already taken by TFT CS, DC, and RST. Every additional CS pin (TOUCH_CS, SD_CS) and the optional TOUCH_IRQ must therefore use one of the boot-sensitive pins:
- GPIO0 / D3 — must be HIGH at boot. Used for TOUCH_CS in these examples.
- GPIO2 / D4 — must be HIGH at boot. Used for TOUCH_IRQ in these examples.
- GPIO15 / D8 — must be LOW at boot. Used for SD_CS in these examples.
These pins are fully usable at runtime. The NodeMCU board's on-board resistors normally satisfy the boot requirements. However, if a module actively drives one of these pins to the wrong level during power-on or reset, the board will boot into flash mode instead of running your sketch. If uploads stop working after adding a touch or SD module, disconnect the module's CS wire and retry the upload, then reconnect.

This image is created using Fritzing. Click to enlarge image
See more in ESP8266's pinout and how to supply power to the ESP8266 and other components.
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 ESP8266 these are D7/GPIO13, D5/GPIO14, and D6/GPIO12. 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 Installation
- Connect the NodeMCU to your computer with a Micro-B USB cable.
- Open Arduino IDE. Pick NodeMCU 1.0 (ESP-12E Module) from the board list and choose the correct port.
- Open the Libraries panel.
- Type "DIYables_TFT_SPI" in the search bar and find the DIYables entry.
- Click Install. Accept all dependency installations.
- Search for DIYables TFT SPI created by DIYables.io and click the Install button.
Starting Point Code
Every ESP8266 TFT sketch starts from this foundation:
Try It Out - Draw Shapes
The DrawShapes example draws circles, triangles, rectangles, rounded rectangles, and lines using the Adafruit GFX drawing API.
Run the Code
- Wire the TFT module to the NodeMCU using the table above. Power from 3.3V only.
- Plug in the Micro-B USB cable.
- In Arduino IDE, select the board and port, paste the code, and press Upload.
- The display shows a repeating pattern of colored shapes.
Drawing Functions
| Method | What It Draws | Example |
|---|---|---|
| begin() | Initialize the display. | TFT_display.begin(); |
| setRotation(r) | Set orientation 0-3. | TFT_display.setRotation(1); |
| fillScreen(color) | Fill screen with solid color. | TFT_display.fillScreen(BLACK); |
| colorRGB(r,g,b) | Build 16-bit color from R, G, B. | colorRGB(255,200,0) |
| fillCircle(x,y,r,color) | Solid circle. | TFT_display.fillCircle(80,80,40,RED); |
| fillRect(x,y,w,h,color) | Solid rectangle. | TFT_display.fillRect(10,10,80,50,BLUE); |
| drawFastHLine(x,y,w,color) | Horizontal line (fast). | TFT_display.drawFastHLine(0,120,240,WHITE); |
Try It Out - Show Text and Number
The ShowTextAndNumber example renders strings and numeric values using the Adafruit GFX text engine.
Run the Code
- Wire and upload as above.
- The display prints lines of text in different sizes and colors.
Drawing Functions
| Method | What It Draws | Example |
|---|---|---|
| setTextColor(color) | Sets the foreground color for text. | TFT_display.setTextColor(WHITE); |
| setTextSize(size) | Scales text. Size 1 = 6×8 px, size 2 = 12×16 px. | TFT_display.setTextSize(2); |
| setCursor(x, y) | Positions the text cursor at pixel (x, y). | TFT_display.setCursor(10, 20); |
| print(value) | Renders a string or number at the cursor. | TFT_display.print("NodeMCU!"); |
| println(value) | Renders and moves cursor to the next line. | TFT_display.println(42); |
Try It Out - Draw Image
Try loading a bitmap image onto the display. The DrawImage example renders a full-color RGB565 image stored in program flash as a PROGMEM const uint16_t array inside bitmap.h. On the ESP8266, PROGMEM data is placed in the SPI flash memory and accessed via a read-from-flash instruction rather than direct pointer dereferencing. The DIYables_TFT_SPI library handles this transparently.
Place bitmap.h in the same folder as the sketch before compiling.
Run the Code
- Copy bitmap.h into the same folder as the sketch.
- Wire the TFT module to the NodeMCU as shown above (GPIO13/14/12 for SPI, GPIO4/5/16 for CS/DC/RST). Use 3.3V for VCC.
- Plug in the Micro-B USB cable.
- In Arduino IDE, select the board and port, press Upload.
- The display shows the bitmap image loaded from program flash.
Drawing Functions
| Method | What It Draws | Example |
|---|---|---|
| drawRGBBitmap(x,y,bitmap,w,h) | Renders an RGB565 PROGMEM bitmap with its top-left at (x, y). Use PROGMEM keyword on the array. | TFT_display.drawRGBBitmap(0, 0, myImage, 240, 320); |
| fillScreen(color) | Paints the entire screen one solid color before drawing. | TFT_display.fillScreen(BLACK); |
Try It Out - Draw Image SD Card
Try reading an image from an SD card. The DrawImageSDcard example streams a raw RGB565 binary file from a micro SD card directly to the display using a small buffer. This avoids holding the full image in the ESP8266's 80 KB heap.
Wire the SD module to the same SPI bus: GPIO13 (MOSI), GPIO14 (SCK), GPIO12 (MISO). The three boot-safe GPIOs (GPIO4, GPIO5, GPIO16) are already used for TFT CS, DC, and RST. SD_CS must therefore use a boot-sensitive pin. The examples use GPIO15 (D8), which must be LOW at boot — standard SD modules have a pull-down resistor that satisfies this automatically. See the boot-pin constraint note in the wiring section.
Run the Code
- Wire the SD module to the NodeMCU, sharing GPIO13/14/12 with the display. Connect SD CS to D8 (GPIO15) as defined in the code. GPIO15 must be LOW at boot — standard SD modules satisfy this with an on-board pull-down.
- Copy a raw RGB565 binary image file to the SD card root. Dimensions must match the panel.
- Plug in the Micro-B USB cable.
- In Arduino IDE, select board and port, press Upload.
- The display renders the image streamed from the SD card.
Drawing Functions
| Method | What It Draws | Example |
|---|---|---|
| startWrite() | Opens a direct SPI write session, asserting the display CS (GPIO4). | TFT_display.startWrite(); |
| setAddrWindow(x0,y0,x1,y1) | Sets the rectangular region to receive pixel data. | TFT_display.setAddrWindow(0, 0, 239, 319); |
| pushColors(buf, len) | Writes a buffer of RGB565 pixel values to the display. Keep the buffer small to stay within heap limits. | TFT_display.pushColors(buf, 256); |
| endWrite() | Closes the SPI session and releases CS. | TFT_display.endWrite(); |
Try It Out - Use External Font
Try rendering text with a sharper font. The UseExternalFont example replaces the built-in 5×7 pixel font with a high-quality Adafruit GFX-compatible custom font. The font descriptor is included as a header file and activated with a single call to setFont(). Call setFont(NULL) to go back to the built-in font.
Run the Code
- Wire the TFT module to the NodeMCU as described in the wiring section. Use 3.3V for VCC.
- Plug in the Micro-B USB cable.
- In Arduino IDE, select board and port, press Upload.
- The display renders text using the custom font. The glyph quality is noticeably better than the built-in font.
Drawing Functions
| Method | What It Draws | Example |
|---|---|---|
| setFont(&FontName) | Switches to a custom GFX font. Passing NULL restores the built-in 5×7 pixel font. | TFT_display.setFont(&FreeSans12pt7b); |
| setCursor(x, y) | Moves the text cursor to the given pixel position. | TFT_display.setCursor(10, 40); |
| setTextColor(color) | Sets the text foreground color. | TFT_display.setTextColor(WHITE); |
| print(text) | Prints a string at the cursor using the active font. | TFT_display.print("NodeMCU!"); |
Try It Out - Touch Get Point
Try reading raw touch coordinates. The TouchGetPoint example wires an XPT2046 touch controller to the NodeMCU and prints raw ADC values to the Serial Monitor for every touch. Run this before the calibration step so you know the actual ADC range your panel produces.
Wiring: T_CLK→D5 (GPIO14), T_DIN→D7 (GPIO13), T_DO→D6 (GPIO12). These share the SPI bus with the display. T_CS→D3 (GPIO0), T_IRQ→D4 (GPIO2). Note: GPIO0 and GPIO2 are boot-mode pins — they must be HIGH at power-on. They are safe to use at runtime. See the boot-pin constraint note in the wiring section above.
Run the Code
- Wire the XPT2046 to the NodeMCU. T_CLK→D5 (GPIO14), T_DIN→D7 (GPIO13), T_DO→D6 (GPIO12), T_CS→D3 (GPIO0), T_IRQ→D4 (GPIO2). GPIO0 and GPIO2 must be HIGH at power-on — see the boot-pin note in the wiring section.
- Plug in the Micro-B USB cable.
- In Arduino IDE, select board and port, press Upload.
- Open the Serial Monitor at 9600 baud. Touch the display to see raw X, Y, and pressure Z values.
Drawing Functions
| Method | What It Draws | Example |
|---|---|---|
| initTouchSPI(cs, irq) | Initializes the XPT2046 on the shared SPI bus. Pass -1 for irq if the interrupt pin is not wired. | TFT_display.initTouchSPI(0, 2); |
| readTouchRaw(x, y, z) | Returns raw ADC touch values without calibration. Returns true when the screen is pressed. | TFT_display.readTouchRaw(x, y, z); |
Try It Out - Touch Draw
Try drawing on screen with a finger. The TouchDraw example uses calibrated XPT2046 coordinates to paint small colored dots wherever the finger touches. Drag across the display to produce a continuous painted stroke.
Run the Code
- Wire the XPT2046 to the NodeMCU as described in the Touch Get Point section above.
- Plug in the Micro-B USB cable.
- In Arduino IDE, select board and port, press Upload.
- Drag a finger across the display to draw.
Drawing Functions
| Method | What It Draws | Example |
|---|---|---|
| initTouchSPI(cs, irq) | Initializes the XPT2046 on the shared SPI bus. | TFT_display.initTouchSPI(0, 2); |
| setTouchCalibration(minX,maxX,minY,maxY) | Maps raw ADC values to screen pixel coordinates. Get these numbers from the TouchCalibration example. | TFT_display.setTouchCalibration(200, 3800, 300, 3700); |
| getTouch(x, y) | 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 position to build up the stroke. | TFT_display.fillCircle(x, y, 3, RED); |
Try It Out - Touch Calibration
Try calibrating your touch panel. The TouchCalibration example walks you through touching each corner of the screen in sequence. The raw ADC minimum and maximum values for X and Y are printed to the Serial Monitor. Record those four numbers — they are the calibration constants for setTouchCalibration() in all other touch sketches.
Run the Code
- Wire the XPT2046 to the NodeMCU as described in the Touch Get Point section.
- Plug in the Micro-B USB cable.
- In Arduino IDE, select board and port, press Upload.
- Open the Serial Monitor at 9600 baud. Touch each corner of the display when prompted.
- Write down the four printed values and paste them into setTouchCalibration() in all other touch sketches.
Drawing Functions
| Method | What It Draws | Example |
|---|---|---|
| initTouchSPI(cs, irq) | Initializes the XPT2046 controller. | TFT_display.initTouchSPI(0, 2); |
| readTouchRaw(x, y, z) | Reads raw ADC values used to determine the calibration range. | TFT_display.readTouchRaw(x, y, z); |
| setTouchCalibration(minX,maxX,minY,maxY) | Stores calibration values so getTouch() maps raw readings to correct pixel positions. | TFT_display.setTouchCalibration(200, 3800, 300, 3700); |
Try It Out - Custom SPI
Try running the display on the explicit SPI bus reference. The ESP8266 has one hardware SPI bus on GPIO13 (MOSI), GPIO14 (SCK), GPIO12 (MISO). The CustomSPI example shows how to pass &SPI explicitly to the constructor and how to reduce the SPI clock frequency — useful when the wires between the NodeMCU and display are long or when the display glitches at the default speed.
Run the Code
- Wire the TFT display to the NodeMCU as described in the wiring section. GPIO13/14/12 for data, GPIO4/5/16 for CS/DC/RST.
- Plug in the Micro-B USB cable.
- In Arduino IDE, select board and port, press Upload.
- The display starts on the configured SPI bus and shows a color-bar test pattern to confirm it is working.
Drawing Functions
| Method | What It Draws | Example |
|---|---|---|
| DIYables_ILI9341_SPI(w,h,cs,dc,rst,spi) | Constructor that accepts an explicit SPIClass pointer. Omit the last argument to default to &SPI. | DIYables_ILI9341_SPI tft(240, 320, 4, 5, 16, &SPI); |
| begin() | Initializes the display on the configured SPI bus. | TFT_display.begin(); |
Troubleshoot
- Blank screen -- Confirm VCC is 3.3V (not 5V). Check MOSI (GPIO13), SCK (GPIO14), CS (GPIO4), DC (GPIO5).
- Board won't upload after adding touch or SD -- GPIO0 (T_CS), GPIO2 (T_IRQ), and GPIO15 (SD_CS) are boot-mode pins. If a module drives one of these to the wrong level at power-on, the ESP8266 boots into flash mode instead of your sketch. Disconnect the module's CS wire, press Upload, wait for "Connecting...", then reconnect.
- Board won't upload (display only) -- TFT CS/DC/RST use boot-safe GPIOs (GPIO4/5/16) and should not cause upload issues. Confirm no other module is connected to GPIO0, GPIO2, or GPIO15.
- Garbled display -- Only one driver constructor should be uncommented. Make sure it matches your panel.
- Touch not responding -- Run the TouchCalibration example first and copy the printed values into your sketch.
- Wrong colors -- Verify SPI MOSI and SCK are not swapped.
Platform Support
The library uses Arduino standard APIs only and the architectures=* setting means it compiles for ESP8266 and all other Arduino-compatible platforms.