Rotating the display by 90 degrees


After about 2-3 weeks of attempts, I finally got my F429 Discovery board to work with my BuyDisplay bare ILI9488 with capacitive touch!

I’m really happy with performance but now in order to upload my own project I need to rotate the display by 90 degrees.

Looking throught the ILI9488 datasheet I didn’t see anything about display or image rotation, so I was wondering if this can be done in the LVGL display driver?
I have seen some examples of swapping x and y but was hoping someone could suggest how to do it with the codebase I have.

What MCU/Processor/Board and compiler are you using?


What LVGL version are you using?

v7.6 +

What do you want to achieve?

Rotatae the display by 90 degrees into landscape mode

What have you tried so far?

Nothing besides research

Code to reproduce

Screenshot and/or video


You should be able to rotate the screen using the MADCTL register of the driver (if it’s similar to my display writing 0x28 or 0xE8 to MADCTL should do it), and either set the rotated field on the lv_disp_drv_t struct, or swapping the values of LV_HOR_RES_MAX and LV_VER_RES_MAX on your lv_config.h file.

Hi Carlos,

I’ve tried that (I set the register to 0x28) and set LV_HOR_RES to 480 and VER_RES to 320
But it just looks like this:

I also tried to flip the column address and page address values to match the landscape mode but that didn’t seem to affect anything.

Any other things I should try?
Is there a way to fill the screen with some colors via LVGL or directly from the LTDC driver? I can use that to test the source of the issue.

It looks like something is still using the old width and height values.

Did you change the values in lv_conf.h and tft.h?

Yes, I did change both, but get the same result.
Is it perhaps related to how DMA2D works with the external SDRAM?
I might try compiling and running it with #define LV_USE_GPU_STM32_DMA2D 0 and see if it persists

We use the following values to rotate the screen on the lv_port_esp32 project.

From the picture it seems like it’s not rotating it. Maybe you can try with the other values of the array.

0x48 and 0x88 work well in portrait mode.
But with 0x28 and 0xe8 it outputs as demonstrated in the 2nd image.

I always get that blank space on the right side of the screen (not physically) plus a triangle in the upper left side of the screen when I have the register set to 0x28 or 0xE8

EDIT: I don’t know if this is related, but my pixel clock is set to 12.5MHz but it should actually be set to 9.5MHz. I will reconfigure the pixel clock when I get home and test it once again

Are you updating the horizontal and vertical values? I don’t think it matters but I’m using SPI interface and one of those chinese displays.

Maybe it’s wired differently than the one you are using.

Are you referring to the timing parameters?

If yes, then No, I am not updating them.
I did try swap the values between to the horizontal and vertical config which did clear up the image a bit, but still left a blank box on the right side of the display and introduces a tearing effect.
I’m not too knowledgeable on how RGB interface works, so I don’t know if it requires updating or not when changing the display orientation.

So after playing around with many of the registers, It all came down to clocking.
If I use the internal clock, it works well in landscape mode but then I get some tearing affect.
If I use the LTDCs dot clock then it kills a 1/3rd of the screen - so the issue is that the dot clock is sending out a command for 320 pixels and not 480 for some reason - which is the next thing I have to find out as to why that is happening (If I understand this right?)

Using the LCDs internal clock but with some tearing

Using LTDCs clock, where 1/3 or the screen is out

I thought it was as easy as using SPI, sorry if I mislead you in something. Thanks for sharing your updates.

No worries!
The MADCTL register does control orientation, so it was the right suggestions.
But the issue I am facing is now related to the DOTCLK from the CPU.

@kisvegabor have you encountered such an issue before?

So after much searching, searching and searching, I have come to the conclution that the only way to get this to work in portrait mode is to flip the image that LVGL is generating.
I found this thread that has an explanation on how to do it, but since my drivers uses DMA, im not sure how to properly swap over X and Y

@embeddedt @kisvegabor can you perhaps provide an example of how to do this on the F429 driver?

static void tft_flush(lv_disp_drv_t * drv, const lv_area_t * area, lv_color_t * color_p)
	/*Return if the area is out the screen*/
	if(area->x2 < 0) return;
	if(area->y2 < 0) return;
	if(area->x1 > TFT_HOR_RES - 1) return;
	if(area->y1 > TFT_VER_RES - 1) return;

	/*Truncate the area to the screen*/
	int32_t act_x1 = area->x1 < 0 ? 0 : area->x1;
	int32_t act_y1 = area->y1 < 0 ? 0 : area->y1;
	int32_t act_x2 = area->x2 > TFT_HOR_RES - 1 ? TFT_HOR_RES - 1 : area->x2;
	int32_t act_y2 = area->y2 > TFT_VER_RES - 1 ? TFT_VER_RES - 1 : area->y2;

	x1_flush = act_x1;
	y1_flush = act_y1;
	x2_flush = act_x2;
	y2_fill = act_y2;
	y_fill_act = act_y1;
	buf_to_flush = color_p;

	  /*##-7- Start the DMA transfer using the interrupt mode #*/
	  /* Configure the source, destination and buffer size DMA fields and Start DMA Stream transfer */
	  /* Enable All the DMA interrupts */
	HAL_StatusTypeDef err;
	err = HAL_DMA_Start_IT(&DmaHandle,(uint32_t)buf_to_flush, (uint32_t)&my_fb[y_fill_act * TFT_HOR_RES + x1_flush],
			  (x2_flush - x1_flush + 1));
	if(err != HAL_OK)
		while(1);	/*Halt on error*/


static void DMA_TransferComplete(DMA_HandleTypeDef *han)
	y_fill_act ++;

	if(y_fill_act > y2_fill) {
	} else {
	  buf_to_flush += x2_flush - x1_flush + 1;
	  /*##-7- Start the DMA transfer using the interrupt mode ####################*/
	  /* Configure the source, destination and buffer size DMA fields and Start DMA Stream transfer */
	  /* Enable All the DMA interrupts */
	  if(HAL_DMA_Start_IT(han,(uint32_t)buf_to_flush, (uint32_t)&my_fb[y_fill_act * TFT_HOR_RES + x1_flush],
						  (x2_flush - x1_flush + 1)) != HAL_OK)
	    while(1);	/*Halt on error*/

Unfortunately as far as I know, you can’t use the DMA for rotation.
You have to do it within two loops.
A small example of a 90 degrees rotation.
And this function is in no way optimized!

void monitor_flush (lv_disp_drv_t* disp_drv, const lv_area_t* area, lv_color_t* color_p)
  int32_t     y;
  int32_t     x;
  int32_t     ys;
  int32_t     xs;
  uint32_t    w;
  uint32_t    ulSource;
  uint32_t    ulDest;

  w = lv_area_get_width (area);

  for (x = area->x1, xs = 0; x <= area->x2; x++, xs++) {                  // Copy the area data rotated into frame buffer 1.
    for (y = area->y1, ys = 0; y <= area->y2; y++, ys++) {
      ulSource = (ULONG) color_p    + ((xs + (ys                          * w))                 * sizeof (lv_color_t));
      ulDest   = (ULONG) 0xd0000000 + ((y  + ((disp_drv->hor_res - 1 - x) * disp_drv->ver_res)) * sizeof (lv_color_t));

      if (sizeof (lv_color_t) == 4)  {
        *((uint32_t*) ulDest) = *((uint32_t*) ulSource);
      else if (sizeof (lv_color_t) == 2)  {
        *((uint16_t*) ulDest) = *((uint16_t*) ulSource);
      else {
         *((uint8_t*) ulDest) = *((uint8_t*) ulSource);

  lv_disp_flush_ready (disp_drv); // IMPORTANT! It must be called to tell the system the flush is ready

Pretty sure @robekras is correct here; I know the STM32 DMA2D can do some fancy stuff but I’m not sure it can rotate on the fly.

Would it be possible to rotate the image with the two for loops inside tft_flush before DMA starts to transfer it?
Basically basically resaving area with the new image orientation?

I’m still trying to wrap my head around the whole buffer/dma transfer functions and how they work

I think you could, but I’m not sure whether that’d be any faster than copying it straight to the framebuffer without DMA.