STM32驱动ILI9341
显示屏驱动
在一个嵌入式系统中,图形界面从设计到显示需要经过以下 3 个步骤:
- 应用程序将界面渲染到渲染缓冲区,这一步通常使用的库是 LVGL 或者 Qt。
- 将图像缓冲区的内容发送到显存,一般需要使用 SPI、I2C、或者并口。
- 显示器控制器将显存内容显示到屏幕上,根据显示器的特性,输出数量和通道数极多的信号。
这个过程中,图像信息依次以程序信息、渲染缓冲区、显存、屏幕像素四种形式存在,逐步从抽象的状态信息转换为物理存在的可见光信息。
对于直接支持显示屏接口的处理器来说,第二步可以直接在内存中进行。实际上最后一步并不适合于通用的处理器,比如 SSD1306 控制 128x64 的 OLED 屏时,需要对 64 行 128 列都给出单独的引脚,光这部分就需要 192 个引脚。而 TFT LCD 控制器也需要对 RGB888 格式使用 24 个引脚根据显示器的扫描时序,逐像素输出颜色。从引脚和时序的要求来看,这一步对于处理性能的要求非常大,但对于计算的要求不高,适合于硬件电路处理。
而对于其余的处理器而言,需要借助一个外接的显示屏控制器,如 SSD1306、ST7789、ILI9341 来完成。这个控制器的接口主要包括:
- 初始化和配置屏幕相关各种设置
- 配置屏幕的方向等信息
- 和主机通信,修改显存内容
SPI 接口
其中 SPI 通信协议通常会使用 4 线进行通信:
- CS 片选
- CLK 时钟
- MOSI 传输数据
- D/C 指定传输的是数据还是命令
- MISO 一般读不到东西,不使用
进行通信时,接口时序基本都一致:
- 初始化,将 CS 拉低,DC 设置为命令,逐个发送命令,并在发送每个命令后等待一段时间,让设备有处理的时间
- 发送命令指定要更新的显存区域,即一个矩形范围,有时候对于这个范围有一定的约束
- DC 设置为数据,开始传输显存内容。
驱动接口
针对于上述要求,可以发现这个通信接口分别依赖于单片机和显示控制器:
- 进行通信的具体方式,如设置寄存器的部分需要根据使用的单片机确定。
- 通信的内容需要根据使用的具体控制器型号确定。
这两个部分互相独立。通常来说,利用特定的单片机进行通信是比较简单的,而确定具体的通信内容则比较复杂。因此,今年发布的 LVGL 9 或者是 ESP-IDF 都内置了一些常见显示屏控制器的驱动协议。这些接口的通信方式实际上只需要两种:
- 发送命令:控制 DC 引脚和通信接口,发送命令。由于命令通常有时序的要求,在发送完毕后才返回。
- 发送数据:控制 DC 引脚和通信接口,发送数据。通常显存数据内容庞大,因此发送数据时可能是异步的。
移植
上述的接口在最近发布的库中通常都会默契地遵守,即使比较古老的库实际上也按照这个方法编写。因此,移植显示屏控制器代码时需要注意:
- 接线:显示屏可能有多个引脚,接起来眼花缭乱。可以在 CubeMX 给每个引脚提供标签如
LCD_MOSI [LCD P6]
,用于辅助接线。 - GPIO:使用了上述的标签后,CubeMX 会提供宏作为标签的接口,可以避免硬编码具体的引脚。对于现有的代码,最好进行修改,只使用宏表示引脚。
- 接口:实际上底层也通常会使用上述的命令、数据二分的方法定义接口,直接根据这个思路查看使用的通信外设配置即可。
基于这个原理移植的 ILI9341 代码见 https://github.com/melonedo/ILI9341-STM32F103C8。