硬件SPI+DMA实现快速刷屏,以及移植LVGL


前言

具体代码看附件资料

软件准备:STM32的标准库模板工程、LVGL的源码、厂家提供的屏幕驱动

硬件准备:正点原子的精英开发板,和一块ST7735S驱动的1.44寸tft彩屏


一、比较

软件SPI刷新屏幕通过算法驱动GPIO的输入和输出实现,没有IO口和硬件资源的限制,,移植起来较方便,但是会占用大量的处理器负载,导致传输速度较低,对于刷屏来说会有拉帘的效果,而且没有DMA功能来加快数据的传输
硬件SPI刷新屏幕是将屏幕引脚接在芯片对应的引脚上,是通过芯片内部专用的硬件SPI模块来实现数据传输,执行效率高,不会占用大量处理器资源,适用于高速设备上,重要的是还可以通过DMA来加速数据传输

举例子:从A点搬运货物到B点
软件SPI:由人从A点搬运货物B点,人需要来回从A点B来回跑
硬件SPI:是A点到B点有一个传送带,人只需要从A点把货物放到传送带上就好,传送带会将货物传输到B点,在传输过程中,人可以腾出手干别的事。

二、SPI配置

以下代码的宏配置

#define X_MAX_PIXEL	        128
#define Y_MAX_PIXEL	        128
#define USE_SPI_HARDWARE    1
#define USE_SPI_SOFE        0
#define USE_DMA             1

1.SPI配置

void LCD_SPI_GPIO_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;	
    
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5| GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
#if USE_SPI_SOFE
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
#else
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
#endif
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
#if USE_SPI_HARDWARE
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
    SPI_InitTypeDef SPI_InitStructure;
    
	SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;  //设置SPI单向或者双向的数据模式:SPI设置为单线传输
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//设置SPI工作模式:设置为主SPI
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//设置SPI的数据大小:SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//串行同步时钟的空闲状态为高电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//串行同步时钟的第二个跳变沿(上升或下降)数据被采样
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;		//定义波特率预分频的值:波特率预分频值为2
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
	SPI_Init(SPI1, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
	
	SPI_Cmd(SPI1, ENABLE); //使能SPI外设
	
#endif
}

向SPI总线传输一个8位数据

void SPI_WriteData(u8 Data)
{
#if USE_SPI_SOFE
	unsigned char i = 0;
	for (i = 8; i > 0; i--)
	{
		if (Data & 0x80)
			LCD_SDA_SET; // 输出数据
		else
			LCD_SDA_CLR;

		LCD_SCL_CLR;
		LCD_SCL_SET;
		Data <<= 1;
	}
#else
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);//等待发送区空  
	
	SPI_I2S_SendData(SPI1, Data); //通过外设SPIx发送一个byte  数据
#endif // USE_SPI_SOFE  

	
}

2.DMA配置

使用DMA需要开辟一块内存来当显存
uint8_t lcd_gram[Y_MAX_PIXEL][X_MAX_PIXEL*2] = {0}; ///<开辟一块内存空间当显存使用

void SPI_DMA_Init(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);// 开启DMA时钟
	DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&SPI1->DR;
	DMA_InitStructure.DMA_MemoryBaseAddr = (u32)lcd_gram;// 内存地址
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;// 方向:从内存到外设	
	DMA_InitStructure.DMA_BufferSize = 0;// 传输大小	
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;// 外设地址不增	    
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;// 内存地址自增
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;// 外设数据单位	
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;// 内存数据单位
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;// DMA模式,一次或者循环模式
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;// 优先级:中	
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;// 禁止内存到内存的传输
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;// 外设数据单位	
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;// 内存数据单位
	DMA_Init(DMA1_Channel3, &DMA_InitStructure);// 配置DMA通道	
    DMA_ClearFlag(DMA1_FLAG_TC3);
	DMA_Cmd(DMA1_Channel3,DISABLE);// 使能DMA  

}

画点函数从向SPI传输一个数据到往显存中写入数据

void Gui_DrawPoint(u16 x, u16 y, u16 Data)
{
#if USE_DMA  
    ///<使用DMA的话,从对点刷屏到对显存数组写入数据,DMA传输数据的时候再统一进行传输
    lcd_gram[y][x*2] = Data >> 8;
    lcd_gram[y][x*2+1] = Data;    
#else
	Lcd_SetRegion(x, y, x + 1, y + 1);
	LCD_WriteData_16Bit(Data);
#endif
    
}

给屏幕填充颜色

void Lcd_Clear(u16 Color)
{
#if USE_DMA
    int x = 0,y = 0;
    for(y = 0 ; y < Y_MAX_PIXEL; y++)
        for(x = 0;x < X_MAX_PIXEL * 2; x += 2)
        {
            lcd_gram[y][x] = Color>>8;
            lcd_gram[y][x+1] = Color;
        }
    Lcd_Refrsh_DMA();
#else
    unsigned int i, m;
	Lcd_SetRegion(0, 0, X_MAX_PIXEL - 1, Y_MAX_PIXEL - 1);
	Lcd_WriteIndex(0x2C);
	for (i = 0; i < X_MAX_PIXEL; i++)
		for (m = 0; m < Y_MAX_PIXEL; m++)
		{
			LCD_WriteData_16Bit(Color);
		}
#endif        
}

DMA传输一次数据,定时往DMA传输一次数据,刷新屏幕

void Lcd_Refrsh_DMA(void)
{
#if USE_DMA
    ///<将整个数据搬运一次到DMA
	Lcd_SetRegion(0,0,X_MAX_PIXEL-1,Y_MAX_PIXEL-1);
    DMA_Cmd(DMA1_Channel3,DISABLE);	
    DMA_SetCurrDataCounter(DMA1_Channel3,X_MAX_PIXEL * Y_MAX_PIXEL*2);          //数据传输量
    DMA_ClearFlag(DMA1_FLAG_TC3);
    LCD_CS_CLR;
    LCD_RS_SET;	
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
    DMA_Cmd(DMA1_Channel3,ENABLE);// 使能DMA
    while(!DMA_GetFlagStatus(DMA1_FLAG_TC3));
    DMA_ClearFlag(DMA1_FLAG_TC3);
    LCD_CS_SET;
#endif
}

三、LVGL移植

1.移植相关

移植相关内容可以看正点原子的视频

第4讲 基础篇-LVGL移植(无操作系统1)

2.重点

1、加了LVGL之后,函数调用深度会加深,需要加大栈空间,打开startup_stm32f10x_md.s文件
在这里插入图片描述
2、打开C99编辑模式
3、使用DMA刷新屏幕,要在主循环中添加DMA刷屏
在这里插入图片描述


总结

使用硬件SPI+DMA来刷新屏幕后基本看不到刷屏事后的拉帘效果了
也可以通过控制宏来只使用软件SPI或者硬件SPI,以及硬件SPI+DMA来看效果

附件链接

链接:https://pan.baidu.com/s/1yKIrG4jAfd11XeM8QjMtGQ
提取码:gzzs

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值