JC3248W535EN event problem

Hi all.
I buy this board : JC3248W535EN
Compile source code and flash . I use Thonny IDE .
IF reset then show me this :

LVGL (9.2.2) MicroPython (1.25.0) Binding compiled on 2025-06-27; Generic ESP32S3 module with Octal-SPIRAM with ESP32S3
Type "help()" for more information.
>>> 

and if try my easy program to detect corect event still have problem .
sthis is my program:


import lv_config
import lvgl as lv

HEIGHT  = 480#lv_config.HEIGHT
WIDTH   = 320#lv_config.WIDTH

import task_handler
th = task_handler.TaskHandler()


scr = lv.screen_active()
lv.screen_load(scr)

scr.set_style_bg_color(lv.color_hex(0x000000), 0)

def Button1_event(event):
	#print(event.get_code())
	kod = event.get_code()
	if kod == 1:
		print('1 Tlacidlo 1')
	if kod == 2:
		print('2 Tlacidlo 1')
	if kod == 3:
		print('3 Tlacidlo 1')
	if kod == 4:
		print('4 Tlacidlo 1')
	if kod == 5:
		print('5 Tlacidlo 1')
	if kod == 6:
		print('6 Tlacidlo 1')
	if kod == 7:
		print('7 Tlacidlo 1')
#def Button2_event_handler(event):

#Definicia tlacidile s textom
Button1 = lv.button(scr)
Button1.align(lv.ALIGN.CENTER,0,0)
#Vytvorime text v strede tlacidla
L_Button1 = lv.label(Button1)
L_Button1.set_text('Tlacidlo 1')
#Priradenie udalosti pre Tlacidlo1
Button1.add_event_cb(Button1_event,lv.EVENT.ALL, None)

During testing, regardless of the button press duration, it consistently returns 1, 2, 4, 7.
Yes I cahnge lv.EVENT.ALL to CLICKED… then still receive 7 …

Where is problem ???
Is problem in my cmpilation ? Source code … etc ??? You have same issue ??

regards.

btn.add_event_cb(on_click, lv.EVENT.CLICKED, None)

def on_click(e):
    # get obj button
    target = e.get_current_target_obj()

Hi.
I change fom ALL to CLICKED
and still same problem :

  1. objekt =event.get_current_target_obj() - return objekt and what ?
  2. event still return 7 , It doesn’t matter how long I press, how long I hold or how short I press, it still cyclically creates an event with code 7.

You have same display , same firmware , I use your code - try it …

regards.

I want to say that this…

    target = e.get_current_target_obj()

should be this

    target = e.get_current_target_obj().__cast__()
1 Like

Yes… and what ? Sorry but what I will do with object ?? give here small example how solve my problem … Ok , I know what object is it …

For example if use widget slider , then work good … if touch blob and not move then not generate event , if move then generate 32 … value are change … work perfect .

If use widget button … if touch button … receive 7 very fast …

Hi all,

Today, I finally found a solution to a problem I was having with touch events in my code.

I’m using:

LVGL (9.2.2) MicroPython (1.25.0) Binding compiled on 2025-06-29; Generic ESP32S3 module with Octal-SPIRAM with ESP32S3

Here’s what was happening: When I set up events for a button, like CLICKED, it wasn’t generating a single event per push. Instead, it would generate multiple events such as PRESSED, RELEASE, CLICKED, LONG_PRESS, and then repeat these as long as my finger stayed on the LCD. This wasn’t logical, as a single press should ideally result in one CLICKED event.

I started investigating and pinpointed the issue.

LVGL calls my _get_coords function in the background to get coordinates, and _get_coords in turn calls _read_data. Inside my _read_data function, I have this:

            self._device.write(self._tx_mv)
            self._device.read(buf=self._rx_mv)
            ...
            data = self._rx_mv
            ...

When reading data from the LCD touch IC via I2C, I can get a result where all 14 bytes are the same (e.g., 0xAF or 0x14), which signifies no touch. Alternatively, I get touch data where the first byte is 0x00 (touch detected), the second byte indicates 0x01 or 0x02 (one or two touches), and the remaining bytes contain position, region ID, press/release state, etc.

The core problem was that when I pressed my finger on the LCD, LVGL immediately called my _get_coords function again, which then triggered a re-read of the touch display data. This happened too quickly (the time between calls was around 2ms), and the AXS15231 sensor couldn’t provide real, stable data so quickly. As a result, the buffer would get filled with unexpected data, often all the same bytes. My _read_data function, unaware of this, would parse these invalid data. This would lead to my system incorrectly interpreting ā€œno touchā€ and sending a RELEASE event to the LVGL core.

Then, after the time defined in lv_config.py (e.g., task_handler.TaskHandler(duration=33) for every 33ms), LVGL would call _get_coords again. By this time, the sensor had enough time (a safe time is around 18ms; I use 20ms) to read real data, and _read_data would return correct position data, etc. This cycle of fast, invalid reads followed by slower, valid reads was causing the flood of unwanted events.

So, I understood the problem, but I wasn’t sure about LVGL’s internal workings regarding input device polling. I decided to solve it based on what I knew.

I modified the _read_data function in AXS15231.py as follows:

# Init
        self._sensor_data_buffer = bytearray(14) # Tento buffer bude uchovƔvat posledne nacitane data
        for i in range(14):
            self._sensor_data_buffer[i] = 0xAF # Alebo 0x14, podľa toho, ktorý je "predvolený"
        self._rx_mv[:] = self._sensor_data_buffer[:]
        self._last_sensor_read_time = utime.ticks_ms()
# Init end 

    def _read_data(self):
        current_time = utime.ticks_ms()
        if utime.ticks_diff(current_time, self._last_sensor_read_time) > 20: # minimum time is 20ms 
            self._device.write(self._tx_mv)
            self._device.read(buf=self._sensor_data_buffer) # Načƭta dĆ”ta do buffera
            self._rx_mv[:] = self._sensor_data_buffer[:]
            self._last_sensor_read_time = current_time # Aktualizuj čas poslednĆ©ho čƭtania

By adding a time check (if utime.ticks_diff(...) > 20:) before performing the I2C read, I ensure that new data is fetched from the touch sensor only if enough time has passed. If less than 20ms has elapsed, the function simply returns the previously read and stored data.

While this might not be the ā€œidealā€ solution from a purely theoretical standpoint (as LVGL still requests data more often than the sensor is physically read), it is a working solution! Now, when I set events for a button as CLICKED, it generates only one event as expected.


My main question (for a deeper understanding): Why does LVGL immediately call my _get_coords function again after a touch is detected, causing an immediate re-read of the touch display coordinates, even when LV_INDEV_DEF_READ_PERIOD is set to 33ms? Is LV_INDEV_DEF_READ_PERIOD merely a recommended period for LVGL’s internal processing, or should it strictly control the frequency of _read_data() calls?

regards.

ā€œCLICKEDā€ show up when there is a press and then a release. It doesn’t matter how long you have held the button for. The ā€œCLICKEDā€ event only gets triggered when the release happens. It’s normal to see something like this happening…

PRESSED
LONG_PRESS
LONG_PRESS
LONG_PRESS
LONG_PRESS
CLICKED
RELEASED

the CLICKED even should show up only a single time for a single press and then release of the touchscreen. If it is not then we need to look at why it’s not and see if there is an issue with the driver, lvgl or if it is a PEBKAC issue.

Just so you know Python is a very dynamic programming language and it allows you to do all kinds of nifty things. One of those things is called ā€œmonkey patchingā€. Monkey patching allows you to override the default behavior of code from an external library. External being not something that you have written. as an example…

import axs15231
import time

class AXS15231(axs15231.AXS15231):

    def __init__(self, device, touch_cal=None,
                        startup_rotation=lv.DISPLAY_ROTATION._0,  # NOQA
                        debug=False):

        super().__init__(device, touch_cal, startup_rotation, debug)
        self.__last_x = -1
        self.__last_y = -1
        self.__last_state = self.RELEASED

    def _get_coords(self):
        touch_data = self._read_data()

        if touch_data:
            self.__last_x = touch_data[0].x
            self.__last_y = touch_data[0].y

            if touch_data[0].event == 1:
                self.__last_state = self.RELEASED
                
            else:
                self.__last_state = self.PRESSED

        return self.__last_state, self.__last_x, self.__last_y


indev = AXS15231(...)

And that code abose is what you would add to your main.py file. No need to alter the code directly in the axs15231.py file. You can patch the code in the python source code files that you write.

Either way the problem you are encountering is an issue with the driver and it reporting some kind of a state change in an odd manner. I would need to add some debugging code to the driver to see what is actually going on. I am thinking that maybe I am reading something from the touch IC incorrectly and it is saying that there is input but the state is released for some reason. This is why you end up with the button being released.

If I am correct in what I am saying then the code above will also fail. It will behave the same way. If that is the case then I will do some debugging.

Also the way you have gone about dealing with the data buffers is wrong. You are going to cause a lot of memory fragmentation doing what you are doing.

…original code … not generate what you write … generate :
PRESSED,CLICKED,RELEASE,PRESSED,CLICKED,RELEASE … around … in cycle …

Yeah that’s because there is an issue in the driver that needs to be sorted out. I don’t have access to all of thew hardware that I have written drivers for. It takes someone willing to spend the time to help me work through bugs like this one. If you are will to do that I am more than happy to fix the issue. What your did may work but there is a better way to correct the problem without needing to consume additional resources.

If you are interested in helping out with debugging it lemme know.

I have a similar problem (event problem ? ) with this display:
I create a slider (running on the ESP32-S3 in async mode) or a button. This works perfectly well at the beginning. After some minutes, the response time is getting slower and slower, meaning, by pushing the button or sliding the slider, the result (which is updating the display with a value), is shown seconds later. I believe it is the event which is delayed, and not the display update because the trace on the serial interface shows the event seconds later. I’m willing to debug or trace or whatever to find the problem. Can you please guide me in the right direction to investigate further?

async is your issue. don’t use it. There is no reason to use async because you are not actually waiting for any IO at all. especially since you should be using double buffering with DMA memory on the S3. It is going to cause a major slowdown. If you want to run things in parallel then I recommend you use threads to do this. It will run far better that way because it actually uses the underlying RTOS to handle the management of the tasks.

Hi Kevin, thanks for your info! I tried to start the parallel tasks in a thread as ā€˜start_new_tread’. The task do run. I’m however struggling now to get lvgl to work, more specifically the task handler. How do I deal with the task_handler in this case. Do I run task_handler also in a tread and which task_handler (sync-async (lv_utils)?

Thanks for your time!

using threads and such are advanced uses and you would not use the task_handler in those cases. You would be responsible for timekeeping in LVGL and also calling lv_task_handler when it needs to be called/when you have time to call it inside of your loop.

Threads need to have a loop inside of them to keep the thread form exiting. something along the lines of

def do():
    # ui setup code goes here

    while True:
        # looping code goes here

The trick with using threads is to be able to context switch. On a PC this is done by the OE automatically but on an MCU you need to tell it to context switch. This is done using lock objects or it is done by calling time.sleep. This will cause the running thread to release the processor for use in another thread.

The main thread should not be used by any looping code as it will keep micropython in a state where it will not respond to repl input. That means you will not have the ability to upload code to the MCU without needing to flash the firmware first. so remember no endless loops in the main thread… This would be an endless loop in the main thread

say you want to watch pin states and update things in the UI based on those states. This would be wrong to do…

# ui setup code.

while True:
    # checking pin states to change things in the UI

this would be right to do…

# ui setup code

def do():

    start_time = time.ticks_ms()

    while True:
        # check pin states here
        # update UI elements as needed

        end_time = time.ticks_ms()
        diff = time.ticks_diff(end_time, start_time)
        start_time = end_time

        if diff < 5:
            time.sleep_ms(5 - diff if diff < 5 else 0)
        
        lv.tick_inc(diff)
        lv.task_handler()

you can also do this…

pin_lock = thread.allocate_lock()

pin_states = [0] * 5
def do_pin_states():

    while True:
        with pin_lock:
            pin_states[0] = pin1.value()
            pin_states[1] = pin2.value()
            pin_states[2] = pin3.value()
            pin_states[3] = pin4.value()
            pin_states[4] = pin5.value()

def do_ui():

    start_time = time.ticks_ms()
    update = False
    with pin_lock:
        saved_states = pin_states[:]

    while True:
        with pin_lock:
            if saved_pin_states != pin_states:
                update = True
                saved_states = pin_states[:]

        if update:
            update = False
            # update UI elements as needed

        end_time = time.ticks_ms()
        diff = time.ticks_diff(end_time, start_time)
        start_time = end_time

        if diff < 5:
            time.sleep_ms(5 - diff if diff < 5 else 0)
        
        lv.tick_inc(diff)
        lv.task_handler()

I tried it out and put the check of the pin states and the receive of messages in the while True loop as you recommended. You saved my day : it works !! Thanks a lot for your valuable advice.
I however have a question about events or interrupts in lvgl: as I said before, pushing a button on the screen or sliding a slider is slowing down when the system is running for a longer time. At the beginning, pushing the screen button and updating the value on the screen is happening instantly. This slowing down is still the case but the delays are more or less acceptable as before (around 2-3 secs till the value gets updated on the screen) and the delays don’t seem to increase anymore overtime. Is there a timing problem in the lvgl configuration and is there a possibility to trace? Could it be that some memory / buffers are getting filled up overtime and not properly released? I’m willing to investigate further when you tell me what to do

I would need to see the code you are using to be able to tell what is going on.

do me a favor and open up an issue on the github repo for this. I tend to check that for responses more than I do the forum…

I’m also struggling with multi-threading right now.

Do I get this correctly: In your example, do_ui() and do_pin_states() would be called by _thread.start_new_thread() after initial gui setup and the main program would not have an infinite while loop?

correct,

Those code examples are pseudo examples only to give a basic idea of what needs to be done. It gets more involved with that keeping things synchronized properly.

I personally like going the route of using a thread worker class that passes functions to be called to a single thread that handles any and all things LVGL. this allows the other threads to keep on doing what they need to do and UI updates don’t get missed at all. I can provide an example of that as well if you like.

My problem is solved. Getting things done parallel can be tricky. I had a problem with a timer somewhere in my program causing this problem. Thanks for your assistance!