做项目的过程中,想要提高LVGL显示帧数;一开始采用的是直接读写寄存器,后来发现不太够用,遂决定使用DMA。在实际编写程序中,遇到很多问题,网上搜索不到正确的解决办法,于是在此记录一下。
DMA与软件片选
下图是正常传输16位RGB信息的数据波形:
其中0x007C就是一个像素的RGB信息。可以看到,在传输RGB信息时,CS(白)信号始终为低,保持选中显示屏。
再来看看使用DMA+软件片选的RGB数据波形:
可以明显看到,开始DMA传输后,CS信号马上变高,显示屏也就接收不到数据了。
出现这种情况的原因在于,我是在之前的基础上直接修改了SPI发送函数,如下:
void LCD_IO_WriteData(uint8_t *RegValue, uint32_t WriteSize)
{
LCD_CS_ON();//CS拉低,选中 LCD
LCD_SET_DATA();//设置DC/RS线为数据
// LCD_SPI_Transmit(&hspi1, RegValue, WriteSize);//直接读写寄存器
// HAL_SPI_Transmit(&hspi1, RegValue, WriteSize*2, 1000);//HAL库发送
HAL_SPI_Transmit_DMA(&hspi1, RegValue, WriteSize*2);//DMA发送
LCD_CS_OFF();//CS拉高, 释放 LCD
}
一开始使用HAL库或直接读写寄存器发送数据时,发送完成后才会从发送函数中跳出,执行CS拉高。而更改为DMA传输后,本质上是通知DMA控制器开始传输,通知完成后数据传输就开始在后台运行,同时跳出此函数,执行CS拉高;这时后台的数据传输才刚开始,SPI还没有将数据发送出去,CS就拉高,LCD肯定就接收不到数据了。
解决办法大致有两种:使用硬件片选、在DMA传输完成中断中拉高CS。为了图省事,我直接改为了硬件片选,但硬件片选存在一个问题:CS信号在初始化完成SPI外设后就始终为低,不会根据发送数据与否自动拉高;我暂时没有发现这对LCD的影响,但在网上看到使用其他SPI芯片时,CS始终为低可能导致错误。
如果你想在DMA传输完成中断中拉高CS,还有其他要注意的问题,下文会说到。
SPI DMA传输完成中断
由于数据的传输被放在了后台执行,LVGL需要知道何时数据传输完成,因此需要在DMA传输完成中断中调用lv_disp_flush_ready(&disp_drv);
函数,网上找到的所有资料均让你使用HAL_DMA_RegisterCallback()
注册回调函数,但这种方法并不适用SPI传输,无论你是否注册HAL_DMA_XFER_CPLT_CB_ID
回调函数,在初始化完成后,其回调均会变为HAL_SPI_TxCpltCallback()
;也就是说,你手动注册的回调函数根本不会被执行!!!这个地方卡了我好久,通过调试才发现。
正确的做法是,直接定义HAL_SPI_TxCpltCallback()
,这个在HAL库中已经有了弱定义,重新定义会覆盖掉弱定义。最终中断回调函数如下:
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
lv_disp_flush_ready(&disp_drv);
}
需要注意的是,disp_drv
本来是在lv_port_disp_init()
中的局部变量,你需要把它手动提到外面才能在中断回调函数中引用。
其他问题
由于HAL_SPI_Transmit()
和HAL_SPI_Transmit_DMA()
的Size
参数均是uint16_t
类型的,因此最大传输数据量为65535,在显示屏中很容易超,需要注意。
附上直接操作寄存器和使用DMA的帧数区别: