使用DMA驱动SPI显示屏

使用DMA驱动SPI显示屏
THEDI背景
在嵌入式图形界面开发中,STM32微控制器常用来驱动各类显示屏(如 TFT LCD、OLED),实现丰富的用户交互。随着用户体验要求提升,显示界面的刷新帧率成为评价系统性能的重要指标之一。
特别是在使用LVGL
这类高性能 GUI 库时,全屏或局部刷新数据量大,对数据搬运速度提出了更高的要求。
最简单的SPI屏幕驱动方式是:CPU逐个将像素将数据通过 SPI 总线写入屏幕。这种方式在低分辨率或静态界面下尚可满足需求,但在高分辨率、频繁刷新的场景下,CPU负载会迅速升高,导致帧率下降,界面卡顿,影响用户体验。
所以我们就需要在原有通讯协议(I2C/SPI
)协议的DMA
来帮助我们搬运数据
为什么使用DMA?
不使用DMA:MCU在整个数据搬运过程中都需要亲自参与,CPU会被大量堵塞
在像素数据的发送上,导致刷新期间其它任务处理变慢,严重影响系统的实时性和整体性能。
使用DMA:由于CPU和DMA是一个并行
的关系,CPU发起DMA请求后,DMA硬件能批量
、高速
搬运数据到外设(SPI)。
在DMA传输期间,CPU可以专注于准备下一帧
的像素数据,或处理其他任务
。等DMA搬运完成后,CPU只需在中断中进行收尾操作,然后可以立即开启下一次数据搬运。这样不仅大幅提升了刷屏效率,也显著提高了系统的帧率和响应速度。
传统SPI(轮询) | DMA驱动SPI |
---|---|
MCU逐像素 搬运数据 |
DMA硬件整体搬运数据 |
CPU全程忙于刷屏 |
CPU只发起DMA,刷屏时可做其它事 |
刷屏速度受CPU限制 | 刷屏速度受DMA+SPI总线限制(更快) |
刷新越大CPU越慢 | 刷新越大DMA优势越明显 |
帧率低 | 帧率高 |
SPI DMA传输完成中断
原本是在lvgl中逐个发送像素后,发送完了才会执行lv_display_flush_ready
通知可以开始下一次刷新了
现在我们使用了DMA,数据传输被放在了后台执行,LVGL需要知道何时数据传输完成了,因此我们需要在DMA传输完成中断
中调用lv_disp_flush_ready(&disp_drv)
进行通知LVGL,LVGL才会进行下一次刷新
由于我们使用的是SPI的DMA模式
,DMA搬运完成,SPI发送完成后,会调用SPI的发送完成中断回调函数,所以就是对HAL_SPI_TxCpltCallback
进行一个重写,我们在其中进行通知LVGL即可,最终中断回调函数如下:
需要注意的是:disp_drv
本来是在lv_port_disp_init()
中的局部变量,我们需要将其变为全局变量才能在中断回调函数中使用
实际使用
下面是实际使用时的具体步骤:
disp变量全局化
如图:将在lv_port_disp_init
定义的disp变量
从局部变量变为全局变量
disp_flush修改
将disp_flush中的LCD输出部分修改为以SPI DMA方式发送数据
注释掉
lv_display_flush_ready(disp_drv)
,后续将在中断回调中调用
LCD_Fill_DMA实现逻辑:
重写中断回调函数
- 将之前的disp全局变量extern到实现SPI中断回调函数的位置
- 然后在SPI中断回调函数中调用
lv_display_flush_ready(disp)
,实现DMA传输完成后通知lvgl开始下一次刷新的功能
注意事项
size大小问题
由于HAL_SPI_Transmit()
和HAL_SPI_Transmit_DMA()
的Size
参数均是uint16_t
类型的,因此最大传输数据量为65535,在显示屏中很容易超,需要非常注意
DMA与SPI宽度问题
注意在使用SPI的DMA模式时要注意,一定确保SPI和DMA的数据宽度
需要匹配,不能出现SPI的数据宽度为16bit,DMA的数据宽度为8bit的情况,否则会出现DMA将`分两次从同一个地址传输两次8bit数据给16bit SPI寄存器。造成数据传输出错,LCD显示异常问题
DMA配置数据宽度:半字(16bit)
SPI数据宽度切换问题
在使用SPI通信的过程中,因为SPI传输存在8bit和16bit的情况,所以我们需要在传输像素数据(RGB565)时,将SPI数据宽度切换为16bit。
但是我在使用的过程中,发现使用HAL库切换数据宽度的方式存在问题,最稳妥的方式是直接操作SPI控制寄存器切换数据宽度为16bit。
方式一(异常)
:使用HAL库的HAL_SPI_Init
重新设置SPI发送的数据宽度为16bit,最开始使用这种方式切换数据宽度,这样就会出问题,导致LCD只有部分行数能正常刷新,且对应LCD像素颜色异常
该异常我暂不知是什么原因,
具体应该抓抓对应的波形看看数据是什么?
AI的解答:
1.HAL库有复杂的状态机和句柄操作,并且DMA和SPI重新关联可能丢失,有时不稳定。
2.可能是HAL库的bug
方式二(正常)
:使用操作寄存器的方式直接修改spi对应控制寄存器的DFF位,将其修改为1,控制SPI数据宽度为16bit。这样修改过后LCD显示就正常了