Arduino Nano - TFT LCD Touch Display SPI
This walkthrough takes you step by step through using an SPI TFT display on the Arduino Nano. The Nano packs the same ATmega328P as the Uno into a compact, breadboard-friendly package - it has the same hardware SPI pins, so TFT wiring is identical to the Uno. The main limitation to keep in mind is the 2KB of SRAM: avoid large in-memory bitmaps and use PROGMEM for image data.
What you will walk through:
- Wiring an SPI TFT display to the Arduino Nano.
- Drawing shapes, lines, and filled areas.
- Displaying text and numbers at multiple sizes.
- 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 walkthrough 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 .
What Is the SPI TFT Display?
An SPI TFT display module is a color LCD panel driven by a controller chip over a 4-wire SPI bus. The DIYables_TFT_SPI library supports three popular driver chips:
- 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.
All drawing is done through the Adafruit GFX API, which provides shapes, text, fonts, and bitmap output.
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
Note — level converter optional: These SPI TFT display modules include on-board level shifting on their signal lines, so the 5V logic from the Arduino Nano can drive MOSI, SCK, CS, DC, RST, and T_CS (if using touch) directly — this wiring is tested and works. For a permanent build, adding a 5V to 3.3V Level Converter on the MCU-driven signal lines is still the safer choice for long-term reliability. The MISO line (display → MCU) never needs one.
Note — SDO (MISO) is optional: The SDO (MISO) line only carries data from the display back to the board. None of the example code in this tutorial reads from the display, so this pin can be left unconnected. On modules where the touch controller shares the same SPI bus, a connected SDO line can interfere with communication, so leaving it unconnected is recommended.
Without Touch
Connect MOSI to D11, SCK to D13, MISO to D12 on the Nano. CS, DC, and RST can be any available GPIO — D10, D8, D9 are used in the examples.
Display:
| TFT Pin | Arduino Nano Pin | Description |
|---|---|---|
| VCC | 3.3V | Power supply (3.3V only — the display runs at 3.3V) |
| GND | GND | Ground |
| CS | D10 | Chip Select |
| DC / RS | D8 | Data / Command select |
| RST | D9 | 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 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
If you do not use a breadboard, wire it like this:

This image is created using Fritzing. Click to enlarge image
With Touch
Connect the XPT2046 touch controller to the Arduino Nano SPI bus, sharing D11, D13, and D12 with the display.
If the project will run for a long time, or sit inside a box, it is better to fix the wires with screws. The Arduino Nano fits a Screw Terminal Expansion Board for Arduino Nano, which turns every pin into a screw terminal. Below is the wiring diagram with the screw terminal board.

This image is created using Fritzing. Click to enlarge image
Display:
| TFT Pin | Arduino Nano Pin | Description |
|---|---|---|
| VCC | 3.3V | Power supply (3.3V only — the display runs at 3.3V) |
| GND | GND | Ground |
| CS | D10 | Chip Select |
| DC / RS | D8 | Data / Command select |
| RST | D9 | 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 Pin | Description |
|---|---|---|
| T_CS | any free GPIO | Touch Chip Select |
| T_IRQ | any free GPIO | 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
Here is the same wiring without a breadboard:

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 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.
If you plan to keep this circuit, think about screw terminals. To do that, mount the Arduino Nano on a Screw Terminal Expansion Board for Arduino Nano. Each wire goes to the screw terminal of the same pin, as in the image below.

This image is created using Fritzing. Click to enlarge image
See The best way to supply power to the Arduino Nano and other components.
Setting Up the Library
- Plug the Arduino Nano into your computer with a Mini-B USB cable.
- Open Arduino IDE. Set the board to Arduino Nano and select the matching port.
- Go to the Libraries panel on the left.
- Type "DIYables_TFT_SPI" in the search box. Look for the library from DIYables.
- Click Install. When asked, install all dependencies.
- Search for DIYables TFT SPI created by DIYables.io and click the Install button.
Note: If your Nano uses an older bootloader, select ATmega328P (Old Bootloader) under Processor in the Tools menu.
Foundation Code
Every sketch built with the DIYables_TFT_SPI library starts from this foundation:
Pass the panel's physical pixel dimensions to the constructor. A wrong size causes the image to be clipped or shifted.
Arduino Nano Code - Draw Shapes
The DrawShapes example draws circles, triangles, rectangles, rounded rectangles, and lines using the Adafruit GFX primitives.
Follow Along
- Wire the TFT module to the Nano using the table above.
- Connect the Nano to your computer with a Mini-B USB cable.
- Open Arduino IDE, pick the board and port, paste the code, and click Upload.
- The display shows a repeating grid of colored shapes.
Method Overview
| Method | Explanation | Call Example |
|---|---|---|
| begin() | Initialize the display controller. | TFT_display.begin(); |
| setRotation(r) | Rotate 0-3. 1=landscape. | TFT_display.setRotation(1); |
| fillScreen(color) | Paint the whole screen. | TFT_display.fillScreen(WHITE); |
| colorRGB(r,g,b) | Make a 16-bit color value. | colorRGB(0,255,0) |
| fillCircle(x,y,r,color) | Solid circle. | TFT_display.fillCircle(60,60,30,RED); |
| drawRect(x,y,w,h,color) | Rectangle outline. | TFT_display.drawRect(10,10,80,50,BLUE); |
| fillTriangle(x0,y0,x1,y1,x2,y2,color) | Filled triangle. | TFT_display.fillTriangle(60,10,10,110,110,110,GREEN); |
Arduino Nano Code - Show Text and Number
The ShowTextAndNumber example prints strings and numeric values on screen using the built-in GFX text engine.
Follow Along
- Wire and upload as above.
- The display prints several lines of text and numbers.
Method Overview
| Method | Explanation | Call Example |
|---|---|---|
| setTextColor(color) | Sets the foreground color for all following text. | TFT_display.setTextColor(WHITE); |
| setTextSize(size) | Scales text by a pixel factor. Size 1 = 6×8 px. Keep size small to fit the Nano's tiny screen. | TFT_display.setTextSize(1); |
| setCursor(x, y) | Positions the text cursor at (x, y). | TFT_display.setCursor(0, 10); |
| print(value) | Prints a string or number at the cursor. Keep strings short to save SRAM. | TFT_display.print("Nano!"); |
| println(value) | Prints and wraps to the next line. | TFT_display.println(42); |
Arduino Nano Code - Draw Image
The DrawImage example shows a full-color RGB565 bitmap on screen. The Nano has only 2 KB of SRAM, so the pixel data must live in program flash using the PROGMEM keyword. The bitmap.h companion file holds a const uint16_t array declared PROGMEM. The library reads directly from flash at draw time, leaving SRAM untouched.
Place bitmap.h in the same sketch folder before compiling.
Follow Along
- Copy bitmap.h into the same folder as the sketch.
- Wire the TFT display to the Nano using the pin table above (D11, D13, D12 for SPI).
- Connect the Nano to your computer with a Mini-B USB cable.
- Open Arduino IDE, pick the board and port, paste the code, and click Upload.
- The display renders the PROGMEM bitmap image.
Method Overview
| Method | Explanation | Call Example |
|---|---|---|
| drawRGBBitmap(x,y,bitmap,w,h) | Draws an RGB565 bitmap from a PROGMEM array. Position (x, y) is the top-left corner. | TFT_display.drawRGBBitmap(0, 0, myImage, 240, 320); |
| fillScreen(color) | Clears the screen to a solid color before drawing. | TFT_display.fillScreen(BLACK); |
Arduino Nano Code - Draw Image SD Card
The DrawImageSDcard example reads a raw RGB565 image file from an SD card and streams it to the display in small chunks. Because data is streamed rather than loaded into a full buffer, even the Nano can display large images without running out of SRAM.
Wire the SD module to the same SPI pins as the display (D11/D13/D12) and connect its CS line to a separate pin defined as SD_CS_PIN.
Follow Along
- Wire the SD card module to the Nano. Share the SPI bus: MOSI→D11, SCK→D13, MISO→D12. Connect SD CS to the pin defined as SD_CS_PIN.
- Copy a raw RGB565 binary image to the root of the SD card. Image width and height must match your panel.
- Connect the Nano to your computer with a Mini-B USB cable.
- Open Arduino IDE, pick board and port, paste the code, and click Upload.
- The image is read from the card and displayed on screen.
Method Overview
| Method | Explanation | Call Example |
|---|---|---|
| startWrite() | Opens a direct SPI transaction, holding the display CS asserted. | TFT_display.startWrite(); |
| setAddrWindow(x0,y0,x1,y1) | Defines the rectangular region to receive pixel data. | TFT_display.setAddrWindow(0, 0, 239, 319); |
| pushColors(buf, len) | Sends a buffer of RGB565 pixel values to the display. Keep the buffer small to stay within the Nano's SRAM budget. | TFT_display.pushColors(buf, 256); |
| endWrite() | Closes the SPI transaction and releases CS. | TFT_display.endWrite(); |
Arduino Nano Code - Use External Font
The UseExternalFont example swaps the built-in 5×7 bitmap font for a sharper Adafruit GFX-compatible custom font. Select the font by passing a pointer to its struct to setFont(). Call setFont(NULL) to go back to the built-in font at any point.
Follow Along
- Wire the TFT display to the Nano using the pin table above.
- Connect the Nano to your computer with a Mini-B USB cable.
- Open Arduino IDE, pick board and port, paste the code, and click Upload.
- The display renders text using the custom font.
Method Overview
| Method | Explanation | Call Example |
|---|---|---|
| setFont(&FontName) | Activates a custom GFX-compatible font. Passing NULL restores the built-in 5×7 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 foreground color for printed text. | TFT_display.setTextColor(WHITE); |
| print(text) | Prints a string using the active font at the current cursor. | TFT_display.print("Nano!"); |
Arduino Nano Code - Touch Get Point
The TouchGetPoint example reads raw ADC values from an XPT2046 touch controller and prints them to the Serial Monitor. Walk through this example first to see the raw coordinate range your panel produces - those extremes are what you will plug into the calibration later.
Wiring: share the SPI bus (D11/D13/D12) between the XPT2046 and the display. Connect T_CS to D7 and T_IRQ to D6.
Follow Along
- Wire the XPT2046 to the Nano. Share D11 (MOSI), D13 (SCK), D12 (MISO) with the display. T_CS→D7, T_IRQ→D6.
- Connect the Nano to your computer with a Mini-B USB cable.
- Open Arduino IDE, pick board and port, paste the code, and click Upload.
- Open the Serial Monitor at 9600 baud. Press different parts of the screen to see the raw X, Y, and Z values.
Method Overview
| Method | Explanation | Call Example |
|---|---|---|
| initTouchSPI(cs, irq) | Starts up the XPT2046 on the shared SPI bus. Pass -1 for irq if the interrupt pin is not wired. | TFT_display.initTouchSPI(7, 6); |
| readTouchRaw(x, y, z) | Returns raw ADC coordinate values with no calibration applied. Returns true when the screen is touched. | TFT_display.readTouchRaw(x, y, z); |
Arduino Nano Code - Touch Draw
The TouchDraw example creates a finger-painting application on the Nano. Each point the finger contacts produces a small filled circle at the corresponding calibrated screen coordinate, and dragging builds a continuous stroke.
Follow Along
- Wire the XPT2046 to the Nano as described in the Touch Get Point section.
- Connect the Nano to your computer with a Mini-B USB cable.
- Open Arduino IDE, pick board and port, paste the code, and click Upload.
- Drag a finger across the display to draw on screen. Lift and press again to start a new stroke.
Method Overview
| Method | Explanation | Call Example |
|---|---|---|
| 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 these numbers from the TouchCalibration example. | TFT_display.setTouchCalibration(200, 3800, 300, 3700); |
| setTouchInvertX(invert) / setTouchInvertY(invert) | Flips the touch axis when X or Y is mirrored on your specific panel or batch. Call BEFORE setTouchCalibration(). | TFT_display.setTouchInvertY(true); |
| getTouch(x, y) | Returns calibrated touch coordinates in pixels. Returns true while the screen is pressed. | if (TFT_display.getTouch(x, y)) { ... } |
| fillCircle(x, y, r, color) | Draws a small dot at the touch position. | TFT_display.fillCircle(x, y, 3, RED); |
Arduino Nano Code - Touch Calibration
The TouchCalibration example walks you through finding the raw ADC range for your XPT2046 panel. Touch each corner of the screen in sequence and record the minimum and maximum X and Y values from the Serial Monitor. Paste those four numbers into setTouchCalibration() in all other touch sketches.
Follow Along
- Wire the XPT2046 to the Nano as described in the Touch Get Point section.
- Connect the Nano to your computer with a Mini-B USB cable.
- Open Arduino IDE, pick board and port, paste the code, and click Upload.
- Open the Serial Monitor at 9600 baud. Touch each corner when prompted.
- Note the four printed values and use them in setTouchCalibration() in your other touch sketches.
Method Overview
| Method | Explanation | Call Example |
|---|---|---|
| initTouchSPI(cs, irq) | Initializes the XPT2046 controller. | TFT_display.initTouchSPI(7, 6); |
| readTouchRaw(x, y, z) | Reads raw ADC values used to establish the calibration range. | TFT_display.readTouchRaw(x, y, z); |
| setTouchCalibration(minX,maxX,minY,maxY) | Stores the four calibration constants so getTouch() maps raw values to pixel coordinates. | TFT_display.setTouchCalibration(200, 3800, 300, 3700); |
| setTouchInvertX(invert) / setTouchInvertY(invert) | Flips the touch axis when X or Y is mirrored on your specific panel or batch. Call BEFORE running calibration so the stored values match your panel. | TFT_display.setTouchInvertY(true); |
Arduino Nano Code - Custom SPI
The CustomSPI example demonstrates how to supply an explicit SPIClass pointer to the display constructor. On the Nano there is a single hardware SPI bus (D11/D13/D12). Passing &SPI explicitly lets you control the SPI settings precisely - for example, reducing the clock speed when running long wires to the display.
Follow Along
- Wire the TFT display to the Nano as shown in the wiring diagram.
- Connect the Nano to your computer with a Mini-B USB cable.
- Open Arduino IDE, pick board and port, paste the code, and click Upload.
- The display starts on the explicitly configured SPI bus and shows a color-bar test pattern.
Method Overview
| Method | Explanation | Call Example |
|---|---|---|
| DIYables_ILI9341_SPI(w,h,cs,dc,rst,spi) | Constructor that takes an explicit SPIClass pointer. Omit the last argument to default to &SPI. | DIYables_ILI9341_SPI tft(240, 320, 10, 9, 8, &SPI); |
| begin() | Initializes the display on the configured SPI bus. | TFT_display.begin(); |
Troubleshoot
- Black screen - Check CS (D10), DC (D8), MOSI (D11), and SCK (D13). A floating CS pin is the most common culprit.
- Wrong colors or garbage pixels - Confirm you have the correct driver constructor uncommented.
- SRAM overflow - The Nano has 2KB of SRAM. Store bitmaps in PROGMEM using the PROGMEM keyword and the DrawImage example.
- Upload fails with "not in sync" - Try selecting ATmega328P (Old Bootloader) in the Processor menu.
Platform Support
The library uses only Arduino standard APIs (SPI, digitalWrite, millis) and works on all Arduino-compatible platforms.