Skip to content
New season Shop the Collection

How to use a 2.4 inch resistive TFT display with a touch sensor?

How to use a 2.4 inch resistive TFT display with a touch sensor?

To get a 2.4 inch resistive TFT display with a touch sensor working, you’ll need to wire it up to a microcontroller like an ESP32 or STM32, configure the SPI interface at 4-wire or 5-wire mode depending on your touch controller, and handle the resistive touch data through an ADC channel. The specific model I’m referencing here, the 2.4 inch resistive tft display, uses a ST7789V driver for the TFT and a separate XPT2046 chip for the resistive touch overlay. That’s a common combo, and you’ll find it in tons of hobbyist and industrial projects because it’s cheap and reliable—but it’s not plug-and-play. You have to understand the pinout, the communication protocol, and the calibration quirks.

Let’s break down the hardware first. The display runs at 240x320 pixels, which is a standard resolution for small TFTs. The ST7789V controller supports SPI up to 62.5 MHz, but in practice, you’ll run it at 20-40 MHz to avoid signal noise on breadboards. The resistive touch layer uses a 4-wire analog interface—two wires for the X-axis (X+ and X-), two for the Y-axis (Y+ and Y-). The XPT2046 chip converts those analog voltages into digital values via a 12-bit ADC. That gives you a resolution of 4096 steps per axis, but the actual usable area on a 2.4-inch panel is about 36.7 mm x 48.9 mm, so the raw touch coordinates will need scaling and calibration. You can’t just map the ADC values directly to pixel positions because of mechanical offsets and non-linearities in the resistive film.

Wiring is straightforward but critical. The TFT requires 8 pins for SPI: VCC (3.3V or 5V, check your module), GND, SCLK, MOSI, MISO (optional if you don’t need readback), CS (chip select for TFT), DC (data/command), and RST (reset). The touch controller adds 4 more pins: T_IRQ (touch interrupt, active low), T_CS (chip select for touch), T_DIN (MOSI for touch), and T_DOUT (MISO for touch). Some modules combine the SPI bus for both TFT and touch, sharing SCLK and MOSI, but you need separate CS lines. On an ESP32, I’d assign GPIO 5 for TFT_CS, GPIO 4 for T_CS, GPIO 2 for DC, GPIO 15 for RST, and GPIO 18 for SCLK, GPIO 23 for MOSI, GPIO 19 for MISO. That’s a common setup, but double-check your board’s pinout because some ESP32 dev boards have pull-up resistors on certain pins that interfere with SPI.

Now, the software side. You need two libraries: one for the ST7789V TFT driver and one for the XPT2046 touch controller. On Arduino IDE, you can use the “Adafruit ST7789” library for the display and “XPT2046_Touchscreen” for the touch. But don’t just install them and run the examples—you’ll hit issues. The Adafruit library expects a specific initialization sequence for the ST7789V, and if your module uses a different variant (like with a built-in voltage regulator), you might see garbled colors or no display. Check the datasheet for your module’s init commands. For the 2.4 inch resistive tft display, the typical init sequence includes commands like 0x11 (sleep out), 0x36 (memory data access control), 0x3A (interface pixel format), and 0x21 (display inversion on). The pixel format should be set to 0x05 for 16-bit color (RGB565). If you skip that, you’ll get 18-bit color which looks washed out on an 8-bit microcontroller.

For the touch controller, the XPT2046 communicates over SPI with a 3-byte transaction: you send a command byte (like 0x90 for Y-axis measurement, 0xD0 for X-axis), then read two bytes back. The command byte includes the channel selection, reference mode, and power-down settings. I recommend using the “XPT2046_Touchscreen” library by Paul Stoffregen—it handles the SPI transactions and returns 12-bit values. But here’s the catch: the raw values from the touch controller are not pixel coordinates. You need to calibrate them. A standard calibration method is to touch the four corners of the display and record the ADC values. Then you compute a linear mapping using a formula like:

pixel_x = (raw_x - x_min) * (240 - 1) / (x_max - x_min)
pixel_y = (raw_y - y_min) * (320 - 1) / (y_max - y_min)

But resistive touch panels have a dead zone around the edges—about 5-10% of the active area—so you’ll get non-linear readings near the bezel. To compensate, you can use a 3-point calibration or a look-up table, but for most hobby projects, linear interpolation is good enough if you only use the central 80% of the screen. Also, the touch panel has a “touch pressure” value (Z-axis) that you can read from the XPT2046 by sending a command byte like 0xB0. That gives you a value between 0 and 4095, where lower values indicate harder pressure. You can use this to detect if the user is pressing lightly or firmly, which is useful for distinguishing accidental touches from intentional ones.

Let’s talk about performance. The ST7789V can refresh the full 240x320 screen at about 60 fps with SPI at 40 MHz, but if you’re also reading touch data, the SPI bus becomes a bottleneck. The XPT2046 takes about 1-2 ms per read, including the SPI transaction and ADC conversion. If you’re polling the touch at 100 Hz, that’s 100-200 ms per second spent just on touch reading, which cuts into your display update time. A better approach is to use the T_IRQ pin. The XPT2046 pulls T_IRQ low when a touch is detected, so you can attach an interrupt to that pin and only read touch data when the user is actually touching. This reduces SPI traffic and saves power. On an ESP32, you can set up a GPIO interrupt on the rising or falling edge, then in the ISR, set a flag and read the touch data in the main loop. Just be careful about debouncing—resistive touch panels can have jitter, so add a 10-20 ms delay after the first touch detection before reading the coordinates.

Now, the display’s color depth and gamma. The ST7789V supports 12-bit, 16-bit, and 18-bit color modes. For 16-bit RGB565, you have 5 bits for red, 6 bits for green, and 5 bits for blue. That’s 65,536 colors, which is fine for UI elements but not for photos. The gamma curve is set by default, but you can adjust it via the 0xE0 and 0xE1 commands (positive and negative gamma correction). The default gamma values are usually good for general use, but if you notice color banding in gradients, you can tweak the gamma registers. For example, setting the first byte of the positive gamma to 0x0F (instead of 0x00) increases the slope of the low-end gamma, making dark areas more visible. This is useful if your display is behind a dark filter or if you’re using it outdoors.

Resistive touch has a few quirks compared to capacitive. It requires physical pressure, so the user must press hard enough to deform the top layer. The activation force is typically 50-100 grams, but it varies with temperature and humidity. In cold environments, the film becomes stiffer, so you might need to increase the threshold for detecting a touch. The XPT2046 has a “touch pressure” threshold register (0x04) that you can set to filter out light touches. The default threshold is 0x00, meaning any touch is detected. I recommend setting it to 0x10 or 0x20 to avoid false triggers from vibration or accidental brushing. Also, resistive touch panels have a limited lifespan—about 1 million touches per point—so if you’re building a kiosk or a device that gets heavy use, consider a glass-based resistive panel instead of a plastic one.

Let’s get into the electrical details. The ST7789V operates at 1.65V to 3.3V, but many modules include a 3.3V regulator that allows 5V input. Check the voltage regulator on your module—if it’s a AMS1117-3.3, you can power it from 5V, but the logic pins are still 3.3V tolerant. The XPT2046 also runs at 2.7V to 5.5V, so you can power it from the same 3.3V rail. The SPI lines should have pull-up resistors (10k ohms) to VCC, especially for MISO, because the XPT2046 has an open-drain output. If you’re using long wires (more than 10 cm), add series resistors (22 ohms) on SCLK and MOSI to reduce ringing. The T_IRQ pin is open-drain as well, so you need a pull-up resistor there too. On some modules, these resistors are already on the PCB, but verify with a multimeter—if you see 0 ohms between T_IRQ and VCC, it’s pulled up internally.

Here’s a table of typical pin assignments for a common 2.4-inch resistive TFT module with the ST7789V and XPT2046:

Pin Label Function Connect to Microcontroller
VCC Power (3.3V or 5V) 3.3V or 5V (check module)
GND Ground GND
SCLK SPI Clock GPIO 18 (ESP32)
MOSI SPI Data In GPIO 23 (ESP32)
MISO SPI Data Out (optional) GPIO 19 (ESP32)
CS TFT Chip Select GPIO 5 (ESP32)
DC Data/Command Select GPIO 2 (ESP32)
RST Reset (active low) GPIO 15 (ESP32)
T_CS Touch Chip Select GPIO 4 (ESP32)
T_IRQ Touch Interrupt GPIO 14 (ESP32)
T_DIN Touch SPI Data In Same as MOSI (GPIO 23)
T_DOUT Touch SPI Data Out Same as MISO (GPIO 19)

When you’re coding the initialization, you need to set up the SPI bus with the correct mode. The ST7789V uses SPI mode 0 (CPOL=0, CPHA=0) or mode 3 (CPOL=1, CPHA=1). Most libraries default to mode 0, but if you see a blank screen, try mode 3. The XPT2046 also uses mode 0, but it expects the data to be sent MSB first. The SPI clock frequency for the touch controller should be no higher than 2 MHz, because the XPT2046’s internal ADC takes time to settle. If you run it at 10 MHz, you’ll get erratic readings. I usually set the touch SPI to 1 MHz and the TFT SPI to 20 MHz, then switch between them using the CS lines. On an ESP32, you can use two different SPI buses (VSPI and HSPI) to avoid reconfiguring the clock speed, but that uses more pins. If you’re short on pins, share the bus and just change the clock speed before each transaction.

Power consumption is another factor. The ST7789V draws about 4 mA at 3.3V with the backlight off, and up to 20 mA with the backlight at full brightness. The backlight LED typically has a forward voltage of 3.0V and current of 40 mA, but it’s often driven by a transistor on the module. If you want to control brightness, you can PWM the backlight pin (usually labeled BL or LED). The XPT2046 draws about 1.5 mA during active conversion and 0.5 µA in power-down mode. To save power, you can put the XPT2046 into power-down mode by sending a command byte with the PD bit set to 1 (e.g., 0x90 for Y-axis with power-down). Then wake it up by sending a new command. This is useful for battery-powered devices.

For the touch calibration, I’ve found that the raw ADC values from the XPT2046 are not linear across the entire screen. The resistive film has a resistance of about 200-500 ohms per square, and the contacts at the edges introduce additional resistance. So the mapping from ADC value to pixel position is slightly curved. To fix this, you can use a quadratic regression or a bilinear interpolation. But for most applications, a simple linear calibration with a 5% margin at the edges works fine. Here’s a calibration routine I use:

1. Display a crosshair at pixel (10, 10) and ask the user to touch it. Record the raw ADC values (x1, y1).
2. Repeat for (230, 10), (10, 310), and (230, 310).
3. Compute scale factors: scale_x = (230 - 10) / (x2 - x1), scale_y = (310 - 10) / (y3 - y1).
4. Compute offsets: offset_x = 10 - x1 * scale_x, offset_y = 10 - y1 * scale_y.
5. For any touch, compute pixel_x = raw_x * scale_x + offset_x, pixel_y = raw_y * scale_y + offset_y.

This assumes the touch panel is aligned with the display, which it usually is on a factory-assembled module. But if you’re using a separate touch panel, you’ll need to account for rotation and skew. The 2.4 inch resistive tft display I mentioned earlier has the touch panel bonded to the TFT, so alignment is good out of the box.

One more thing about the display driver: the ST7789V has a built-in frame buffer of 240x320x16 bits, which is 153,600 bytes. That’s too big for most microcontrollers’ RAM, so you’ll need to send data in chunks. The Adafruit library uses a 512-byte buffer, which is fine for small updates. But if you’re drawing a full-screen image, you’ll need to send it row by row. The ST7789V supports a “window” command (0x2A for column address, 0x2B for row address) that lets you define a rectangular area to update. This is useful for partial updates, like a button press or a slider. For example, to update a 50x50 pixel button, you set the column address to 100-149 and the row address to 200-249, then send 2500 pixels of data. This reduces SPI traffic and improves responsiveness.

Finally, let’s talk about common issues you’ll run into. First, the display might show random lines or garbage after reset. This is usually because the initialization sequence is incomplete. Make sure you send the sleep-out command (0x11) and wait 120 ms before sending any other commands. Second, the touch might not respond at all. Check the T_IRQ pin with a voltmeter—it should be high when no touch is present and low when you press. If it’s always low, the touch controller is not initialized or the SPI bus is misconfigured. Third, the touch coordinates might be flipped or rotated. This is because the X and Y axes of the touch panel are swapped relative to the display. You can fix this in software by swapping the x and y values in your calibration routine. Fourth, the display might have a yellow tint, which is a common issue with ST7789V panels that have a different color filter. You can adjust the color balance by modifying the gamma registers or by using a color correction matrix in your drawing code.

For a real-world project, I used this display with an ESP32 to build a simple thermostat. The touch interface had three buttons: set temperature up, down, and confirm. I