Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RGBDisplay frame buffer allocation failure on ESP32-S3 #20

Closed
bland328 opened this issue Mar 24, 2024 · 173 comments
Closed

RGBDisplay frame buffer allocation failure on ESP32-S3 #20

bland328 opened this issue Mar 24, 2024 · 173 comments

Comments

@bland328
Copy link

I have a Sunton ESP32-8048S043 board with ESP32-S3 (16MB QIO flash / 8MB OPI SPIRAM), 800×480 parallel IPS display, and GT911 capacitive touch digitizer.

lvgl_micropython is building, installing and fundamentally working, but I'm running into a problem allocating the RGBDisplay frame buffer.

Here's how I'm building the firmware under macOS Sonoma:
python3 make.py esp32 clean mpy_cross BOARD=ESP32_GENERIC_S3 BOARD_VARIANT=SPIRAM_OCT DISPLAY=rgb_display INDEV=GT911

Here's my main.py:

import micropython
micropython.mem_info()

import lcd_bus
display_bus = lcd_bus.RGBBus(
    hsync = 39,
    vsync = 41,
    de = 40,
    disp = -1,
    pclk = (12 * 1000 * 1000),
    data0 = 8, data1 = 3, data2 = 46, data3 = 9, data4 = 1,  # B
    data5 = 5, data6 = 6, data7 = 7, data8 = 15, data9 = 16, data10 = 4,  # G
    data11 = 45, data12 = 48, data13 = 47, data14 = 21, data15 = 14,  # R
    hsync_front_porch = 8,
    hsync_back_porch = 8,
    hsync_pulse_width = 4,
    hsync_idle_low = True,
    vsync_front_porch = 8,
    vsync_back_porch = 8,
    vsync_pulse_width = 4,
    vsync_idle_low = True,
    de_idle_high = False,
    pclk_idle_high = False,
    pclk_active_low = True,
    disp_active_low = False,
)

import rgb_display
import lvgl as lv
display = rgb_display.RGBDisplay(
    data_bus = display_bus,
    display_width = 800,
    display_height = 480,
    color_byte_order = rgb_display.BYTE_ORDER_BGR,
    color_space = lv.COLOR_FORMAT.RGB565,
)

And here's the resulting output:

ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fce3810,len:0xf8c
load:0x403c9700,len:0xb3c
load:0x403cc700,len:0x2dd4
entry 0x403c989c
stack: 656 out of 15360
GC: total: 64000, used: 3120, free: 60880, max new split: 8257536
 No. of 1-blocks: 20, 2-blocks: 6, max blk sz: 30, max free sz: 3704
E (5974) lcd_panel.rgb: lcd_rgb_panel_alloc_frame_buffers(156): no mem for frame buffer
E (5974) lcd_panel.rgb: esp_lcd_new_rgb_panel(285): alloc frame buffers failed
Guru Meditation Error: Core  1 panic'ed (LoadProhibited). Exception was unhandled.

Core  1 register dump:
PC      : 0x420bb3ac  PS      : 0x00060030  A0      : 0x820bbf8e  A1      : 0x3fced740
A2      : 0x3fcac9a0  A3      : 0x3c1f7a5c  A4      : 0x3c1f7e3c  A5      : 0x00001756
A6      : 0x3c1f7a5c  A7      : 0x3c1f7f74  A8      : 0x00000000  A9      : 0x7fffffff
A10     : 0x0000005a  A11     : 0x3fcedefc  A12     : 0x3fced720  A13     : 0x0000000c
A14     : 0x3c1f7a5c  A15     : 0x00000008  SAR     : 0x00000004  EXCCAUSE: 0x0000001c
EXCVADDR: 0x00000000  LBEG    : 0x400556d5  LEND    : 0x400556e5  LCOUNT  : 0xfffffffa

Backtrace: 0x420bb3a9:0x3fced740 0x420bbf8b:0x3fced760 0x420034bd:0x3fced7f0 0x42001cc1:0x3fced850 0x4200164b:0x3fced8a0 0x420c295d:0x3fced8f0 0x420c9719:0x3fced920 0x420c97dd:0x3fced940 0x4037a7e1:0x3fced960 0x420c28fc:0x3fceda00 0x420c9719:0x3fceda30 0x420c97dd:0x3fceda50 0x420c83b8:0x3fceda70 0x420c81ed:0x3fcedad0 0x420c9719:0x3fcedaf0 0x420c97dd:0x3fcedb10 0x4037a7e1:0x3fcedb30 0x420c28fc:0x3fcedbd0 0x420c9719:0x3fcedc00 0x420c972e:0x3fcedc20 0x420d5cfa:0x3fcedc40 0x420d609d:0x3fcedcd0 0x420dd713:0x3fcedd00

Any thoughts on what I'm getting wrong, or what I might try next?

@kdschlosser
Copy link
Owner

I need to make a change to the code to handle that issue. This is due to how the RGB driver works.

In the meantime here is what you can do.

import micropython
micropython.mem_info()

import lcd_bus
display_bus = lcd_bus.RGBBus(
    hsync = 39,
    vsync = 41,
    de = 40,
    disp = -1,
    pclk = (12 * 1000 * 1000),
    data0 = 8, data1 = 3, data2 = 46, data3 = 9, data4 = 1,  # B
    data5 = 5, data6 = 6, data7 = 7, data8 = 15, data9 = 16, data10 = 4,  # G
    data11 = 45, data12 = 48, data13 = 47, data14 = 21, data15 = 14,  # R
    hsync_front_porch = 8,
    hsync_back_porch = 8,
    hsync_pulse_width = 4,
    hsync_idle_low = True,
    vsync_front_porch = 8,
    vsync_back_porch = 8,
    vsync_pulse_width = 4,
    vsync_idle_low = True,
    de_idle_high = False,
    pclk_idle_high = False,
    pclk_active_low = True,
    disp_active_low = False,
)

buf1 = display_bus.allocate_framebuffer(800 * 480 * 2, lcd_bus.MEMORY_SPIRAM)
buf2 = display_bus.allocate_framebuffer(800 * 480 * 2, lcd_bus.MEMORY_SPIRAM)

import rgb_display
import lvgl as lv
display = rgb_display.RGBDisplay(
    data_bus = display_bus,
    display_width = 800,
    display_height = 480,
    frame_buffer1=buf1,
    frame_buffer2=buf2,
    color_byte_order = rgb_display.BYTE_ORDER_BGR,
    color_space = lv.COLOR_FORMAT.RGB565,
)

@kdschlosser
Copy link
Owner

I updated the code so clone it again and give it a try the problem should be fixed without you needing to manually create the buffers like in my last post.

@bland328
Copy link
Author

bland328 commented Mar 25, 2024

Thanks very much for that, @kdschlosser!

Sadly, either way I do it (manually or the newly-updated way), I get this failure immediately after the rgb_display.RGBDisplay() call:

Guru Meditation Error: Core  1 panic'ed (LoadProhibited). Exception was unhandled.

Core  1 register dump:
PC      : 0x420bbe78  PS      : 0x00060030  A0      : 0x8200356c  A1      : 0x3fced760
A2      : 0x3c251b38  A3      : 0x3fcac9a0  A4      : 0x00000000  A5      : 0x00000000
A6      : 0x60009050  A7      : 0x00000000  A8      : 0x820bbe4d  A9      : 0x3c1f5540
A10     : 0x00b71b00  A11     : 0x0000001f  A12     : 0x00000000  A13     : 0x00000000
A14     : 0xffffffc0  A15     : 0x3c3d7b0c  SAR     : 0x00000017  EXCCAUSE: 0x0000001c
EXCVADDR: 0x00000000  LBEG    : 0x400570e8  LEND    : 0x400570f3  LCOUNT  : 0x00000000


Backtrace: 0x420bbe75:0x3fced760 0x42003569:0x3fced7f0 0x42001ccd:0x3fced850 0x42001657:0x3fced8a0 0x420c2a09:0x3fced8f0 0x420c97c5:0x3fced920 0x420c9889:0x3fced940 0x4037a7e1:0x3fced960 0x420c29a8:0x3fceda00 0x420c97c5:0x3fceda30 0x420c9889:0x3fceda50 0x420c8464:0x3fceda70 0x420c8299:0x3fcedad0 0x420c97c5:0x3fcedaf0 0x420c9889:0x3fcedb10 0x4037a7e1:0x3fcedb30 0x420c29a8:0x3fcedbd0 0x420c97c5:0x3fcedc00 0x420c97da:0x3fcedc20 0x420d5da6:0x3fcedc40 0x420d6149:0x3fcedcd0 0x420dd7bf:0x3fcedd00

It appears this may be a result of accessing an illegal memory location; unfortunately, I'm not yet quite ESP32-savvy enough to determine if this might be my fault somehow, or beyond my control.

@kdschlosser
Copy link
Owner

Lets decode the backtrace.

~/.espressif/tools/xtensa-esp32s3-elf/esp-2022r1-11.2.0/xtensa-esp32s3-elf/bin/xtensa-esp32-elf-addr2line -e /lib/micropython/ports/esp32/build-ESP32_GENERIC_S3-SPIRAM_OCT/micropython.elf 0x420bbe75:0x3fced760 0x42003569:0x3fced7f0 0x42001ccd:0x3fced850 0x42001657:0x3fced8a0 0x420c2a09:0x3fced8f0 0x420c97c5:0x3fced920 0x420c9889:0x3fced940 0x4037a7e1:0x3fced960 0x420c29a8:0x3fceda00 0x420c97c5:0x3fceda30 0x420c9889:0x3fceda50 0x420c8464:0x3fceda70 0x420c8299:0x3fcedad0 0x420c97c5:0x3fcedaf0 0x420c9889:0x3fcedb10 0x4037a7e1:0x3fcedb30 0x420c29a8:0x3fcedbd0 0x420c97c5:0x3fcedc00 0x420c97da:0x3fcedc20 0x420d5da6:0x3fcedc40 0x420d6149:0x3fcedcd0 0x420dd7bf:0x3fcedd00

You may have to adjust the path to xtensa-esp32-elf-addr2line. I am not sure where it gets put on MACOS

@kdschlosser
Copy link
Owner

download the attached file and unzip it. The upload it to your MCU. When the driver framework loads it will use this one ionstead of the one that is baked into the firmware automatically.

display_driver_framework.zip

I added a bunch of print statements to see where it is tanking. The backtrace stuff isn't going to give any insight into location for the python code so this will help locate the function call that is tanking it.

@bland328
Copy link
Author

Here's the output from your alternate display_driver_framework.zip:

stack: 656 out of 15360
GC: total: 64000, used: 3472, free: 60528, max new split: 8257536
 No. of 1-blocks: 24, 2-blocks: 12, max blk sz: 30, max free sz: 3666
START
if not lv.is_initialized
DONE
databus is not None
self._disp_drv = lv.display_create
DONE
self._disp_drv.set_color_format
DONE
self._disp_drv.set_driver_data
DONE
framebuffer is None
allocating buffers
2056
1032
DONE
checking framebuffer 2
data_bus.init
Guru Meditation Error: Core  1 panic'ed (LoadProhibited). Exception was unhandled.

Core  1 register dump:
PC      : 0x420bbe78  PS      : 0x00060030  A0      : 0x8200356c  A1      : 0x3fced760
A2      : 0x3c251b38  A3      : 0x3fcac9a0  A4      : 0x00000000  A5      : 0x00000000
A6      : 0x60009050  A7      : 0x00000000  A8      : 0x820bbe4d  A9      : 0x3c1f5540
A10     : 0x00b71b00  A11     : 0x0000001f  A12     : 0x00000000  A13     : 0x00000000
A14     : 0xffffffc0  A15     : 0x3c3d7b0c  SAR     : 0x00000017  EXCCAUSE: 0x0000001c
EXCVADDR: 0x00000000  LBEG    : 0x400570e8  LEND    : 0x400570f3  LCOUNT  : 0x00000000

Backtrace: 0x420bbe75:0x3fced760 0x42003569:0x3fced7f0 0x42001ccd:0x3fced850 0x42001657:0x3fced8a0 0x420c2a09:0x3fced8f0 0x420c97c5:0x3fced920 0x420c9889:0x3fced940 0x4037a7e1:0x3fced960 0x420c29a8:0x3fceda00 0x420c97c5:0x3fceda30 0x420c9889:0x3fceda50 0x420c8464:0x3fceda70 0x420c8299:0x3fcedad0 0x420c97c5:0x3fcedaf0 0x420c9889:0x3fcedb10 0x4037a7e1:0x3fcedb30 0x420c29a8:0x3fcedbd0 0x420c97c5:0x3fcedc00 0x420c97da:0x3fcedc20 0x420d5da6:0x3fcedc40 0x420d6149:0x3fcedcd0 0x420dd7bf:0x3fcedd00

@bland328
Copy link
Author

I just realized that this line from my main.py:

pclk = (12 * 1000 * 1000),

was written under the assumption that pclk is a frequency, but now I'm realizing it should likely be the number of the GPIO pin which runs to the DCLK line of the display. Does this sound right?

@kdschlosser
Copy link
Owner

Yup I just saw that too.

@kdschlosser
Copy link
Owner

I mean JUST now I saw that... LOL..

@kdschlosser
Copy link
Owner

pclk you set to 42
and freq you set to 12000000

@kdschlosser
Copy link
Owner

Everything else looks to be correct.

@kdschlosser
Copy link
Owner

kdschlosser commented Mar 25, 2024

You might have to dink about with the freq setting a bit if your display image is not centered.. You might be able to push it up to a higher clock frequency than 12mhz. you have to test it to find out.

I have a waveshare display here that in the docs it says 16mhz but 13mhz is what works with the image centered correctly and not scrolling horizontally when the display redraws

@kdschlosser
Copy link
Owner

It will work like it should once you make those changes.

@bland328
Copy link
Author

The crash is gone, and the output with your alternate display_driver_framework.py still installed is this:

START
if not lv.is_initialized
DONE
databus is not None
self._disp_drv = lv.display_create
DONE
self._disp_drv.set_color_format
DONE
self._disp_drv.set_driver_data
DONE
framebuffer is None
allocating buffers
2056
1032
DONE
checking framebuffer 2
data_bus.init
DONE
self._disp_drv.set_flush_cb
DONE
self._disp_drv.set_buffers
DONE
data_bus.register_callback
INIT DONE

I added display.init() to the end of main.py, hoping I'd at least see the backlight come on, but no luck so far.

It's very late for me here, so I'll tackle this again after I've slept. Thanks very much for your help to this point!

@kdschlosser
Copy link
Owner

kdschlosser commented Mar 25, 2024

display = rgb_display.RGBDisplay(
    data_bus = display_bus,
    display_width = 800,
    display_height = 480,
    backlight_pin = 2,
    color_byte_order = rgb_display.BYTE_ORDER_BGR,
    color_space = lv.COLOR_FORMAT.RGB565,
)

display.init()
display.set_backlight(100)


scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0x000000), 0)

slider = lv.slider(scrn)
slider.set_size(200, 50)
slider.center()

import time

while True:
    time.sleep_ms(1)
    lv.tick_inc(1)
    lv.task_handler()

That will get you rolling.

@bland328
Copy link
Author

bland328 commented Mar 25, 2024

Thanks so much, @kdschlosser, both for your time creating lvgl_micropython and for your generous assistance.

At this point, the backlight is on and I have a white screen, but nothing renders.

Would you guess I'm up against a problem in my definition of either lcd_bus.RGBBus or rgb_display.RGBDisplay?

Or is it more likely I'm mishandling lvgl itself somehow?

Here's main.py as it stands now:

import time, micropython
micropython.mem_info()

import lcd_bus
display_bus = lcd_bus.RGBBus(
    hsync = 39,
    vsync = 41,
    de = 40,
    disp = -1,
    pclk = 42,  # to TFT DCLK
    data0  = 8,  data1  = 3,  data2  = 46, data3  = 9,  data4  = 1,  # B
    data5  = 5,  data6  = 6,  data7  = 7,  data8  = 15, data9  = 16, data10 = 4,  # G
    data11 = 45, data12 = 48, data13 = 47, data14 = 21, data15 = 14,  # R
    freq = 12000000,
    hsync_front_porch = 8,
    hsync_back_porch = 8,
    hsync_pulse_width = 4,
    hsync_idle_low = True,
    vsync_front_porch = 8,
    vsync_back_porch = 8,
    vsync_pulse_width = 4,
    vsync_idle_low = True,
    de_idle_high = False,
    pclk_idle_high = False,
    pclk_active_low = True,
    disp_active_low = False,
)

import rgb_display
import lvgl as lv
display = rgb_display.RGBDisplay(
    data_bus = display_bus,
    display_width = 800,
    display_height = 480,
    backlight_pin = 2,
    color_byte_order = rgb_display.BYTE_ORDER_BGR,
    color_space = lv.COLOR_FORMAT.RGB565,
)

display.init()
display.set_backlight(100)

scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0x000000), 0)

slider = lv.slider(scrn)
slider.set_size(200, 50)
slider.center()

while True:
    time.sleep_ms(1)
    lv.tick_inc(1)
    lv.task_handler()

@kdschlosser
Copy link
Owner

kdschlosser commented Mar 25, 2024

By looking at the specs for the display I am going to say this is what you need to be running..

import time, micropython
micropython.mem_info()

import lcd_bus
display_bus = lcd_bus.RGBBus(
    hsync = 39,
    vsync = 41,
    de = 40,
    disp = -1,
    pclk = 42,  # to TFT DCLK
    data0  = 8,  data1  = 3,  data2  = 46, data3  = 9,  data4  = 1,  # B
    data5  = 5,  data6  = 6,  data7  = 7,  data8  = 15, data9  = 16, data10 = 4,  # G
    data11 = 45, data12 = 48, data13 = 47, data14 = 21, data15 = 14,  # R
    freq = 12000000,
    hsync_front_porch = 8,
    hsync_back_porch = 8,
    hsync_pulse_width = 4,
    hsync_idle_low = True,
    vsync_front_porch = 8,
    vsync_back_porch = 8,
    vsync_pulse_width = 4,
    vsync_idle_low = True,
    de_idle_high = False,
    pclk_idle_high = False,
    pclk_active_low = True,
    disp_active_low = False,
)

import rgb_display
import lvgl as lv
display = rgb_display.RGBDisplay(
    data_bus = display_bus,
    display_width = 800,
    display_height = 480,
    backlight_pin = 2,
    color_space = lv.COLOR_FORMAT.RGB565,
    rgb565_byte_swap=True
)

display.init()
display.set_backlight(100)

scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0x000000), 0)

slider = lv.slider(scrn)
slider.set_size(200, 50)
slider.center()

while True:
    time.sleep_ms(1)
    lv.tick_inc(1)
    lv.task_handler()

@kdschlosser
Copy link
Owner

I know it works. it's just a matter of dialing in the right settings for your display. Unfortunately the information is just published to a datasheet somewhere. we have to go digging through the code examples that are used in order to get it to display properly.

The code example I just gave you is only as a test. if it works then we will change some other stuff around so we don't have to iterate over the buffer to move bytes around. This will give you better performance.

@kdschlosser
Copy link
Owner

I just updated the code to deal with using rgb565_byte_swap in a different manner. It will increase the performance by about 30%.

@bland328
Copy link
Author

bland328 commented Mar 25, 2024

I'm now running precisely the code you provided, but I'm still getting just a blank white display.

I'll do more digging for display specs.

FYI, a couple sources assert that this is the screen being used, and at first glance it certainly appears consistent with what's mounted on the board.

EDIT: Also, this page appears to be a dumping ground for technical data on this specific ESP32-8048S043 board.

EDIT 2: Based on the sample LovyanGFX configuration code from that page, I bumped the freq up to 16000000, and now I'm seeing a slider, though it occasionally changes position horizontally, sometimes wrapping around from the right edge to the left edge of the screen.

Also, zones across the top and bottom of the screen are flickering at a high frequency.

This progress is inspirational! I'll continue to experiment.

@kdschlosser
Copy link
Owner

turn the freq down to 14mhz or even 13mhz and that will fix the slider moving.
I ran into this very same problem with the waveshare display I have.

@kdschlosser
Copy link
Owner

kdschlosser commented Mar 25, 2024

I believe the flickering will go away once you get the frequency dialed in

EDIT

I am going to double check your vsync and hsync settings just to make sure they are right.

@bland328
Copy link
Author

I believe the flickering will go away once you get the frequency dialed in

And so it did! 13mhz appears solid. Very exciting, thank you.

Your work on this is well-conceived, nicely executed, and incredibly appreciated--is there a link I can use to buy you a coffee, beer or microcontroller? :)

@kdschlosser
Copy link
Owner

kdschlosser commented Mar 25, 2024

The settings look right with what is in the examples for the display so it should just be a matter of dialing in the pixel clock.

I want to let you know what the last few code changes I made actually do.

The very last one combines the 3 bin files into a single binary for easier flashing of the firmware.
The change before that is for the rgb565_byte_swap. the byte order for each pixel when using RGB565 sometimes needs to be flip flopped. Typically that means the frame buffer needs to be iterated over and the bytes changed for each pixel. While this is not a big deal to do, when you are doing it for something like an 800x480 display that is a lot of pixels that need to have this done. That is going to take some time. This could be the cause of the flicker you are seeing.

In the second to last code change what I did was add code that checks to see if rgb565 byte swapping is wanted, if it is then I check to make sure the color depth is 16bpp and if 16 lanes are being used. If all 3 of those conditions are met then I change the order of the pins instead of iterating the frame buffer moving bytes around. Same effect just much faster, about 30% faster.

so give the new code a shot and lower that pixel clock and that should do the trick for ya.

@bland328
Copy link
Author

I pulled your changes and tried to build, but eventually got this failure:

/opt/homebrew/Cellar/python@3.12/3.12.2_1/Frameworks/Python.framework/Versions/3.12/Resources/Python.app/Contents/MacOS/Python: can't open file '/Users/bland328/code/esp-idf/lvgl_micropython/lib/esp-idf/components/esptool_py/esptool/esptool.py,': [Errno 2] No such file or directory

Note the comma trailing esptool.py. Perhaps just a typo?

@kdschlosser
Copy link
Owner

Quit drinking over a decade ago so beer is not something I would use.
Coffee is not something I buy, I will make it at home and I only drink 1/2 a cup in the morning. So that's probably not ideal either.

An MCU would be nice. can never have too many of those.

https://www.paypal.com/ncp/payment/4NZXTMZD25PMJ

@kdschlosser
Copy link
Owner

yup that's a fat finger. lemme fix it.

@kdschlosser
Copy link
Owner

all fixed

@kdschlosser
Copy link
Owner

I am super happy we got you up and running. I knew the RGB bus driver worked but you having issues with it exposed the memory issue which is now fixed and it also exposed a better way of handling the RGB565 byte swap that should be a HUGE improvement in performance.

I still have to tell you how to get the gt911 driver working...

you need to change the INDEV=GT911 in your build command to INDEV=gt911 I know it wouldn't matter on windows because file names are not case sensitive and I cannot remember if I added code to make the filename all lower case or not. so just to make sure I would make it all lowercase.

Now I added a special way of dealing with I2C in MicroPython. it actually makes it easier to use and it will deal with using I2C from more than one thread. The ESP32 supports threading and if you have any attached I2C devices you are able to collect or send data to those devices in a thread.

so this is how the gt911 driver works

import i2c
import gt911

i2c_bus = i2c.I2CBus(scl=10, sda=11, freq=4000000, host=None, use_pullups=False, use_locks=False)

touch_driver = gt911.GT911(i2c_bus=i2c_bus, reset_pin=None, interrupt_pin=None, touch_cal=None)

You can change the host if you like and you can specify to use built in pullups if you like. The use_locks you set to True if you are going to use threading.

The reset_pin you can use. The interrupt_pin doesn't get used as an interrupt, it is required in order to perform a hardware reset of the touch driver IC.

touch_cal is something I have to finish up making. I wrote into the firmware the ability to calibrate the touch display. On the ESP32 the calibration data gets saved to NVRAM. This is nice because it would give you the ability to calibrate the touch at runtime and not have to hard code the calibration data. It allows for real time calibrations to take place for the touch screen while the MCU is running and you will not need to perform a reboot in order for the settings to take effect.

@bland328
Copy link
Author

Works fine now, thanks! And I've sent a gift your way 🎁

Two quick notes:

  1. In the To flash firmware: instructions that appear after building, the esptool.py, trailing comma fat-fingering appears again.

  2. Also, FWIW, the suggested erase_flash command specifies a speed of 460800, whereas the write_flash command specifies a speed of 921600. Perhaps that's correct in many cases, but for me, I'm only able to successfully write flash if I decrease the speed to 460800.

@bland328
Copy link
Author

bland328 commented Mar 31, 2024

Perhaps the bootloader is reclaiming pins 19 and 20 on some level? When flashing, I'm using --after no_reset as suggested by your make.py script, so that first test I run after flashing is before any soft or hard reset.

I'm not sure that makes any sense, unless the bootloader can and does behave differently on power-up than it does for soft/hard resets.

@kdschlosser
Copy link
Owner

don't worry about the response you get when doing a scan. I don't think that I2C has an official way of collecting the device id's It is kind of a hack thing that it is doing to detect them. It is sending out empty packets and if no errors occurs then it adds the ID to the list. I really wouldn't 100% trust that method.

Use the display driver and the gt911 driver as it is. If you run into a problem lemme know from there.

I am happy that we did find the cause of the pins getting mucked up. There could still be something that is causing an issue with those pins. I did open an issue on micropython's repo for the problem and the main author was in agreement that the automatic assignment of the USB OTG to those 2 pins is wrong. Because of how this binding is written it makes it super easy to update the version of micropython that is being used. so as soon as they make a release with the fix I will be able to add it. I only made a modification for what I saw. there could be others areas buried in the source code for the ESP32 port that I missed.

@bland328
Copy link
Author

bland328 commented Mar 31, 2024

Thanks so much for everything you've put into this! You certainly didn't owe me all this patience and assistance, and I'm incredibly appreciative that you've been so generous.

Also, FYI, I've figured out the 95 vs 20 I2C address weirdness.

95 (0x5d) and 20 (0x14) are both valid GT911 addresses, decided by the status of the GT911 INT pin while its RESET pin (GPIO 38 per this board) goes low for >100µs.

As this board is shipped, however, the INT pin appears to be disconnected (from GPIO 18) by an open pad.

So, if I pull GPIO 38 low for a moment after any power-up or reset, the GT911 address reliably becomes 95 (0x5d).

And perhaps the GT911 driver already handles that maneuver itself--I'll find out soon :)

@kdschlosser
Copy link
Owner

kdschlosser commented Mar 31, 2024

The 2 addresses are for read a write so don't worry about the address flip flopping and just use the thing already. It should work the way it is.

The INT pin we don't have to worry about because we are not changing the standard address to the secondary one and LVGL collects the touch input by use of polling. The reset pin really is going to do nothing as well. You don't have to bother passing those to the GT911 constructor in order for it to work.

Everything should now work exactly as it should.

Give it a go test it out.

Here is a sample script to test with to see if it works. Just upload it to your MCU and see if it works and if the colors are all correct. You are going to have to enter you pin numbers and your display settings before you upload it.

from micropython import const  # NOQA
import i2c


_WIDTH = const(800)
_HEIGHT = const(480)

_CTP_SCL = const(9)
_CTP_SDA = const(8)
_CTP_IRQ = const(4)

_SD_MOSI = const(11)
_SD_SCK = const(12)
_SD_MISO = const(13)

_LCD_FREQ = const(13000000)
_PCLK_ACTIVE_NEG = const(0)

_HSYNC_PULSE_WIDTH = const(10)
_HSYNC_BACK_PORCH = const(10)
_HSYNC_FRONT_PORCH = const(10)

_VSYNC_PULSE_WIDTH = const(10)
_VSYNC_BACK_PORCH = const(10)
_VSYNC_FRONT_PORCH = const(20)

_PCLK = const(7)
_HSYNC = const(46)
_VSYNC = const(3)
_DE = const(5)
_DISP = const(-1)
_BCKL = None
_DRST = None
_DPWR = None

I2C_BUS = i2c.I2CBus(
    scl=_CTP_SCL,
    sda=_CTP_SDA,
    freq=400000,
    use_locks=False
)

_DATA15 = const(10)  # B7
_DATA14 = const(17)  # B6
_DATA13 = const(18)  # B5
_DATA12 = const(38)  # B4
_DATA11 = const(14)  # B3
_DATA10 = const(21)  # G7
_DATA9 = const(47)  # G6
_DATA8 = const(48)  # G5
_DATA7 = const(45)  # G4
_DATA6 = const(0)  # G3
_DATA5 = const(39)  # G2
_DATA4 = const(40)  # R7
_DATA3 = const(41)  # R6
_DATA2 = const(42)  # R5
_DATA1 = const(2)  # R4
_DATA0 = const(1)  # R3

import lcd_bus  # NOQA


bus = lcd_bus.RGBBus(
    hsync=_HSYNC,
    vsync=_VSYNC,
    de=_DE,
    disp=_DISP,
    pclk=_PCLK,
    data0=_DATA0,
    data1=_DATA1,
    data2=_DATA2,
    data3=_DATA3,
    data4=_DATA4,
    data5=_DATA5,
    data6=_DATA6,
    data7=_DATA7,
    data8=_DATA8,
    data9=_DATA9,
    data10=_DATA10,
    data11=_DATA11,
    data12=_DATA12,
    data13=_DATA13,
    data14=_DATA14,
    data15=_DATA15,
    freq=_LCD_FREQ,
    hsync_front_porch=_HSYNC_FRONT_PORCH,
    hsync_back_porch=_HSYNC_BACK_PORCH,
    hsync_pulse_width=_HSYNC_PULSE_WIDTH,
    hsync_idle_low=False,
    vsync_front_porch=_VSYNC_FRONT_PORCH,
    vsync_back_porch=_VSYNC_BACK_PORCH,
    vsync_pulse_width=_VSYNC_PULSE_WIDTH,
    vsync_idle_low=False,
    de_idle_high=False,
    pclk_idle_high=False,
    pclk_active_low=_PCLK_ACTIVE_NEG,
    disp_active_low=False,
    refresh_on_demand=False
)

buf1 = bus.allocate_framebuffer(_WIDTH * _HEIGHT * 2, lcd_bus.MEMORY_SPIRAM)
buf2 = bus.allocate_framebuffer(_WIDTH * _HEIGHT * 2, lcd_bus.MEMORY_SPIRAM)

import lvgl as lv  # NOQA
import rgb_display  # NOQA

lv.init()

display = rgb_display.RGBDisplay(
    data_bus=bus,
    display_width=_WIDTH,
    display_height=_HEIGHT,
    frame_buffer1=buf1,
    frame_buffer2=buf2,
    reset_pin=_DRST,
    reset_state=rgb_display.STATE_HIGH,
    power_pin=_DPWR,
    power_on_state=rgb_display.STATE_HIGH,
    backlight_pin=_BCKL,
    backlight_on_state=rgb_display.STATE_HIGH,
    color_space=lv.COLOR_FORMAT.RGB565,
    rgb565_byte_swap=True
)

display.set_power(True)
display.init()
display.set_backlight(100)


import time  # NOQA
import gt911  # NOQA

indev = gt911.GT911(I2C_BUS)

scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0x000000), 0)

slider = lv.slider(scrn)
slider.set_size(500, 75)
slider.center()


while True:
    time.sleep_ms(1)
    lv.tick_inc(1)
    lv.task_handler()

I also want to note that since we are not using an interrupt based timer to handle calling the task handler you you optionally edit /lib/lv_conf.h and change line 78 I believe it is from

#define LV_DEF_REFR_PERIOD  33

to

#define LV_DEF_REFR_PERIOD  1

That will greatly improve the touch input and also how smooth the UI responds.

@kdschlosser
Copy link
Owner

They are aware of the problem upstream in MicroPython so hopefully they will update it in the next release to fix this issue. It's kind of a biggie so I would imagine that it should get fixed sooner than later.

@kdschlosser
Copy link
Owner

I added something new that you might like.

if you add --LVGL_API to the command line when compiling the code that is generated will be the same as the LVGL C API. easier to read the docs. the stub file generation will need some work for it to output everything, it;s missing the enumerations.

@bland328
Copy link
Author

bland328 commented Apr 1, 2024

I added something new that you might like.

That sounds great, and I'll check as soon as I get things running, thanks!

At the moment, I can't build the latest:

-- Adding linker script /Users/brian/code/esp-idf/lvgl_micropython/lib/esp-idf/components/soc/esp32s3/ld/esp32s3.peripherals.ld
-- Configuring incomplete, errors occurred!
-e See https://github.com/micropython/micropython/wiki/Build-TroubleshootingIncluding User C Module(s) from ../../../../../micropython.cmake
Traceback (most recent call last):
  File "/Users/brian/code/esp-idf/lvgl_micropython/gen/python_api_gen_mpy.py", line 570, in <module>
    ast = pycparser.parse_file(
          ^^^^^^^^^^^^^^^^^^^^^
  File "/Users/brian/code/esp-idf/lvgl_micropython/lib/pycparser/pycparser/__init__.py", line 90, in parse_file
    return parser.parse(text, filename)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/brian/code/esp-idf/lvgl_micropython/lib/pycparser/pycparser/c_parser.py", line 147, in parse
    return self.cparser.parse(
           ^^^^^^^^^^^^^^^^^^^
  File "/Users/brian/code/esp-idf/lvgl_micropython/lib/pycparser/pycparser/ply/yacc.py", line 331, in parse
    return self.parseopt_notrack(input, lexer, debug, tracking, tokenfunc)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/brian/code/esp-idf/lvgl_micropython/lib/pycparser/pycparser/ply/yacc.py", line 1199, in parseopt_notrack
    tok = call_errorfunc(self.errorfunc, errtoken, self)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/brian/code/esp-idf/lvgl_micropython/lib/pycparser/pycparser/ply/yacc.py", line 193, in call_errorfunc
    r = errorfunc(token)
        ^^^^^^^^^^^^^^^^
  File "/Users/brian/code/esp-idf/lvgl_micropython/lib/pycparser/pycparser/c_parser.py", line 1936, in p_error
    self._parse_error('At end of input', self.clex.filename)
  File "/Users/brian/code/esp-idf/lvgl_micropython/lib/pycparser/pycparser/plyparser.py", line 67, in _parse_error
    raise ParseError("%s: %s" % (coord, msg))
pycparser.plyparser.ParseError: /Users/brian/code/esp-idf/lvgl_micropython/build/lvgl_header.h: At end of input
OUTPUT:
RESULT: 1
CMake Error at /Users/brian/code/esp-idf/lvgl_micropython/micropython.cmake:34 (message):
  Failed to generate
  /Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/ports/esp32/build-ESP32_GENERIC_S3-SPIRAM_OCT/lv_mp.c
Call Stack (most recent call first):
  /Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/py/usermod.cmake:42 (include)
  esp32_common.cmake:22 (include)
  main_esp32s3/CMakeLists.txt:11 (include)


cmake failed with exit code 1, output of the command is in the /Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/ports/esp32/build-ESP32_GENERIC_S3-SPIRAM_OCT/log/idf_py_stderr_output_7815 and /Users/brian/code/esp-idf/lvgl_micropython/lib/micropython/ports/esp32/build-ESP32_GENERIC_S3-SPIRAM_OCT/log/idf_py_stdout_output_7815
make: *** [all] Error 1

@kdschlosser
Copy link
Owner

I just pushed a commit so if you can try it again.

@bland328
Copy link
Author

bland328 commented Apr 1, 2024

Thanks--that builds.

When I try the test script you provided above, I get this:

Touch Product id: bytearray(b'911\x00')
Touch Firmware version: 0x1060
Touch Vendor id: 0x0
Touch Configured width: 480
Touoch Configured height: 272
Traceback (most recent call last):
  File "main.py", line 141, in <module>
  File "gt911.py", line 100, in __init__
  File "pointer_framework.py", line 18, in __init__
AttributeError: 'GT911' object has no attribute 'id'

@kdschlosser
Copy link
Owner

fixed.

@bland328
Copy link
Author

bland328 commented Apr 2, 2024

When I run the sample code you provided above, the slider GUI renders, and the GT911 driver fundamentally works (I can tell by printing indev._get_coords() results).

The problem is that I can't actually interact with the slider.

Is there something else I need to do to associate display and indev with each another? Or maybe something else to get display to update?

@kdschlosser
Copy link
Owner

nope. it should work.

@bland328
Copy link
Author

bland328 commented Apr 4, 2024

It wasn't working because the touch digitizer and display are different resolutions, which pointer_framework.py attempts to handle, but the values passed to _remap() there are wrong--it is remapping both to and from the resolution of the display, and not considering the resolution of the touch digitizer at all.

I now have it working, and happy to share my changes to pointer_framework.py and gt911.py, if you like.

@kdschlosser
Copy link
Owner

The resolution differences between the display and the touch need to be handled at the hardware level.

When the driver starts this is what you are seeing.

Touch Configured width: 480
Touoch Configured height: 272

That is the resolution the touch IC is set to at the hardware level. If this doesn't match what the resolution of the display is it can be changed using the _reflash_config(width, height) method that is in the GT911 driver.

You do not want to set any rotation on the display when you do this. Does the display show natively in landscape or portrait? if it's portrait then the resolutions needs to be reversed in the touch IC.

The purpose to the _remap function is ONLY to handle rotation changes and calibration. The calibration I have set up is the user touches a cross that appears in each corner of the display. Those touches are used to locate where the corners should be. 0,0 on a display might have 21,11 for touch coordinates and that function is what remaps the touch value to display coordinates.

It sounds like all that you needed to do was to run the calibration program to correct your problem. You should undo any changes you have made and try that to see if it fixes the issue.

Take the code below and put it into a file called touch_cal_data.py Then upload the file to your board.

import esp32


class TouchCalData(object):

    def __init__(self, name):
        self._config = esp32.NVS(name)

        try:
            self._left = self._config.get_i32('left')
            if self._left == -1:
                self._left = None
        except OSError:
            self._left = None
        try:
            self._right = self._config.get_i32('right')
            if self._right == -1:
                self._right = None
        except OSError:
            self._right = None

        try:
            self._top = self._config.get_i32('top')
            if self._top == -1:
                self._top = None
        except OSError:
            self._top = None
        try:
            self._bottom = self._config.get_i32('bottom')
            if self._bottom == -1:
                self._bottom = None
        except OSError:
            self._bottom = None

        self._is_dirty = False

    def save(self):
        if self._is_dirty:
            self._config.commit()

    @property
    def left(self):
        return self._left

    @left.setter
    def left(self, value):
        self._left = value

        if value is None:
            value = -1

        self._config.set_i32('left', value)
        self._is_dirty = True

    @property
    def right(self):
        return self._right

    @right.setter
    def right(self, value):
        self._right = value

        if value is None:
            value = -1

        self._config.set_i32('right', value)
        self._is_dirty = True

    @property
    def top(self):
        return self._top

    @top.setter
    def top(self, value):
        self._top = value

        if value is None:
            value = -1

        self._config.set_i32('top', value)
        self._is_dirty = True

    @property
    def bottom(self):
        return self._bottom

    @bottom.setter
    def bottom(self, value):
        self._bottom = value

        if value is None:
            value = -1

        self._config.set_i32('bottom', value)
        self._is_dirty = True

and this is the script you want to run making changes for your board as needed.

from micropython import const  # NOQA
import i2c


_WIDTH = const(800)
_HEIGHT = const(480)

_CTP_SCL = const(9)
_CTP_SDA = const(8)
_CTP_IRQ = const(4)

_SD_MOSI = const(11)
_SD_SCK = const(12)
_SD_MISO = const(13)

_LCD_FREQ = const(13000000)
_PCLK_ACTIVE_NEG = const(0)

_HSYNC_PULSE_WIDTH = const(10)
_HSYNC_BACK_PORCH = const(10)
_HSYNC_FRONT_PORCH = const(10)

_VSYNC_PULSE_WIDTH = const(10)
_VSYNC_BACK_PORCH = const(10)
_VSYNC_FRONT_PORCH = const(20)

_PCLK = const(7)
_HSYNC = const(46)
_VSYNC = const(3)
_DE = const(5)
_DISP = const(-1)
_BCKL = None
_DRST = None
_DPWR = None

I2C_BUS = i2c.I2CBus(
    scl=_CTP_SCL,
    sda=_CTP_SDA,
    freq=400000,
    use_locks=False
)

_DATA15 = const(10)  # B7
_DATA14 = const(17)  # B6
_DATA13 = const(18)  # B5
_DATA12 = const(38)  # B4
_DATA11 = const(14)  # B3
_DATA10 = const(21)  # G7
_DATA9 = const(47)  # G6
_DATA8 = const(48)  # G5
_DATA7 = const(45)  # G4
_DATA6 = const(0)  # G3
_DATA5 = const(39)  # G2
_DATA4 = const(40)  # R7
_DATA3 = const(41)  # R6
_DATA2 = const(42)  # R5
_DATA1 = const(2)  # R4
_DATA0 = const(1)  # R3

import lcd_bus  # NOQA


bus = lcd_bus.RGBBus(
    hsync=_HSYNC,
    vsync=_VSYNC,
    de=_DE,
    disp=_DISP,
    pclk=_PCLK,
    data0=_DATA0,
    data1=_DATA1,
    data2=_DATA2,
    data3=_DATA3,
    data4=_DATA4,
    data5=_DATA5,
    data6=_DATA6,
    data7=_DATA7,
    data8=_DATA8,
    data9=_DATA9,
    data10=_DATA10,
    data11=_DATA11,
    data12=_DATA12,
    data13=_DATA13,
    data14=_DATA14,
    data15=_DATA15,
    freq=_LCD_FREQ,
    hsync_front_porch=_HSYNC_FRONT_PORCH,
    hsync_back_porch=_HSYNC_BACK_PORCH,
    hsync_pulse_width=_HSYNC_PULSE_WIDTH,
    hsync_idle_low=False,
    vsync_front_porch=_VSYNC_FRONT_PORCH,
    vsync_back_porch=_VSYNC_BACK_PORCH,
    vsync_pulse_width=_VSYNC_PULSE_WIDTH,
    vsync_idle_low=False,
    de_idle_high=False,
    pclk_idle_high=False,
    pclk_active_low=_PCLK_ACTIVE_NEG,
    disp_active_low=False,
    refresh_on_demand=False
)

buf1 = bus.allocate_framebuffer(_WIDTH * _HEIGHT * 2, lcd_bus.MEMORY_SPIRAM)
buf2 = bus.allocate_framebuffer(_WIDTH * _HEIGHT * 2, lcd_bus.MEMORY_SPIRAM)

import lvgl as lv  # NOQA
import rgb_display  # NOQA

lv.init()

display = rgb_display.RGBDisplay(
    data_bus=bus,
    display_width=_WIDTH,
    display_height=_HEIGHT,
    frame_buffer1=buf1,
    frame_buffer2=buf2,
    reset_pin=_DRST,
    reset_state=rgb_display.STATE_HIGH,
    power_pin=_DPWR,
    power_on_state=rgb_display.STATE_HIGH,
    backlight_pin=_BCKL,
    backlight_on_state=rgb_display.STATE_HIGH,
    color_space=lv.COLOR_FORMAT.RGB565,
    rgb565_byte_swap=True
)

display.set_power(True)
display.init()
display.set_backlight(100)


import time  # NOQA
import gt911  # NOQA

indev = gt911.GT911(I2C_BUS)

scrn = lv.screen_active()

if not indev.is_calibrated:
    indev.calibrate()

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

slider = lv.slider(scrn)
slider.set_size(500, 75)
slider.center()


while True:
    time.sleep_ms(1)
    lv.tick_inc(1)
    lv.task_handler()

The touch calibration will only run a single time. the settings get saved into NVRAM on the ESP32. At the moment there are 2 ways to reset the saved settings, a full erase and flash again or access indev._config and set the right left top and bottom values to None

@kdschlosser
Copy link
Owner

kdschlosser commented Apr 4, 2024

Yes I have already built into the firmware a way to calibrate the touchscreen. 😄

That would make it a whole lot easier then having to make sure you modify code each and every time you want to update from the repo.

@kdschlosser
Copy link
Owner

OH I did want to mention the downside in your situation. Because your display is 800x480 and the resolution that is set currently for the touch is 480x272 you are going to loose touch resolution by using just the calibration. The better way to do it is going to be updating the resolution for the touch at the hardware level to the 800x480 resolution. I would still recommend doing the calibration after you do that.

@bland328
Copy link
Author

bland328 commented Apr 4, 2024

I understand what you are saying, and I'll do it, thanks.

And would also suggest that since the gt911.py driver already queries the digitizer for its resolution, perhaps it could automatically generate the calibration data at that point, if it does not already exist?

EDIT: Or, alternately, it would be pretty slick if the gt911.py driver would automatically set the resolution of the digitizer to match the display, so no calibration data would be needed.

But, no big deal either way, as I can simply make that change to the GT911 configuration in my code, before the driver initializes.

@kdschlosser
Copy link
Owner

Doing that might not be an ideal way to go about it if there are any offsets. I also have not tested the code that writes the firmware yet.

@bland328
Copy link
Author

Doing that might not be an ideal way to go about it if there are any offsets. I also have not tested the code that writes the firmware yet.

I have since tested it, and it nearly works--the only change needed is that buf = bytearray(185) should be buf = bytearray(184).

@kdschlosser
Copy link
Owner

I have since tested it, and it nearly works--the only change needed is that buf = bytearray(185) should be buf = bytearray(184).

Where did you get this information? according to the datasheet the config data is 185 bytes long...

@bland328
Copy link
Author

I got it from Goodix datasheet GT911 Programming Guide_v0.1.pdf downloaded from https://www.crystalfontz.com/controllers/GOODIX/GT911ProgrammingGuide/478.

The checksum documentation in the table on Page 10 says that the checksum byte at 0x80FF is based on bytes 0x8047 to 0x80FE, which is a range of 184 bytes.

@kdschlosser
Copy link
Owner

yes but the checksum byte is the 185th byte. and that needs to be set. While I don't dump the entire config back to the touch IC that is the reason why it is there. The byte is allocated dynamically so it's not causing any memory use that is resident in memory all the time. In fact you would only really use that function to change the resolution of the touch just a single time and that's it. The code would never need to be run again. I probably shouldn't even have it added. It should be in a separate module the user is able import and run IF the resolution is not correct. When the python code gets loaded form a module that takes up memory so the smaller the code footprint the lower the memory use is going to be.

I think I am going to cut that code out of the touch driver and make a new source file for it. I will do more work on that driver to improve it. It is commonly used driver so adding more bells and whistles is a good thing to have but only if it needs to be used...

@kdschlosser
Copy link
Owner

Typically when the config gets set in most drivers I have seen the entire config including the checksum gets written in a single go. where as I target the specific registers to set the width and height and calculate the checksum and those 3 things get set separately. The entire config still needs to be gotten in order to calculate the checksum.

@bland328
Copy link
Author

yes but the checksum byte is the 185th byte. and that needs to be set.

Yes, the 185th byte needs to be written, but it mustn't be considered in the calculation of the checksum.

It seems clear to me that changing the size of buf to 184 aligns with the documentation and the logic of not including the checksum byte in the calculation of the checksum byte. Plus, it absolutely makes it work on my system.

@kdschlosser
Copy link
Owner

kdschlosser commented Apr 11, 2024

I reworked the gt911 driver. I made the change with the buffer size as you have suggested. I also moved the code for the firmware portion of the driver to it's own module. That module will get imported if needing to adjust the firmware. If the firmware doesn't need to be changed at all the module will not be imported and that is going to be a reduction in memory use due to the code footprint being smaller for the driver.

What is returned is a class instance. Using properties you are able top set the left_padding, right_padding, top_padding, bottom_padding, width, height, touch_press_level, touch_leave_level and noise_reduction. These are properties so you can collect the currently set value and set the value as if they are attributes. You need to call save() in order to write the data to the touch IC.

I also made a change to the driver that allows for programmatically adjusting the firmware set width and height.

import gt911
from micropython import const
import i2c

i2c_bus = i2c.I2CBus(sdl=20, sca=19)

_WIDTH = const(800)
_HEIGH = const(480)

indev = gt911.G911(i2c_bus)

if indev.hw_size != (_WIDTH, _HEIGHT):
    fw_config = indev.firmware_config
    fw_config.width = _WIDTH
    fw_config.height = _HEIGHT
    fw_config.save()

@bland328
Copy link
Author

Thanks for that! I'll give it a try very soon, and start a new issue if there's any issues.

@kdschlosser
Copy link
Owner

no worries m8

@kdschlosser
Copy link
Owner

I am going to close this issue as the original problem has been solved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants