Garbage appears in the horizontal direction of the widgets

Hi guys,
In my platform run lvgl printer demo, When the switch is switched, garbage can be seen at the edge during the animation。You can find the video in https://github.com/lvgl/lvgl/files/4961156/VID_20200723_001040.zip。

In addition to switch, other widgets can also see this garbage when performing animation effects, such as dorp list and button。These garbage will only appear in the horizontal direction. For example, when you click a button, the button will become bigger, you will only see garbage in the horizontal direction, but not in the vertical direction.

Below link uses the lvgl dial, you can clearly see garbage appearing in the horizontal direction during the movement of the pointer
https://github.com/lvgl/lvgl/files/4967208/VID_20200723_232520.zip

I am not sure if this is the lvgl problem or the LCD screen problem. Does anyone have a similar problem? If you can get your guidance, I would be very grateful.

My platform is rt1052 , I config the elcd control of rt1052 to 16bit RGB interface and hardware refresh rate is 60fps, The resolution is 800*480.

lvgl is configured as double buffer, refresh time LV_DISP_DEF_REFR_PERIOD = 25. Execute handle task every 10ms.

	while(1){
		s64_t start = k_uptime_get();
		lv_task_handler();
		start = k_uptime_get() - start;
		if(start > 0)
			printk("flush %lld\n", start);
		lv_tick_inc(10);
		k_sleep(K_MSEC(10));
	}

Use the lvgl frame buffer directly as the buffer output of the lcd controller, and flush the cache before updating the lcd buffer each time

RGBLCDUpdate(void *buf, u32_t size){

		DCACHE_CleanByRange((uint32_t) buf, size);	
		k_sem_take(&rgblcd_data.sem, K_FOREVER);	
	
		ELCDIF_SetNextBufferAddr(rgblcd_data.elcdinfo.base, buf);
		return 0;

}

static void lvgl_flush_cb(struct _disp_drv_t *disp_drv,
		const lv_area_t *area, lv_color_t *color_p)
{
	RGBLCDUpdate((void *) color_p, fb_size);

	lv_disp_flush_ready(disp_drv);
}

Hi @lgl88911,

The cache being flushed is definitely good…

With out knowing the way the DMA is implemented (I assume it’s a DMA driver underneath) and not knowing the details of the display driver chip etc. it’s hard to speculate here. This kind of issue is normally caused by either the video memory buffer being stale(which it shouldn’t if you are flushing the cache) or by the buffer being modified while the DMA is transferring data to the display. Another possibility might be if the display has it’s own memory buffer the DMA might be transferring data to it while it is being read & written to the display from it’s own internal memory by the display driver chip, in which case you may need a way to synchronise this operation. Having developed with LVGL myself this is very unlikely to be an issue on the library side.

Just some thoughts that could be useful…

If you want a sounding board for your thoughts feel free to post here.

Kind Regards,

Pete

I suspect that it’s an LCD issue. Or more precisely it might be a signal integrity problem.

A few things to try:

  • Lower LCD refresh rate (e.g. to 30 FPS)
  • Be sure pixel clock’s polarity is correct
  • Can you reproduce it without LVGL too? E.g. just draw a rectangle to the frame buffer, wait a little, clear it and draw again on one px to right.

Hi @pete-pjb, @kisvegabor,
Thanks for your suggest!
I use RT1052 on-chip LCD controller. In order to save memory, I use lvgl’s framebuffer as the buffer of the LCD controller. The LCD controller will directly convert lvgl’s framebuffer data to RGB signals and send it to the LCD interface. There will be a FIFO on the hardware of the LCD controller to send the framebuffer data to the signal generator , but there is no DMA transfer to another framebuffer of LCD. Since lvgl uses double buffers, every time lvgl requests flush will update different buffer to the LCD controller, so the frameBuffer being displayed will not be modified by lvgl at the same time.
Indeed lvgl’s lib shouldn’t be a problem, I use SDL simulation and did not see the garbage. But from the current inspection, the LCD driver seems to be normal. I will make some other attempts.

I followed kisvegabor’s suggestion and drew a moving rectangle. No matter if I moved it to the right by a few pixels, there was no garbage on the horizontal edge.
I also tried to lower or raise the LCD refresh, but it didn’t seem to improve.
Check the spec of LCD, It using rasie edge. I config the LCD controller to rasie or fall, The result has not changed.

At present, I have no better idea. The next step is to dump every frame of lvgl with garbage for a single check to see if there are clues.

If there are some other suggestions, please let me know. I will be very grateful and try these suggestions.

Hi @pete-pjb, @kisvegabor,
I have done some experiments, and indeed found a problem on the LCD side.
I increased the height of the rectangle. During the movement of the rectangle, I can see that the part of rectangle flickers or is missing part of the content. This may be a fps problem, but lowering the fps to 30 will still cause problems. No clue right now

Hi @lgl88911,

On the hardware side of things…
Do you have access to an oscilloscope? Might be worth checking the signal integrity if you can. Are the wires as short as possible between the display and the CPU board? Do you have a good ground connection between the display and the CPU board? If I have time on Monday morning I will take a look at the display driver data sheet to see if I can spot anything.

Kind Regards,

Pete

Hi @pete-pjb,
Thank you for your suggestion, there will be no problem with displaying static images, so hardware connection problems can be ruled out. The problem will only appear on the dynamic display. When the interval time of the moving rectangle is increased, there will be no problem. But the strange thing is that reducing the refresh rate of the LCD does not solve the problem. I will do more attempts. If there are new discoveries, Share with you in time.

Hi @pete-pjb
Maybe I found the reason. It seems that the garbage was caused by not being sent to the LCD completely in the previous frame, and the LCD was sent to the next frame.
The problematic LCD driver code is as follows

int RGBLCDUpdate(void *buf, u32_t size){


		DCACHE_CleanByRange((uint32_t) buf, size);

		if(firstframe == 1){
			firstframe = 0;
			s_framePending = 1;
		}
		k_sem_take(&rgblcd_data.sem, K_FOREVER);	


		ELCDIF_SetNextBufferAddr(rgblcd_data.elcdinfo.base, buf);
		s_framePending = 1;
	
		return 0;

}

static void mcux_elcdif_isr(void *arg)
{
	swift_rgblcd_data_t *data = (swift_rgblcd_data_t *)arg;

	u32_t status;

	status = ELCDIF_GetInterruptStatus(data->elcdinfo.base);
	ELCDIF_ClearInterruptStatus(data->elcdinfo.base, status);
	if (s_framePending == 1)
	{
		if(status & kELCDIF_CurFrameDone){
			s_framePending = 0;
			k_sem_give(&data->sem);
		}
	}

	__DSB();
}

Everything became normal after I modified it to the following process

int RGBLCDUpdate(void *buf, u32_t size){


		DCACHE_CleanByRange((uint32_t) buf, size);
		
		ELCDIF_SetNextBufferAddr(rgblcd_data.elcdinfo.base, buf);
		s_framePending = 1;
		k_sem_take(&rgblcd_data.sem, K_FOREVER);	

		return 0;

}

That is to say, it is correct to wait until the current frame is rendered and then exit. But I don’t understand why this is necessary, because before the next render, waiting for the last render to complete is not the same?.
For two different situations:
There is garbage, case1: When A buffer is rendered (RGBLCDUpdate(A)), prepare the content of B buffer, and wait for sem(RGBLCDUpdate(B)) before rendering B buffer to ensure that A buffer has been rendered.
Normally displayed, case2: When A buffer is rendered, wait for SEM to ensure that A buffer has been rendered, then prepare B buffer content, and then render B buffer.

For case1, the data of B buffer will be prepared while rendering A. I think this process can improve efficiency, but this will cause garbage. So far I have not found the reason.

There’s a limited amount of data that can be transferred to/from memory at a time. Exceeding this bandwidth will result in some transactions having to wait, which might lead to unexpected behavior for hardware that requires fast transactions (like a display). I don’t know exactly how LCD interfaces handle this, but at the very least it will result in tearing or something similar.

Hi @lgl88911,

I’m glad you got it working correctly, it sounds like the transfer of the data to the LCD is taking longer than the creation of a frame in memory so with out you halting the buffer switch while the LCD is updated your are getting the corruption. The only way you could do things any faster would be to increase the clock speed of the LCD parallel interface, but I suspect that is probably not feasible so your approach should be fine in my opinion.

Kind Regards,

Pete

Hi @embeddedt,
Thank you for your suggestion. The bandwidth problem is indeed a very important clue. The current situation is not caused by bandwidth. The real reason will be discussed below.

Hi @pete-pjb,
Thank you for your attention and reply.
I have already figured out the difference between the two writing methods. For lvgl double buffer, when lvgl_flush_cb is executed, the color_p buffer should have been displayed on the LCD. But for the driver, ELCDIF_SetNextBufferAddr just notifies the LCD controller of the framebuffer, and the LCD still displays the previous one, and will not switch to the next buffer until the current render is completed . If lvgl_flush_cb returns at this time, lvgl will think that the previous framebuffer is free and draw the next picture, which will cause garbage when drawing the framebuffer being displayed.
An example may be clearer
In case 1, lvgl call RGBLCDUpdate:

  1. A buffer is rendering
  2. wait sem done, the A buffer is rendered, the LCD continues to render the next time, but the A buffer is still rendered.
  3. call ELCDIF_SetNextBufferAddr(B)
  4. At this time, the rendering is still A buffer, return to lvgl

After returning to lvgl, lvgl thinks that the B buffer should be rendered at this time, so it starts to prepare the next picture and write it to A buffer.
Therefore, the correct approach should be case 2, wait for rendering B before exiting.

Here is another set of double buffer buffer data in RGBLCDUpdate, so that LCD render and LVGL can establish the next frame synchronously, as follows:

int swiftHal_RGBLCDUpdate(void *buf, u32_t size)
{
        char *pbuf = buf[widx];
        widx = !widx;
        memcpy(pbuf, buf, size);

		DCACHE_CleanByRange((uint32_t) pbuf, size);
		k_sem_take(&rgblcd_data.sem, K_FOREVER);	
		ELCDIF_SetNextBufferAddr(rgblcd_data.elcdinfo.base, pbuf);
		s_framePending = 1;
		return 0;

}

But this will waste more memory, and memcpy will also take some time. According to the current measurement situation, lvgl is also very smooth in case 2, so I plan to keep the modification method of case 2. Thank you again for your help!

1 Like