If you need a reliable, full-color display for an Arduino project, buy a 2.8-inch 240x320 ILI9341 SPI TFT module. While the market is flooded with cheap parallel-interface shields and tiny I2C OLEDs, the ILI9341 SPI variant hits the exact sweet spot for hobbyists and prototypers: it requires only 5 logic pins, updates fast enough for smooth UI animations, and has rock-solid library support via Adafruit_GFX.
This guide cuts through the datasheet noise. We will cover the exact hardware you need, the mandatory 5V-to-3.3V logic shifting that most tutorials ignore (and why skipping it will fry your display), complete compilable code with hardware fault detection, and a decision tree for when things inevitably go wrong on the bench.
The Verdict: Which Arduino LCD TFT Should You Buy?
Not all TFTs are created equal. The controller chip on the back of the glass dictates your wiring complexity, refresh rate, and library support. Use this decision matrix to select the right module for your specific constraints.
| Controller / Interface | Resolution | Pins Required | Refresh Speed | Best Use Case |
|---|---|---|---|---|
| ILI9341 (SPI) | 240x320 | 5 (CS, DC, MOSI, SCK, RST) | High (~30fps) | Default Pick: Sensor dashboards, UI menus, general prototyping. |
| ST7789 (SPI) | 240x240 or 135x240 | 4 or 5 | High | Wearables, compact enclosures where 2.8" is too large. |
| ILI9488 (8-bit Parallel) | 320x480 | 12+ | Low (CPU bottleneck) | High-res static images where pin count is not an issue. |
| MCUFRIEND (Uno Shield) | 240x320 | All digital pins | Medium | Quick plug-and-play testing, but blocks all Uno I/O for sensors. |
Parts List and Pin Mapping for the ILI9341 SPI Build
The most common point of failure in Arduino LCD TFT builds is ignoring voltage domains. The Arduino Uno R3 operates at 5V logic. The ILI9341 controller is strictly a 3.3V device. Feeding 5V into the TFT's MISO or DC pins will degrade the silicon and eventually result in a dead display. You must use a logic level converter.
Exact Parts List
- Microcontroller: Arduino Uno R3 (ATmega328P variant) or genuine Arduino Uno R4 Minima.
- Display: 2.8" ILI9341 SPI TFT LCD Module (Generic or Adafruit Product ID 1651). Expect to pay $12-$18.
- Logic Shifter: 4-channel BSS138 bidirectional logic level converter (SparkFun BOB-12009 or generic equivalent). Do not use resistor dividers; they ruin SPI signal rise times at high clock speeds.
- Power: Dedicated 3.3V LDO regulator (like the AMS1117-3.3) if your module lacks one, though most red generic boards include a 3.3V LDO for the backlight and VCC.
- Wiring: 22 AWG solid core jumper wires, half-size breadboard.
Spec-Sheet Pin Mapping
Wire the display through the BSS138 level shifter as follows. The level shifter has a low-voltage side (LV) connected to 3.3V, and a high-voltage side (HV) connected to 5V.
| Arduino Uno R3 Pin | Level Shifter HV | Level Shifter LV | ILI9341 TFT Pin | Function |
|---|---|---|---|---|
| 5V | HV | - | - | High-side logic reference |
| 3.3V | - | LV | VCC (if no onboard LDO) | Low-side logic reference |
| GND | GND | GND | GND | Common ground |
| Pin 13 (SCK) | HV1 | LV1 | SCK / SCL | SPI Clock |
| Pin 11 (MOSI) | HV2 | LV2 | SDI / MOSI | SPI Master Out Slave In |
| Pin 12 (MISO) | HV3 | LV3 | SDO / MISO | SPI Master In Slave Out |
| Pin 10 | HV4 | LV4 | CS | Chip Select (Active Low) |
| Pin 9 | - | - | DC / RS | Data/Command (Direct 3.3V safe if using PWM) |
| Pin 8 | - | - | RESET | Hardware Reset (Active Low) |
Compilable Code: Booting the Dashboard with Error Handling
The following code targets the Arduino Uno R3 (AVR ATmega328P). It uses the industry-standard Adafruit GFX and Adafruit ILI9341 libraries. Unlike basic tutorials that blindly push pixels, this sketch includes a hardware verification step that reads the display's ID register over SPI to confirm the wiring is correct before attempting to draw.
Prerequisite: Install "Adafruit GFX Library" and "Adafruit ILI9341" via the Arduino Library Manager.
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
// --- PIN DEFINITIONS ---
#define TFT_CS 10 // Chip Select
#define TFT_DC 9 // Data/Command
#define TFT_RST 8 // Reset
#define TFT_MOSI 11 // Hardware SPI MOSI
#define TFT_MISO 12 // Hardware SPI MISO
#define TFT_SCK 13 // Hardware SPI Clock
// Initialize hardware SPI instance for maximum speed
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
void setup() {
Serial.begin(115200);
while (!Serial); // Wait for serial monitor on native USB boards
Serial.println(F("ILI9341 TFT Initialization..."));
// Standard initialization
tft.begin();
// ERROR HANDLING: Read the display ID to verify SPI communication
uint8_t x = tft.readcommand8(ILI9341_RDMODE);
Serial.print(F("Display Power Mode: 0x")); Serial.println(x, HEX);
uint8_t id = tft.readcommand8(ILI9341_RDID4);
Serial.print(F("Display ID: 0x")); Serial.println(id, HEX);
if (id == 0x00 || id == 0xFF) {
Serial.println(F("FATAL: Unknown LCD driver chip or SPI failure."));
Serial.println(F("Check MISO wiring, logic levels, and CS pin."));
// Halt execution to prevent drawing to a dead bus
while (1) {
delay(1000);
}
}
// Screen is verified, proceed to draw UI
tft.setRotation(1); // Landscape mode
tft.fillScreen(ILI9341_BLACK);
// Draw a sample dashboard header
tft.setTextColor(ILI9341_CYAN);
tft.setTextSize(2);
tft.setCursor(10, 10);
tft.println(F("SYSTEM ONLINE"));
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.setCursor(10, 40);
tft.println(F("Sensor 1: 24.5 C"));
tft.println(F("Sensor 2: 1013 hPa"));
}
void loop() {
// Main application loop
// Update dynamic variables here using tft.setCursor() and tft.print()
delay(100);
}
Debugging the "White Screen of Death" and Common TFT Errors
When an Arduino LCD TFT project fails, it almost always results in a brightly illuminated, completely white screen. This happens because the backlight LEDs turn on (they are wired directly to 3.3V/GND), but the ILI9341 controller never receives the initialization sequence to turn on the pixel matrix.
The First Three Things to Check
- Logic Level Voltages: Put a multimeter on the LV side of your level shifter. It must read exactly 3.3V. If it reads 5V, your shifter is wired backward or unpowered, and you are currently cooking the TFT's input registers.
- The MISO Line: The Adafruit library relies on reading the MISO line during
tft.begin()to auto-detect the chip. If MISO is disconnected, the library assumes failure or defaults to incorrect timing. - Backlight (LED/BLK) Pin: On generic modules, the backlight pin is sometimes labeled "LED". If it is not internally tied to 3.3V, you must jumper it to 3.3V. Never connect this pin to 5V—you will burn out the onboard backlight resistor or the LEDs themselves.
Decoding Exact Error Strings
If you are monitoring the Serial output, you will encounter specific error states. Here is how to resolve them.
| Exact Serial Error String | Ranked Causes (Most Likely First) | Fix / Measurement Threshold |
|---|---|---|
Unknown LCD driver chip: 0 |
1. MISO line floating or broken. 2. CS pin stuck HIGH. 3. SPI clock too fast for wire length. |
Measure CS pin with scope/meter; it must drop to <0.5V during init. If wires are >6 inches, add tft.setSPISpeed(8000000); before begin(). |
Display Power Mode: 0xFF |
1. 5V logic destroying MISO buffer. 2. TFT reset pin held LOW. |
Verify Reset pin is pulled HIGH (3.3V) after init. Check level shifter directionality. |
| Screen is white, no serial output | 1. Code hanging in tft.begin().2. Insufficient 3.3V current. |
The ILI9341 draws ~120mA with backlight. The Uno's onboard 3.3V regulator maxes out at ~150mA and overheats. Use an external AMS1117 3.3V LDO. |
Extending the Build: Adding Touch and Simplifying for Production
Once the basic display is rendering, you will likely want to add interactivity or move the project out of the prototype phase.
How to Extend: Adding Capacitive/Resistive Touch
Many 2.8" ILI9341 modules come with an XPT2046 resistive touch controller on a separate SPI bus.
Do not share the CS pin. Wire the touch controller's T_CLK, T_MOSI, and T_MISO to the same SPI pins as the display, but use a dedicated GPIO (like Pin 7) for T_CS. Use the XPT2046_Touchscreen library by Paul Stoffregen. Poll the touch controller in your loop() no faster than 50Hz to prevent SPI bus contention with the display rendering.
How to Simplify: Moving to a 3.3V Native Architecture
If you are designing a custom PCB or moving to a production enclosure, drop the Arduino Uno and the BSS138 level shifter entirely. Switch to an ESP32-DevKitC V4 or an Arduino Nano 33 IoT. Because these microcontrollers operate natively at 3.3V logic, you can wire the ILI9341 directly to the GPIO pins. This eliminates the level shifter, reduces BOM cost by $3, cuts wiring complexity in half, and allows you to push the SPI clock to 40MHz (on the ESP32), resulting in buttery-smooth 60fps UI rendering when paired with the Arduino SPI reference libraries or LVGL.
By standardizing on the ILI9341 SPI module and respecting the 3.3V logic domain, you bypass the most common hardware traps and get straight to building functional, vibrant embedded interfaces.






