Better frame rate with lpm013m126a

Description

I have interface mip memory LCD with lvgl (LPM013M126C). But the frame rate is really slow, i want to know if you have a better idea for interface this display. Maybe with the round function callback?

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

STM32L496, STMCubeIDE

What do you want to achieve?

Get a better frame rate, optimize my flush function.

What have you tried so far?

I have a working driver with or without DMA but the frame rate is still slow.

Code to reproduce


void lv_init(void)
{
   static lv_disp_draw_buf_t disp_buf;
   static lv_color_t 
   buf1[LV_VER_RES_MAX*LV_HOR_RES_MAX / 10 ];
   lv_disp_draw_buf_init( &disp_buf, buf1, NULL, LV_VER_RES_MAX * LV_HOR_RES_MAX / 10 );

   static lv_disp_drv_t disp_drv;
   lv_disp_drv_init( &disp_drv ); 

   disp_drv.hor_res = LV_HOR_RES_MAX;
   disp_drv.ver_res = LV_VER_RES_MAX;

   disp_drv.flush_cb = lv_port_flush;
   disp_drv.set_px_cb = lv_port_set_px;
   disp_drv.rounder_cb = lv_port_rounder_cb;
   disp_drv.draw_buf = &disp_buf;

   lv_disp_drv_register(&disp_drv);
}

void lv_port_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
   int row = area->y1; // select the row
	int address = ( unsigned int )row + area->x1 / 2; // select the address in the column
	char *buffer;

	int linelen = ( area->x2 - area->x1 ) / 2;

	buffer = ( char * )color_p;

lpm013m126a_WriteMultipleLines(buffer, address, (area->y2 - area->y1 + 1), linelen);

   lv_disp_flush_ready(disp_drv);
}

void lpm013m126a_WriteMultipleLines(char* line_cmd, int firstLine, int linesNb, int linelen)
{
    int32_t j;

    if( ( firstLine < 0 )||
        ( firstLine + linesNb > LV_VER_RES_MAX ) ) {
        /* out of device size */
        return;
    }

    lpm013m16a_StartUpdate();

    for(uint8_t line = firstLine; line < (firstLine + linesNb); line+=lcd_cmd_line)
    {

    	lpm013m16a_Cmd( line + 1 );             // Gate line address (8 bits = 10bits - 2 dummy bits of address)

    	for( j = 0 ; j < (LV_HOR_RES_MAX/2) ; j++ ) {
    		if( j >= (LV_HOR_RES_MAX/2) ) {
    			/* out of device size */
    			break;
    		}
    		lpm013m16a_Cmd(line_cmd[j]);        // data (n x 2 x 4 bits)
    	}
    	for( ; j < (LV_HOR_RES_MAX/2) ; j++ ) {
    		/* padding to device size */
    		lpm013m16a_Cmd( 0x00 );
    	}

    	lpm013m16a_Cmd( 0x00 );  // dummy (6bits + 2 dummy bits of address)
    	line_cmd += linelen+1;
    }
    lpm013m16a_Cmd( 0x00 );  // dummy (8bits)

	lpm013m16a_StopUpdate();
}

void lpm013m16a_Cmd(uint8_t data)
{
	/* Add data the the transmit buffer */
	my_fb[y_fill_act] = data;

	/* Increase counter */
	y_fill_act++;
}

void lpm013m16a_StartUpdate( void )
{
	/* reset buffer to 0 */
	memset( my_fb, 0, y_fill_act );
	/* Reset buffer counter to 0 */
	y_fill_act = 0;

	lpm013m16a_Cmd( LCD_CMD_UPDATE );

}

uint8_t lpm013m16a_StopUpdate(void)
{
	GpioWrite( &LcdNss, 1 );

	HAL_SPI_Transmit( &lcdSpi, my_fb, y_fill_act, 4000 );

	GpioWrite( &LcdNss, 0 );
}

static void lv_port_set_px(lv_disp_drv_t * disp_drv, uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa)
{
	buf += buf_w/2 * y;
	buf += x/2;

	uint8_t color4Bit = (( lv_color_to8( color ) & (1<<7) ) >> 4 ) | // keep Red LSB shifted from bit-7 to bit-3
			(( lv_color_to8( color ) & (1<<4) ) >> 2 ) | // keep Green LSB shifted from bit-4 to bit-2
			(( lv_color_to8( color ) & (1<<1) ) ) ; // keep Blue LSB shifted from bit-1 to bit-1

	if( ( x % 2 ) == 0 )
	{
		(*buf) = 0; // Clean the pair of pixel --> TODO : Risk of artefact if accessing only an impair pixel
		(*buf) |= (color4Bit << 4 ); // set color bits to 4 MSB
	}
	else
	{
		(*buf) |= (color4Bit ); // set color bits to 4 LSB
	}
}

static void lv_port_rounder_cb( lv_disp_drv_t * disp_drv, lv_area_t * area )
{
	/* Round area to a whole line */
	area->x1 = 0;
	area->x2 = LV_HOR_RES_MAX - 1;

}

first thing is you are not using double buffering or DMA memory. Those will give you a decent bump up in performance. The other thing is your code doesn’t show the connection specific bits for the display. Is the display connected using SPI, RGB, I8080, etc…?

I also thought that the STM SDK has libraries to handle connecting to the display and if it does it would handle writing the data you just have to pass it the buffer.

what is the display you are using?

Thanks for your response, i will try the double buffering.
The display is LPM013M126A, it’s a color MIP display with 8 colors deep.
It’s interface in SPI, and here is my implementation with DMA

uint8_t lpm013m16a_StopUpdate( void )
{
	GpioWrite( &LcdNss, 1 );

	if( HAL_SPI_GetState( &lcdSpi ) == HAL_SPI_STATE_READY )
	{
		if( HAL_SPI_Transmit_DMA( &lcdSpi, my_fb, y_fill_act) == HAL_OK )
		{
			return 1; // Succeed
		}
		else
		{
			GpioWrite( &LcdNss, 0 ); // end of com
			return 0;
		}
	}
	else
	{
		return 0;
	}
}

I am seeing this

   static lv_color_t 
   buf1[LV_VER_RES_MAX*LV_HOR_RES_MAX / 10 ];

which is telling me that you are not allocating the display buffers in DMA memory. I also see this

   lv_disp_draw_buf_init( &disp_buf, buf1, NULL, LV_VER_RES_MAX * LV_HOR_RES_MAX / 10 );

which tells me that you are only using a single buffer and you will not get any benefit of using DMA memory if only a single buffer is used.

When using DMA memory there is a special function thaat has to get called in the MCU’s SDK. You pointed out the use of HAL_SPI_Transmit_DMA which is a good thing you know about that function. You are not using it correctly. you are passing a pointer to lcdSpi to that function. I don’t know what that pointer is but I would make the assumption it is specific information about the SPI connection. in that structure you should find a field that you set a callback function to. That callback function would get called when a DMA transfer has completed. Inside of that callback function is where you would call lv_disp_flush_ready(disp_drv); That is what lets LVGL know the data from the buffer has been flushed.

If you don’t know exactly what DMA is I can explain it to you. If you don’t know what it is you will once I explain it and it will make complete sense on how the code needs to be in order for it to work. Let me know if you want to explain how DMA works with LVGL.

I am also seeing that display as having a color depth of 24 bits and not 8 bits. I will have to do more reading to know if that can be changed to 16 bit as LVGL doesn’t have direct support for 24bit color which means LVGL would need to be set to 32 bit color and the alpha bits would need to be removed form the buffer in the flush function. Having to do that will add quite a bit of overhead.

Little match irelevant DMA.
Disp is 176x176px type MIP max spi clk 2MHz
full screen load time 2M/ 30976 / 8 = 8Hz = 8FPS.
Little part can do higher WPS

math is a little off here.

176 * 176 = 30,976
30,976 * (color depth?) = ?

lets say the color depth is 16 bit
30,976 * 2 = 61,952 bytes
61,952 * 8 = 495,616 bits

so lets say there is a max transfer speed of 2MHz like you said. that’s 2,000,000bps.
2,000,000 / 495,616 = 4.04. that is the number of times the entire display would be able to be redrawn per second. Theoretical of course. it is more likely to be closer to 3 FPS.

as I had said, I did not know what the display is able to do and I have not had the chance to look into it yet. I have to read over the data sheet for the display to see if any changes can be made to the code to give better performance and stay within the boundaries of what the display IC is able to do.

This display depth is real 3 bit and my math is for 8bit…

I believe the display is 24 bit which is 3 bytes but I did my calculation for the display being 16 bit(2 bytes) if the display is in fact 24 bit then the frame rate would be closer to 2 frames per second and not 3.

with some times but i finally come to the solution. i made an public project on github, go check-out. GitHub - aeschimannr/mip_colors_lvgl_drivers: Full project for driving LPM013M126A display with LVGL V8 & SquareLineStudio