From 5cea992a704bf66836f32010422c78a31b974577 Mon Sep 17 00:00:00 2001 From: PGNetHun Date: Wed, 10 Jan 2024 00:49:15 +0100 Subject: [PATCH] Update ili9xxx, st77xx drivers and examples, remove lv.COLOR_DEPTH (#312) --- driver/esp32/espidf.c | 1 - driver/esp32/ili9XXX.py | 188 +++++++++++++++++--------------------- driver/generic/ili9xxx.py | 5 +- driver/generic/st77xx.py | 7 +- examples/advanced_demo.py | 7 +- examples/example3.py | 7 +- examples/png_example.py | 2 +- 7 files changed, 97 insertions(+), 120 deletions(-) diff --git a/driver/esp32/espidf.c b/driver/esp32/espidf.c index f548783fa..88e42e30d 100644 --- a/driver/esp32/espidf.c +++ b/driver/esp32/espidf.c @@ -9,7 +9,6 @@ #include "freertos/task.h" #include "esp_system.h" #include "soc/cpu.h" -#include "lvgl/src/draw/sw/lv_draw_sw.h" // ESP IDF has some functions that are declared but not implemented. diff --git a/driver/esp32/ili9XXX.py b/driver/esp32/ili9XXX.py index 80eb6bd3e..d05d80952 100644 --- a/driver/esp32/ili9XXX.py +++ b/driver/esp32/ili9XXX.py @@ -1,62 +1,42 @@ ############################################################################## -# Pure/Hybrid micropython lvgl display driver for +# Pure/Hybrid MicroPython LVGL display driver for # ili9341, ili9488, ili9488g, gc9a01, st7789 on ESP32 # -# For ili9341 display: # -# Build micropython with -# LV_CFLAGS="-DLV_COLOR_DEPTH=16" -# (make parameter) to configure LVGL use the same color format as ili9341 -# and prevent the need to loop over all pixels to translate them. -# -# For ili9488 display: +# Critical function for high FPS are flush and ISR. +# when "hybrid=True", use C implementation for these functions instead of +# pure python implementation. This improves each frame in about 15ms! # -# Build micropython with -# LV_CFLAGS="-DLV_COLOR_DEPTH=32" -# (make parameter) to configure LVGL use the ARGB color format, which can be -# easily converted to RGB format used by ili9488 display. +# When hybrid=False driver is pure micropython. +# Pure Micropython could be viable when ESP32 supports Viper code emitter. # -# Default SPI freq is set to 40MHz. This means cca 9fps of full screen -# redraw. to increase FPS, you can use 80MHz SPI - easily add parameter -# mhz=80 in initialization of driver. +# ili9488 driver DO NOT support pure micropython now (because of required +# color convert). Pure MicroPython is only supported for ili9341 and +# gc9a01 displays! # -# For ili9488g (gCore) display: # -# Build micropython with either -# LV_CFLAGS="-DLV_COLOR_DEPTH=32" -# or -# LV_CFLAGS="-DLV_COLOR_DEPTH=16 -DLV_COLOR_16_SWAP=1" +# Display configurations: # -# Default SPI freq is set to 80MHz. +# ili9341: +# - # -# For gc9a01 display: +# ili9488: +# - SPI frequency: 40 MHz +# This means cca 9fps of full screen +# redraw. to increase FPS, you can use 80MHz SPI - easily add parameter +# mhz=80 in initialization of driver. # -# Build micropython with -# LV_CFLAGS="-DLV_COLOR_DEPTH=16" -# (make parameter) to configure LVGL use the same color format as ili9341 -# and prevent the need to loop over all pixels to translate them. +# ili9488g (gCore): +# - SPI frequency: 80 MHz # -# Default SPI freq is set to 60MHz as that is the maximum the tested display +# gc9a01: +# - SPI frequency: 60 MHz, as that is the maximum the tested display # would support despite the datasheet suggesting that higher freqs would be # supported # -# For st7789 display: -# -# Build micropython with -# LV_CFLAGS="-DLV_COLOR_DEPTH=16" -# (make parameter) to configure LVGL use the same color format as ili9341 -# and prevent the need to loop over all pixels to translate them. +# st7789: +# - # -# Critical function for high FPS are flush and ISR. -# when "hybrid=True", use C implementation for these functions instead of -# pure python implementation. This improves each frame in about 15ms! -# -# When hybrid=False driver is pure micropython. -# Pure Micropython could be viable when ESP32 supports Viper code emitter. -# -# ili9488 driver DO NOT support pure micropython now (because of required -# color convert). Pure micropython is only supported the for ili9341 and -# gc9a01 displays! ############################################################################## import espidf as esp @@ -68,10 +48,9 @@ from micropython import const micropython.alloc_emergency_exception_buf(256) -# gc.threshold(0x10000) # leave enough room for SPI master TX DMA buffers -# Constants +# Constants COLOR_MODE_RGB = const(0x00) COLOR_MODE_BGR = const(0x08) @@ -97,13 +76,10 @@ DISPLAY_TYPE_ST7789 = const(4) DISPLAY_TYPE_ST7735 = const(5) -_TRANS_BUFFER_LEN = const(16) +TRANSFER_BUFFER_LENGTH = const(16) class ili9XXX: - display_name = 'ili9XXX' - init_cmds = [ ] - # Default values of "power" and "backlight" are reversed logic! 0 means ON. # You can change this by setting backlight_on and power_on arguments. @@ -111,7 +87,7 @@ def __init__(self, miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, power=14, backlight=15, backlight_on=0, power_on=0, spihost=esp.HSPI_HOST, spimode=0, mhz=40, factor=4, hybrid=True, width=240, height=320, start_x=0, start_y=0, invert=False, double_buffer=True, half_duplex=True, display_type=0, asynchronous=False, initialize=True, - color_format=None, swap_rgb565_bytes=False + color_format=lv.COLOR_FORMAT.RGB565, swap_rgb565_bytes=False ): # Initializations @@ -119,6 +95,8 @@ def __init__(self, if not lv.is_initialized(): lv.init() + self.display_name = self.display_name or 'ili9XXX' + self.init_cmds = self.init_cmds or [ ] self.asynchronous = asynchronous self.initialize = initialize @@ -144,9 +122,28 @@ def __init__(self, self.hybrid = hybrid self.half_duplex = half_duplex self.display_type = display_type + self.color_format = color_format self.swap_rgb565_bytes = swap_rgb565_bytes + self.rgb565_swap_func = lv.draw_sw_rgb565_swap if swap_rgb565_bytes else None - self.buf_size = (self.width * self.height * lv.COLOR_DEPTH // 8) // factor + # SPI + self.start_time_ptr = esp.C_Pointer() + self.end_time_ptr = esp.C_Pointer() + self.trans_result_ptr = esp.C_Pointer() + self.trans = esp.spi_transaction_t() + self.flush_acc_setup_cycles = 0 + self.flush_acc_dma_cycles = 0 + + # Flush + self.pixel_size = lv.color_format_get_size(color_format) + + # Monitor + self.monitor_acc_time = 0 + self.monitor_acc_px = 0 + self.monitor_count = 0 + self.cycles_in_ms = esp.esp_clk_cpu_freq() // 1000 + + self.buf_size = (self.width * self.height * self.pixel_size) // factor if invert: self.init_cmds.append({'cmd': 0x21}) @@ -174,10 +171,9 @@ def __init__(self, 'dt': self.display_type, 'swap_rgb565_bytes': self.swap_rgb565_bytes, 'start_x': self.start_x, - 'start_y': self.start_y}) + 'start_y': self.start_y + }) - # TODO: enable monitor by listening to LV_EVENT_RENDER_READY - # self.disp_drv.monitor_cb = self.monitor if color_format: self.disp_drv.set_color_format(color_format) @@ -244,7 +240,7 @@ def disp_spi_init(self): ret = esp.spi_bus_initialize(self.spihost, buscfg, 1) if ret != 0: raise RuntimeError("Failed initializing SPI bus") - self.trans_buffer = esp.heap_caps_malloc(_TRANS_BUFFER_LEN, esp.MALLOC_CAP.DMA) + self.trans_buffer = esp.heap_caps_malloc(TRANSFER_BUFFER_LENGTH, esp.MALLOC_CAP.DMA) self.cmd_trans_data = self.trans_buffer.__dereference__(1) self.word_trans_data = self.trans_buffer.__dereference__(4) @@ -285,8 +281,6 @@ def flush_isr(spi_transaction_ptr): # This function is called from finilizer during gc sweep - therefore must not allocate memory! # - trans_result_ptr = esp.C_Pointer() - def deinit(self): print('Deinitializing {}..'.format(self.display_name)) @@ -330,13 +324,8 @@ def deinit(self): esp.heap_caps_free(self.trans_buffer) self.trans_buffer = None - ###################################################### - - trans = esp.spi_transaction_t() # .__cast__( -# esp.heap_caps_malloc( -# esp.spi_transaction_t.__SIZE__, esp.MALLOC_CAP.DMA)) - + def spi_send(self, data): self.trans.length = len(data) * 8 # Length is in bytes, transaction length is in bits. self.trans.tx_buffer = data # data should be allocated as DMA-able memory @@ -350,7 +339,6 @@ def spi_send_dma(self, data): esp.spi_device_queue_trans(self.spi, self.trans, -1) ###################################################### - ###################################################### def send_cmd(self, cmd): esp.gpio_set_level(self.dc, 0) # Command mode @@ -359,7 +347,7 @@ def send_cmd(self, cmd): def send_data(self, data): esp.gpio_set_level(self.dc, 1) # Data mode - if len(data) > _TRANS_BUFFER_LEN: raise RuntimeError('Data too long, please use DMA!') + if len(data) > TRANSFER_BUFFER_LENGTH: raise RuntimeError('Data too long, please use DMA!') trans_data = self.trans_buffer.__dereference__(len(data)) trans_data[:] = data[:] self.spi_send(trans_data) @@ -441,15 +429,8 @@ def power_down(self): if self.backlight != -1: esp.gpio_set_level(self.backlight, 1 - self.backlight_on) - ###################################################### - start_time_ptr = esp.C_Pointer() - end_time_ptr = esp.C_Pointer() - flush_acc_setup_cycles = 0 - flush_acc_dma_cycles = 0 - _rgb565_swap = lv.draw_sw_rgb565_swap - def flush(self, disp_drv, area, color_p): if self.end_time_ptr.int_val and self.end_time_ptr.int_val > self.start_time_ptr.int_val: @@ -472,7 +453,6 @@ def flush(self, disp_drv, area, color_p): self.send_trans_word() # Page addresses - self.send_cmd(0x2B) y1 = area.y1 + self.start_y @@ -485,14 +465,13 @@ def flush(self, disp_drv, area, color_p): self.send_trans_word() # Memory write by DMA, disp_flush_ready when finished - self.send_cmd(0x2C) size = (x2 - x1 + 1) * (y2 - y1 + 1) - data_view = color_p.__dereference__(size * lv.COLOR_DEPTH // 8) + data_view = color_p.__dereference__(size * self.pixel_size) - if self.swap_rgb565_bytes: - self._rgb565_swap(data_view, size) + if self.rgb565_swap_func: + self.rgb565_swap_func(data_view, size) esp.get_ccount(self.end_time_ptr) if self.end_time_ptr.int_val > self.start_time_ptr.int_val: @@ -503,12 +482,6 @@ def flush(self, disp_drv, area, color_p): ###################################################### - monitor_acc_time = 0 - monitor_acc_px = 0 - monitor_count = 0 - - cycles_in_ms = esp.esp_clk_cpu_freq() // 1000 - def monitor(self, disp_drv, time, px): self.monitor_acc_time += time self.monitor_acc_px += px @@ -552,13 +525,13 @@ def __init__(self, miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, power=14, backlight=15, backlight_on=0, power_on=0, spihost=esp.HSPI_HOST, spimode=0, mhz=40, factor=4, hybrid=True, width=240, height=320, start_x=0, start_y=0, colormode=COLOR_MODE_BGR, rot=PORTRAIT, invert=False, double_buffer=True, half_duplex=True, - asynchronous=False, initialize=True, color_format=lv.COLOR_FORMAT.NATIVE, swap_rgb565_bytes=True + asynchronous=False, initialize=True, color_format=lv.COLOR_FORMAT.RGB565, swap_rgb565_bytes=True ): - # Make sure Micropython was built such that color won't require processing before DMA + # Make sure MicroPython was built such that color won't require processing before DMA - if lv.COLOR_DEPTH != 16: - raise RuntimeError('ili9341 micropython driver requires defining LV_COLOR_DEPTH=16') + if lv.color_format_get_bpp(color_format) != 16: + raise RuntimeError('ili9341 micropython driver requires 16 bit color format') self.display_name = 'ILI9341' @@ -604,11 +577,11 @@ def __init__(self, miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, power=14, backlight=15, backlight_on=0, power_on=0, spihost=esp.HSPI_HOST, spimode=0, mhz=40, factor=8, hybrid=True, width=320, height=480, colormode=COLOR_MODE_RGB, rot=PORTRAIT, invert=False, double_buffer=True, half_duplex=True, asynchronous=False, initialize=True, - color_format=None, display_type=DISPLAY_TYPE_ILI9488, p16=False + color_format=lv.COLOR_FORMAT.XRGB8888, display_type=DISPLAY_TYPE_ILI9488, p16=False, swap_rgb565_bytes=False ): - if (lv.COLOR_DEPTH != 32) and not p16: - raise RuntimeError('ili9488 micropython driver requires defining LV_COLOR_DEPTH=32') + if (lv.color_format_get_bpp(color_format) != 32) and not p16: + raise RuntimeError('ili9488 micropython driver requires 32 bit color format') if not hybrid: raise RuntimeError('ili9488 micropython driver do not support non-hybrid driver') @@ -649,32 +622,35 @@ def __init__(self, backlight_on=backlight_on, power_on=power_on, spihost=spihost, spimode=spimode, mhz=mhz, factor=factor, hybrid=hybrid, width=width, height=height, invert=invert, double_buffer=double_buffer, half_duplex=half_duplex, display_type=display_type, asynchronous=asynchronous, initialize=initialize, - color_format=color_format) + color_format=color_format, swap_rgb565_bytes=swap_rgb565_bytes) class ili9488g(ili9488): def __init__(self, miso=-1, mosi=23, clk=18, cs=5, dc=27, rst=-1, power=-1, backlight=-1, backlight_on=0, power_on=0, spihost=esp.VSPI_HOST, spimode=0, mhz=80, factor=8, hybrid=True, width=320, height=480, - rot=PORTRAIT, invert=False, double_buffer=True, half_duplex=True, asynchronous=False, initialize=True + rot=PORTRAIT, invert=False, double_buffer=True, half_duplex=True, asynchronous=False, initialize=True, + color_format=lv.COLOR_FORMAT.XRGB8888, swap_rgb565_bytes=False ): - if lv.COLOR_DEPTH == 32: + if lv.color_format_get_bpp(color_format) == 32: colormode=COLOR_MODE_RGB - color_format=None + color_format=lv.COLOR_FORMAT.XRGB8888 display_type=DISPLAY_TYPE_ILI9488 # 24-bit pixel handling p16=False - if lv.COLOR_DEPTH == 16: + if lv.color_format_get_bpp(color_format) == 16: colormode=COLOR_MODE_BGR - color_format=lv.COLOR_FORMAT.NATIVE_REVERSE + color_format=lv.COLOR_FORMAT.RGB565 + swap_rgb565_bytes=True display_type=DISPLAY_TYPE_ILI9341 # Force use of 16-bit pixel handling p16=True super().__init__(miso=miso, mosi=mosi, clk=clk, cs=cs, dc=dc, rst=rst, power=power, backlight=backlight, backlight_on=backlight_on, power_on=power_on, spihost=spihost, spimode=spimode, mhz=mhz, factor=factor, hybrid=hybrid, width=width, height=height, colormode=colormode, rot=rot, invert=invert, double_buffer=double_buffer, half_duplex=half_duplex, - asynchronous=asynchronous, initialize=initialize, color_format=color_format, display_type=display_type, p16=p16) + asynchronous=asynchronous, initialize=initialize, color_format=color_format, display_type=display_type, p16=p16, + swap_rgb565_bytes=swap_rgb565_bytes) class gc9a01(ili9XXX): @@ -685,11 +661,11 @@ def __init__(self, miso=5, mosi=18, clk=19, cs=13, dc=12, rst=4, power=14, backlight=15, backlight_on=0, power_on=0, spihost=esp.HSPI_HOST, spimode=0, mhz=60, factor=4, hybrid=True, width=240, height=240, colormode=COLOR_MODE_RGB, rot=PORTRAIT, invert=False, double_buffer=True, half_duplex=True, asynchronous=False, initialize=True, - color_format=None, swap_rgb565_bytes=True + color_format=lv.COLOR_FORMAT.RGB565, swap_rgb565_bytes=True ): - if lv.COLOR_DEPTH != 16: - raise RuntimeError('gc9a01 micropython driver requires defining LV_COLOR_DEPTH=16') + if lv.color_format_get_bpp(color_format) != 16: + raise RuntimeError('gc9a01 micropython driver requires 16 bit color format') # This is included as the color mode appears to be reversed from the # datasheet and the ili9XXX driver values @@ -774,12 +750,12 @@ def __init__(self, miso=-1, mosi=19, clk=18, cs=5, dc=16, rst=23, power=-1, backlight=4, backlight_on=1, power_on=0, spihost=esp.HSPI_HOST, spimode=0, mhz=40, factor=4, hybrid=True, width=320, height=240, start_x=0, start_y=0, colormode=COLOR_MODE_BGR, rot=PORTRAIT, invert=True, double_buffer=True, half_duplex=True, - asynchronous=False, initialize=True, color_format=lv.COLOR_FORMAT.NATIVE, swap_rgb565_bytes=True): + asynchronous=False, initialize=True, color_format=lv.COLOR_FORMAT.RGB565, swap_rgb565_bytes=True): # Make sure Micropython was built such that color won't require processing before DMA - if lv.COLOR_DEPTH != 16: - raise RuntimeError('st7789 micropython driver requires defining LV_COLOR_DEPTH=16') + if lv.color_format_get_bpp(color_format) != 16: + raise RuntimeError('st7789 micropython driver requires 16 bit color format') self.display_name = 'ST7789' @@ -826,12 +802,12 @@ def __init__(self, miso=-1, mosi=19, clk=18, cs=13, dc=12, rst=4, power=-1, backlight=15, backlight_on=1, power_on=0, spihost=esp.HSPI_HOST, spimode=0, mhz=40, factor=4, hybrid=True, width=128, height=160, start_x=0, start_y=0, colormode=COLOR_MODE_RGB, rot=PORTRAIT, invert=False, double_buffer=True, half_duplex=True, - asynchronous=False, initialize=True, color_format=lv.COLOR_FORMAT.NATIVE, swap_rgb565_bytes=True): + asynchronous=False, initialize=True, color_format=lv.COLOR_FORMAT.RGB565, swap_rgb565_bytes=True): # Make sure Micropython was built such that color won't require processing before DMA - if lv.COLOR_DEPTH != 16: - raise RuntimeError('st7735 micropython driver requires defining LV_COLOR_DEPTH=16') + if lv.color_format_get_bpp(color_format) != 16: + raise RuntimeError('st7735 micropython driver requires 16 bit color format') self.display_name = 'ST7735' diff --git a/driver/generic/ili9xxx.py b/driver/generic/ili9xxx.py index 06452fb35..e29ca36e0 100644 --- a/driver/generic/ili9xxx.py +++ b/driver/generic/ili9xxx.py @@ -101,10 +101,7 @@ class Ili9341_hw(st77xx.St77xx_hw): def __init__(self, **kw): - """ILI9341 TFT Display Driver. - - Requires ``LV_COLOR_DEPTH=16`` when building lv_micropython to function. - """ + """ILI9341 TFT Display Driver.""" super().__init__( res=(240, 320), suppRes=[ diff --git a/driver/generic/st77xx.py b/driver/generic/st77xx.py index 1aba6e8a3..6a7a6d279 100644 --- a/driver/generic/st77xx.py +++ b/driver/generic/st77xx.py @@ -446,9 +446,8 @@ def __init__(self,doublebuffer=True,factor=4): import lvgl as lv import lv_utils - if lv.COLOR_DEPTH!=16: raise RuntimeError(f'LVGL *must* be compiled with LV_COLOR_DEPTH=16 (currently LV_COLOR_DEPTH={lv.COLOR_DEPTH}.') - - bufSize=(self.width * self.height * lv.COLOR_DEPTH // 8) // factor + color_format = lv.COLOR_FORMAT.RGB565 + bufSize=(self.width * self.height * lv.color_format_get_size(color_format)) // factor if not lv.is_initialized(): lv.init() # create event loop if not yet present @@ -458,7 +457,7 @@ def __init__(self,doublebuffer=True,factor=4): self.disp_drv = lv.disp_create(self.width, self.height) self.disp_drv.set_flush_cb(self.disp_drv_flush_cb) self.disp_drv.set_draw_buffers(bytearray(bufSize), bytearray(bufSize) if doublebuffer else None, bufSize, lv.DISP_RENDER_MODE.PARTIAL) - self.disp_drv.set_color_format(lv.COLOR_FORMAT.NATIVE) + self.disp_drv.set_color_format(color_format) class St7735(St7735_hw,St77xx_lvgl): def __init__(self,res,doublebuffer=True,factor=4,**kw): diff --git a/examples/advanced_demo.py b/examples/advanced_demo.py index 62a3586d9..6e6c22fea 100644 --- a/examples/advanced_demo.py +++ b/examples/advanced_demo.py @@ -352,14 +352,17 @@ def init_gui_stm32(self): hres = 480 vres = 272 + color_format = lv.COLOR_FORMAT.ARGB8888 # Register display driver self.event_loop = event_loop() lcd.init(w=hres, h=vres) self.disp_drv = lv.display_create(hres, vres) self.disp_drv.set_flush_cb(lcd.flush) - buf1_1 = bytearray(hres * 50 * lv.COLOR_DEPTH // 8) - buf1_2 = bytearray(hres * 50 * lv.COLOR_DEPTH // 8) + self.disp_drv.set_color_format(color_format) + buf_size = hres * 50 * lv.color_format_get_size(color_format) + buf1_1 = bytearray(buf_size) + buf1_2 = bytearray(buf_size) self.disp_drv.set_draw_buffers(buf1_1, buf1_2, len(buf1_1), lv.DISPLAY_RENDER_MODE.PARTIAL) # Register touch sensor diff --git a/examples/example3.py b/examples/example3.py index 22a21b4f4..a6b3f8931 100644 --- a/examples/example3.py +++ b/examples/example3.py @@ -10,13 +10,16 @@ hres = 480 vres = 272 + color_format = lv.COLOR_FORMAT.ARGB8888 + lv.init() event_loop = lv_utils.event_loop() lcd.init(w=hres, h=vres) disp_drv = lv.disp_create(hres, vres) disp_drv.set_flush_cb(lcd.flush) - buf1_1 = bytearray(hres * 10 * lv.COLOR_DEPTH // 8) - buf1_2 = bytearray(hres * 10 * lv.COLOR_DEPTH // 8) + buf_size = hres * 10 * lv.color_format_get_size(color_format) + buf1_1 = bytearray(buf_size) + buf1_2 = bytearray(buf_size) disp_drv.set_draw_buffers(buf1_1, buf1_2, len(buf1_1), lv.DISP_RENDER_MODE.PARTIAL) # disp_drv.gpu_blend_cb = lcd.gpu_blend diff --git a/examples/png_example.py b/examples/png_example.py index cdfca2ce3..f2ada5882 100644 --- a/examples/png_example.py +++ b/examples/png_example.py @@ -1,7 +1,7 @@ ############################################################################## # # Example of the PNG image decoder Usage. -# For dragging to work reasonable, make sure LV_image_CACHE_DEF_SIZE is not 0! +# For dragging to work reasonable, make sure LV_CACHE_DEF_SIZE is not 0! # ##############################################################################