First of all, I want to say that I’m not really an expert in LVGL or graphical interface software!
My problem is that I’m using a screen with an ST7701S in MIPI-DSI (ESP32-P4), and unfortunately, the driver doesn’t support hardware rotation, so I’d like to know if it’s possible to do this via software. The function (lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_90);) does not work (that would be too simple!). Is there another possibility? If so, what modifications do I need to implement in my code?
// LVGL library is not thread-safe, this example will call LVGL APIs from different tasks, so use a mutex to protect it
static _lock_t lvgl_api_lock;
extern void lv_example(void);
static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
int offsetx1 = area->x1;
int offsetx2 = area->x2;
int offsety1 = area->y1;
int offsety2 = area->y2;
// pass the draw buffer to the driver
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, px_map);
}
static void example_increase_lvgl_tick(void *arg)
{
/* Tell LVGL how many milliseconds has elapsed */
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}
static void example_lvgl_port_task(void *arg)
{
ESP_LOGI(TAG, "Starting LVGL task");
uint32_t time_till_next_ms = 0;
while (1) {
_lock_acquire(&lvgl_api_lock);
time_till_next_ms = lv_timer_handler();
_lock_release(&lvgl_api_lock);
// in case of triggering a task watch dog time out
time_till_next_ms = MAX(time_till_next_ms, EXAMPLE_LVGL_TASK_MIN_DELAY_MS);
// in case of lvgl display not ready yet
time_till_next_ms = MIN(time_till_next_ms, EXAMPLE_LVGL_TASK_MAX_DELAY_MS);
usleep(1000 * time_till_next_ms);
}
}
static bool example_notify_lvgl_flush_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
{
lv_display_t *disp = (lv_display_t *)user_ctx;
lv_display_flush_ready(disp);
return false;
}
Check the docs Rotation - LVGL 9.3 documentation, apparently the rotation itself needs to be handled by the user in flush_cb, it has some example code on how to do that.
Never done this, but if you integrate the code in documentation in your flush_cb, it should work…
Something like this:
static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
esp_lcd_panel_handle_t panel_handle = lv_display_get_user_data(disp);
lv_display_rotation_t rotation = lv_display_get_rotation(disp);
lv_area_t rotated_area;
if(rotation != LV_DISPLAY_ROTATION_0) {
lv_color_format_t cf = lv_display_get_color_format(disp);
/*Calculate the position of the rotated area*/
rotated_area = *area;
lv_display_rotate_area(disp, &rotated_area);
/*Calculate the source stride (bytes in a line) from the width of the area*/
uint32_t src_stride = lv_draw_buf_width_to_stride(lv_area_get_width(area), cf);
/*Calculate the stride of the destination (rotated) area too*/
uint32_t dest_stride = lv_draw_buf_width_to_stride(lv_area_get_width(&rotated_area), cf);
/*Have a buffer to store the rotated area and perform the rotation*/
/* NOTE: This buffer should be large enough for the area, it seems that it may require to be the size of a PARTIAL_BUFFER, since that should be the max area size to flush at once.. */
static uint8_t rotated_buf[500*1014];
int32_t src_w = lv_area_get_width(area);
int32_t src_h = lv_area_get_height(area);
lv_draw_sw_rotate(px_map, rotated_buf, src_w, src_h, src_stride, dest_stride, rotation, cf);
/*Use the rotated area and rotated buffer from now on*/
area = &rotated_area;
px_map = rotated_buf;
}
// pass the draw buffer to the driver
esp_lcd_panel_draw_bitmap(panel_handle, area->x1, area->y1, area->x2 + 1, area->y2 + 1, px_map);
}
Please note that, (This is what I think and may be wrong) the buffer rotated_buf should be large enough to hold an entire flush frame, that in the case of LV_DISPLAY_RENDER_MODE_PARTIAL (that I am assuming you are using) could be the same size as the Partial buffers you have defined on LVGL initialization.
Since you are using the esp_lcd_panel component, not sure if you can use the functions esp_lcd_panel_swap_xy() and/or esp_lcd_panel_mirror()LCD - ESP32-P4 - — ESP-IDF Programming Guide v5.4.2 documentation to control the Display Rotation or not, or even if ESP32-P4 as something extra to handle that…
Hi, lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_90) should work, but remember to swap height and width dimensions in lv_display_create(disp, height, width).
When you say that it does not work, can you explain the difference between before and after? Nothing happens? Or maybe you see corrupted image?
Without rotation the display works fine (except for the rotation, of course)?
You should open an issue on the ESP-IDF’s github respoitory about this. They have proper rotation handling for the RGB driver but not for the DSI driver. It should be corrected at the driver level ideally.
The reason why is because the rotation that is provided by LVGL is less than ideal because of using full frame buffers. There are things that are able to be done at the driver level that will speed up the process, like using the built in 2d accelerator in the P4 to perform the rotation and/or using DMA transfers for copying the data from one buffer to the next buffer. LVGL is not hardware specific so it is not going to include MCU specific function calls in most cases so having it implemented at the driver level in the ESP-IDF is the best way to handle it.