How to do 90 degree display rotation when using TFT-RGB interface on STM32?

What do you want to achieve?

I would like to rotate an ILI9488 based display (Riverdi RVT35HITNWC00-B) 90 and 270 degrees when using TFT-RGB interface.

The problem is that (as specified on page 55 of the datasheet of ILI9488) the width of the display GRAM is 320 px while the height is 480 px. I would like to rotate the display and use it in landscape mode instead of portrait, but I cannot find a solution.

As the display driver expects an “array” with the dimensions of 320x480px, no matter how I try to implement rotation, it seems impossible to achieve my goal.

What have you tried so far?

  • Software rotation in LVGL itself: not possible, as the stride will be 480px, but on the TFT-RGB interface the stride must be 320px which messes up the buffer. As I use RGB interface instead of SPI for example I cannot swap array indexing when sending data to the display.
  • Swapping PASET and CASET register contents (while turning on column/row swap in MADCTL). I also swapped the dimensions in the STM32 LTDC. (480 columns and 320 rows.) I did not expect it to work, but I tried it anyways.

Important notes

I am using an STM32H723VGT6 microcontroller which has 320kB of RAM. With RGB565 interface the 480*320 resolution needs 307.2kB of GRAM, so the RAM in the microcontroller is just enough currently. I could use partial buffering, but that would not solve my rotation issue. (The stride of the partial buffer and the TFT-RGB interface would be different.) Because of this memory constraint, software rotation for the full buffer is not possible, but that would have the same stride related issue as well.

How could I solve this problem on the given hardware?

Environment

  • MCU: STM32H723VGT6 with internal GRAM and custom PCB
  • LVGL version: 9.4

Hello @Matqux

Can you share your current buffer size, flush callback implementation, and lv_display setup code that currently works without rotation?

Hi @fbiego!

My current buffer size is 320*480*2 bytes, allocated as an uint16_t array. (The color mode is RG565 (beacuse of memory constraints) while the display interface is RGB888, LTDC does the coversion.)

So my working lv_port_disp_init code is:

void lv_port_disp_init(void)
{
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    disp_init();

    /*------------------------------------
     * Create a display and set a flush_cb
     * -----------------------------------*/
    LV_ATTRIBUTE_MEM_ALIGN
    static uint16_t buf_1[MY_DISP_HOR_RES * MY_DISP_VER_RES];
    lv_display_t * disp = lv_display_create(MY_DISP_HOR_RES, MY_DISP_VER_RES);
    lv_display_set_flush_cb(disp, disp_flush);
    lv_display_set_buffers(disp, buf_1, NULL, sizeof(buf_1), LV_DISPLAY_RENDER_MODE_DIRECT);
    HAL_LTDC_SetAddress(&hltdc, (uint32_t)buf_1, LTDC_LAYER_1);
}

MY_DISP_HOR_RES is 320 while MY_DISP_VER_RES is 480.
disp_init() contains register writes through SPI for the ILI9488 display driver.

My flush callback is the following currently:

static void disp_flush(lv_display_t * disp_drv, const lv_area_t * area, uint8_t * px_map)
{
    /*Inform the graphics library that you are ready with the flushing*/
    lv_display_flush_ready(disp_drv);
}

In the curent test code I just create a screen before my while loop and I don’t update it later. In a recent project I’ve synchronized the LTDC line event with the flush callback with great success. Although that was for double buffering. I don’t know what would be the best method for single buffer. I think the HAL_LTDC_LineEventCallback(LTDC_HandleTypeDef *hltdc) could set a flag which is monitored in the disp_flush callback and the flag is set, reset it and call lv_display_flush_ready.

Thanks for the help in advance!