Screen not responding to touch

I connected a 3.5in ili9488 TFT with xpt2046 to the esp32 wrover, the display itself worked fine, but it didn’t respond to the touch at all.

The test code I used was a touch calibration script which I used last year with LVGL V6 on an ili9341 TFT with xpt2046 touch controller. I only modified the code a little bit since some API’s been changed in V7, but it didn’t work out this time, as mentioned, display was good but not responding to my touch, and no errors showed either. Wiring double checked.

I measured the voltage between the touch irq pin and the ground, it dropped when I touch the screen, so the hardware was functioning.

It’s strange as last time when I worked with V6 it was very straight forward…

Where should I start to trouble shoot? BTW, I have zero background in C programming, I don’t think I can test the C driver…

This is the touch calibration script I used to test the screen.

import machine
import ujson
import utime

import lvgl as lv
import lvesp32

class TouchCali:
    def __init__(self, xpt2046_obj, cali_json_path='touch_cali.json'):
        self.touch_obj = xpt2046_obj
        self.cali_json_path = cali_json_path
        self.cali_counter = 0
        self.touch_cali_scr = lv.obj()
        self.scr_width = self.touch_obj.screen_width
        self.scr_height = self.touch_obj.screen_height
        self.marker_pos = [
            (int(self.scr_width * 0.6), int(self.scr_height * 0.4)),
            (int(self.scr_width * 0.1), int(self.scr_height * 0.1)),
            (int(self.scr_width * 0.9), int(self.scr_height * 0.1)),
            (int(self.scr_width * 0.1), int(self.scr_height * 0.9)),
            (int(self.scr_width * 0.9), int(self.scr_height * 0.9)),
            (int(self.scr_width * 0.4), int(self.scr_height * 0.6)),
        self.marker_x, self.marker_y = self.marker_pos[self.cali_counter]
        self.marker_x_coords = []
        self.marker_y_coords = []
        self.raw_x_coords = []
        self.raw_y_coords = []

        self.marker_label = lv.label(self.touch_cali_scr)
        self.marker_label.set_text('#FF0000 ' + lv.SYMBOL.PLUS + '#')
        # self.marker_label.align_origo(self.touch_cali_scr, lv.ALIGN.IN_TOP_LEFT, self.marker_x, self.marker_y)
        self.marker_label.align_mid(self.touch_cali_scr, lv.ALIGN.IN_TOP_LEFT, self.marker_x, self.marker_y)

        # text_style = lv.style_t()
        # lv.style_copy(text_style, lv.style_transp_tight)
        # text_style.text.font = lv.font_roboto_12
        self.text_label = lv.label(self.touch_cali_scr)
        # self.text_label.set_style(lv.label.STYLE.MAIN, text_style)
        # text_label.align(cali_scr, lv.ALIGN.IN_TOP_LEFT, marker_x, marker_y)
        self.text_label.set_text('Click the marker\nto calibrate.')
        # self.text_label.align_origo(self.touch_cali_scr, lv.ALIGN.CENTER, 0, 0)
        self.text_label.align_mid(self.touch_cali_scr, lv.ALIGN.CENTER, 0, 0)

    def start(self):

    def touch_cali_handler(self, obj, event):
        if event == lv.EVENT.PRESSED:
            if self.touch_obj.transpose:
                raw_y, raw_x = self.touch_obj.get_med_coords(3)
                raw_x, raw_y = self.touch_obj.get_med_coords(3)
            # globals()['coord_' + str(cali_counter)] = lv.label(cali_scr)
            # globals()['coord_' + str(cali_counter)].align(cali_scr, lv.ALIGN.IN_TOP_LEFT, marker_x, marker_y)
            # globals()['coord_' + str(cali_counter)].set_text('Raw_X: {}\nRaw_Y: {}'.format(raw_x, raw_y))
            if self.cali_counter < len(self.marker_pos) - 1:
                self.cali_counter += 1
                self.marker_x, self.marker_y = self.marker_pos[self.cali_counter]
                # self.marker_label.align_origo(self.touch_cali_scr, lv.ALIGN.IN_TOP_LEFT, self.marker_x, self.marker_y)
                self.marker_label.align_mid(self.touch_cali_scr, lv.ALIGN.IN_TOP_LEFT, self.marker_x, self.marker_y)
                # text_label.align(cali_scr, lv.ALIGN.IN_TOP_LEFT, marker_x, marker_y)
                self.text_label.set_text('#16A000 Calibration Done!#\n#16A000 Click the screen to reboot.#')
                # self.text_label.align_origo(self.touch_cali_scr, lv.ALIGN.CENTER, 0, 0)
                self.text_label.align_mid(self.touch_cali_scr, lv.ALIGN.CENTER, 0, 0)
                print('calibration done.')
                    lambda obj, event: machine.reset() if event == lv.EVENT.PRESSED else None)

    def touch_cali_result(self):
        cal_x0_list = []
        cal_x1_list = []
        cal_y0_list = []
        cal_y1_list = []
        counter = len(self.raw_x_coords) // 2
        for i in range(counter * 2):
            if i % 2 == 0:
                x1 = (-self.scr_width * self.raw_x_coords[i] + self.raw_x_coords[i] * self.marker_x_coords[
                    i + 1] + self.scr_width *
                      self.raw_x_coords[i + 1] - self.raw_x_coords[i + 1] * self.marker_x_coords[i]) \
                     / \
                     (-self.marker_x_coords[i] + self.marker_x_coords[i + 1])
                x0 = (self.scr_width * self.raw_x_coords[i] - self.marker_x_coords[i] * x1) \
                     / \
                     (self.scr_width - self.marker_x_coords[i])
                y1 = (-self.scr_height * self.raw_y_coords[i] + self.raw_y_coords[i] * self.marker_y_coords[
                    i + 1] + self.scr_height *
                          i + 1] - self.raw_y_coords[i + 1] * self.marker_y_coords[i]) \
                     / \
                     (-self.marker_y_coords[i] + self.marker_y_coords[i + 1])
                y0 = (self.scr_height * self.raw_y_coords[i] - self.marker_y_coords[i] * y1) \
                     / \
                     (self.scr_height - self.marker_y_coords[i])


        cal_x0 = int(sum(cal_x0_list) / len(cal_x0_list))
        cal_x1 = int(sum(cal_x1_list) / len(cal_x1_list))
        cal_y0 = int(sum(cal_y0_list) / len(cal_y0_list))
        cal_y1 = int(sum(cal_y1_list) / len(cal_y1_list))
        print('cal_x0 = {}; cal_x1 = {};'.format(cal_x0, cal_x1))
        print('cal_y0 = {}; cal_y1 = {};'.format(cal_y0, cal_y1))
        with open(self.cali_json_path, 'w') as f:
            data = {
                'cal_x0': cal_x0,
                'cal_x1': cal_x1,
                'cal_y0': cal_y0,
                'cal_y1': cal_y1,
                ujson.dump(data, f)
                print('Error occurs when saving calibration results.')
                print('Calibration params saved.')

This is the pin configuration.

# The VCC & LED pins of the TFT are directly connected to 3.3V pin of the dev board
TFT_CS_PIN = const(13)
TFT_SCK_PIN = const(19)
TFT_MOSI_PIN = const(18)
TFT_MISO_PIN = const(23)
TFT_DC_PIN = const(26)
TFT_RST_PIN = const(4)
TFT_WIDTH_PX = const(480)
TFT_HEIGHT_PX = const(320)
TFT_PORTRAIT = const(0x40)  # See line 54 in driver file
TFT_LANDSCAPE = const(0x20)  # See line 55 in driver file

# The XPT2046 touch controller shares the same SPI bus with the TFT screen
TOUCH_CS_PIN = const(25)
TOUCH_CALI_FILE = 'touch_cali.json'  # json file used to store touch calibration params

This is the

import lvgl as lv
import lvesp32
import ujson
import uos

from ili9XXX import ili9488
from xpt2046 import xpt2046

from config import *


disp = ili9488(

touch_args = dict()
touch_args['cs'] = TOUCH_CS_PIN
touch_args['transpose'] = TOUCH_TRANSPOSE

file_list = uos.listdir()

if TOUCH_CALI_FILE in file_list:
    with open(TOUCH_CALI_FILE, 'r') as f:
        cali_params_dict = ujson.load(f)

touch = xpt2046(**touch_args)

if TOUCH_CALI_FILE not in file_list:
    from touch_cali import TouchCali
    touch_cali = TouchCali(touch, TOUCH_CALI_FILE)
    scr = lv.obj()
    btn = lv.btn(scr)
    btn.align(lv.scr_act(), lv.ALIGN.CENTER, 0, 0)
    label = lv.label(btn)
    label.set_text("Hello World!")

    # Load the screen


You can try using for calibration, it was ported to v7.

It’s not clear to me how you initialize the SPI bus for xpt2046.
I’ve noticed that you read the arguments of xpt2046 from a file. Could you print out the arguments before calling xpt2046 constructor? Let’s make sure all the SPI arguments are correct.
Are you sharing the SPI bus with the ili9488? If you do, did you try using a separate SPI bus? (although it should work with a shared bus as well).

I started all over again by removing all the wires first, and then reconnected them to the default pins per ili9XXX & xpt2046 drivers (they shared the same SPI bus), and imported tpcal (also changed a bit in to use ili9488 instead of ili9341), again graphic display was good, but still not responding to the touch.

Then I tried to use a separate spi bus for the xpt2046. I set spihost=1 for ili9488, and spihost=2 for xpt2046, but the spi bus initializing always failed for xpt2046, the error said “some dma channels are in use”.

Anything else I can try?

I have a spare ili9341 display with xpt2046 at hand, so I reflashed the firmware and tried again with that ili9341 display, and it worked! All wirings remain the same. Can I assume it’s a hardware issue my ili9488 display? The irq pin voltage level did respond to the touch though…

Try this with a different ili9488+xpt2046 display, maybe the one you are using is malfunctioning.
Another thing you can try is the C (non micropython) driver with some C example project.

Tried resoldering the pins of the xpt2046 chip, no luck…

I ordered another ili9488 with xpt2046, will test and report back when I get it.


Well, I have received a new ili9488 + xpt2046 display, but the touch doesn’t work either :frowning:

How can I initialize the xpt2046 on a separate spi bus? Setting spihost=1 for ili9488 and spihost=2 for xpt2046 gave me an error “some dma channels are in use”.

I googled around and found this discussion, it’s not just me having this issue with ili9488 + xpt2046 display.

The temporary solution is to disconnect the MISO of the ili9488, so only the Dout of the xpt2046 is connected to the SPI bus. However the side effect is that the refreshing of the display becomes notably slower.

Is there a workaround other than disconnecting MISO of TFT which causes slow refreshing rate?

I don’t see why it would affect the display speed. Do you have any idea?
The display driver only writes data to the display and never reads, so MISO is not really used.

I wasn’t aware of this problem of ili9488, but disconnecting MISO sounds like a good option if you solve the refresh rate problem.
Another option is to use the other SPI host, but I don’t know why you are getting errors when you try to do that. Perhaps there is some other device registered to the other SPI host, maybe the Flash or an sdcard.

It may be not very accurate when I said “slower refreshing rate”, that’s purely based on my observation - when the MISO was NOT connected, I ran import tpcal from the REPL, the scanning from the top to the bottom of the screen can be seen clearly like a slowmotion, by scanning I meant the display changed from blank to show content in a scanning fashion. And after the calibration, dragging the circle around was not smooth. However, with the MISO connected, the scanning was much faster, the screen switched from blank to the content almost instantly. I have no idea why caused this.

Regarding the SPI, I think the extenal psram of the Wrover module was using the SPI bus, thanks for reminding.

  • What happens if you disconnect both MISO and xpt2046? Is the display still slow? (make sure MISO is not floating, just pull it to up or down).
  • Did you try both half-duplex and full-duplex?

Disconnecting both MISO and xpt2046 makes no difference, but connecting the MISO of ili9488 to 3.3V to pull it up seems to be helpful in the speed.

Tried both half_duplex=True and half_duplex=False, no difference.

Even if I set double_buffer=True when initialized the ili9488 constructor, it still showed Single buffer in REPL, why is that?

I suggest you add a 10K resistor if you didn’t, and not connect MISO directly to VCC.
What happens if you keep both ili9488 and xpt2046 connected, but instead of connecting the display’s MISO to the SPI bus, you pull it up? Did you try that?

When double_buffer=True the display driver tries to allocate two buffers. But if there is not enough DMA-able RAM available, it would allocate only a single buffer.
You can try setting factor=8 (or a larger value) to make the buffer smaller, such that two buffer would fit in the DMA-able RAM.

Not sure I understood you correctly - this is how I did it: I connected the display’s MISO to GPIO 26, and pull the pin up by setting machine.Pin(26, machine.Pin.OUT, machine.Pin.PULL_UP), then import tpcal. The refreshing speed seemed to be not different from connecting the MISO to 3.3v. In fact, pull the pin up or down, or even simply connect MISO to the GND of ESP32 makes no difference, but leaving the MISO unconnected will cause that speed issue.

Regarding double_buffer, I tried different values, it need to be larger than 10, however with ili9431, the double_buffer is enabled with default settings. Double or single buffer, will it make a difference in the performance?

Another thing I noticed: the display had a lag when responding to the very first touch. Take the tpcal for example, it requires the circle to be clicked 5 times, it will take half a second ~ish for the counter to change from 5 to 4 after the very first touch, then it becomes more responsive as expected. This issue is not related to the wiring. Is this normal?

Well, what I meant was to physically connect the display’s MISO to either GND or to VCC (through a resistor). The display driver never reads anything from the display, it only writes, so you could simply leave that GPIO unconnected. On the other hand, you need to connect the touch panel’s MISO to GPIO.

That depends. The advantage of double buffer is that while the SPI DMAs data to the display from one buffer, the CPU can render the next frame to the other buffer on the same time.
But if rendering is very fast and DMA is slow (or the other way around) then it won’t matter.
You can simply check what is the FPS with/without double buffer, and with different values of factor.

I didn’t notice that. Is this special to ili9488+xpt2046? Or does it happen with ili9431 too?
Also, is this specific to tpcal? Does it happen with a simple screen and a button?

I haven’t tried it with ili9431, or any other widgets on ili9488. I will report back.

Thanks for answering all my questions. Much appreciated!

I encountered another strange issue with the ili9488 + xpt2046.

After the previous discussion on wiring, everything worked fine, I could calibrate the touch with either tpcal or my own touch calibration script. So I started from the very basic hello world button example to play with the widgets, however it always prompt an error messge after the lvgl had initialized for 10-15mins, even if I just left the display there doing nothing.

>>> Traceback (most recent call last):
  File "", line 209, in read
  File "", line 181, in get_coords
  File "", line 170, in get_med_coords
AttributeError: 'list' object has no attribute 'CMD_X_READ'

The REPL was still working, but the display froze and wouldn’t respond to any command.

According to the traceback, this is the method get_med_coords:

    def get_med_coords(self, count : int):
        mid = count//2
        values = []
        for i in range(0, count):
            values.append(self.xpt_cmds([self.CMD_X_READ, self.CMD_Y_READ]))
        # values = self.xpt_cmds([self.CMD_X_READ]*count + [self.CMD_Y_READ]*count)
        # x_values = sorted(values[:count])
        # y_values = sorted(values[count:])
        x_values = sorted([x for x,y in values])
        y_values = sorted([y for x,y in values])
        if int(x_values[0]) == 0 or int(y_values[0]) == 0 : return None
        return x_values[mid], y_values[mid]

And this is line 170 that gave the error, but I can’t figure out the reason since CMD_X_READ is defined as contant.

values.append(self.xpt_cmds([self.CMD_X_READ, self.CMD_Y_READ]))

I tried several times to reproduce the error.

If I restart the board and leave the hello world button there, it will eventually freeze without any error message (usually 10-15min after the restart), the REPL freezes too.

If I create a couple of labels on the screen along with the hello world button, the error message will pop up. But this time it’s:

>>> Traceback (most recent call last):
  File "", line 209, in read
  File "", line 181, in get_coords
  File "", line 170, in get_med_coords
  File "", line 158, in xpt_cmds
AttributeError: 'Blob' object has no attribute '__dereference__'

This smells like memory corruption.
Next step is to identify which component is causing it.
So I suggest trying to remove/disable different components and see when the problem disappears.

  • Is the problem unique to ili9488? Or does it happen with ili9341+xpt2046 as well?
  • Is the problem unique to xpt2046? You can try not to register touch but instead trigger events manually and see if this reproduces.
  • Is it related to lvesp32 module? A different option is to use uasyncio instead.

The esp32 board I’m using has 4MB external spiram.

I will try to start with disabling the xpt2046 module since the tracebacks all point to it.

Then I will try on an ili9341 + xpt2046.

For lvesp32 module, I’m afraid that at the moment I don’t have enough understanding on the mechanism to replace it with uasyncio…