I too am struggling with this a bit. I’m using the RP2350-based https://www.waveshare.com/2inch-capacitive-touch-lcd.htm which has an ST7789T3.
The vendor code uses a possibly modified version of v8, whereas I am running v9.4. CPU clock is at 200MHz and everything is running on a single core.
To test speeds, I set a GPIO pin high when the display callback is run, and then set it low after the SPI DMA is complete. A full screen update (320x240 RGB565) takes ~30-40ms at 100MHz SPI clock.
You need to use the RGB565 swap function for this display. You can save a bit of time using direct render mode, but you can’t use lv_draw_sw_rgb565_swap directly because this render mode only updates small areas of the buffer and the function expects a linear array: lv_draw_sw_rgb565_swap doesn't work with DIRECT render mode · Issue #9084 · lvgl/lvgl · GitHub
Instead do something like this:
// swap channels in RGB565
size_t row_size = area->x2 - area->x1 + 1;
for (int i=area->y1; i<=area->y2; i++) {
uint8_t *row = px_map + ((LCD_WIDTH * i) + area->x1) * 2;
lv_draw_sw_rgb565_swap(row, row_size);
}
Flushing to areas smaller than the full display is tricky, because DMA can’t use a stride or anything like that to transfer a small window. You only really have three options:
- Flush the whole display regardless of how small an area was updated
- Run multiple DMA SPI transactions, one per row that was updated
memcpy the window of data into a separate buffer, then use DMA on that
Option 1 takes around 16ms as outlined above. Option 2 kind of defeats the purpose of DMA, so it’s not great. Option 3 is risky depending on the update size - you may run out of RAM depending on how large the area is, so you may need to fall back to just doing a full screen update if the window is large enough.
For option 1, you must only redraw on the final flush call, because on the non-final calls the rendering has not finished and you will get weird artifacts on the screen. Something like this will do:
// redraw the whole display
const uint16_t x1 = 0;
const uint16_t x2 = LCD_WIDTH-1;
const uint16_t y1 = 0;
const uint16_t y2 = LCD_HEIGHT-1;
if (lv_display_flush_is_last(disp)) {
lcd_set_window(x1, y1, x2+1, y2+1);
transfer_count = LCD_WIDTH * LCD_HEIGHT * 2;
} else {
lv_disp_flush_ready(tmp_disp_handle);
return;
}
LVGL also seems to render a lot slower when a debugger is attached (J-Link) or has been previously attached. I have no idea why, but my frame render times can go from 100ms to 10ms or less (with a spinner) just by hard resetting the chip to disconnect the debugger.
If I just disconnect the debugger, render times are still very slow. Power cycling or hard resetting improves the speed a lot.