Skip to Content

Interface TFT ILI9341 Touch Display with ESP32

Interface TFT ILI9341 Touch Display with ESP32

In this tutorial you will learn how to control a 2.8 inch 240×320 TFT ILI9341 Touch Display with an WEMOS Lolin32 lite (ESP32) using the TFT_eSPI library.

The instructions and code will work with some minor changes for other ESP32’s and TFT’s as well, as long as the display uses the ILI9341 display driver and the XPT2046 touch controller.

Required Parts

You will need an ESP32 and an 2.8 inch TFT Touch Display with a resolution of 240×320 pixels and an ILI9341 display driver IC. Some cables and a breadboard might come in handy as well.

2.8 inch TFT ILI9341 Touch Display

ESP32 lite Lolin32

ESP32 lite

USB data cable

USB Data Cable

Dupont wire set

Dupont Wire Set

Half_breadboard56a

Breadboard

Makerguides.com is a participant in the Amazon Services LLC Associates Program, an affiliate advertising program designed to provide a means for sites to earn advertising fees by advertising and linking to products on Amazon.com. As an Amazon Associate we earn from qualifying purchases.

2.8″ TFT ILI9341 Touch Display Module

There are a few variations and clones of this 2.8 inch Display Module but they tend to be very similar and all/most of them should work. The TFT display has a resolution of 240×320 pixels with 65K RGB colors and is controlled via SPI using the ILI9341 display driver chip.

In addition, the display has a resistive touchscreen attached with a XPT2046 touch controller IC. Furthermore, there is also a MicroSD card socket at the back, which you could use to store images, for instance. The picture below shows the front and back of the display module.

Front and back of 2.8" TFT ILI9341 Touch Display Module
Front and back of 2.8″ TFT ILI9341 Touch Display Module (source)

The display module has has a built in voltage regulator and you can power it with 3.3V … 5V on VCC. However, there is no logic level shifter, which means you cannot connect directly connect an Arduino Uno that operates with 5V logic to the SPI interface of the module that operates with 3.3V!

Connect TFT ILI9341 Display Module to Arduino

If you want to use the display with an Arduino you will need a level shifter. You can use voltage dividers or a proper level shifter module. As a hack Techtonics suggests to add 10k resistors on the SPI lines:

Connect Arduino Uno to TFT Display using 10K resistors
Connect Arduino Uno to TFT Display using 10K resistors (source)

This will probably work (I did not try this) but using a proper level shifter module would be a more reliable and safer solution. Alternatively, simply employ a microcontroller that operates on 3.3V logic, such as the ESP32 that we are going to use here.

Run TFT ILI9341 Touch Display Module on 3.3V

If you know that you will run the Display Module on 3.3V (VCC=3.3V), you can bypass the voltage regulator (U1) by closing (soldering) the jumper pads labeled J1 at the back of the module. See the picture below:

Jumper J1 to bypass Voltage Regulator

This potentially will make the display to run more stably since you avoid the voltage drop of the regulator and it also will reduce power consumption by a tiny bit. See the schematics of the voltage regulator below and how the J1 jumper affects the connection:

Schematic for Voltage Regulator and Bypass Jumper J1
Schematic for Voltage Regulator and Bypass Jumper J1 (source)

I did not close J1 (left it open as it is) and the display was working fine but should you have stability issues, you can try closing J1.

However, once you soldered (closed) J1 you cannot provide 5V to VCC anymore! You must use 3.3V on VCC!

Pinout of TFT ILI9341 Touch Display Module

The picture below shows the back of the Display Module with the connector pins. You can see two groups: the pins for the touch controller and the SPI pins for the display below:

Pinout of TFT ILI9341 Touch Display Module
Pinout of TFT ILI9341 Touch Display Module

The following table taken from lcdwiki lists the individual pins and their functions:

NumberPin LabelDescription
1VCC5V/3.3V power input
2GNDGround
3CSLCD chip select signal, low level enable
4RESETLCD reset signal, low level reset
5DC/RSLCD register / data selection signal,high level: register, low level: data
6SDI(MOSI)SPI bus write data signal
7SCKSPI bus clock signal
8LEDBacklight control, if not controlled, connect 3.3V
9SDO(MISO)SPI bus read data signal, if you do not need to the read function, don’t connect it
10T_CLKTouch SPI bus clock signal
11T_CSTouch screen chip select signal, low level enable
12T_DINTouch SPI bus input
13T_DOTouch SPI bus output
14T_IRQTouch screen interrupt signal, low level when touch is detected

Connecting the TFT ILI9341 Touch Display to ESP32

The following picture shows how to connect the Touch Display module to an WEMOS Lolin32 lite (ESP32):

Connecting the TFT ILI9341 Touch Display to ESP32
Connecting the TFT ILI9341 Touch Display to ESP32

There are quite a few connections to make and the following table should help. Note that the SDI(MOSI) and SCK lines of the SPI interface are shared between the touch and the TFT display controller:

ESP32TFTTouch
5CS
4T_CS
17RESET
16DC
23SDI(MOSI)T_DIN
18SCKT_CLK
19T_DO
22LED

You could connect pin 19 of the ESP32 to SDO(MISO) as well but since we don’t read data from the TFT display controller we won’t need it and there has been reports that in some cases this causes issues. I did not connect pin 19 to SDO(MISO) and the display was working fine.

Note that we also leave T_IRQ of the display module unconnected. This pin signals if a touch on the display was detected and you could use it to wake up the ESP32 from deep-sleep, for instance. However, in this tutorial we don’t implement this functionality.

The wiring is quite complex and I connected two breadboards to place the ESP32 and the TFT display on it. The good news is, apart from ground (GND) all connections are on one side of the ESP32, which simplifies the wiring a bit. The picture below shows my setup:

Wiring of TFT ILI9341 Touch Display with ESP32 on breadboard
Wiring of TFT ILI9341 Touch Display with ESP32 on breadboard

Backlight Control for TFT ILI9341 Touch Display

Note that the backlight LED of the module is switched via a transistor and we therefore can control it directly via a GPIO, in our case pin 22 of the ESP32 is used. See the schematic of the LED control circuit below:

Schematic for LED backlight
Schematic for LED backlight (source)

Touch Controller for TFT ILI9341 Touch Display

As mentioned before the touch controller for the display module is a XPT2046. The schematic below shows how the controller is connected within the display module.

Schematic for Touch Controller
Schematic for Touch Controller (source)

SD Card Socket for TFT ILI9341 Touch Display

Finally, the display module has an SD Card Socket. The picture below shows the schematics for this SD Card Socket. Internally it is only connected to VCC and GND.

Schematic for SD Card Socket
Schematic for SD Card Socket (source)

The SPI interface (SD_CS, SD_MOSI, SD_CLK) is accessible via external pins on the back of the module. But note that SD_MISO is not connected:

SPI pins for SD Card Socket

However, we will not use the SD Card Socket in this tutorial.

Code for TFT ILI9341 Touch Display with TFT_eSPI Library

In the this section, we are going to utilize the TFT_eSPI library to control the display and the touch interface. To install this library open the Library Manager, search for “TFT_eSPI” and press “INSTALL”. After a successful installation it should look like this:

TFT_eSPI library in Library Manager
TFT_eSPI library in Library Manager

Next we need to create the correct project folder structure. Open your Arduino IDE and create a project “tft_test” and save it (Save As …). This will create a folder “tft_test” with the file “tft_test.ino” in it. In this folder create another file named “tft_setup.h“. Your project folder should look like this

Project Folder Structure
tft_test

If you want to learn more about this setup and other options to configure a TFT display for the TFT_eSPI library have a look at the How to configure TFT_eSPI Library for TFT display tutorial.

tft_setup.h for TFT ILI9341 Touch Display

After the project folder with the two files is created, copy the following configuration code for the TFT display into the tft_setup.h file:

// tft_setup.h
// 2.8" TFT Touch Display
// 240x 320, Driver: ILI9341 

#define ILI9341_DRIVER      
//#define ILI9341_2_DRIVER  

#define TFT_WIDTH  240
#define TFT_HEIGHT 320
#define TFT_RGB_ORDER TFT_BGR  

// WEMOLS Lolin32 lite
#define TFT_CS    5   
#define TFT_RST   17  
#define TFT_DC    16   
#define TFT_MOSI  23  // SDA // HW MOSI
#define TFT_SCLK  18  // SCL // HW SCLK
#define TFT_MISO  19  // HW MISO
#define TFT_BL    22  // LED back-light
#define TFT_BACKLIGHT_ON HIGH

#define TOUCH_CS 4 
#define TOUCH_CLK TFT_SCLK
#define TOUCH_DIN TFT_MOSI
#define TOUCH_DO TFT_MISO

#define LOAD_GLCD   
#define LOAD_FONT2  
#define LOAD_FONT4  
#define LOAD_FONT6  
#define LOAD_FONT7  
#define LOAD_FONT8  
#define LOAD_GFXFF
#define SMOOTH_FONT 

#define SPI_FREQUENCY  27000000
#define SPI_READ_FREQUENCY  20000000
#define SPI_TOUCH_FREQUENCY  2500000

The most important part of this configuration file is to select the correct display driver. In our case this is the ILI9341. Note that there is an alternative definition:

#define ILI9341_DRIVER      
//#define ILI9341_2_DRIVER  

If you have issues with your display, try the ILI9341_2_DRIVER instead of the ILI9341_DRIVER.

Also important are the constants for the width and height of the display and the order of the color channels.

#define TFT_WIDTH  240
#define TFT_HEIGHT 320
#define TFT_RGB_ORDER TFT_BGR  

If your content appears cut off, or the colors of the displayed content are wrong, make sure that you have the dimensions (TFT_WIDTH, TFT_HEIGHT) and the TFT_RGB_ORDER correct. The TFT_RGB_ORDER can be TFT_BGR or TFT_RGB.

Note, that there is also a definition to invert black and white (#define TFT_INVERSION_ON), should you encounter this problem. For all the possible settings see the User_Setup.h file.

Next we have the pin definitions. I am using a WEMOS Lolin32 lite (ESP32) and the pins for Hardware SPI are (MOSI=23, MSIO=19, SCK=18):

#define TFT_CS    5   
#define TFT_RST   17  
#define TFT_DC    16   
#define TFT_MOSI  23  // SDA // HW MOSI
#define TFT_SCLK  18  // SCL // HW SCLK
#define TFT_MISO  19  // HW MISO
#define TFT_BL    22  // LED back-light
#define TFT_BACKLIGHT_ON HIGH

#define TOUCH_CS 4 
#define TOUCH_CLK TFT_SCLK
#define TOUCH_DIN TFT_MOSI
#define TOUCH_DO TFT_MISO

Depending on your microcontroller these pins may differ. You should find and use the pins for Hardware SPI, since it allows a faster communication and therefore a faster display. The other pins you can select freely.

The constants for the fonts you usually don’t have to change. However, should you run out of memory, you can remove unused fonts from the list.

#define LOAD_GLCD   
...
#define SMOOTH_FONT 

The constants for SPI_FREQUENCY and SPI_TOUCH_FREQUENCY are somewhat critical. If there are two high you will see distorted content and the touch recognition will be brittle. The values here worked for me, but for a different microcontroller or display you may have to lower them.

#define SPI_FREQUENCY  27000000
#define SPI_READ_FREQUENCY  20000000
#define SPI_TOUCH_FREQUENCY  2500000

Test code for TFT ILI9341 Touch Display

In this section I’ll show you some test code you can use to try out the display and the touch detection. Just copy the following code into the tft_test.ino file:

// tft_test.ino
#include "tft_setup.h"
#include "TFT_eSPI.h"

TFT_eSPI tft = TFT_eSPI();
uint16_t cal[5] = { 0, 0, 0, 0, 0 };

void calibrate_touch() {
  if (!cal[1]) {
    tft.fillScreen(TFT_BLACK);
    tft.calibrateTouch(cal, TFT_YELLOW, TFT_BLACK, 20);
    Serial.printf("cal[5] = {%d, %d, %d, %d, %d};\n",
                  cal[0], cal[1], cal[2], cal[3], cal[4]);
  }
}

void setup(void) {
  Serial.begin(115200);

  tft.init();
  tft.setRotation(1);
  calibrate_touch();
  tft.setTouch(cal);

  tft.fillScreen(TFT_BLACK);
  tft.setTextFont(1);
  tft.setTextSize(2);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextDatum(CC_DATUM);
  tft.drawString("Makerguides", TFT_HEIGHT / 2, TFT_WIDTH / 2);
}

void loop() {
  uint16_t x, y;
  if (tft.getTouch(&x, &y)) {
    Serial.printf("%d %d\n", x, y);
    tft.fillCircle(x, y, 2, TFT_YELLOW);
  }
}

The code starts by including the required library and the configuration file.

#include "tft_setup.h"
#include "TFT_eSPI.h"

Next we create the TFT display object and an array to store the calibration parameters for the touch screen:

TFT_eSPI tft = TFT_eSPI();
uint16_t cal[5] = { 0, 0, 0, 0, 0 };

calibrate_touch Function

Initially the calibration parameters are set to zero but we will fill them in later. The calibrate_touch() function is used to retrieve these calibration parameters:

void calibrate_touch() {
  if (!cal[1]) {
    tft.fillScreen(TFT_BLACK);
    tft.calibrateTouch(cal, TFT_YELLOW, TFT_BLACK, 20);
    Serial.printf("cal[5] = {%d, %d, %d, %d, %d};\n",
                  cal[0], cal[1], cal[2], cal[3], cal[4]);
  }
}

If they are not yet set (!cal[1]), the function clears the screen and then calls tft.calibrateTouch(), which fills the cal array. This function draws arrows (in yellow color with a black background and a size of 20 pixels) in the corners of the display and the user has to touch the corners to calibrate the display. More about that later. Once we have the cal parameters, we print them to the Serial Monitor.

setup Function

In the setup() function we initialize the Serial Monitor and the TFT screen, calibrate the touch screen if necessary and then print the text “Makerguides” on the display:

void setup(void) {
  Serial.begin(115200);

  tft.init();
  tft.setRotation(1);
  calibrate_touch();
  tft.setTouch(cal);

  tft.fillScreen(TFT_BLACK);
  ...
  tft.drawString("Makerguides", TFT_HEIGHT / 2, TFT_WIDTH / 2);
}

loop Function

In the loop() function we call getTouch to check if touch was detected. If so, we print the coordinates of the touch to the Serial Monitor and draw a small yellow circle at the touch point:

void loop() {
  uint16_t x, y;
  if (tft.getTouch(&x, &y)) {
    Serial.printf("%d %d\n", x, y);
    tft.fillCircle(x, y, 2, TFT_YELLOW);
  }
}

Backlight Control

Finally, if you want to switch of the backlight LED of the display to reduce power consumption when the display is unused, you can set the TFT_BL pin to LOW:

pinMode(TFT_BL, OUTPUT);
digitalWrite(TFT_BL, LOW);    // Switch off Backlight  

I don’t use this in this code but it works. This could be useful to conserve power when putting the ESP32 in deep-sleep and only wake up when a touch was detected.

In the next section, we will learn how to calibrate the touch screen.

Calibration of TFT ILI9341 Touch Display

If you upload and run the code the display will start in calibration mode. This is needed to calibrate the touch screen so that the tft.getTouch(&x, &y) function returns the correct screen coordinates for a touch.

In calibration mode the display first shows a yellow arrow in the left upper corner. Use the pen and touch the display at the corner the arrow is pointing to. Note, if you want bigger/smaller arrows, or a different color or background you can change the parameters for the tft.calibrateTouch() function:

tft.calibrateTouch(cal, TFT_YELLOW, TFT_BLACK, 20);

If the touch was registered successfully, the display show an arrow in the right upper corner next. Touch this corner and repeat the process until all four corners have been touched. The picture below shows the four arrows during the four steps of the calibration process:

Calibration of TFT ILI9341 Touch Display

After the fourth touch the display will show the text “Makerguides in the center (without any arrows):


End of calibration of TFT ILI9341 Touch Display
End of calibration of TFT ILI9341 Touch Display

Most importantly the code will also print out the calibration parameters cal on the Serial Monitor. You should see a print out similar to the following:

cal[5] = {397, 3495, 294, 3495, 7};

Copy these values and replace the zero calibration parameters for the cal constant (cal[5] = { 0, 0, 0, 0, 0 }) in the tft_test.ino sketch by the values you read on the Serial Monitor:

// tft_test.ino

...

uint16_t cal[5] = {397, 3495, 294, 3495, 7};

void calibrate_touch() {
...

Then compile and upload the code again.

Detecting touches with TFT ILI9341 Touch Display

When the cal parameters are set, the code will skip the calibration step, directly print “Makerguides” and will be ready to detect touch inputs. Use your pen and a yellow dot should appear where ever you touch the display. Below a picture of the display with me fooling around with touch inputs:

Touch input on TFT ILI9341 Touch Display
Touch input on TFT ILI9341 Touch Display

If the yellow dots appear offset from the place where you touched the display, the calibration is incorrect. You can redo the calibration be setting the cal parameters back to zero (cal[5] = { 0, 0, 0, 0, 0 }).

Note that the Serial Monitor will print out the detected touch coordinates once the calibration is completed. You can use this to check the calibration or to add buttons that react on touch, for instance.

241 51
240 54
240 53
241 47
243 46
...

See the Digital Clock with CrowPanel 3.5″ ESP32 Display tutorial for an example.

Conclusions

In this tutorial you learned how to control a 2.8 inch 240×320 TFT ILI9341 Touch Display with an WEMOS Lolin32 lite (ESP32) using the TFT_eSPI library.

If you have difficulties with TFT_eSPI the library, our How to configure TFT_eSPI Library for TFT display might help. There is also a lengthy discussion here, regarding issues with this specific display.

Should you have a different display with a ST7735 driver IC or want to learn how to use the Adafruit Library, the Interface TFT ST7735 Display with ESP32 tutorial might be useful for you.

And for round TFT displays have a look at the Digital Clock on CrowPanel 1.28″ Round Display tutorial.

If you have any comments, feel free to leave them in the comment section.

Happy Tinkering ; )

Maf70

Sunday 23rd of February 2025

Many thanks for the high quality of this article ! Just a remark: on the SD connecteur on my ili9341 board, MISO is connected via a track on the screen side of the pcb (without 1K resistor) . I suppose it is the same on your board.

Stefan Maetschke

Sunday 23rd of February 2025

Thanks for the info. Much appreciated!