提高LCD刷新速度

针对常见的LCD屏幕,比如IL9341、ST7789、NT5510等,这里以ST7789S为例,分享一下LCD刷新速度的改进方案。

一、硬件方式

使用DMA方式传递数据,再使用信号量进行通知。

常见的SPI、I2C接口,都用对应的DMA(内存到外设)方式可以选择,对于8080接口(即MCU的FMC接口)可以使用DMA(内存到内存)方式实现传输。对于RGB接口,则可以使用DMA2D。

二、软件方式

这是着重要说的,正点原子等开发板的例程的显示效率是比较低的。

1)更高效快速的显示单字符

比如下面的 LCD_ShowChar 显示单个字符函数,每画一个像素点,都需要写2次寄存器和4次数据。而 LCD_Fill 函数是用来填充区域的,只需要设定光标位置,然后按照顺序写入数据即可。

假设 显示字符大小为24*28,则用例程的LCD_ShowChar函数至少需要2*672次寄存器和4*672次数据,而用 LCD_Fill 函数只需要3次寄存器+4次数据+672次数据,效率提升了(672*6 - 679)/ 672*6 = 83% !!!

那么我们就可以基于此改造一下 LCD_ShowChar 函数。对于画线、画矩形、画圆这种空心图形,只能单点逐个操作,我们沿用这种方式,对于画字符、实现矩形、实心圆这种实心图像,我们应该用这种方式来操作。

//快速画点
//x,y:坐标
//color:颜色
void LCD_Fast_DrawPoint(u16 x,u16 y,u32 color)
{	   
	if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0x7789)
	{
		LCD_WR_REG(lcddev.setxcmd); 
		LCD_WR_DATA(x>>8);LCD_WR_DATA(x&0XFF);  			 
		LCD_WR_REG(lcddev.setycmd); 
		LCD_WR_DATA(y>>8);LCD_WR_DATA(y&0XFF); 		 	 
	}else if(lcddev.id==0X5510)
	{
		LCD_WR_REG(lcddev.setxcmd);LCD_WR_DATA(x>>8);  
		LCD_WR_REG(lcddev.setxcmd+1);LCD_WR_DATA(x&0XFF);	  
		LCD_WR_REG(lcddev.setycmd);LCD_WR_DATA(y>>8);  
		LCD_WR_REG(lcddev.setycmd+1);LCD_WR_DATA(y&0XFF); 
	}else if(lcddev.id==0X1963)
	{
		if(lcddev.dir==0)x=lcddev.width-1-x;
		LCD_WR_REG(lcddev.setxcmd); 
		LCD_WR_DATA(x>>8);LCD_WR_DATA(x&0XFF); 		
		LCD_WR_DATA(x>>8);LCD_WR_DATA(x&0XFF); 		
		LCD_WR_REG(lcddev.setycmd); 
		LCD_WR_DATA(y>>8);LCD_WR_DATA(y&0XFF); 		
		LCD_WR_DATA(y>>8);LCD_WR_DATA(y&0XFF); 		
	}		 
	LCD->LCD_REG=lcddev.wramcmd; 
	LCD->LCD_RAM=color; 
}	 

//在指定位置显示一个字符
//x,y:起始坐标
//num:要显示的字符:" "--->"~"
//size:字体大小 12/16/24/32
//mode:叠加方式(1)还是非叠加方式(0)
void LCD_ShowChar(u16 x,u16 y,u8 num,u8 size,u8 mode)
{  							  
    u8 temp,t1,t;
	u16 y0=y;
	u8 csize=(size/8+((size%8)?1:0))*(size/2);		//得到字体一个字符对应点阵集所占的字节数	
 	num=num-' ';//得到偏移后的值(ASCII字库是从空格开始取模,所以-' '就是对应字符的字库)
	for(t=0;t<csize;t++)
	{   
		if(size==12)temp=asc2_1206[num][t]; 	 	//调用1206字体
		else if(size==16)temp=asc2_1608[num][t];	//调用1608字体
		else if(size==24)temp=asc2_2412[num][t];	//调用2412字体
		else if(size==32)temp=asc2_3216[num][t];	//调用3216字体
		else return;								//没有的字库
		for(t1=0;t1<8;t1++)
		{			    
			if(temp&0x80)LCD_Fast_DrawPoint(x,y,POINT_COLOR);
			else if(mode==0)LCD_Fast_DrawPoint(x,y,BACK_COLOR);
			temp<<=1;
			y++;
			if(y>=lcddev.height)return;		//超区域了
			if((y-y0)==size)
			{
				y=y0;
				x++;
				if(x>=lcddev.width)return;	//超区域了
				break;
			}
		}  	 
	}  	    	   	 	  
}   

//设置光标位置(对RGB屏无效)
//Xpos:横坐标
//Ypos:纵坐标
void LCD_SetCursor(u16 Xpos, u16 Ypos)
{	 
 	if(lcddev.id==0X9341||lcddev.id==0X5310||lcddev.id==0x7789)
	{		    
		LCD_WR_REG(lcddev.setxcmd); 
		LCD_WR_DATA(Xpos>>8);LCD_WR_DATA(Xpos&0XFF); 			 
		LCD_WR_REG(lcddev.setycmd); 
		LCD_WR_DATA(Ypos>>8);LCD_WR_DATA(Ypos&0XFF); 		
	}else if(lcddev.id==0X1963)
	{  			 		
		if(lcddev.dir==0)//x坐标需要变换
		{
			Xpos=lcddev.width-1-Xpos;
			LCD_WR_REG(lcddev.setxcmd); 
			LCD_WR_DATA(0);LCD_WR_DATA(0); 		
			LCD_WR_DATA(Xpos>>8);LCD_WR_DATA(Xpos&0XFF);		 	 
		}else
		{
			LCD_WR_REG(lcddev.setxcmd); 
			LCD_WR_DATA(Xpos>>8);LCD_WR_DATA(Xpos&0XFF); 		
			LCD_WR_DATA((lcddev.width-1)>>8);LCD_WR_DATA((lcddev.width-1)&0XFF);		 	 			
		}	
		LCD_WR_REG(lcddev.setycmd); 
		LCD_WR_DATA(Ypos>>8);LCD_WR_DATA(Ypos&0XFF); 		
		LCD_WR_DATA((lcddev.height-1)>>8);LCD_WR_DATA((lcddev.height-1)&0XFF); 			 		
		
	}else if(lcddev.id==0X5510)
	{
		LCD_WR_REG(lcddev.setxcmd);LCD_WR_DATA(Xpos>>8); 		
		LCD_WR_REG(lcddev.setxcmd+1);LCD_WR_DATA(Xpos&0XFF);			 
		LCD_WR_REG(lcddev.setycmd);LCD_WR_DATA(Ypos>>8);  		
		LCD_WR_REG(lcddev.setycmd+1);LCD_WR_DATA(Ypos&0XFF);			
	} 
} 	

//开始写GRAM
void LCD_WriteRAM_Prepare(void)
{
 	LCD->LCD_REG=lcddev.wramcmd;	  
}	 

//在指定区域内填充单个颜色
//(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1)   
//color:要填充的颜色
void LCD_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u32 color)
{          
	u16 i,j;
	u16 xlen=0; 
	xlen=ex-sx+1;	 
	for(i=sy;i<=ey;i++)
	{
		LCD_SetCursor(sx,i);      				//设置光标位置 
		LCD_WriteRAM_Prepare();     			//开始写入GRAM	  
		for(j=0;j<xlen;j++)LCD->LCD_RAM=color;	//显示颜色 	    
	}  
}

修改如下:

新增注释:这里着重说明下,以这种方式显示字符,要保证LCD设置的扫描方式和字模方向一致的!!!比如LCD扫描方向是从左到右再从从上到下的逐行式扫描,那么取字模时也要按照这个方向来,否则显示的就是错的。

//用于显示标准的95个Ascii字符 从空格到~

//入参:x,y为起始坐标,ch为字符,color为画点颜色,back为背景色

void LCD_ShowEnglish(uint16_t x, uint16_t y, const char ch, uint16_t color, uint16_t back)
{
    uint8_t n, bit, num, temp;
    
    num = ch - ' ';//获取字符索引
    
    LCD_SetCursor(x, y, x+FONT_WIDTH_HALF, y+FONT_HEIGHT);//设置光标位置
    
    for(n = 0;n < FONT_BYTE_NUM_HALF; n++)
    {
        temp = font_28[num*FONT_BYTE_NUM_HALF + n];
        
        for( bit = 0; bit < 8; bit++)
        {
            if(temp&0x80)        //写入像素点数据
                LCD_WR_DATA(color);
            else
                LCD_WR_DATA(back);
            
            temp<<=1;
        }
    }
}

//用于显示标准的95个Ascii字符 从空格到~

//入参:x,y为起始坐标,ch为字符,color为画点颜色,back为背景色

void LCD_ShowEnglish(uint16_t x, uint16_t y, const char ch, uint16_t color, uint16_t back)
{
    uint8_t n, bit, num, temp;
    
    num = ch - ' ';//获取字符索引
    
    LCD_SetCursor(x, y, x+FONT_WIDTH_HALF, y+FONT_HEIGHT);//设置光标位置
    
    for(n = 0;n < FONT_BYTE_NUM_HALF; n++)
    {
        temp = font_28[num*FONT_BYTE_NUM_HALF + n];
        
        for( bit = 0; bit < 8; bit++)
        {
            if(temp&0x80)        //写入像素点数据
                LCD_WR_DATA(color);
            else
                LCD_WR_DATA(back);
            
            temp<<=1;
        }
    }
}

2)高效显示整块区域

也是基于 LCD_Fill 改造下,

比如我在前面分享的STM32示波器中,对于波形数据的显示可以用这种方法,

用宽300*高200的区域显示波形图,最直接的方案是每两个点之间画一条线,最终拟合出一条线出来,但是这种方法不仅效率低,而且重复刷新时需要消隐上一次的画线。用填充区域的方式,就不会有这种问题,但是这会带来内存的问题。

显示一个300*200的区域,每个像素点16bit,则完整直接的显示需要117.1875KB。

肯定会面临内存不够的问题,

1)假如颜色只有2种,则可以用1bit标识颜色,这样就只需要73KB

2)假如颜色有多种,则可以用多个bit标识颜色进行映射

3)假如内存依然不够,则可以拆分显示,比如将300*200的区域拆分成4个75*200的子块,每次刷新1个子块

//在指定区域内填充颜色
//(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1)   
//color:要填充的颜色buf|方向是从左到右从上到下
void LCD_FillColor_Buf(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t *color)
{
	uint16_t i,j;
	
    LCD_SetCursor(x1,y1,x2,y2);
	
	for(j = 0; j < (y2-y1+1); j++)
	for(i = 0; i < (x2-x1+1); i++)
	LCD_WR_DATA( *color++ );
}

3)DMA方式改进

以上都是基于ST7789S+STM32芯片进行测试的,还没有用到DMA方式。

寄存器我一般选择不用DMA,直接写入,

但是对于写数据可以改成DMA方式,然后等待信号量通知传输完成。

比如

for(j = 0; j < (y2-y1+1); j++)
for(i = 0; i < (x2-x1+1); i++)
LCD_WR_DATA( *color++ );

实测显示整屏240*320需要21ms,这点时间完全可以用DMA剩下来,去做其他事。

具体实现方法可以去Cubemx 选择XCube display的例程参考下。

  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猪熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值