做项目的过程中,想要提高LVGL显示帧数;一开始采用的是直接读写寄存器,后来发现不太够用,遂决定使用DMA。在实际编写程序中,遇到很多问题,网上搜索不到正确的解决办法,于是在此记录一下。

DMA与软件片选

下图是正常传输16位RGB信息的数据波形:
屏幕截图 2024-12-20 190333.png
其中0x007C就是一个像素的RGB信息。可以看到,在传输RGB信息时,CS(白)信号始终为低,保持选中显示屏。

再来看看使用DMA+软件片选的RGB数据波形:
屏幕截图 2024-12-20 190012.png
可以明显看到,开始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的帧数区别:
ABEB9E0ECBF52E09CAA2B645F28569B3.jpg
57E8D26640DF59A7966695829B971895.jpg