STM32F469I-DISCO lv_img_set_src flicker

Description

On the STM32F469I-DISCO I would like to have a window (400x300) that shows the image of a IR cam (interpolated from 32x24). For that I’m using a image (created with lv_img_create) and write the (interpolated) IR-cam image to the buffer I have provided. For a test I switch in main-loop only the color of the buffer (toggeling between memset(img_buff, 0x90, 240000); and memset(img_buff, 0x99, 240000);). Everything works … except it flickers unacceptable. Since the driver already uses double buffering I assume a VSYNC-problem … but I’m unable to solve this by my own.

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

STM32F469I-DISCO
LVGL 8.3.11

What do you want to achieve?

Showing the interpolated image of a IR-cam in a window (image) on the screen

What have you tried so far?

No difference between
lv_img_cache_invalidate_src(img_buff);
or
lv_img_set_src(ui_imgFrame, therm_img_dsc);

No difference in using
memset(img_buff, 0x99, 240000);
or
lv_img_buf_set_px_color

No difference in using
lv_timer_handler();
or
lv_task_handler();

I found a hint that it may help to use a short delay at the end of the flush-cb, that does not change anything

.
.
lv_disp_flush_ready(disp_drv);
HAL_Delay(2);
}

For the hack of it I tried with single-buffer - that makes it much worse.

Code to reproduce

   uint32_t startTime = HAL_GetTick();  
   bool bToggleColor = false;
  while (1)
  {
      if((HAL_GetTick() - startTime) > 1000)
      {
        if(bToggleColor)
        {
            memset(img_buff, 0x90, 240000);
            // for(int y=0; y<300; ++y)
            //   for(int x=0; x<400; ++x)
            //     lv_img_buf_set_px_color(therm_img_dsc, x, y, lv_color_make(50,50,50));
        }
        else
        {
            memset(img_buff, 0x99, 240000);
            // for(int y=0; y<300; ++y)
            //   for(int x=0; x<400; ++x)
            //     lv_img_buf_set_px_color(therm_img_dsc, x, y, lv_color_make(150,150,150));

        }

        bToggleColor = !bToggleColor;

        HAL_Delay(5);
        startTime = HAL_GetTick();
        // lv_obj_invalidate(ui_imgFrame);
        // lv_img_cache_invalidate_src(img_buff); // also makes no difference
        lv_img_set_src(ui_imgFrame, therm_img_dsc);  
      }

	  HAL_Delay(5);
	  // lv_timer_handler();
    lv_task_handler();
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

Here is the flush cb

void stm32_flush_cb(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{
  // _lv_disp_refr_timer(NULL);

	lv_disp_t *disp = _lv_refr_get_disp_refreshing();
	uint16_t *dma_xfer_src, *dma_xfer_dst;
	
  if(!lv_disp_flush_is_last(disp_drv))
  {
		lv_disp_flush_ready(disp_drv);
		return;
	}

	// Swap the buffer for the one to display and reload the screen at the next vertical blanking
	HAL_LTDC_SetAddress_NoReload(&hltdc, (uint32_t)color_p, 0);
	HAL_LTDC_Reload(&hltdc, LTDC_RELOAD_VERTICAL_BLANKING); // VSYNC
  // HAL_LTDC_Reload(&hltdc, LTDC_RELOAD_IMMEDIATE);  // makes no difference

	// Determine source and destination of transfer
	dma_xfer_src = (uint16_t *)color_p;
	if(color_p == framebuffer_1){
		dma_xfer_dst = (uint16_t *)framebuffer_2;
	}else{
		dma_xfer_dst = (uint16_t *)framebuffer_1;
	}

	for(size_t i = 0; i < disp->inv_p; i++){
		// If the area was not joined (and thus should not be ignored)
		if(!disp->inv_area_joined[i]){
			dma2d_copy_area(disp->inv_areas[i], (uint32_t)dma_xfer_src, (uint32_t)dma_xfer_dst);
		}
	}

	lv_disp_flush_ready(disp_drv);
  HAL_Delay(2);
}

Screenshot and/or video

If possible, add screenshots and/or videos about the current state.

I do have a short video … but couldnt find a way to upload. Shall I upload it to a share and provide a link?

HAL_LTDC_Reload(&hltdc, LTDC_RELOAD_VERTICAL_BLANKING); // VSYNC

This function changes the display in the blanking period of the vsync so when you call this function you should not expect that the display buffer to be changed immediately.
If you run this function without any waiting for the blanking period this would result in tearing issue. so for prevent this issue add

		// wait for VSYNC to avoid tearing
		while (!(LTDC->CDSR & LTDC_CDSR_VSYNCS));
use this 

Check AN4861 page 31 it a very good document.

This link might help too.

Hi Nima,

many, many thanks for you taking the time to look into this.

Although I fully understand the principle behind double-buffering (as also nicely explained in AN4861 5.4.2) - I’m struggling to understand how the two framebuffers in the demo I’ve found are used by LVGL and how the and when they are copied over to the display. And this in turn is the reason why I fail to find the cause of that tearing.
Unfortunately, the while (!(LTDC->CDSR & LTDC_CDSR_VSYNCS)); did not change anything.
I did manage to capture the tearing effect in a video, here is the frame with the distortion. Strangly, it is always on the same spot - that size of that ‘triangle’ there is always the same (but toggles colors of course)

This triangle is based on portrait vs landscape tearing. You dont show how you manage and place memories, but LTDC in normal config render screen physical data orientation and if you logical rotate 90 , then triangle tearing is normal. Based on speeds configured.
Too idea about image data 240000 is on internal RAM limits.

Never heard of ‘portrait vs landscape tearing’. And I’m also not aware, that something (or the whole screen) is rotated by 90°. Just for the hack of it, I rotated the image control by 90° - the result is exactly the same (except that the image control is in portrait mode,the flickering triangle is still on the same spot - left lower corner)
The triangle only occurs (very briefly) when I change the color of the image control (as I wrote in my first post every second once)

You never heard about moon Europa isnt fact, that Europa dont exist…
Simply pixels in framebuffer exist in some order normal x x+1 x+2 and this order is used for read and move into display and too for update and change pixels. Result is line tearing on horizontal or vertical. But when display as your is order y y+1 y+2 , but memset update is x x+1 x+2 result is diagonal line tearing.

I actually do know about moon Europa :slight_smile: besides other hobbies I’m a big Space enthusiast.

To avoid trouble with the alignment I’m setting a solid color in my image buffer via memset (as said before, alternating the color every second) and calling ‘lv_obj_invalidate’ afterwards.

Here are the relevant loc:


  img_buff = malloc(uiImBuffSize);
  memset(img_buff, 0xAA, uiImBuffSize);
  
  therm_img_dsc = malloc(sizeof(lv_img_dsc_t));
  memset(therm_img_dsc, 0, sizeof(lv_img_dsc_t));

  therm_img_dsc->header.always_zero = 0,
  therm_img_dsc->header.w = IMG_WIDTH;   // Updated to 240 (width)
  therm_img_dsc->header.h = IMG_HEIGHT;   // Updated to 320 (height)
  therm_img_dsc->header.cf = LV_IMG_CF_TRUE_COLOR;    // RGB565 format
  therm_img_dsc->data = (const uint8_t *)img_buff;
  therm_img_dsc->data_size = uiImBuffSize;  // 240 * 320 * 2

  lv_img_set_src(ui_imgFrame, therm_img_dsc);  
  

After that - as said before - basically toggling the color through ‘memset’ and invalidating the image. Everything works exactly as expected - apart from that tearing in the left lower corner.

Ok your w=240 , then your LTDC layer and screen active width is 480 ?

you could also use ltdc interrupts
LTDC has a line mark interrupt which is configurable.
You could also use that interrupt to decouple the lvgl timer.
This means lvgl would start rendering in line for example 10 and change buffer in the line 600

Put this code in driver lvgl driver init

    lv_disp_t *pStuDisp = lv_disp_get_default ();
    lv_timer_del (pStuDisp->refr_timer);
    pStuDisp->refr_timer = NULL;

Use flags in the interrupt of ltdc
pull this flags for changing the buffers in lvgl.