LVGL_Micropython - driver for ST7701 #76
Replies: 24 comments 40 replies
-
|
The SPI that is used for the configuration is a bit different. It is at an extremely low speed and it needs to he handled by a special driver and not your typical SPI that is available for an MCU. I have not added the special driver yet that is needed to handle the config. You do have the ability to write your own bit bang driver in python to handle turning the pins on and off when they need to be turned on and off using the viper code emitter for MicroPython. RGB bus is going to be limited to 16 lanes for the ESP32. This is a limitation that is set by the ESP SDK. That means the maximum you can set the color depth to is going to be 16 bit which is RGB565.... Instead of using extra pins for the config SPI you can share the pins with some of the lanes for the RGB. You would have to run the config settings and then set up the bus driver and display driver. You will not be able to send any config settings while the display is active if you do this. |
Beta Was this translation helpful? Give feedback.
-
|
HEY!!!! I wanted to let you know that I wrote in the handling of that funky SPI config IC. I am going to key up a display driver for it right now for your display.... It shouldn't take me that long to put together for ya. Maybe 20 minutes or so. |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
|
Hello. Driver for ST7701 now work or not ? I can do some test. |
Beta Was this translation helpful? Give feedback.
-
|
Also infor from ESPHOME https://devices.esphome.io/devices/Guition-ESP32-S3-4848S040 |
Beta Was this translation helpful? Give feedback.
-
|
I did that for 480x480 square (86 Box) GUITION ESP32-4848S040 |
Beta Was this translation helpful? Give feedback.
-
|
Code: lcd_9bit_spi.py lcd.py st7701.py |
Beta Was this translation helpful? Give feedback.
-
|
Absolutely right. But before that not work and not was understand problem in driver 3-wire or in setting. Now we have right setting. And can back to day ago and try use 3-wire SPI with right setting. |
Beta Was this translation helpful? Give feedback.
-
|
just update _st7701_type9.py now work and color right (set RED background) and without artifact. |
Beta Was this translation helpful? Give feedback.
-
|
for i2c touch for 19 pin, default use as USB-. Need reconfigure firmware. sdkconfig.board mpconfigboard.h |
Beta Was this translation helpful? Give feedback.
-
|
If use own board config and with own micropython i just want use make with your code as user_module. It ok for me. |
Beta Was this translation helpful? Give feedback.
-
|
Beta Was this translation helpful? Give feedback.
-
|
working config: on today. Work with file from repo as is. 480x480 square (86 Box) GUITION ESP32-4848S040 display PWM frequency for backlight - 2000display._backlight_pin.freq(2000) |
Beta Was this translation helpful? Give feedback.
-
|
How it works. 2024-09-21.08.44.42.mp4 |
Beta Was this translation helpful? Give feedback.
-
|
@kdschlosser When the slider moves - lcd draws with artifacts. |
Beta Was this translation helpful? Give feedback.
-
|
The result is the same with DMA, without DMA, and with Internal or PSRAM. Only if the Fullframe buffer, works without artifacts. |
Beta Was this translation helpful? Give feedback.
-
|
I am testing with a Waveshare 4in 480x480 LCD w ST7701 and see the same behavior as @straga regarding the buffers. I'm using display.init(13) for this display. |
Beta Was this translation helpful? Give feedback.
-
|
First update is without your changes I am not seeing the artifact with these settings Possibly from changing init(type=6) to init(type=13) , which fixed colors. Rotation doesn't seem to work well Doing display.set_rotation(0) no change |
Beta Was this translation helpful? Give feedback.
-
|
Tested the version with y_end+=1; commented out. Artifacts are still there with //20. Rotation 90 looks different, has these horizontal lines. |
Beta Was this translation helpful? Give feedback.
-
|
I've been looking for a driver for the st7701 as well and just found one at https://github.com/DFRobot-Embedded-Software/Waveshare_LVGL_Micropython/tree/master/api_drivers/common_api_drivers/display/st7701. I don't know anything about it but thought I'd point it out in case it was helpful. |
Beta Was this translation helpful? Give feedback.
-
|
Hi @kdschlosser, I took a video of the display, so you can see the remaining issues. These are unlisted youtube videos... |
Beta Was this translation helpful? Give feedback.
-
|
Hi @kdschlosser, I have read every comment on this thread before beginning, in order to do my best in getting up to speed. I will post my code last, but here are my assumptions. Please tell me where my logic or understanding is incorrect:
Since my first comment, I have done some further testing. Here are my additional observations:
I created a some further tests, by just drawing a 1 pixel rectangle around the perimeter of the screen. If I draw a rectangle on a display with zero degrees rotation, I see a rectangle. If I set rotation to 90, 180 or 270 degrees, one of the sides is missing. This says to me that there is a bug in the display rotation code. My gut says that the loop iteration is stopping one short, but I'd have to check. Here are some screenshots. Maybe I'll take a look at the code and see if I can spot the problem to save you some time. I have drawn a blob in the x=0, y=0 corner, for my own sanity... All my tests begin with the following code, borrowed from earlier in this discussion: from micropython import const # NOQA
# Full frame size for 4 inch display
WIDTH = const(480)
HEIGHT = const(480)
# Full frame buffer option
# BUFFER_SIZE = int(WIDTH * HEIGHT * 2) # 2 bytes per pixel for RGB565
# 1/20th of the full frame
BUFFER_SIZE = int(WIDTH * HEIGHT * 2 // 20) # 2 bytes per pixel for RGB565
import lcd_bus # NOQA
display_bus = lcd_bus.RGBBus(
hsync=16,
vsync=17,
de=18,
pclk=21,
# B
data0=4,
data1=5,
data2=6,
data3=7,
data4=15,
# G
data5=8,
data6=20,
data7=3,
data8=46,
data9=9,
data10=10,
# R
data11=11,
data12=12,
data13=13,
data14=14,
data15=0,
freq=12000000,
hsync_pulse_width=8,
hsync_back_porch=20,
hsync_front_porch=10,
vsync_pulse_width=8,
vsync_back_porch=10,
vsync_front_porch=10
)
from spi3wire import Spi3Wire
spi3wire = Spi3Wire(
scl=48,
sda=47,
cs=39,
freq=500000 #76 Гц
)
spi3wire.init(8, 8) # cmd_bits=8, param_bits=8
import st7701
import lvgl as lv # NOQA
fb1 = display_bus.allocate_framebuffer(BUFFER_SIZE, lcd_bus.MEMORY_SPIRAM) # Use PSRAM for LVGL buffer too
fb2 = display_bus.allocate_framebuffer(BUFFER_SIZE, lcd_bus.MEMORY_SPIRAM) # Use PSRAM for LVGL buffer too
display = st7701.ST7701(
display_bus,
spi3wire,
WIDTH,
HEIGHT,
frame_buffer1=fb1,
frame_buffer2=fb2,
backlight_pin=38,
color_space=lv.COLOR_FORMAT.RGB565,
rgb565_byte_swap=False,
bus_shared_pins=False
)
print('Hello LCD')
display.set_power(True)
display.init(type=9)
display.set_backlight(100)
import gt911
import i2c
i2c_bus = i2c.I2C.Bus(host=1, scl=45, sda=19, freq=100000)
touch_i2c = i2c.I2C.Device(i2c_bus, gt911.I2C_ADDR, gt911.BITS)
indev = gt911.GT911(touch_i2c, debug=False)
import time
def create_line(colour, start, end, width=1):
# 1. Create a Line Object
line_style = lv.style_t()
line_style.init()
line_style.set_line_width(width)
line_style.set_line_color(colour)
# Define the vertical line points (x1, y1) to (x2, y2)
# We keep X at 0 initially; the animation will move the object's X coordinate
line_points = [lv.point_precise_t({'x': start[0], 'y': start[1]}), lv.point_precise_t({'x': end[0], 'y': end[1]})]
line = lv.line(lv.screen_active())
line.set_points(line_points, 2)
line.add_style(line_style, 0)
return line
def rectangle(colour, line_width=1):
create_line(lv.color_hex(colour), ( 0, 0), ( 0, 479), width=line_width)
create_line(lv.color_hex(colour), ( 0, 479), (479, 479), width=line_width)
create_line(lv.color_hex(colour), (479, 479), (479, 0), width=line_width)
create_line(lv.color_hex(colour), (479, 0), ( 0, 0), width=line_width)Testing with sliders, I add this: def slider_test():
scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0xFF0000), 0)
slider = lv.slider(scrn)
slider.set_size(400, 50)
slider.center()
slider2 = lv.slider(scrn)
slider2.set_size(80, 400)
slider2.center()
label = lv.label(scrn)
label.set_text('HELLO WORLD!')
label.align(lv.ALIGN.CENTER, 0, -50)
import task_handler
th = task_handler.TaskHandler()
i=0
try:
while True:
time.sleep_ms(10000)
except Exception as e:
print('Error occurred: ', e)
except KeyboardInterrupt:
print('Program Interrupted by the user')Testing with an animation, I add variations of this, depending on how many lines (1 to 3) that I want: def anim_test():
scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0x1F1F1F), 0)
label = lv.label(scrn)
label.set_text('HELLO WORLD!')
label.center()
LINE_WIDTH = 50
# line = create_line(lv.color_hex(0x00FF00), (0, 0), (480, 0), width=LINE_WIDTH)
line2 = create_line(lv.color_hex(0xFFFFFF), (0, 0), ( 0, 479), width=LINE_WIDTH)
# line3 = create_line(lv.color_hex(0xFF0000), (0, 0), (480, 480), width=LINE_WIDTH)
def anim_cb(a, v):
# line.set_y(v)
line2.set_x(v)
# line3.set_points([lv.point_precise_t({'x': v-200, 'y': v+200}), lv.point_precise_t({'x': v+200, 'y': v-200})], 2)
anim = lv.anim_t()
anim.init()
anim.set_var(line2)
anim.set_values(0 + LINE_WIDTH // 2, 479 - LINE_WIDTH // 2)
anim.set_duration(4000)
anim.set_reverse_duration(4000)
anim.set_repeat_count(lv.ANIM_REPEAT_INFINITE)
anim.set_custom_exec_cb(anim_cb)
lv.anim_t.start(anim)
import task_handler
th = task_handler.TaskHandler()
i=0
while True:
time.sleep_ms(4000)
i = (i+1) % 4
if i == 0:
rotation = lv.DISPLAY_ROTATION._0
elif i == 1:
rotation = lv.DISPLAY_ROTATION._90
elif i == 2:
rotation = lv.DISPLAY_ROTATION._180
else:
rotation = lv.DISPLAY_ROTATION._270
display.set_rotation(rotation)
label.set_text(f'Rotation: {i * 90} degrees')Testing with rectangles, I add this: def rectangle_test():
scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0x1F1F1F), 0)
label = lv.label(scrn)
# label.set_text('HELLO WORLD!')
label.center()
import task_handler
th = task_handler.TaskHandler()
i=0
while True:
i = (i+1) % 4
if i == 0:
rotation = lv.DISPLAY_ROTATION._0
colour = 0xFF0000
elif i == 1:
rotation = lv.DISPLAY_ROTATION._90
colour = 0x00FF00
elif i == 2:
rotation = lv.DISPLAY_ROTATION._180
colour = 0x0000FF
else:
rotation = lv.DISPLAY_ROTATION._270
colour = 0xFFFFFF
display.set_rotation(rotation)
label.set_text(f'Rotation: {i * 90} degrees')
rectangle(colour, line_width=1)
time.sleep_ms(1000)This is what I created the screenshots with. It highlights the edge cases without any animation. So you must alter the def rectangle_test_2():
scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0x1F1F1F), 0)
label = lv.label(scrn)
label.center()
import task_handler
th = task_handler.TaskHandler()
DEGREES=90
if DEGREES == 0:
rotation = lv.DISPLAY_ROTATION._0
elif DEGREES == 90:
rotation = lv.DISPLAY_ROTATION._90
elif DEGREES == 180:
rotation = lv.DISPLAY_ROTATION._180
else:
rotation = lv.DISPLAY_ROTATION._270
display.set_rotation(rotation)
label.set_text(f'Rotation: {DEGREES} degrees')
colour = 0xFFFFFF
create_line(lv.color_hex(colour), ( 0, 0), ( 0, 479))
create_line(lv.color_hex(colour), ( 0, 479), (479, 479))
create_line(lv.color_hex(colour), (479, 479), (479, 0))
create_line(lv.color_hex(colour), (479, 0), ( 0, 0))
create_line(lv.color_hex(colour), (4, 4), (4, 9))
create_line(lv.color_hex(colour), (4, 9), (9, 9))
create_line(lv.color_hex(colour), (9, 9), (9, 4))
create_line(lv.color_hex(colour), (9, 4), (4, 4))
while True:
time.sleep_ms(1000)Then I choose which to run at the end: # slider_test()
# anim_test()
# rectangle_test()
rectangle_test_2() |
Beta Was this translation helpful? Give feedback.
-
|
Hi @kdschlosser, I've found the bug. It is in the driver. It relates to mixing end-inclusive and end-exclusive co-ordinate systems in the With my updates, I no longer get any artifacts with rotation=0 or missing lines of pixels with rotation=[90, 180, 270]. The reason that the rotate 0 behaves differently to the 90, 180 and 270 is because 'rotate 0' uses end inclusive co-ordinates and '90, 180 and 270' mostly assume end exclusive co-ordinates, with a couple of hacks to get it working, that could be removed, once the co-ordinates were standardised. Since I have no experience of this code, I turned on debug and then confirmed the standard used in the tx_color calls: This is quite lengthy to explain here in a comment, so let me know if you would like a pull request. But anyway, here are the mods I made. Caveat: I have only modified and tested 16-bit non-dithered operations. Equivalent changes would be required for other formats. It is also quite possible that I have missed some cases. Happy to discuss or test further - I'm only doing this for fun and to exercise my brain a bit! First, I modified the end co-ordinates to always be end-exclusive, since most of your code already operates that way. void copy_pixels(void *dst, void *src, uint32_t x_start, uint32_t y_start,
uint32_t x_end, uint32_t y_end, uint32_t dst_width, uint32_t dst_height,
uint32_t bytes_per_pixel, uint8_t rotate, uint8_t rgb565_dither)
{
// Be consistent with using end exclusive co-ordinates
x_end += 1;
y_end += 1;
// Now...
// start points should always be compared with width/height -1
// end points should be compared with width/height
if (rotate == RGB_BUS_ROTATION_0) {
rotate0(src, dst, MIN(x_start, dst_width-1), MIN(y_start, dst_height-1),
MIN(x_end, dst_width), MIN(y_end, dst_height),
dst_width, dst_height, bytes_per_pixel, rgb565_dither);
} else {
// Remove hack
// y_end += 1; // removes black lines between blocks
if (rotate == RGB_BUS_ROTATION_90 || rotate == RGB_BUS_ROTATION_270) {
x_start = MIN(x_start, dst_height-1); // tweak
x_end = MIN(x_end, dst_height);
y_start = MIN(y_start, dst_width-1); // tweak
y_end = MIN(y_end, dst_width);
} else {
x_start = MIN(x_start, dst_width-1); // tweak
x_end = MIN(x_end, dst_width);
y_start = MIN(y_start, dst_height-1); // tweak
y_end = MIN(y_end, dst_height);
}
if (bytes_per_pixel == 1) {
rotate_8bpp(src, dst, x_start, y_start, x_end, y_end, dst_width, dst_height, rotate);
} else if (bytes_per_pixel == 2) {
rotate_16bpp(src, dst, x_start, y_start, x_end, y_end, dst_width, dst_height, rotate, rgb565_dither);
} else if (bytes_per_pixel == 3) {
rotate_24bpp(src, dst, x_start, y_start, x_end, y_end, dst_width, dst_height, rotate);
} else if (bytes_per_pixel == 4) {
rotate_32bpp(src, dst, x_start, y_start, x_end, y_end, dst_width, dst_height, rotate);
}
}
}I had to modify rotate0 to use end exclusive co-ordinates, as that was the standard I chose: void rotate0(uint8_t *src, uint8_t *dst, uint32_t x_start, uint32_t y_start,
uint32_t x_end, uint32_t y_end, uint32_t dst_width,
uint32_t dst_height, uint8_t bytes_per_pixel, uint8_t rgb565_dither)
{
dst += ((y_start * dst_width + x_start) * bytes_per_pixel);
if(x_start == 0 && x_end == (dst_width - 1) && !rgb565_dither) {
// memcpy(dst, src, dst_width * (y_end - y_start + 1) * bytes_per_pixel);
// Change to end exclusive
memcpy(dst, src, dst_width * (y_end - y_start) * bytes_per_pixel);
} else {
// uint32_t src_bytes_per_line = (x_end - x_start + 1) * bytes_per_pixel;
// Change to end exclusive
uint32_t src_bytes_per_line = (x_end - x_start) * bytes_per_pixel;
uint32_t dst_bytes_per_line = dst_width * bytes_per_pixel;
if (rgb565_dither) {
for(uint32_t y = y_start; y < y_end; y++) {
for (uint32_t x=0;x<x_end;x++) {
rgb565_dither_pixel(CALC_THRESHOLD(x, y), (uint16_t *)(src) + x);
copy_16bpp((uint16_t *)(src) + x, (uint16_t *)(dst) + x);
}
dst += dst_bytes_per_line;
src += src_bytes_per_line;
}
} else {
for(uint32_t y = y_start; y < y_end; y++) {
memcpy(dst, src, src_bytes_per_line);
dst += dst_bytes_per_line;
src += src_bytes_per_line;
}
}
}
}Next, I updated rotate_16bpp... void rotate_16bpp(uint16_t *src, uint16_t *dst, uint32_t x_start, uint32_t y_start,
uint32_t x_end, uint32_t y_end, uint32_t dst_width, uint32_t dst_height,
uint8_t rotate, uint8_t rgb565_dither)
{
uint32_t i;
uint32_t j;
// uint32_t src_bytes_per_line = x_end - x_start + 1;
// Change to end exclusive
// Also, this variable is not well named. It is really src_uint16s_per_line. See copy_16bpp call pointer arithmetic.
uint32_t src_bytes_per_line = x_end - x_start;
uint32_t offset = y_start * src_bytes_per_line + x_start;
if (rgb565_dither) {
switch (rotate) {
case RGB_BUS_ROTATION_90:
for (uint32_t y = y_start; y < y_end; y++) {
for (uint32_t x = x_start; x < x_end; x++) {
i = y * src_bytes_per_line + x - offset;
j = (dst_height - 1 - x) * dst_width + y;
rgb565_dither_pixel(CALC_THRESHOLD(x, y), src + i);
copy_16bpp(src + i, dst + j);
}
}
break;
// MIRROR_X MIRROR_Y
case RGB_BUS_ROTATION_180:
LCD_UNUSED(j);
LCD_UNUSED(src_bytes_per_line);
LCD_UNUSED(offset);
for (uint32_t y = y_start; y < y_end; y++) {
i = (dst_height - 1 - y) * dst_width + (dst_width - 1 - x_start);
for (uint32_t x = x_start; x < x_end; x++) {
rgb565_dither_pixel(CALC_THRESHOLD(x, y), src);
copy_16bpp(src, dst + i);
src++;
i--;
}
src++;
}
break;
// SWAP_XY MIRROR_X
case RGB_BUS_ROTATION_270:
for (uint32_t y = y_start; y < y_end; y++) {
for (uint32_t x = x_start; x < x_end; x++) {
i = y * src_bytes_per_line + x - offset;
j = (x * dst_width + dst_width - 1 - y);
rgb565_dither_pixel(CALC_THRESHOLD(x, y), src + i);
copy_16bpp(src + i, dst + j);
}
}
break;
default:
LCD_UNUSED(i);
LCD_UNUSED(j);
LCD_UNUSED(src_bytes_per_line);
LCD_UNUSED(offset);
break;
}
} else {
switch (rotate) {
case RGB_BUS_ROTATION_90:
for (uint32_t y = y_start; y < y_end; y++) {
for (uint32_t x = x_start; x < x_end; x++) {
i = y * src_bytes_per_line + x - offset;
j = (dst_height - 1 - x) * dst_width + y;
copy_16bpp(src + i, dst + j);
}
}
break;
// MIRROR_X MIRROR_Y
case RGB_BUS_ROTATION_180:
LCD_UNUSED(j);
LCD_UNUSED(src_bytes_per_line);
LCD_UNUSED(offset);
for (uint32_t y = y_start; y < y_end; y++) {
i = (dst_height - 1 - y) * dst_width + (dst_width - 1 - x_start);
for (uint32_t x = x_start; x < x_end; x++) {
copy_16bpp(src, dst + i);
src++;
i--;
}
// Hey, I needed that pixel!!!
// src++;
}
break;
// SWAP_XY MIRROR_X
case RGB_BUS_ROTATION_270:
for (uint32_t y = y_start; y < y_end; y++) {
for (uint32_t x = x_start; x < x_end; x++) {
i = y * src_bytes_per_line + x - offset;
j = (x * dst_width + dst_width - 1 - y);
copy_16bpp(src + i, dst + j);
}
}
break;
default:
LCD_UNUSED(i);
LCD_UNUSED(j);
LCD_UNUSED(src_bytes_per_line);
LCD_UNUSED(offset);
break;
}
}
} |
Beta Was this translation helpful? Give feedback.
-
|
By the way, I think this is a great little touchscreen display. And because of the way @kdschlosser has implemented the driver, there is absolutely no screen tearing. For anyone who is interested, here is a link to the device. Where I live, this is cheaper than a nice burger and chips (fries)! |
Beta Was this translation helpful? Give feedback.






Uh oh!
There was an error while loading. Please reload this page.
-
Is it relevant to write a driver for ST7701 ?
If the SPI is just used in initialization LCD, will be possible to write a command initialization and then use parallel RGB driver?
https://focuslcds.com/journals/application-notes/3-wire-spi-parallel-rgb-interface/
The 3-wire Serial Peripheral Interface (SPI) and the 16/18/24-bit RGB parallel interface are used in a sequence to communicate data to the display. The SPI interface is first used to initialize the display parameters through command registers. Once initialized, the display can receive data over the RGB parallel interface at a higher speed. This data can be sent in segments of 16, 18 or 24-bit lengths depending on what is specified by the SPI initialization commands.
Beta Was this translation helpful? Give feedback.
All reactions