STM32 CUBE SPI+DMA+LVGL驱动ST7789 LCD屏
SPI驱动ST7789屏的相关资料较多,相比之下较少使用DMA驱动方式。在尝试通过LVGL库和SPI进行数据传输时,由于屏幕刷新效果较为缓慢,在更换为DMA驱动后问题有所改善。然而,按照网上的部分配置方法实施后,发现持续出现屏幕显示颜色不准确的问题。经过一番努力排查后,找到了有效的解决方案,并对此进行了详细记录。
引脚参数设置
LCD_BL驱动背光,高电压水平起作用,低电压水平导致屏幕黑色显示。

SPI配置
半双工模式:接收发送一条数据线,与屏幕的半双工接口配合使用。
硬件上控制CS的话,另外一种方式也可以通过软件实现,具体采用哪种则取决于个人的偏好。
最大波特率设为60M以下,并以达到更快刷新速度为目标,追求最高水平。
可以选择的数据位数包括8和16,具体原因将在后续内容中阐述(大坑)。

DMA数据采用16位精度存储或处理,并等同于用半字节的长度表示数据块。

这些文件被用来...。
st7789.c
#include "st7789.h"
#include "spi.h"
// ST7789命令定义
#define ST7789_NOP 0x00
#define ST7789_SWRESET 0x01
#define ST7789_SLPIN 0x10
#define ST7789_SLPOUT 0x11
#define ST7789_INVOFF 0x20
#define ST7789_INVON 0x21
#define ST7789_DISPOFF 0x28
#define ST7789_DISPON 0x29
#define ST7789_CASET 0x2A
#define ST7789_RASET 0x2B
#define ST7789_RAMWR 0x2C
#define ST7789_MADCTL 0x36
// MADCTL参数
#define ST7789_MADCTL_MY 0x80
#define ST7789_MADCTL_MX 0x40
#define ST7789_MADCTL_MV 0x20
#define ST7789_MADCTL_ML 0x10
#define ST7789_MADCTL_MH 0x04
#define ST7789_MADCTL_RGB 0x00
#define ST7789_MADCTL_BGR 0x08
#define ST7789_SPI_INSTANCE hspi1
#define SPI_DC_LOW() HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_RESET)
#define SPI_DC_HIGH() HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET)
#define SPI_RST_LOW() HAL_GPIO_WritePin(LCD_RES_GPIO_Port, LCD_RES_Pin, GPIO_PIN_RESET);
#define SPI_RST_HIGH() HAL_GPIO_WritePin(LCD_RES_GPIO_Port, LCD_RES_Pin, GPIO_PIN_SET);
//#define SPI_WriteByte(cmd) HAL_SPI_Transmit(&ST7789_SPI_INSTANCE, &cmd, 1, HAL_MAX_DELAY)
#define SPI_WriteByte(cmd) HAL_SPI_Transmit_DMA(&ST7789_SPI_INSTANCE, &cmd, 1)
// 设置SPI数据大小为8位
void SPI_SetDataSize_8bit(SPI_HandleTypeDef *hspi) {
hspi->Init.DataSize = SPI_DATASIZE_8BIT;
if (HAL_SPI_Init(hspi) != HAL_OK) {
// 初始化失败处理
}
}
// 设置SPI数据大小为16位
void SPI_SetDataSize_16bit(SPI_HandleTypeDef *hspi) {
hspi->Init.DataSize = SPI_DATASIZE_16BIT;
if (HAL_SPI_Init(hspi) != HAL_OK) {
// 初始化失败处理
}
}
// 向ST7789发送命令
void ST7789_SendCommand(uint8_t cmd) {
SPI_SetDataSize_8bit(&ST7789_SPI_INSTANCE);
SPI_DC_LOW();
HAL_SPI_Transmit(&ST7789_SPI_INSTANCE, &cmd , 1, HAL_MAX_DELAY);
}
// 向ST7789发送命令
void ST7789_SendData(uint8_t cmd) {
SPI_SetDataSize_8bit(&ST7789_SPI_INSTANCE);
SPI_DC_HIGH();
HAL_SPI_Transmit(&ST7789_SPI_INSTANCE, &cmd , 1, HAL_MAX_DELAY);
}
// 向ST7789发送单个16位数据
void ST7789_SendData_16bit(uint16_t data) {
SPI_SetDataSize_16bit(&ST7789_SPI_INSTANCE);
SPI_DC_HIGH();
HAL_SPI_Transmit_DMA(&ST7789_SPI_INSTANCE, (uint8_t *)&data, 1);
while (HAL_SPI_GetState(&ST7789_SPI_INSTANCE) != HAL_SPI_STATE_READY);
}
// 向ST7789发送多个16位数据(使用DMA)
void ST7789_SendData_DMA_16bit(const uint16_t *pData, uint16_t Size) {
SPI_SetDataSize_16bit(&ST7789_SPI_INSTANCE);
SPI_DC_HIGH();
HAL_SPI_Transmit_DMA(&ST7789_SPI_INSTANCE, (uint8_t *)pData, Size);
while (HAL_SPI_GetState(&ST7789_SPI_INSTANCE) != HAL_SPI_STATE_READY);
}
void setRotation(uint8_t r)
{
uint8_t rotation = r % 4;
ST7789_SendCommand(ST7789_MADCTL);
switch(rotation)
{
case 0:
ST7789_SendData(0x00);
break;
case 1:
ST7789_SendData(0xC0);
break;
case 2:
ST7789_SendData(0x70);
break;
case 3:
ST7789_SendData(0xA0);
break;
}
}
// 初始化ST7789
void ST7789_Init(void) {
HAL_GPIO_WritePin(GPIOA, LCD_BL_Pin, GPIO_PIN_SET);
SPI_CS_HIGH(); // 拉高CS
SPI_RST_LOW();
HAL_Delay(100);
SPI_RST_HIGH();
HAL_Delay(100);
ST7789_SendCommand(ST7789_SLPOUT); //Sleep out
HAL_Delay(120);
// 设置颜色模式
setRotation(0);
ST7789_SendCommand(0x3A);
ST7789_SendData(0x05);
#if SCREEN_USE_LITTLE_ENDIAN
/* Change to Little Endian */
ST7789_SendCommand(0xB0);
ST7789_SendData(0x00); // RM = 0; DM = 00
ST7789_SendData(0xF8); // EPF = 11; ENDIAN = 1; RIM = 0; MDT = 00 (ENDIAN -> 0 MSBFirst; 1 LSB First)
#endif
ST7789_SendCommand(0xB2);
ST7789_SendData(0x0C);
ST7789_SendData(0x0C);
ST7789_SendData(0x00);
ST7789_SendData(0x33);
ST7789_SendData(0x33);
ST7789_SendCommand(0xB7);
ST7789_SendData(0x35);
ST7789_SendCommand(0xBB);
ST7789_SendData(0x32); //Vcom=1.35V
ST7789_SendCommand(0xC2);
ST7789_SendData(0x01);
ST7789_SendCommand(0xC3);
ST7789_SendData(0x15); //GVDD=4.8V
ST7789_SendCommand(0xC4);
ST7789_SendData(0x20); //VDV, 0x20:0v
ST7789_SendCommand(0xC6);
ST7789_SendData(0x0F); //0x0F:60Hz
ST7789_SendCommand(0xD0);
ST7789_SendData(0xA4);
ST7789_SendData(0xA1);
ST7789_SendCommand(0xE0);
ST7789_SendData(0xD0);
ST7789_SendData(0x08);
ST7789_SendData(0x0E);
ST7789_SendData(0x09);
ST7789_SendData(0x09);
ST7789_SendData(0x05);
ST7789_SendData(0x31);
ST7789_SendData(0x33);
ST7789_SendData(0x48);
ST7789_SendData(0x17);
ST7789_SendData(0x14);
ST7789_SendData(0x15);
ST7789_SendData(0x31);
ST7789_SendData(0x34);
ST7789_SendCommand(0xE1);
ST7789_SendData(0xD0);
ST7789_SendData(0x08);
ST7789_SendData(0x0E);
ST7789_SendData(0x09);
ST7789_SendData(0x09);
ST7789_SendData(0x15);
ST7789_SendData(0x31);
ST7789_SendData(0x33);
ST7789_SendData(0x48);
ST7789_SendData(0x17);
ST7789_SendData(0x14);
ST7789_SendData(0x15);
ST7789_SendData(0x31);
ST7789_SendData(0x34);
ST7789_SendCommand(ST7789_INVON);
ST7789_SendCommand(ST7789_DISPON);
}
// 设置显示窗口
void ST7789_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
ST7789_SendCommand(ST7789_CASET);
ST7789_SendData_16bit(x0);
ST7789_SendData_16bit(x1);
ST7789_SendCommand(ST7789_RASET);
ST7789_SendData_16bit(y0);
ST7789_SendData_16bit(y1);
ST7789_SendCommand(ST7789_RAMWR);
}
// 填充屏幕
void ST7789_FillScreen(uint16_t color) {
ST7789_SetWindow(0, 0, ST7789_WIDTH - 1, ST7789_HEIGHT - 1);
for (uint32_t i = 0; i < ST7789_WIDTH * ST7789_HEIGHT; i++) {
ST7789_SendData_16bit(color);
}
}
// 绘制像素
void ST7789_DrawPixel(uint16_t x, uint16_t y, uint16_t color) {
if (x >= ST7789_WIDTH || y >= ST7789_HEIGHT) return;
ST7789_SetWindow(x, y, x, y);
ST7789_SendData_16bit(color);
}
// 绘制矩形
void ST7789_DrawRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
for (uint16_t i = x; i < x + w; i++) {
for (uint16_t j = y; j < y + h; j++) {
ST7789_DrawPixel(i, j, color);
}
}
}
// 绘制图像
void ST7789_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *data) {
ST7789_SetWindow(x, y, x + w - 1, y + h - 1);
ST7789_SendData_DMA_16bit(data,(w*h));
}
// 绘制字符
void ST7789_DrawChar(uint16_t x, uint16_t y, char ch, FontDef font, uint16_t color, uint16_t bgcolor) {
uint32_t i, b, j;
for (i = 0; i < font.height; i++) {
b = font.data[(ch - 32) * font.height + i];
for (j = 0; j < font.width; j++) {
if ((b << j) & 0x8000) {
ST7789_DrawPixel(x + j, y + i, color);
} else {
ST7789_DrawPixel(x + j, y + i, bgcolor);
}
}
}
}
// 绘制字符串
void ST7789_DrawString(uint16_t x, uint16_t y, const char *str, FontDef font, uint16_t color, uint16_t bgcolor) {
while (*str) {
ST7789_DrawChar(x, y, *str, font, color, bgcolor);
x += font.width;
str++;
}
}
software test 7789.h
#ifndef ST7789_H
#define ST7789_H
#include <stdint.h>
#include <stdbool.h>
#include "fonts.h" // 字体库
// 定义屏幕尺寸
#define ST7789_WIDTH 240
#define ST7789_HEIGHT 320
// 定义颜色格式
#define ST7789_COLOR(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3))
// 常用颜色
#define ST7789_WHITE ST7789_COLOR(255, 255, 255)
#define ST7789_BLACK ST7789_COLOR(0, 0, 0)
#define ST7789_RED ST7789_COLOR(255, 0, 0)
#define ST7789_GREEN ST7789_COLOR(0, 255, 0)
#define ST7789_BLUE ST7789_COLOR(0, 0, 255)
#define ST7789_YELLOW ST7789_COLOR(255, 255, 0)
#define ST7789_CYAN ST7789_COLOR(0, 255, 255)
#define ST7789_MAGENTA ST7789_COLOR(255, 0, 255)
// 初始化函数
void ST7789_Init(void);
// 设置显示区域
void ST7789_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
// 填充屏幕
void ST7789_FillScreen(uint16_t color);
// 绘制像素
void ST7789_DrawPixel(uint16_t x, uint16_t y, uint16_t color);
// 绘制矩形
void ST7789_DrawRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color);
// 绘制图像
void ST7789_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *data);
// 绘制字符
void ST7789_DrawChar(uint16_t x, uint16_t y, char ch, FontDef font, uint16_t color, uint16_t bgcolor);
// 绘制字符串
void ST7789_DrawString(uint16_t x, uint16_t y, const char *str, FontDef font, uint16_t color, uint16_t bgcolor);
#endif // ST7789_H
字体文件就不放了,网上很多。
该文档旨在介绍其核心资源库的构建与应用框架
1. 数据信息的精度与分辨率由其数值信息的位宽决定
通过查看这段代码可以发现关键在于,在配置LCD寄存器并使用DMA传输像素数据的过程中,将SPI的数据总线宽度进行了调整。写寄存器时为8位,而DMA传输像素数据改成了16位。为什么要这样做呢?
主要原因是LCD控制器仅能处理8位操作指令。当尝试发送16位指令时,补充前导零至0x00后,该控制器无法识别此修改后的指令序列,因此显示屏未能点亮。
而LVGL的输出数据为16位精度,在采用DMA直接传输8位时会产生高低位翻转问题,从而导致显色不准确的现象。为了在传输过程中避免这种问题的发生,建议在数据前通过临时缓冲数组进行端序调整处理,这样既可有效防止因数据顺序颠倒而导致的显示异常现象发生,又可在一定程度上减少对系统资源和计算能力的占用。然而,在实际操作中若一味追求传输效率,则需要付出更多的内存占用和计算时间代价,最终导致整体传输速率明显降低。
2.SPI信号线的下降幅度
在初步对ST7789屏进行测试过程中,观察到屏控逻辑处于低电平状态,无法正常点亮。然而,在接通通信线后,屏控逻辑恢复至高电平状态,能够实现正常点亮。经分析判断,问题可能与屏线电阻有关,并采取措施将通信线的供电端接地,最终消除了故障
GPIOPinConfig.PULL = GPIO Pin Down;
/**SPI1 GPIO Configuration
PA4 ------> SPI1_NSS
PA5 ------> SPI1_SCK
PA7 ------> SPI1_MOSI
*/
GPIO_InitStruct.Pin = GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
该部分的性能表现显著。
采用DMA技术进行图像数据传输后,LVGL能够以每秒53毫秒的速度完成整个屏幕的刷新过程

通过DMA机制传输图像信息是没有间隙的流程,最大传输速率达到48M,这一数值受制于逻辑分析仪的精度限制,实际测量值可能达到50M。

配置步骤所用时间为278微秒,采用基于标准SPI的通信方式,在此过程中存在较多的闲置时间。

系统将配置寄存器设定为采用8位编码模式。

