Closed Voinic closed 1 month ago
you should have your spi host set to 1
This is from the ESP-IDF
typedef enum {
//SPI1 can be used as GPSPI only on ESP32
SPI1_HOST=0, ///< SPI1
SPI2_HOST=1, ///< SPI2
#if SOC_SPI_PERIPH_NUM > 2
SPI3_HOST=2, ///< SPI3
#endif
SPI_HOST_MAX, ///< invalid host value
} spi_host_device_t;
and this is also from the ESP-IDF for the ESP32-S3
#define SPI2_FUNC_NUM 4
#define SPI2_IOMUX_PIN_NUM_HD 9
#define SPI2_IOMUX_PIN_NUM_CS 10
#define SPI2_IOMUX_PIN_NUM_MOSI 11
#define SPI2_IOMUX_PIN_NUM_CLK 12
#define SPI2_IOMUX_PIN_NUM_MISO 13
#define SPI2_IOMUX_PIN_NUM_WP 14
The pins you are using align with "SPI2" and "SPI2" aligns with the numerical value of 1
as seen in the enum in the first code block.
Don't ask me why they did that because it is confusing that they did. It would have been better if they called the first one SPI0 or if they started the numbering off at 1 instead of 0. either way it would have made more sense.
Also... unless there is a specific reason why you are putting the frame buffers in SPIRAM I would suggest not doing that. It is going to be a lot slower to transfer the data. Also you are not using DMA memory so using 2 frame buffers is not going to do anything except use up memory for no real reason. I would suggest letting the display driver hammer out the size and where to place the frame buffers. It will attempt to make 2 buffers and to place both buffers in SRAM and if that fails then it will attempt to make 2 buffers in SPIRAM. both of those attempts are done using DMA memory. If the second fails then it will attempt to make a single buffer in SRAM non DMA and if that fails then it will try the single buffer in SPIRAM.
I wrote the code for optimum performance so the buffer sizes that are created are exactly the same as what you are setting them to.
Also there is no need to call lv.init()
. This is already done in the driver code.
If you are using an SPI interface you will need to set the RGB565 byte swap to True
if you still have a white screen after making those changes then it is going to be a configuration issue with the display. I can make adjustments to the driver if that happens.
Thanks for detailed explanation on framebuffers. I made the suggested changes to the code, but still no result... Another question, is there a way to test display drivers separately from LVGL?
You changed the SPI host to a 1?
I want to make sure that is done. I can work on messing about with the init commands if that is done.
Yes, my actual code is
import machine
import sys
import lcd_bus
import ili9488
import lvgl as lv
# Display settings
_WIDTH = const(320)
_HEIGHT = const(480)
# Common SPI bus settings
_SPI_HOST = const(1)
_MOSI_PIN = const(11)
_MISO_PIN = const(13)
_SCLK_PIN = const(12)
# Display SPI bus settings
_LCD_SPI_FREQ = const(40_000_000)
_LCD_CS_PIN = const(10)
# display additional pins
_LCD_RST_PIN = const(15)
_LCD_DC_PIN = const(14)
_LCD_BKL_PIN = const(16)
spi_bus = machine.SPI.Bus(
host=_SPI_HOST,
miso=_MISO_PIN,
mosi=_MOSI_PIN,
sck=_SCLK_PIN,
)
display_bus = lcd_bus.SPIBus(
spi_bus=spi_bus,
freq=_LCD_SPI_FREQ,
dc=_LCD_DC_PIN,
cs=_LCD_CS_PIN
)
display = ili9488.ILI9488(
data_bus=display_bus,
display_width=_WIDTH,
display_height=_HEIGHT,
reset_pin=_LCD_RST_PIN,
reset_state=ili9488.STATE_LOW,
backlight_pin=_LCD_BKL_PIN,
backlight_on_state=ili9488.STATE_HIGH,
color_space=lv.COLOR_FORMAT.RGB565,
rgb565_byte_swap=True
)
display.init()
display.set_backlight(100)
scr = lv.obj()
btn = lv.button(scr)
btn.align(lv.ALIGN.CENTER, 0, 0)
label = lv.label(btn)
label.set_text("Hello World!")
lv.screen_load(scr)
import task_handler
th = task_handler.TaskHandler()
I made some changes in display driver according to https://github.com/lovyan03/LovyanGFX/blob/master/src/lgfx/v1/panel/Panel_ILI948x.hpp Now it works with color_space=lv.COLOR_FORMAT.RGB888 but image gets horizontally mirrored. Still don't work with RGB565.
My ili9488.py driver code:
from micropython import const # NOQA
import lvgl as lv # NOQA
import lcd_bus # NOQA
import display_driver_framework
_IFMODE = const(0xB0)
_FRMCTR1 = const(0xB1)
_DIC = const(0xB4)
_DFC = const(0xB6)
_EM = const(0xB7)
_PWR1 = const(0xC0)
_PWR2 = const(0xC1)
_VCMPCTL = const(0xC5)
_PGC = const(0xE0)
_NGC = const(0xE1)
_AC3 = const(0xF7)
_MADCTL = const(0x36)
_COLMOD = const(0x3A)
_NOP = const(0x00)
_SLPOUT = const(0x11)
_DISPON = const(0x29)
_IDMOFF = const(0x38)
STATE_HIGH = display_driver_framework.STATE_HIGH
STATE_LOW = display_driver_framework.STATE_LOW
STATE_PWM = display_driver_framework.STATE_PWM
BYTE_ORDER_RGB = display_driver_framework.BYTE_ORDER_RGB
BYTE_ORDER_BGR = display_driver_framework.BYTE_ORDER_BGR
class ILI9488(display_driver_framework.DisplayDriver):
# The st7795 display controller has an internal framebuffer
# arranged in 320 x 480
# configuration. Physical displays with pixel sizes less than
# 320 x 480 must supply a start_x and
# start_y argument to indicate where the physical display begins
# relative to the start of the
# display controllers internal framebuffer.
# this display driver supports RGB565 and also RGB666. RGB666 is going to
# use twice as much memory as the RGB565. It is also going to slow down the
# frame rate by 1/3, This is becasue of the extra byte of data that needs
# to get sent. To use RGB666 the color depth MUST be set to 32.
# so when compiling
# make sure to have LV_COLOR_DEPTH=32 set in LVFLAGS when you call make.
# For RGB565 you need to have LV_COLOR_DEPTH=16
# the reason why we use a 32 bit color depth is because of how the data gets
# written. The entire 8 bits for each byte gets sent. The controller simply
# ignores the lowest 2 bits in the byte to make it a 6 bit color channel
# We just have to tell lvgl that we want to use
display_name = 'ILI9488'
def init(self):
param_buf = bytearray(15)
param_mv = memoryview(param_buf)
param_buf[:15] = bytearray([
0x00, 0x03, 0x09, 0x08, 0x16,
0x0A, 0x3F, 0x78, 0x4C, 0x09,
0x0A, 0x08, 0x16, 0x1A, 0x0F
])
self.set_params(_PGC, param_mv[:15])
param_buf[:15] = bytearray([
0x00, 0x16, 0x19, 0x03, 0x0F,
0x05, 0x32, 0x45, 0x46, 0x04,
0x0E, 0x0D, 0x35, 0x37, 0x0F
])
self.set_params(_NGC, param_mv[:15])
param_buf[0] = 0x17
param_buf[1] = 0x15
self.set_params(_PWR1, param_mv[:2])
param_buf[0] = 0x41
self.set_params(_PWR2, param_mv[:1])
param_buf[0] = 0x00
param_buf[1] = 0x12
param_buf[3] = 0x80
self.set_params(_VCMPCTL, param_mv[:3])
param_buf[0] = (
self._madctl(
self._color_byte_order,
self._ORIENTATION_TABLE
)
)
self.set_params(_MADCTL, param_mv[:1])
color_size = lv.color_format_get_size(self._color_space)
if color_size == 2: # NOQA
pixel_format = 0x55
elif color_size == 3:
pixel_format = 0x66
else:
raise RuntimeError(
'ILI9488 IC only supports '
'lv.COLOR_FORMAT.RGB565 or lv.COLOR_FORMAT.RGB888'
)
param_buf[0] = pixel_format
self.set_params(_COLMOD, param_mv[:1])
param_buf[0] = 0x00
self.set_params(_IFMODE, param_mv[:1])
param_buf[0] = 0xA0
self.set_params(_FRMCTR1, param_mv[:1])
param_buf[0] = 0x02
self.set_params(_DIC, param_mv[:1])
param_buf[0] = 0x02
param_buf[1] = 0x22
param_buf[2] = 0x3B
self.set_params(_DFC, param_mv[:3])
param_buf[0] = 0xC6
self.set_params(_EM, param_mv[:1])
param_buf[:4] = bytearray([
0xA9, 0x51, 0x2C, 0x82
])
self._data_bus.tx_param(_AC3, param_mv[:4])
param_buf[:4] = bytearray([
0xA9, 0x51, 0x2C, 0x82
])
self._data_bus.tx_param(_AC3, param_mv[:4])
param_buf[:2] = bytearray([
0x80, 0x78
])
self._data_bus.tx_param(_SLPOUT, param_mv[:2])
param_buf[0] = 0x00
self._data_bus.tx_param(_IDMOFF, param_mv[:1])
param_buf[:2] = bytearray([
0x80, 0x64
])
self._data_bus.tx_param(_DISPON, param_mv[:2])
self.set_params(_NOP)
display_driver_framework.DisplayDriver.init(self)
try not using the reset pin.
the display you have only supports RGB565 and RGB666 it doesn't support RGB888. LVGL doesn't support RGB666 so we need to get RGB565 working properly for ya.
There have been people that have reported the exact same issue you are having and it being linked to the SPI speed or the reset pin. so try the reset pin first and then if that doesn't work change the SPI speed to 20000000 and see what happens.
I tried setting different SPI speeds from 10 to 80 MHz but result is the same: screen gets grey (not white as it was before) and nothing else. Connecting reset to reset pin of the microcontroller also did not help
I found out that ILI9488 doesn't support RGB565 in 4-line SPI mode, only RGB666, so no further investigation needed. Unfortunately, RGB565 is unavailable for me.
I fixed some errors including rotation issue in my previous driver code. Now it works well (with RGB666 colorspace).
My actual display driver code:
from micropython import const # NOQA
import time
import lvgl as lv # NOQA
import lcd_bus # NOQA
import display_driver_framework
_IFMODE = const(0xB0)
_FRMCTR1 = const(0xB1)
_DIC = const(0xB4)
_DFC = const(0xB6)
_EM = const(0xB7)
_PWR1 = const(0xC0)
_PWR2 = const(0xC1)
_VCMPCTL = const(0xC5)
_PGC = const(0xE0)
_NGC = const(0xE1)
_AC3 = const(0xF7)
_MADCTL = const(0x36)
_COLMOD = const(0x3A)
_NOP = const(0x00)
_SLPOUT = const(0x11)
_DISPON = const(0x29)
_SWRESET = const(0x01)
STATE_HIGH = display_driver_framework.STATE_HIGH
STATE_LOW = display_driver_framework.STATE_LOW
STATE_PWM = display_driver_framework.STATE_PWM
BYTE_ORDER_RGB = display_driver_framework.BYTE_ORDER_RGB
BYTE_ORDER_BGR = display_driver_framework.BYTE_ORDER_BGR
_MADCTL_MH = const(0x04) # Refresh 0=Left to Right, 1=Right to Left
_MADCTL_BGR = const(0x08) # BGR color order
_MADCTL_ML = const(0x10) # Refresh 0=Top to Bottom, 1=Bottom to Top
_MADCTL_MV = const(0x20) # 0=Normal, 1=Row/column exchange
_MADCTL_MX = const(0x40) # 0=Left to Right, 1=Right to Left
_MADCTL_MY = const(0x80) # 0=Top to Bottom, 1=Bottom to Top
class ILI9488(display_driver_framework.DisplayDriver):
# The st7795 display controller has an internal framebuffer
# arranged in 320 x 480
# configuration. Physical displays with pixel sizes less than
# 320 x 480 must supply a start_x and
# start_y argument to indicate where the physical display begins
# relative to the start of the
# display controllers internal framebuffer.
# this display driver supports RGB565 and also RGB666. RGB666 is going to
# use twice as much memory as the RGB565. It is also going to slow down the
# frame rate by 1/3, This is becasue of the extra byte of data that needs
# to get sent. To use RGB666 the color depth MUST be set to 32.
# so when compiling
# make sure to have LV_COLOR_DEPTH=32 set in LVFLAGS when you call make.
# For RGB565 you need to have LV_COLOR_DEPTH=16
# the reason why we use a 32 bit color depth is because of how the data gets
# written. The entire 8 bits for each byte gets sent. The controller simply
# ignores the lowest 2 bits in the byte to make it a 6 bit color channel
# We just have to tell lvgl that we want to use
display_name = 'ILI9488'
_ORIENTATION_TABLE = (
_MADCTL_MX,
_MADCTL_MV,
_MADCTL_MY,
_MADCTL_MX | _MADCTL_MY | _MADCTL_MV
)
def init(self):
param_buf = bytearray(15)
param_mv = memoryview(param_buf)
time.sleep_ms(120)
self.set_params(_SWRESET)
time.sleep_ms(200)
self.set_params(_SLPOUT)
time.sleep_ms(120)
param_buf[:15] = bytearray([
0x00, 0x03, 0x09, 0x08, 0x16,
0x0A, 0x3F, 0x78, 0x4C, 0x09,
0x0A, 0x08, 0x16, 0x1A, 0x0F
])
self.set_params(_PGC, param_mv[:15])
param_buf[:15] = bytearray([
0x00, 0x16, 0x19, 0x03, 0x0F,
0x05, 0x32, 0x45, 0x46, 0x04,
0x0E, 0x0D, 0x35, 0x37, 0x0F
])
self.set_params(_NGC, param_mv[:15])
param_buf[0] = 0x17
param_buf[1] = 0x15
self.set_params(_PWR1, param_mv[:2])
param_buf[0] = 0x41
self.set_params(_PWR2, param_mv[:1])
param_buf[0] = 0x00
param_buf[1] = 0x12
param_buf[3] = 0x80
self.set_params(_VCMPCTL, param_mv[:3])
param_buf[0] = (
self._madctl(
self._color_byte_order,
self._ORIENTATION_TABLE
)
)
self.set_params(_MADCTL, param_mv[:1])
color_size = lv.color_format_get_size(self._color_space)
if color_size == 2: # NOQA
pixel_format = 0x55
elif color_size == 3:
pixel_format = 0x66
else:
raise RuntimeError(
'ILI9488 IC only supports '
'lv.COLOR_FORMAT.RGB565 or lv.COLOR_FORMAT.RGB888'
)
param_buf[0] = pixel_format
self.set_params(_COLMOD, param_mv[:1])
param_buf[0] = 0x00
self.set_params(_IFMODE, param_mv[:1])
param_buf[0] = 0xA0
self.set_params(_FRMCTR1, param_mv[:1])
param_buf[0] = 0x02
self.set_params(_DIC, param_mv[:1])
param_buf[0] = 0x02
param_buf[1] = 0x02
param_buf[2] = 0x3B
self.set_params(_DFC, param_mv[:3])
param_buf[0] = 0xC6
self.set_params(_EM, param_mv[:1])
param_buf[:4] = bytearray([
0xA9, 0x51, 0x2C, 0x02
])
self.set_params(_AC3, param_mv[:4])
self.set_params(_DISPON)
time.sleep_ms(100)
display_driver_framework.DisplayDriver.init(self)
I updated the driver with your code. I also added proper handling of the RGB666 color space. It will convert the colors from rgb888 to rgb666 properly. Give it a shot and lemme know if it works.
I changed int(buf_len / 3) to buf_len // 3 at line 194 because it gives ViperTypeError: binary op truediv not implemented
As a result I got green stripes on the display
Did you meant for i in range(0, buf_len, 3)
at line 194?
Your right. I goofed that. I was thinking of doing it one way while typing a different way and I got them all jumbled together..
It's fixed..
I updated the driver but colors are distorted and clipped. Here is how it looks like
But when I comment out transformation to rgb666, colors seem normal:
What is the need to do this transformation if the display controller ignores the 2 LSBs in each byte of 24-bit color and everything works by itself?
according to the docs it's not a matter of ignoring the 2 lowest bits. the spec states that the bits are shifted.
This would be the bits for red
r7, r6 r5, r4, r3, r2, r1, r0
and this is what is supposed to be sent
r5, r4, r3, r2, r1, r0, ignored, ignored
After doing some digging I discovered that the datasheet for the display IC is wrong. using RGB888 without doing anything is the correct way to handle it. The lowest 2 bits are the ones that should get dropped and not the highest 2 bits.
I corrected the driver so it should be good to go now.
It works
YAY!!
Can't get it to work with ili9488 display with SPI interface on ESP32-S3. I've tried changing CS and DC to other pins, setting reset_state to high, changing color_space to RGB888 but nothing helped.