Inject a touch event

Description

I am wanting to use the interrupt of a touch screen interface instead of having LVGL constantly polling it to see if there are any touches that are occurring.

What MCU/Processor/Board and compiler are you using?

ESP32

What LVGL version are you using?

9.0

What do you want to achieve?

Stop LVGL from polling the touch screen driver

What have you tried so far?

Nothing, in the process of collecting information to see if it can be done.

Did you ever get info on this? I would like to do the same as well.
I have an idea but not sure if it’s the right approach

The easiest way to go about doing it is going to be using the indev driver callback function.

Do you have a code example?

Hi @reso,

I took a quick look at the indev code… This may or may not work as I can’t test it right now…

I think if you register your indev drivers as usual but after they are registered call:

lv_timer_del( indev_drv.read_timer );

To stop LVGL from polling.

Then just have your input device interrupt handler trigger a call to:

lv_indev_read_timer_cb( NULL );

It should hopefully work for you…

cc @kdschlosser

Kind Regards,

Pete

I’m currently trying this as well, but it seems that the structure with lvgl 8.3.7 is different, as seen below… I keep getting an error.

Then I have another question:
How can I manually send touch coordinates to a widget (e.g., lv_arc) and not automatically via LVGL?

image

Hi @epikao ,

Your code is just structured slightly differently to my example(I have changed my original post for simplicity), for your code this should work:

lv_timer_del( indev_drv.read_timer );

To send touch coordinates, you would need to write the coordinates to the variables which are updated by your my_touchpad_read() function and then call lv_indev_read_timer_cb( NULL );

Kind Regards,

Pete

Ok, is it possible to send the touch coordinates directly to a lv_arc widget for example?

There is no mechanism to send coordinates directly to a widget that I am aware of this is best left to the internals of LVGL in my opinion, can you describe what you are trying to achieve?

The method described above would emulate a touch or a click on the screen where ever you would like then the internals of LVGL work out which widget the click has hit etc.

see follwing video link:

As long as you don’t release your finger from the touch, you can still control a widget even when your finger is no longer within the widget’s area.
This is actually undesired for me. How can this be deactivated?

Best point to change this is your indev read callback or value changed callback.

Hi @epikao ,

You should be able to clear the press lock flag:

lv_obj_clear_flag(your_slider, LV_OBJ_FLAG_PRESS_LOCK);

Hopefully that will work for you…

Kind Regards,

Pete

Please help us under stand by showing code examples how you achieve…

  1. stoping the touch screen polling
  2. and using the INDEV driver

you don’t stop the polling. you inject a fake touch event into the polling loop using the callback function.

as an example. This is in Python

PORTRAIT = const(-1)
LANDSCAPE = const(-2)
REVERSE_PORTRAIT = const(-3)
REVERSE_LANDSCAPE = const(-4)


height = 320
width = 480


rot = LANDSCAPE

# this is a class instance that stores calibration data for the touch screen
config = None

class ft6x36_touch_input_t:
    last_x = -1
    last_y = -1
    current_state = 0


# function to alter the touch coordinates for rotation and also calibration
def remap(value, old_min, old_max, new_min, new_max):
    return int((
        ((value - old_min) * (new_max - new_min)) / (old_max - old_min)
    ) + new_min)


data_buf = bytearray(5)


# this would be specific to the touch display you are using.
# it returns x, y coordinates of where the display as been touched.
def _get_coords(self):

    try:
        i2c.read(FT6X36_TD_STAT_REG, buf=data_buf)
    except OSError:
        return None

    touch_pnt_cnt = data_buf[0]

    if touch_pnt_cnt != 1:
        return None

    x = (
        ((data_buf[1] & FT6X36_MSB_MASK) << 8) |
        (data_buf[2] & FT6X36_LSB_MASK)
    )
    y = (
        ((data_buf[3] & FT6X36_MSB_MASK) << 8) |
        (data_buf[4] & FT6X36_LSB_MASK)
    )
    return x, y

touch_injection = False
injected_x = -1
injected_y = -1


def read(drv, data):
    global touch_injection

    coords = get_coords()

    # check to see if the data returned is valid, if it is not then change the touched state to be
    # released and pass the last known touch coordinates to LVGL
    if not touch_injection and coords is None:  
        if ft6x36_touch_input_t.current_state != lv.INDEV_STATE.RELEASED:
            ft6x36_touch_input_t.current_state = lv.INDEV_STATE.RELEASED
            res = True
        else:
            res = False

        data.point.x = ft6x36_touch_input_t.last_x
        data.point.y = ft6x36_touch_input_t.last_y
        data.state = ft6x36_touch_input_t.current_state
        return res

    this is where the injected input gets placed into the callback stream so LVGL can process it. 
    if touch_injection:
        xpos = injected_x
        ypos = injected_y 

        touch_injection  = False
    else:
        x, y = coords

        # handle display rotation and calibration
        if rot == PORTRAIT:
            xpos = remap(x, config.left, config.right, 0, width)
            ypos = remap(y, config.top, config.bottom, 0, height)
        elif rot == LANDSCAPE:
            xpos = remap(y, config.top, config.bottom, 0, width)
            ypos = remap(x, config.right, config.left, 0, height)
        elif rot == REVERSE_PORTRAIT:
            xpos = remap(x, config.right, config.left, 0, width)
            ypos = remap(y, config.bottom, config.top, 0, height)
        elif rot == REVERSE_LANDSCAPE:
            xpos = remap(y, config.bottom, config.top, 0, width)
            ypos = remap(x, config.left, config.right, 0, height)
        else:
            raise RuntimeError

    # set the coordinates to store for later
    ft6x36_touch_input_t.current_state = lv.INDEV_STATE.PRESSED
    ft6x36_touch_input_t.last_x = xpos
    ft6x36_touch_input_t.last_y = ypos

    # set the data to bbe sent to LVGL
    data.point.x = ft6x36_touch_input_t.last_x
    data.point.y = ft6x36_touch_input_t.last_y
    data.state = ft6x36_touch_input_t.current_state

    return True


indev_drv = lv.indev_t()
indev_drv.init()
indev_drv.type = lv.INDEV_TYPE.POINTER
indev_drv.read_cb = read
indev_t = indev_drv.register()

By reading that I thought you were trying to stop the polling but in fact you don’t mind it, you just want to inject a manual touch.
I am actually trying to stop the polling because it interferes with an SD card on the same bus.
I also prefer to use an interrupt when needed which is very infrequent and eliminates the SPI noise.

If @kdschlosser have any thoughts how to achieve this would be much appreciated !!!
More details are in this thread…

LVGL calling the function to poll for touch input really should not interfere with external communications to an SD card. If your touch controller has interrupt based touch detection, you could use that interrupt to set a flag. The when LVGL calls the touch_cb, only read the touch value from your touch controller when that flag is positive.
LVGL itself will not hog your SPI unless you somehow programmed the touch and/or driver callback to do so.

I have actually found that quite a few touch interfaces that have an interrupt for when touch occurs and for whatever reason they do not work properly. They typically send an endless stream of interrupts even when no touch is taking place. The other thing is that LVGL is not written in a manner that allows a user to use interrupts in the manner in which they are intended. The polling loop to collect touch input still has to take place so the only thing that occurs when an interrupt takes place is it changes the data that the polling loop collects. So there really is no point to using an interrupt in the first place. The other problem is the allocation of memory in an ISR. So you couldn’t do anything other than collect the data from the touch driver and that data still has to get collected via the main loop. Using an interrupt to collect touch input is not really beneficial and it actually has a cost to use it and it would make things run slower (not noticeable to the user but it does impact performance)

All you have to do to stop the loop is create a boolean variable at the bottom most level and check that variable before you collect touch information from the SPI bus. So say you call that variable “READING_SD” if you have that variable set to True you set the data for the touch to the last values that were used. if it is set to false then have the touch callback do it’s thing and read the SPI for touch data. There is a better way and that is using the CS line state to determine what is going on.

If you are running into an issue with the touch causing an issue with the SD card this is going to be because of the CS either not being used properly by the touch interface or you are not checking to see if the SD card CS line is pulled low before collecting data from the SPI bus. The CS line is what controls what device is listening and is able to send on the SPI bus. the devices themselves are not aware of another other devices CS line state this is something you have to check in your code before sending and receiving data to the bus. Any time you want to write or read from the touch driver you have to set the CS line low and another other device that is on the bus must have it’s CS line set to high. and if you want to read/write the SD you have to set that CS line low and all the others high. This needs to be done before any data is sent on the bus or any data is to be read from the bus.

If you check CS line states and set them properly then there is no need to create that variable I was mentioning. User beware that you have to ensure that you are not in the middle of a read or write operation on the bis if you are using multiple threads or DMA memory so be cautious of that. You do not want to change the CS line state if the device is in the middle of receiving data or having data written to it. If something is being written or read you can either stall the program until it is finished or if it is something like the touch loop you can pass the last data that was gotten and let the loop come back around full circle and the bus might be free to read from.

Due to the nature of CD cards and also displays and the huge amounts of data that gets sent to and read from it is always a wise idea to have the display and the SD card on their own buses. If you do that you don’t have to attach the CS line to the MCU, you can connect it to ground and the display you only need to connect the MISO line and the CLK lines to the MCU because you are only sending data to the display. With the CD card you need to have both the MISO and the MOSI lines connected because of how they work.

The touch you would also need to have the MISO and MOSI lines connected. If it was on it’s own bus then you could tie the CS to ground as well.

IDK what MCU you are using or how many available GPIO’s you have or if you are using DMA memory. There is a whole lot that plays into this and I would need to know more specifics to give you the best solution.

What display are you using? Not the driver but the actual manufacturer of the display board. send me a photo of it. What MCU are you using?

I see that you are using MicroPython. I do not know from your code example what is in the “sdcard” module as this module is not apart of MicroPython at all.

Here is an example of how you want to initialize the SDCard that may work with the display and touch. Depending on what board you are using (looks like an esp32) you might have to do some monkey patching to the drivers you are using in order to get things to work properly. Lets start off with the SDCard first tho.

You need to manually set the pins. I see you are using host #2. the default pins for host 2 are as follows

alter you code to have the following items in it.

import machine


MISO = 12
MOSI = 13
CLK = 14
SD_CS = 15
DP_CS = 16
TS_CS = 17

sd_card = machine.SDCard(slot=2, miso=MISO, mosi=MOSI , sck=CLK , cs=SD_CS, freq=40000000)
disp = ili9341(spihost=2, miso=MISO, mosi=MOSI, clk=CLK , cs=DP_CS, mhz=40, dc=13, rst=-1, power=-1, factor=8, half_duplex=False, double_buffer=True, backlight=27, backlight_on=1)
touch = xpt2046(spihost=2, miso=MISO, mosi=MOSI, sxck=CLK, cs=TS_CS, mhz=1, half_duplex=False)

The biggest problem is how the display and touch drivers in LVGL MicroPython work. they don’t use the internal mechanics if MicroPython. they use the ESP-IDF API to run the SPI and this is where you are going to have a bumping if heads between the display/touch and the SDCard. If the above works then fantastic. if not It is going to get a lot more complex to have all of those things on the same bus.