How to display an image on a 2.4 inch resistive TFT display?
Hardware Setup and Pin Mapping
Getting the hardware right is critical. The 2.4 inch resistive tft display typically comes as a breakout board with a 14-pin header or a 8-pin header for the display and a separate 4-pin header for the touch. On the display side, pins include VCC (3.3V or 5V, check your module), GND, CS (chip select), RESET, DC (data/command), SDA (MOSI), and SCL (SCK). Some modules add a backlight pin (LED or BL) that you can PWM for brightness control. For the resistive touch, you’ll see X+, X-, Y+, Y- pins. These connect to analog inputs on your MCU—on an Arduino Uno, that’s A0 to A3. The touch controller is passive; you read the voltage divider formed by pressing the screen. For image display, you don’t need the touch pins wired, but they’re handy for interactive projects. Here’s a typical wiring table for an ESP32:
| Display Pin | ESP32 Pin | Notes |
|---|---|---|
| VCC | 3.3V | Some modules work at 5V, but check datasheet |
| GND | GND | Common ground |
| CS | GPIO 5 | Chip select, can be any GPIO |
| RESET | GPIO 18 | Reset pin, tied to 3.3V via resistor if not used |
| DC | GPIO 19 | Data/Command, high for data, low for command |
| SDA (MOSI) | GPIO 23 | SPI data out from MCU |
| SCL (SCK) | GPIO 18 | SPI clock |
| BL (Backlight) | GPIO 4 | Optional, PWM for brightness |
| Touch X+ | GPIO 34 (ADC) | Analog input, 12-bit resolution |
| Touch X- | GPIO 35 (ADC) | Analog input |
| Touch Y+ | GPIO 32 (ADC) | Analog input |
| Touch Y- | GPIO 33 (ADC) | Analog input |
For an Arduino Uno, the SPI pins are fixed: MOSI on pin 11, SCK on pin 13, CS on pin 10, DC on pin 9, RESET on pin 8. The touch pins go to A0-A3. The Uno’s 5V logic works with the display’s 3.3V logic if you use level shifters, but many modules have onboard regulators. I’ve run them directly at 5V on the VCC pin with the ST7789V tolerating 5V logic—check your module’s datasheet. The resistive touch layer’s analog outputs are ratiometric, meaning you get a voltage proportional to the touch position. For a 240x320 display, the X-axis spans 0 to 240, and the Y-axis 0 to 320, but the ADC values depend on your reference voltage. On an ESP32 with 3.3V reference, a touch at the left edge might give 0.1V (ADC 12), and at the right edge 3.2V (ADC 4095). You’ll need to calibrate by reading the min and max values.
Image Data Preparation and Conversion
To display an image, you can’t just send a JPEG file—the ST7789V expects raw pixel data in RGB565 format. Each pixel is 2 bytes: 5 bits for red, 6 bits for green, 5 bits for blue. That’s 16 bits per pixel, so a full 240x320 image is 153,600 bytes. You need to convert your source image to this format. Tools like ImageMagick (command line) or online converters (e.g., 2.4 inch resistive tft display’s own tool) can do this. For example, using ImageMagick: `convert input.jpg -resize 240x320! -depth 8 -colorspace sRGB -define bmp:subtype=RGB565 output.bmp`. That gives you a BMP with RGB565 encoding. Then you extract the pixel data, skipping the BMP header (54 bytes for a 24-bit BMP, but for RGB565, the header is 54 bytes too, but the pixel data starts at offset 54). You can write a Python script to read the BMP and output a C array:
import struct
with open('output.bmp', 'rb') as f:
f.seek(54) # Skip header
pixels = []
for y in range(320):
for x in range(240):
# Read 2 bytes (little-endian RGB565)
data = f.read(2)
if len(data) < 2:
break
pixel = struct.unpack('<H', data)[0]
pixels.append(pixel)
# Now you have a list of 76800 16-bit values
# Output as C array: const uint16_t image[76800] = {0x0000, ...};
That array can be stored in flash memory (PROGMEM on Arduino) or in SPIFFS on ESP32. For an ESP32, storing it in SPIFFS as a binary file is more efficient—you can read it in chunks and write to the display. The ST7789V’s RAM write command (0x2C) expects continuous pixel data. You set the window using CASET (0x2A) and RASET (0x2B) to define the rectangle, then send the pixel data. For a full-screen image, you set the column address to 0 to 239 and row address to 0 to 319, then send 153,600 bytes. The SPI bus speed matters here—at 40 MHz, transferring 153,600 bytes takes about 30.7 ms (153600 * 8 bits / 40e6 Hz = 0.03072 seconds). But that’s theoretical; overhead from command writes and MCU latency adds 10-20 ms. So a full-screen update takes around 40-50 ms, giving you about 20-25 frames per second. For a static image, you only write once, so it’s fine. But if you’re updating partial regions, you can use windowing to reduce data. For example, updating a 50x50 pixel icon takes only 5000 bytes (50*50*2), which at 40 MHz takes 1 ms.
Touch Calibration and Interaction
The resistive touch layer on the 2.4 inch resistive tft display is a 4-wire analog input. It works by pressing the top layer onto the bottom layer, creating a voltage divider. To read a touch, you set one axis (e.g., X+) to VCC and the other (X-) to GND, then read the voltage on Y+ or Y- (depending on orientation). Typically, you read X by setting X+ to VCC, X- to GND, and measuring Y+; then read Y by setting Y+ to VCC, Y- to GND, and measuring X+. The ADC values are raw, from 0 to 4095 on a 12-bit ADC. To map them to pixel coordinates, you need calibration. Here’s a typical calibration routine:
// Read raw values at four corners int x_min = 300, x_max = 3800, y_min = 200, y_max = 3700; // Example values // Map to pixel coordinates int pixel_x = map(raw_x, x_min, x_max, 0, 239); int pixel_y = map(raw_y, y_min, y_max, 0, 319);
But raw values drift with temperature and pressure, so you might need to average multiple readings or use a median filter. I’ve found that reading 10 samples and taking the median gives stable results. The touch interface is not multi-touch; it’s single-point only. For image display, touch can trigger image changes—like a button to switch to the next photo. You’d define touch zones (e.g., a rectangle from (0,0) to (80,320) for a “next” button) and check if the touch coordinates fall within that zone. The resistive touch is pressure-sensitive, but you don’t get pressure data directly—only the voltage changes. You can threshold the touch detection by checking if the ADC values are within a valid range (e.g., not at the rails). If the screen is not pressed, the ADC might read near VCC or GND, so you ignore those. A typical threshold: if raw_x < 100 or raw_x > 4000, treat as no touch. This prevents false triggers.
Performance Optimization for Image Display
If you’re displaying multiple images or animations, you need to optimize. The ST7789V supports a 16-bit parallel interface, but most breakout boards only expose SPI. SPI is slower, but you can use DMA (Direct Memory Access) on ESP32 to offload data transfer. The ESP32’s SPI driver supports DMA, meaning you can send the entire image buffer without CPU intervention. For example, using the TFT_eSPI library, you can call `tft.pushImage(0, 0, 240, 320, imageArray)` and the library uses DMA if enabled. This reduces CPU load from 100% to near 0% during the transfer. On an Arduino Uno, you don’t have DMA, so the CPU is busy during the entire transfer. You can also use double buffering: allocate a buffer in RAM (e.g., 153,600 bytes on ESP32, which has 520 KB SRAM), prepare the next frame while the current one is being sent, then swap. But for a 240x320 image, that buffer is 150 KB, which is a lot for an ESP32 (520 KB total, but other tasks use some). You can use a smaller buffer and update in chunks—like 16 lines at a time. That’s 240*16*2 = 7680 bytes per chunk, which is manageable. The display’s internal RAM is the frame buffer, so you don’t need to store the full image in MCU RAM—you can read from flash or SD card. For example, reading a JPEG from an SD card and decoding it on the fly is possible with libraries like JPEGDecoder. But decoding JPEG on an ESP32 takes about 100-200 ms per frame, depending on compression. For a slideshow, that’s fine. For a game, you’d use pre-decoded raw data. The resistive touch doesn’t affect image display performance, but reading touch adds about 5-10 ms per sample if you’re using analogRead() with averaging. You can read touch in parallel with image updates if you use a separate task (on ESP32 FreeRTOS).
Common Pitfalls and Debugging
One common issue is the display not initializing. The ST7789V requires a specific sequence: hardware reset (pull RESET low for 10 ms, then high), then software initialization commands. The TFT_eSPI library handles this, but if you’re using a custom initialization, you must send commands like SWRESET (0x01), SLPOUT (0x11), and DISPON (0x29). Another pitfall: the display’s SPI mode is mode 0 (CPOL=0, CPHA=0) or mode 2 (CPOL=1, CPHA=0)—check the datasheet. The ST7789V typically uses mode 0. If you see a blank screen, check your wiring, especially the CS and DC pins. The backlight pin might need to be pulled high—some modules have a jumper to enable backlight, others require a PWM signal. For the resistive touch, if you get no readings, check the analog pins—they should be connected to ADC-capable pins. On ESP32, only certain GPIOs have ADC (e.g., GPIO 32-39). Also, the touch layer has a protective film that you might need to peel off. I’ve seen modules where the touch connector is reversed—check the pinout against the datasheet. The 2.4 inch resistive tft display from DisplayModule has a detailed pinout diagram on their product page, which helps. If your image is garbled, it’s likely a byte order issue—RGB565 can be little-endian or big-endian. The ST7789V expects big-endian (MSB first), but some libraries send little-endian. You can swap bytes: `pixel = ((pixel & 0xFF) << 8) | ((pixel >> 8) & 0xFF)`. Another issue: the display’s RAM write command (0x2C) expects continuous data; if you send a command in between, it breaks the pixel stream. So you must set the window, then send all pixels without interruption. For partial updates, you set the window to the region you want to update—this reduces data transfer and speeds up the process. The ST7789V’s maximum SPI clock is 80 MHz, but some modules have long wires that cause signal integrity issues at high speeds. I’ve run it at 40 MHz with 10 cm wires without problems. If you see artifacts, reduce the clock to 20 MHz. Also, the display’s internal oscillator for the backlight might cause flicker at low PWM frequencies—use a frequency above 1 kHz to avoid visible flicker.
The Weekly Design Drop — new entries, style deep-dives, and the TDB Index, every Friday.
Get the Weekly Design Drop 142,000 subscribers · 38% open rate · verified by Mailchimp Q1 2024