kdschlosser / lvgl_micropython

LVGL module for MicroPython
MIT License
42 stars 11 forks source link

OK folks, I need an accounting of what problems still exist. #65

Open kdschlosser opened 1 week ago

kdschlosser commented 1 week ago

This is what I know still has an issue.

ste7anste7an commented 1 week ago

I just pulled the latest version (commit ee1bdbcbbb86ecb1d627f347d2032857d214b3d6 ) from github. For non SPIRAM it comples fine, but when using SPIRAM I get the IRAM error:

 python3 make.py esp32 submodules clean mpy_cross BOARD=ESP32_GENERIC BOARD_VARIANT=SPIRAM DISPLAY=ili9341 INDEV=xpt2046 LV_CFLAGS="-DLV_COLOR_DEPTH=16"

with this error:

 cat /home/stefan/projects/esp32/lvgl_micropython/lib/micropython/ports/esp32/build-ESP32_GENERIC-SPIRAM/log/idf_py_stderr_output_14003
make[1]: warning: -j6 forced in submake: resetting jobserver mode.
/home/stefan/.espressif/tools/xtensa-esp-elf/esp-13.2.0_20230928/xtensa-esp-elf/bin/../lib/gcc/xtensa-esp-elf/13.2.0/../../../../xtensa-esp-elf/bin/ld: micropython.elf section `.iram0.text' will not fit in region `iram0_0_seg'
/home/stefan/.espressif/tools/xtensa-esp-elf/esp-13.2.0_20230928/xtensa-esp-elf/bin/../lib/gcc/xtensa-esp-elf/13.2.0/../../../../xtensa-esp-elf/bin/ld: IRAM0 segment data does not fit.
/home/stefan/.espressif/tools/xtensa-esp-elf/esp-13.2.0_20230928/xtensa-esp-elf/bin/../lib/gcc/xtensa-esp-elf/13.2.0/../../../../xtensa-esp-elf/bin/ld: region `iram0_0_seg' overflowed by 3128 bytes
collect2: error: ld returned 1 exit status
make[3]: *** [CMakeFiles/micropython.elf.dir/build.make:5218: micropython.elf] Error 1
make[2]: *** [CMakeFiles/Makefile2:2065: CMakeFiles/micropython.elf.dir/all] Error 2
make[1]: *** [Makefile:136: all] Error 2

Is there a simple fix for this IRAM overflow error?

kdschlosser commented 1 week ago

do you get this error if you compile vanilla MicroPython 1.23 as well?? I think this issue might be upstream and if it is I will open a PR there.

I will clone MicroPython and see if there is an issue. Not a big deal to do,

ste7anste7an commented 1 week ago

That would be great. I just compiled vanilla python 1.24. Works fine with native installed esp-idf 5.0.4

kdschlosser commented 1 week ago

OK so it does compile for vanilla MicroPython. I wonder if there is not enough IRAM for the esp_lcd component to run. I will ask the folks over at MicroPython about squeezing more space. I am pretty sure that is what the issue is. It may end up being a case of the ESP32 not being supported or I am going to have to write custom drivers possibly. we will see what they say over there.

ste7anste7an commented 1 week ago

Have you tried with SPIRAM support. Without SPIRAM support your code compiles fine.

kdschlosser commented 1 week ago

with SPIRAM the code size is larger and there is more that is placed into IRAM.

I just pushed a commit which takes the keyboard exception and scheduling exceptions and places them into flash instead of IRAM. See if that does the trick for ya.

kdschlosser commented 1 week ago

3k in space is not a whole lot of space to try and free up. I don't know how much IRAM a function uses up and I am hoping that between the 2 things I moved it solves the issue.

Basically what I am doing is this...

This code block is in mpconfigport.h

#if !(CONFIG_IDF_TARGET_ESP32 && CONFIG_SPIRAM && CONFIG_SPIRAM_CACHE_WORKAROUND)
#define MICROPY_WRAP_MP_BINARY_OP(f) IRAM_ATTR f
#endif
#define MICROPY_WRAP_MP_EXECUTE_BYTECODE(f) IRAM_ATTR f
#define MICROPY_WRAP_MP_LOAD_GLOBAL(f) IRAM_ATTR f
#define MICROPY_WRAP_MP_LOAD_NAME(f) IRAM_ATTR f
#define MICROPY_WRAP_MP_MAP_LOOKUP(f) IRAM_ATTR f
#define MICROPY_WRAP_MP_OBJ_GET_TYPE(f) IRAM_ATTR f
#define MICROPY_WRAP_MP_SCHED_EXCEPTION(f) IRAM_ATTR f
#define MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(f) IRAM_ATTR f

I am changing it to read..

#if !(CONFIG_IDF_TARGET_ESP32 && CONFIG_SPIRAM && CONFIG_SPIRAM_CACHE_WORKAROUND)
#define MICROPY_WRAP_MP_SCHED_KEYBOARD_INTERRUPT(f) IRAM_ATTR f
#define MICROPY_WRAP_MP_SCHED_EXCEPTION(f) IRAM_ATTR f
#define MICROPY_WRAP_MP_BINARY_OP(f) IRAM_ATTR f
#endif
#define MICROPY_WRAP_MP_EXECUTE_BYTECODE(f) IRAM_ATTR f
#define MICROPY_WRAP_MP_LOAD_GLOBAL(f) IRAM_ATTR f
#define MICROPY_WRAP_MP_LOAD_NAME(f) IRAM_ATTR f
#define MICROPY_WRAP_MP_MAP_LOOKUP(f) IRAM_ATTR f
#define MICROPY_WRAP_MP_OBJ_GET_TYPE(f) IRAM_ATTR f

The other things I really want to leave in IRAM because if they are taken out of IRAM it could end up causing a pretty large performance hit.

kdschlosser commented 1 week ago

You can also change the following in the same file

#ifndef MICROPY_PY_MACHINE_I2S
#define MICROPY_PY_MACHINE_I2S              (SOC_I2S_SUPPORTED)
#endif

to read

#ifndef MICROPY_PY_MACHINE_I2S
#define MICROPY_PY_MACHINE_I2S              (0)
#endif

If you are not using I2S. There are a couple of function that MicroPython has defined using IRAM when using I2S.

kdschlosser commented 1 week ago

You can also change the following code in the same file

#define MICROPY_PY_MACHINE_BITSTREAM        (1)

to

#define MICROPY_PY_MACHINE_BITSTREAM        (0)

if you are not using the cycle counter in MicroPython (machine.bitstream)

ste7anste7an commented 1 week ago

There is a typo in /lvgl_micropython/builder/esp32.py. Line 856

with open(mpconfigport, 'rb') as f:

should be

with open(mpconfigport, 'wb') as f:

I toke the branch with the changed mpconfigport.h (lines moved for IRM), disabled I2S, but I need bitstream as it is needed for NeoPixels. Still runs out of IRAM. What would be the reason the the former lvgl_micropython version did not have this problem?

GC-RnD commented 1 week ago

Hi Kevin...

Using ESP32 SPIRAM.. Built using 6/20/2024 repo command...

python3 make.py esp32 submodules clean mpy_cross BOARD=ESP32_GENERIC BOARD_VARIANT=SPIRAM DISPLAY=ili9341 INDEV=xpt2046 --flash-size=4 --optimize-size

Build went fine !!!

Flashed to ESP32 Got REPL !

Ran the following code with no issues Screen turns on (command to driver) but have no text 'HELLO WORLD!'


import lvgl as lv  # NOQA
import ili9341  # NOQA
import lcd_bus
from machine import SPI, Pin  # NOQA
from micropython import const  # NOQA

# Display settings
WIDTH = const(240)
HEIGHT = const(320)

# Display SPI bus settings
LCD_HOST = const(2) # I use vspi which is 
LCD_FREQ = const(400000)
LCD_MISO = const(23)
LCD_MOSI = const(19)
LCD_SCK = const(18)
LCD_CS = const(15)
LCD_DC = const(13)
LCD_BKL = const(27)

# create the SPI bus for the display
spi_bus = SPI(
    LCD_HOST,
    LCD_FREQ,
    mosi=LCD_MOSI,
    miso=LCD_MISO,
    sck=LCD_SCK
)

# create the SPI device on the bus for the display
display_bus = lcd_bus.SPIBus(
    spi_bus=spi_bus,
    dc=LCD_DC,
    freq=LCD_FREQ,
    cs=LCD_CS
)

# create the display driver
display = ili9341.ILI9341(
    data_bus=display_bus,
    display_width=WIDTH,
    display_height=HEIGHT,
    reset_pin=None,
    power_pin=None,
    backlight_pin=LCD_BKL,
    color_space=lv.COLOR_FORMAT.RGB565,
    rgb565_byte_swap=True
)

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

# lv.init()
# scr = lv.obj()
# scr = lv.scr_act()
scrn = lv.screen_active()
label = lv.label(scrn)
label.set_text('HELLO WORLD!')
label.align(lv.ALIGN.CENTER, 0, 50)

# lv.scr_load(scr)
lv.refr_now(None)

Doubble checked my pins... I know, I am using VSPI Also, leading underscores are not working with const . I must have over looked something very simple.

by the way... I'm the Galileo thermometer guy.

kdschlosser commented 1 week ago

OH Hey what's going on?

I will get you up and running as fast as I am able to.

Having the underscore prefix to a constant means it is not a variable. IE if you have a module named some_module1 and in that module you have this code

from micropython import const

_TEST = const(1)

In a second module named some_module2 you have the following code

import some_module1

print(some_module1._TEST)

You will get an attribute error.

This is because _TEST is not actually a variable so it is not accessible. Using const works in a similar manner to using the c preprocessor macro define (#define) in C code. except that it's access is limited to only the module it is declared in.

VSPI pins are defined as

MISO: 19 MOSI: 23 CLK: 18 CS: 5 WP: 22 HD: 21

It looks like you have MISO and MOSI flip flopped. Your CS pin is also wrong as well. Pin 5 is the standard pin for the CS line.

This is from the ESP-IDF

#define VSPI_IOMUX_PIN_NUM_MISO 19
#define VSPI_IOMUX_PIN_NUM_MOSI 23
#define VSPI_IOMUX_PIN_NUM_CLK  18
#define VSPI_IOMUX_PIN_NUM_CS   5
#define VSPI_IOMUX_PIN_NUM_WP   22
#define VSPI_IOMUX_PIN_NUM_HD   21

You can use display.set_backlight(100) to turn the backlight on. This is actually a better way and makes your code more portable in the event you are using a display that you can dim the backlight on.

also replace lv.refr_now(None) with the following code.

import task_handler

th = task_handler.TaskHandler()
kdschlosser commented 1 week ago

@ste7anste7an

You are correct. good catch!!!

GC-RnD commented 1 week ago

That was it... I swapped MO and MI I have done this before... couldn't they picked easier names. display.set_backlight(100) works but not with proportions with my screen. If understand correctly you're working on touch and SD card to all coexist on one SPI bus. My cs is 15 due to pcb layout.

Thank you very much Kevin !!!

kdschlosser commented 1 week ago

yes they can all exist right now.

give me a few to key out some examples for ya,

kdschlosser commented 1 week ago

Here is an example if the display touch and SDCard are all on the same bus,

import lcd_bus
from machine import SPI  # NOQA
from micropython import const  # NOQA

# Display settings
_WIDTH = const(240)
_HEIGHT = const(320)

# Display SPI bus settings
_SPI_HOST = const(2) # I use vspi which is 
_SPI_MISO = const(19)
_SPI_MOSI = const(23)
_SPI_SCK = const(18)

_LCD_FREQ = const(400000)
_LCD_CS = const(15)
_LCD_DC = const(13)
_LCD_BKL = const(27)

_SD_CS = const(5)  # change this
_SD_FREQ = const(10000)  # change this

_TS_CS = const(10)  # change this
_TS_FREQ = const(1000)  # change this

# create the SPI bus for the display
spi_bus = SPI(
    _SPI_HOST,
    _TS_FREQ,
    mosi=_SPI_MOSI,
    miso=_SPI_MISO,
    sck=_SPI_SCK,
    cs=_TS_CS
)

# create the SPI device on the bus for the display
display_bus = lcd_bus.SPIBus(
    spi_bus=spi_bus,
    dc=_LCD_DC,
    freq=_LCD_FREQ,
    cs=_LCD_CS
)

import lvgl as lv  # NOQA
import ili9341  # NOQA

# create the display driver
display = ili9341.ILI9341(
    data_bus=display_bus,
    display_width=_WIDTH,
    display_height=_HEIGHT,
    reset_pin=None,
    power_pin=None,
    backlight_pin=_LCD_BKL,
    color_space=lv.COLOR_FORMAT.RGB565,
    rgb565_byte_swap=True
)

from machine import SDCard  # NOQA
sdcard = SDCard(
    spi_bus=spi_bus,
    cs=_SD_CS,
    freq=_SD_FREQ
)

display.init()
display.set_backlight(100)

import xpt2046  # NOQA
import task_handler  # NOQA

touch = xpt2046.XPT2046(spi_bus)

th = task_handler.TaskHandler()

scrn = lv.screen_active()
label = lv.label(scrn)
label.set_text('HELLO WORLD!')
label.align(lv.ALIGN.CENTER, 0, 50)
kdschlosser commented 1 week ago

I am going to very strongly suggest people use an esp32-s3 with octal flash and octal spiram. I was able to get a crap load of LVGL functions into IRAM which really steps up the performance. and I was also able to overclock both the spiram and the flash to 120Mhz (actually 240Hz because of DDR) which is a HUGE performance gain over the original 80 MHZ. I recommend adding some form of passive cooling to the ESP32 if you are going to do this.

GC-RnD commented 1 week ago

I take it there "is" a pecking order for the spi bus whereby you can't load SD card display and touch in any order.

Trying to understand where you're going with this a little bit. Could we create a generic spi_bus spi_bus = SPI(2, miso=19, mosi=23, sck=18) with no baud rate or cs

Then display = ili9341.ILI9341( data_bus=(spi_bus=spi_bus, cs=15, freq=400_000, dc=13) ) touch = xpt2046.XPT2046( data_bus=(spi_bus=spi_bus, cs=5, freq=1_000) ) sd = SDCard(spi_bus=spi_bus, cs=14, freq=10_000)

So what is lcd_bus.SPIBus doing ?? And why the touch doesn't need a similar module.

My display background is black my text is white and my buttons are yellow. In the past the generic setup gave me... white background black text and blue button. Do I have a color issue?

btn_cb() needs lv.refr_now(None) or I don't see text changes

and the response seems to be a little bit slow. any speed tweaks?

import lvgl as lv
import ili9341
import xpt2046
import lcd_bus
import task_handler 
import os, time
from machine import SDCard, SPI  # NOQA

def scr_txt(x):
    print(x)
    try:
        if lv.screen_active() == main:
            msg_txt.set_text(x)
    except Exception as e:
        pass

def btn_cb(event):
    if event.get_code() == lv.EVENT.CLICKED:
        scr_txt('clicked')
        lv.refr_now(None)
        time.sleep(1)
        msg_txt.set_text('I feel you')
        lv.refr_now(None)
        time.sleep(2)
        msg_txt.set_text('Do Again !!')
        lv.refr_now(None)
        th

# Create the SPI bus
spi_bus = SPI(2, 1000, miso=19, mosi=23, sck=18, cs=5)

# SPI device for display
display_bus = lcd_bus.SPIBus(spi_bus=spi_bus, cs=15, freq=400_000, dc=13)
# Start display driver
display = ili9341.ILI9341(
    data_bus=display_bus,
    display_width=240,
    display_height=320,
    reset_pin=None,
    power_pin=None,
    backlight_pin=27,
    color_space=lv.COLOR_FORMAT.RGB565,
    rgb565_byte_swap=True
)

# Start touch driver
touch = xpt2046.XPT2046(spi_bus)

# Mount SD Card
try:
    sd = SDCard(spi_bus=spi_bus, cs=14, freq=10000)
    os.mount(sd, '/sd')
    scr_txt('Flash Mounted')
    scr_txt(os.listdir('/'))
except Exception as er:
    scr_txt(f'Flash Error\n{er}\nrebooting')
    time.sleep(5)
    reset()

main = lv.obj()
msg_txt = lv.label(main)
msg_txt.align(lv.ALIGN.CENTER, 0, 50)
msg_txt.set_text('Hello World !!')

btn = lv.button(main)
btn.set_size(90,40)
btn.align(lv.ALIGN.CENTER, 0, 0)
btn.add_event_cb(btn_cb, lv.EVENT.CLICKED, None)
btn_lbl = lv.label(btn)
btn_lbl.set_text("Click Me !")
btn_lbl.align(lv.ALIGN.LEFT_MID, 0, 0)

lv.screen_load(main)
display.init()
display.set_backlight(100)
th = task_handler.TaskHandler()

print(lv.display_get_screen_active())

lv.display_get_screen_active() does not give me currently loaded screen. I get... AttributeError: 'module' object has no attribute 'display_get_screen_active' was hoping to get main.

from lvgl... lv_obj_t lv_display_get_screen_active(lv_display_t disp) Return a pointer to the active screen on a display Parameters: disp -- pointer to display which active screen should be get. (NULL to use the default screen) Returns: pointer to the active screen object (loaded by 'lv_screen_load()')

Thanks again Kevin this is great work !!!!

GC-RnD commented 1 week ago

So went in and changed lv_conf.h and changed #define LV_THEME_DEFAULT_DARK 0 button is still yellow.

kdschlosser commented 1 week ago

lv_display_get_screen_active is not at the module level. it is apart of the display you created


display = ili9341.ILI9341(
    data_bus=display_bus,
    display_width=240,
    display_height=320,
    reset_pin=None,
    power_pin=None,
    backlight_pin=27,
    color_space=lv.COLOR_FORMAT.RGB565,
    rgb565_byte_swap=True
)

scrn = display.get_screen_active()

But lets say you created the display in the main.py file and in there you imported another python module, lets call it ui.py. well you cannot import main.py form ui.py because of circular imports. So how is that handled? Like this...

this is ui.py that is imported in main.py

import ili9341

display = ili9341.ILI9341.get_default()

scrn = display.get_screen_active()

I thought of damned near everything. 😃

There are some things that I will be addressing when I eventually decide to redo the C code generator. One of those things would solve this issue.


display = ili9341.ILI9341(
    data_bus=display_bus,
    display_width=240,
    display_height=320,
    reset_pin=None,
    power_pin=None,
    backlight_pin=27,
    color_space=lv.COLOR_FORMAT.RGB565,
    rgb565_byte_swap=True
)

scrn = display.get_screen_active()

print(display  == scrn.get_display())

that evaluates to False. This is because the display that is returned by get_display is an instance of the lv_display_t structure where as display is a python wrapper around lv_display_t. Even if I sub classing lv_display_t that doesn't fix that problem either.

The reason why it doesn't work as a sub class is because of how the binding code is written. in order for it to work properly the first field in every structure in LVGL would need to be a void pointer that could be used to hold a micropython base type. That would be the only way it could be done so that things would get returned properly.

kdschlosser commented 1 week ago

I need to change the machine.SPI class so it is a little easier to understand what is happening. I need to make an SPIBus class and an SPIDevice class. I would ideally like to have these in the machine module but because of how it is written it is not possible to do without rewriting the machine module. That's the crappy part.

kdschlosser commented 1 week ago

Also. There is a reason why I have the imports all scattered through the code. This is because of memory use. You want to have as much SRAM free as possible when the frame buffers get allocated. This is even more important if using the ESP32 because there is only a small amount of DMA memory available for the ESP32. With the ESP32-S3 you can allocate SPIRAM as DMA memory, this is not possible with the ESP32. So the more you import the less chance there is of having enough DMA'able memory for running double buffering.

For convenience purposes I have written the code so the display driver will create the frame buffer(s). It will first try to create the buffers as DMA in SRAM. If that fails then it will try and create the buffers in DMA SPIRAM. If that fails then it will try and create a single buffer in SRAM and if that fails then it will try and make it in SPIRAM. The best is going to be 2 buffers that are DMA in SRAM. That is going to give the best performance.

This is a good example that shows some really advanced use. It shows how to ensure that the frame buffers get allocated in SRAM using DMA memory and double buffering. It also shows how to check for the SDCard without having to keep on rebooting the ESP32. AND it even goes into using threads and a mutex/lock to ensure that everything is going to p[lay nice nice with each other. AND it also shows you how to use timers in LVGL instead of having to make a change and then telling LVGL to update the display then pausing the program before making another change.

Click here for example code ```python import lcd_bus import machine # NOQA # Create the SPI bus spi_bus = machine.SPI(2, 1000, miso=19, mosi=23, sck=18, cs=5) # SPI device for display display_bus = lcd_bus.SPIBus(spi_bus=spi_bus, cs=15, freq=400_000, dc=13) # doing is this way makes sure that less SRAM # is used prior to the frame buffers being made # as you can see the imports for LVGL and also the display driver happen # after the buffers get created. fb1 = display_bus.allocate_framebuffer(240 * 320 * 2, lcd_bus.MEMORY_INTERNAL | lcd_bus.MEMORY_SPIRAM) fb2 = display_bus.allocate_framebuffer(240 * 320 * 2, lcd_bus.MEMORY_INTERNAL | lcd_bus.MEMORY_SPIRAM) import ili9341 import lvgl as lv # Start display driver display = ili9341.ILI9341( data_bus=display_bus, display_width=240, display_height=320, frame_buffer1=fb1, frame_buffer2=fb2, reset_pin=None, power_pin=None, backlight_pin=27, color_space=lv.COLOR_FORMAT.RGB565, rgb565_byte_swap=True ) # you need to initilize the display # prior to constructing the touch driver display.init() import xpt2046 # NOQA # Start touch driver touch = xpt2046.XPT2046(spi_bus) # Mount SD Card import os, time, _thread lock = _thread.allocate_lock() import task_handler th = task_handler.TaskHandler() # overrides the calling of lv.task_handler so we can call # it here with a lock object def new_handler(event, user_data): with lock: lv.task_handler() # tells task_handler.TaskHandler not to call lv.task_handler return False th.add_event_cb(new_handler, th.TASK_HANDLER_STARTED, None) def timer2_callback(_): with lock: msg_txt.set_text('Do Again !!') def timer1_callback(_): with lock: msg_txt.set_text('I feel you') timer2.reset() timer1 = lv.timer_create(timer1_callback, 1000, None) timer1.set_repeat_count(1) timer1.set_auto_delete(False) timer1.pause() timer2 = lv.timer_create(timer2_callback, 2000, None) timer2.set_repeat_count(1) timer2.set_auto_delete(False) timer2.pause() def btn_cb(event): if event.get_code() == lv.EVENT.CLICKED: if not timer1.get_paused(): timer1.pause() if not timer2.get_paused(): timer2.pause() scr_txt('clicked') timer1.reset() def scr_txt(x): with lock: try: if lv.screen_active() == main: msg_txt.set_text(x) except: # NOQA pass sd = None def try_load_sd(): global sd try: # I used the 10000 as an example. I do not know if that # is what the frequency is supposed to be. That is something # you will haver to check on. # there are also other pins like cd (Card Detect) and # wp "Write Protect". so you need to see if those are available as well. # I have not messed about with using the SDCard in MicroPython. sd = machine.SDCard(spi_bus=spi_bus, cs=14, freq=10000) os.mount(sd, '/sd') return True except: # NOQA return False exit_thread = False # this allows for the script to keep on checking # to see if the SD Card is available. def run(): while not exit_thread: if sd is None and try_load_sd(): scr_txt(f'Flash Mounted\n\n{"\n".join(os.listdir("/"))}') # you can run whatever other code you want from here. # this will allow the repl to still function so if you nmeed to access # the esp32 you can do so while the program is running. You just have # to remember that you cannot access anything in LVGL from the repl # because you would be doing so from 2 different threads and LVGL is not # thread safe. # you can force this thread to exist from the repl by typing in # exit_thread = True time.sleep(1) main = lv.obj() msg_txt = lv.label(main) msg_txt.align(lv.ALIGN.CENTER, 0, 50) msg_txt.set_text('Hello World !!') btn = lv.button(main) btn.set_size(90, 40) btn.align(lv.ALIGN.CENTER, 0, 0) btn.add_event_cb(btn_cb, lv.EVENT.CLICKED, None) btn_lbl = lv.label(btn) btn_lbl.set_text("Click Me !") btn_lbl.align(lv.ALIGN.LEFT_MID, 0, 0) lv.screen_load(main) display.set_backlight(100) # you will notice there is a lock object. This is because # the task_handler.TaskHander insteance works by using an interrupt timer # to schedule a micropython task that gets executed in the main thread. # you don't want to call lv.TaskHander from the main thread while possibly # updating an object from the thread that does the SD Card checking. # start the SDCard check thread _thread.start_new_thread(run, ()) ```
GC-RnD commented 1 week ago

Kevin... that code is giving me sensory overload !!! Even though it is well commented, it's going to take a while to digest it. I wasn't aware that the order of modules imported affected memory use.

it is not possible to do without rewriting the machine module. That's the crappy part.

It makes me question if MicroPython is ready for mainstream use when a basic interface has had an issue for so dam long.

The example had an error... Traceback (most recent call last): File "", line 26, in MemoryError: Unable to allocate frame buffer

I have 4mb ESP32-WROVER-E-N4R8 ... MicroPython v1.24.0-preview.39.g411d66586.dirty on 2024-06-17; Generic ESP32 module with SPIRAM with ESP32

lv_display_get_screen_active issue what i needed was a way to determine what screen is currently active if lv.screen_active() == main: is working for me now.

By the way... what is the standard button color? Because I'm getting yellow sort of orange color.

kdschlosser commented 1 week ago

the N4R8 is 4 MB of flash and 8mb of ram.

The reason why you are getting the memory error is because there is not enough DMA'able memory available. I forgot to divide the buffers sizes by 10.

changes the lines that read

fb1 = display_bus.allocate_framebuffer(240 * 320 * 2, lcd_bus.MEMORY_INTERNAL | lcd_bus.MEMORY_SPIRAM)
fb2 = display_bus.allocate_framebuffer(240 * 320 * 2, lcd_bus.MEMORY_INTERNAL | lcd_bus.MEMORY_SPIRAM)

to

fb1 = display_bus.allocate_framebuffer(int(240 * 320 * 2 / 10), lcd_bus.MEMORY_INTERNAL | lcd_bus.MEMORY_SPIRAM)
fb2 = display_bus.allocate_framebuffer(int(240 * 320 * 2 / 10), lcd_bus.MEMORY_INTERNAL | lcd_bus.MEMORY_SPIRAM)
kdschlosser commented 1 week ago

some of the ILI series of displays for whatever reason hold the bytes in their internal GRAM reversed. so you need to change this code

display = ili9341.ILI9341(
    data_bus=display_bus,
    display_width=240,
    display_height=320,
    frame_buffer1=fb1,
    frame_buffer2=fb2,
    reset_pin=None,
    power_pin=None,
    backlight_pin=27,
    color_space=lv.COLOR_FORMAT.RGB565,
    rgb565_byte_swap=True
)

to read

display = ili9341.ILI9341(
    data_bus=display_bus,
    display_width=240,
    display_height=320,
    frame_buffer1=fb1,
    frame_buffer2=fb2,
    reset_pin=None,
    power_pin=None,
    backlight_pin=27,
    color_space=lv.COLOR_FORMAT.RGB565,
    color_byte_order=ili9341.BYTE_ORDER_BGR,
    # if the colors still are not right then remove the comment marker for the next line
    # rgb565_byte_swap=True  
)
kdschlosser commented 1 week ago

There is no intention of changing the way the SPI works in MicroPython. It's just how they decided to code it. They decided on a design that would make the API work across all boards and use less memory and flash space instead. You can make it work the way they have it but it ends up costing more in the end. The thought process is that most people only have a single device attached to the bus. The big issue is the SDCard CANNOT work properly if there is more than one device on the bus using the ESP32 series of MCU's. It is an impossibility to get it to work properly with the way the MicroPython SPI is done.

It's fine the way it is and it works... It's a shortcoming that I was able to rectify so not a big deal.

GC-RnD commented 1 week ago

so the color is good to go...

    color_space=lv.COLOR_FORMAT.RGB565,
    color_byte_order=ili9341.BYTE_ORDER_BGR,
    rgb565_byte_swap=True

frame buffer divided... incremented by 10 up to 80

fb1 = display_bus.allocate_framebuffer(int(240 * 320 * 2 / 80), lcd_bus.MEMORY_INTERNAL | lcd_bus.MEMORY_SPIRAM)
fb2 = display_bus.allocate_framebuffer(int(240 * 320 * 2 / 80), lcd_bus.MEMORY_INTERNAL | lcd_bus.MEMORY_SPIRAM)

still got...

Traceback (most recent call last):
  File "<stdin>", line 105, in <module>
MemoryError: Unable to allocate frame buffer
kdschlosser commented 1 week ago

I am wondering if the machine.SPI is chewing up all of the available SRAM and that is what is causing the allocation issue.

I am going to have to rework the machine.SPI driver so instead of needing to pass a machine.SPI instance\ it will instead take the host number.

Give me a day to hammer that out. That has got to be what is causing the problem.

Can you do one thing for me. change the buffer allocation code to read this...

try:
    fb1 = display_bus.allocate_framebuffer(int(240 * 320 * 2 / 80), lcd_bus.MEMORY_INTERNAL | lcd_bus.MEMORY_SPIRAM)
except MemoryError:
    raise RuntimeError('Failing on first buffer')
try:
    fb2 = display_bus.allocate_framebuffer(int(240 * 320 * 2 / 80), lcd_bus.MEMORY_INTERNAL | lcd_bus.MEMORY_SPIRAM)
except MemoryError:
    raise RuntimeError('Failing on second buffer')

tell me if it is failing on the first or second buffer.

GC-RnD commented 6 days ago
Traceback (most recent call last):
  File "<stdin>", line 112, in <module>
RuntimeError: Failing on first buffer

I also changed display freq to 40Mhz and is running snappy now.

import lcd_bus
display_bus = lcd_bus.SPIBus(spi_bus=spi_bus, cs=15, freq=40_000_000, dc=13)

So I'm not doing any animations or needing any kind of fast screen refreshes at all. I have a static screen that updates every 59 seconds and a couple of buttons. Honesty I never liked having the task handler running interference in the background. I really liked to refresh the screen when I want to. That is why I use lv.refr_now(None)

I am having an issue with a widget..

m_box = lv.msgbox(lv.screen_active(), "So", "Hellow World Again", {"Ok", "Cancel", None}, True)

getting error...

Traceback (most recent call last):
  File "<stdin>", line 177, in <module>
TypeError: function takes 1 positional arguments but 5 were given

I posted on LVGL fourm also.

kdschlosser commented 6 days ago

lv.msgbox is like any other widget. You have to create the widget passing a parent or None and set other things

There are these methods to set things

m_box = lv.msgbox(lv.screen_active())
m_box.add_title("some title")
m_box.add_header_button(image_data)
m_box.add_text("Hellow World Again")
m_box.add_footer_button("OK")
m_box.add_footer_button("Cancel")
m_box.add_close_button()
GC-RnD commented 6 days ago

LVGL Doc... image

one liner worked in past

kdschlosser commented 6 days ago

In the past you were probably using version 8.3 of LVGL. This binding is using version 9.1

GC-RnD commented 6 days ago

Absolutely true that but this is not as good looking as the old message box

kdschlosser commented 6 days ago

I didn't make the change to it. You would have to open an issue in LVGL's repo about it.

I did make an issue for the documentation being incorrect tho.

kdschlosser commented 6 days ago

As a suggestion. If you are going to use the LVGL documentation I would only use the API section of the documentation. That section is pulled directly from the C code so it is going to show the real functions and parameters/types that need to be passed to those functions.

There is some name mangling that ends up being done because of the binding but for the most part it isn't that hard to figure out.

You can see here https://docs.lvgl.io/master/API/widgets/msgbox/lv_msgbox.html exactly what msgbox_create takes for arguments.

GC-RnD commented 6 days ago

You beat me to it... I was just gonna say that !! Also the old message box used to have a margin around it and fit within the screens proportions. I have a 240 wide screen and it's going right off the left and right sides. Also there is so much white space between the two buttons. And now you need a callback for each button... wtf

kdschlosser commented 6 days ago

Hey, I only work here... you have to complain to the higher ups. It's not within my scope of work.. LOL

kdschlosser commented 6 days ago

Just to let you know also.. the msgbox is a widget and like all widgets they are subclasses of lv.obj so it inherits all of the methods of lv.obj. so you would more then likely need to use msgbox.set_size(width, height) and then msgbox.center() to get it to the size and center it on the screen.

GC-RnD commented 6 days ago

Aren't these widgets supposed to make our lives easier... Yeah I usually look at the example code but it's true sometimes the transition from c to the micropython isn't as easy as it looks.

GC-RnD commented 6 days ago

Now having an issue with my portrait screen and touch relationship. Instead of us continuing in this thread I think you should have a widgets category. Where people can talk about their issues or funky things they're doing.

kdschlosser commented 6 days ago

but it's true sometimes the transition from c to the micropython isn't as easy as it looks.

I still to this day have not figured out why things are organized the way they are. Or how some of the things are done. One of these days I am going to write the whole code generator over again and flatten the entire thing so it matches with the LVGL C API. I had attempted to flatten it using the script in it's current form but there are too many glitches in it because of all of the wrapper code to create the classes and that causes a lot of issues.

I like the idea of generating the code but the issue there is the code is not going to be efficient. This is because of general ways of handling things on a per use basis. I would also like to have LVGL run on the core that is not being used. This would greatly improve the performance of it. MicroPython pitches a fit when trying to do things like calling a python function from c code that is running on a different core. It has a complete heart attach about it. Unfortunately I do not think multi core processing will ever be available for the ESP32 in MicroPython which is kind of sad because 50% of the processing power goes unused.

GC-RnD commented 6 days ago

If remember correctly that second core wasn't as powerfull... but still you could run a lot of dopey repetitive routines there. Weren't you originally trying to make LVGL a module?

GC-RnD commented 6 days ago

Thank you Kevin I really appreciate that !!!

GC-RnD commented 6 days ago

Also do you have any idea why the online simulator is busted ?

kdschlosser commented 6 days ago

I am going to move this repo to that organization. Right now there is nothing in the organization as I just made it. So there is nothing to really do in there just yet.

When I move the repo you will be able to create a discussion group for widgets or whatever else you want. You have to give me a while to get things set up on it tho.

It has been a long time since I have used github organizations and a lot has changed. So I have to go back to school and learn it.

GC-RnD commented 6 days ago

Got It !

kdschlosser commented 6 days ago

the core of the binding is a module. Most of what you see is a build script. The single most important thing about Python is ease of use. Getting MicroPython to run is anything but easy. That is the reason why the build script is so damned elaborate. That is what makes it easy to use. The majority of the build commands apply across all ports. which makes it easier to use. It handles collecting build requirements that do not need to be installed into the system which is port dependent.

The big issue is having to modify the MicroPython build system and source code in order to get things to work properly. The script handles doing these things for you. Upgrading to a new MicroPython version is pretty simple to do and that holds true for LVGL as well. In fact every time a change gets made in LVGL's master branch those changes get populated into this binding without having to do anything.

kdschlosser commented 6 days ago

I have developed a common API for display drivers and for touch drivers.. The importance of this is it's not port specific. It works the same across all ports. That allows code like this to run.


import sys
import lcd_bus
from micropython import const

_WIDTH = const(320)
_HEIGHT = const(480)

if sys.platform in ('darwin', 'linux'):
    import sdl_display

    DisplayDriver = sdl_display.SDLDisplay
    display_bus = lcd_bus.SDLBus(flags=0)

    color_byte_order = 0
    rgb565_byte_swap = False
    spi_bus = None

else:
    from machine import SPI
    import ili9341

    # Create the SPI bus
    spi_bus = SPI(2, 1000, miso=19, mosi=23, sck=18, cs=5)
    display_bus = lcd_bus.SPIBus(spi_bus=spi_bus, cs=15, freq=400_000, dc=13)

    color_byte_order = ili9341.BYTE_ORDER_BGR
    rgb565_byte_swap = True

    DisplayDriver = ili9341.ILI9341

fb1 = display_bus.allocate_framebuffer(_WIDTH * _HEIGHT * 2, lcd_bus.MEMORY_SPIRAM)
fb2 = display_bus.allocate_framebuffer(_WIDTH * _HEIGHT * 2, lcd_bus.MEMORY_SPIRAM)

import lvgl as lv

display = DisplayDriver(
    data_bus=display_bus,
    display_width=_WIDTH,
    display_height=_HEIGHT,
    frame_buffer1=fb1,
    frame_buffer2=fb2,
    reset_pin=None,
    power_pin=None,
    backlight_pin=27,
    color_space=lv.COLOR_FORMAT.RGB565,
    color_byte_order=color_byte_order,
    rgb565_byte_swap=rgb565_byte_swap
)

display.init()

if sys.platform in ('darwin', 'linux'):
    import sdl_pointer

    touch = sdl_pointer.SDLPointer()
else:
    import xpt2046

    touch = xpt2046.XPT2046(spi_bus)

import task_handler

th = task_handler.TaskHandler()

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

slider = lv.slider(scrn)
slider.set_size(_WIDTH - 50, 25)
slider.center()

so if you compile the Unix port either on macOS or Linux and run that script it will work. If you compile for the ESP32 and upload the same script it will work. This single feature allows you to develop your UI on a desktop without having to constantly upload to the MCU at all. That's because of the API being the same for the display drivers. The touch drivers get a little bit hairy due to accessing pins directly. I am still hammering that out to come up with something that is more consistent across them.

GC-RnD commented 6 days ago

So im using WSL2... Build for unix... correct how do I run the (firmware.bin) or is it a .exe ???? never done this before !!!

This single feature allows you to develop your UI on a desktop

kdschlosser commented 6 days ago

well you have to compile using this command

python3 make.py unix mpy_cross submodules DISPLAY=sdl_display INDEX=sdl_pointer --heap-size={BYTES}

Change {BYTES} to how many bytes of RAM you want to make available to MicroPython...

then you run the following

chmod +rwx build/lvgl_micropy_unix
build/lvgl_micropy_unix

the directory you run it from is the root that micropython starts in. so if you want to import a module you created you must place the module into the folder you ran micropython from in this case it would be lvgl_micropython if you cd into build and then run it using ./lvgl_micropy_unix then you would place the module in the build folder.

Everything is self contained in that single binary so you can move it to any directory of your choosing.