1. 项目概述与硬件选型
HS280S030RX这款2.4英寸TFT-LCD模块在嵌入式领域应用广泛,其核心优势在于采用了ST7789驱动芯片,这种组合在中小尺寸显示屏中性价比突出。我在多个物联网设备项目中都采用过这个方案,实测显示效果和稳定性都令人满意。
该模块的硬件接口设计非常典型:采用SPI通信协议,只需要4根信号线(CS、DC、RST、BL)加上SPI总线即可驱动。这种精简的接口设计特别适合STM32这类资源有限的微控制器。根据我的经验,使用STM32F103C8T6这类基础型号就能流畅驱动,不需要高端型号。
硬件连接提示:务必注意模块工作电压是3.3V,与STM32的IO电平完全匹配,不需要额外电平转换电路。但若使用5V单片机系统,必须添加电平转换器。
2. 底层驱动实现解析
2.1 GPIO引脚配置
在STM32的HAL库环境下,首先需要初始化相关GPIO引脚。以下是经过实战验证的配置代码:
c复制// 引脚定义(以STM32F103为例)
#define ST7789_CS_PIN GPIO_PIN_4 // 片选信号
#define ST7789_DC_PIN GPIO_PIN_5 // 数据/命令选择
#define ST7789_RST_PIN GPIO_PIN_6 // 硬件复位
#define ST7789_BL_PIN GPIO_PIN_7 // 背光控制
// GPIO初始化函数
void ST7789_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
// 配置CS、DC、RST为输出模式
GPIO_InitStruct.Pin = ST7789_CS_PIN | ST7789_DC_PIN | ST7789_RST_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 背光控制单独初始化
GPIO_InitStruct.Pin = ST7789_BL_PIN;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 默认状态设置
HAL_GPIO_WritePin(GPIOA, ST7789_CS_PIN, GPIO_PIN_SET); // 初始不选中
HAL_GPIO_WritePin(GPIOA, ST7789_DC_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(GPIOA, ST7789_RST_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, ST7789_BL_PIN, GPIO_PIN_SET); // 背光默认开启
}
2.2 SPI通信实现
ST7789支持3线或4线SPI模式,我推荐使用4线模式(包含DC线)以获得更好的稳定性。以下是SPI初始化的关键参数:
c复制void ST7789_SPI_Init(void) {
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // 时钟极性
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // 时钟相位
hspi1.Init.NSS = SPI_NSS_SOFT; // 软件控制片选
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // 18MHz @72MHz主频
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi1) != HAL_OK) {
Error_Handler();
}
}
通信速率提示:经过实测,当SPI时钟超过20MHz时,长距离布线可能出现数据错误。如果使用飞线连接,建议将预分频设为4(18MHz)或8(9MHz)。
3. ST7789驱动核心函数
3.1 基础命令函数
驱动LCD的核心是正确实现命令和数据发送函数。以下是经过优化的实现:
c复制// 发送命令
void ST7789_WriteCommand(uint8_t cmd) {
HAL_GPIO_WritePin(GPIOA, ST7789_DC_PIN, GPIO_PIN_RESET); // 命令模式
HAL_GPIO_WritePin(GPIOA, ST7789_CS_PIN, GPIO_PIN_RESET); // 选中设备
HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, ST7789_CS_PIN, GPIO_PIN_SET); // 释放设备
}
// 发送数据
void ST7789_WriteData(uint8_t data) {
HAL_GPIO_WritePin(GPIOA, ST7789_DC_PIN, GPIO_PIN_SET); // 数据模式
HAL_GPIO_WritePin(GPIOA, ST7789_CS_PIN, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, ST7789_CS_PIN, GPIO_PIN_SET);
}
// 批量发送数据(优化版)
void ST7789_WriteData_Burst(uint8_t *data, uint32_t length) {
HAL_GPIO_WritePin(GPIOA, ST7789_DC_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, ST7789_CS_PIN, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, data, length, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, ST7789_CS_PIN, GPIO_PIN_SET);
}
3.2 显示屏初始化序列
ST7789的初始化需要严格按照时序发送一系列命令。以下是经过验证的初始化代码:
c复制void ST7789_Init(void) {
// 硬件复位
HAL_GPIO_WritePin(GPIOA, ST7789_RST_PIN, GPIO_PIN_RESET);
HAL_Delay(120);
HAL_GPIO_WritePin(GPIOA, ST7789_RST_PIN, GPIO_PIN_SET);
HAL_Delay(120);
// 发送初始化命令序列
ST7789_WriteCommand(0x36); // 内存数据访问控制
ST7789_WriteData(0x00); // 默认方向
ST7789_WriteCommand(0x3A); // 接口像素格式
ST7789_WriteData(0x55); // 16位RGB565格式
ST7789_WriteCommand(0xB2); // porch设置
ST7789_WriteData(0x0C);
ST7789_WriteData(0x0C);
ST7789_WriteData(0x00);
ST7789_WriteData(0x33);
ST7789_WriteData(0x33);
// 更多初始化命令...
// 省略部分命令以节省篇幅
ST7789_WriteCommand(0x29); // 开启显示
HAL_Delay(100);
}
4. 高级功能实现
4.1 屏幕旋转控制
ST7789支持四种显示方向,通过修改内存访问控制寄存器(0x36)实现:
c复制typedef enum {
ST7789_ROTATION_0 = 0x00, // 默认方向
ST7789_ROTATION_90 = 0x60, // 顺时针90度
ST7789_ROTATION_180 = 0xC0, // 180度
ST7789_ROTATION_270 = 0xA0 // 270度
} ST7789_Rotation;
void ST7789_SetRotation(ST7789_Rotation rotation) {
ST7789_WriteCommand(0x36);
ST7789_WriteData(rotation | 0x08); // 同时设置RGB顺序
}
4.2 区域填充优化
全屏填充是图形显示中的高频操作,优化后的实现可以显著提升性能:
c复制void ST7789_FillRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
// 设置显示窗口
ST7789_SetWindow(x, y, x + w - 1, y + h - 1);
// 准备颜色数据(高位在前)
uint8_t colorData[2] = {color >> 8, color & 0xFF};
// 发送RAM写入命令
ST7789_WriteCommand(0x2C);
// 批量发送颜色数据
HAL_GPIO_WritePin(GPIOA, ST7789_DC_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, ST7789_CS_PIN, GPIO_PIN_RESET);
for(uint32_t i = 0; i < w * h; i++) {
HAL_SPI_Transmit(&hspi1, colorData, 2, HAL_MAX_DELAY);
}
HAL_GPIO_WritePin(GPIOA, ST7789_CS_PIN, GPIO_PIN_SET);
}
5. 性能优化技巧
5.1 DMA传输加速
对于刷屏等大数据量操作,使用DMA可以显著降低CPU占用率:
c复制void ST7789_FillRect_DMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
// 设置显示窗口(同上)
// ...
// 准备DMA缓冲区
uint16_t buffer[128]; // 双缓冲大小可根据实际情况调整
for(int i = 0; i < 128; i++) {
buffer[i] = color;
}
ST7789_WriteCommand(0x2C);
HAL_GPIO_WritePin(GPIOA, ST7789_DC_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, ST7789_CS_PIN, GPIO_PIN_RESET);
uint32_t total = w * h;
while(total > 0) {
uint32_t chunk = total > 64 ? 64 : total;
HAL_SPI_Transmit_DMA(&hspi1, (uint8_t*)buffer, chunk * 2);
while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);
total -= chunk;
}
HAL_GPIO_WritePin(GPIOA, ST7789_CS_PIN, GPIO_PIN_SET);
}
5.2 双缓冲机制
对于动画显示,实现双缓冲可以避免画面撕裂:
c复制// 定义两个显示缓冲区
uint16_t frameBuffer1[240][320];
uint16_t frameBuffer2[240][320];
uint16_t *activeBuffer = frameBuffer1;
uint16_t *drawBuffer = frameBuffer2;
void ST7789_SwapBuffers(void) {
// 切换缓冲区指针
uint16_t *temp = activeBuffer;
activeBuffer = drawBuffer;
drawBuffer = temp;
// 将活动缓冲区内容刷新到屏幕
ST7789_WriteCommand(0x2C);
HAL_GPIO_WritePin(GPIOA, ST7789_DC_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, ST7789_CS_PIN, GPIO_PIN_RESET);
HAL_SPI_Transmit_DMA(&hspi1, (uint8_t*)activeBuffer, 240*320*2);
}
6. 常见问题排查
6.1 显示花屏问题
可能原因及解决方案:
-
SPI时钟相位错误:确认CLKPolarity和CLKPhase设置与ST7789要求一致。多数情况下应为:
c复制
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; -
电源不稳定:测量模块电源引脚电压,确保在3.3V±5%范围内。建议在VCC和GND之间添加100μF电解电容和0.1μF陶瓷电容。
-
复位时序不足:硬件复位后需要保持至少120ms延时,建议:
c复制HAL_GPIO_WritePin(GPIOA, ST7789_RST_PIN, GPIO_PIN_RESET); HAL_Delay(120); HAL_GPIO_WritePin(GPIOA, ST7789_RST_PIN, GPIO_PIN_SET); HAL_Delay(120);
6.2 显示颜色异常
颜色异常通常与像素格式设置有关:
-
确认初始化时正确设置了16位RGB565格式:
c复制ST7789_WriteCommand(0x3A); ST7789_WriteData(0x55); // RGB565 -
检查颜色数据发送顺序,ST7789默认是高位在前:
c复制uint8_t colorData[2] = {color >> 8, color & 0xFF}; // R/G/B分量顺序正确 -
如果颜色完全反相,可能是模块的RGB顺序需要调整,尝试修改内存访问控制寄存器:
c复制ST7789_WriteCommand(0x36); ST7789_WriteData(0x08); // 反转RGB顺序
7. 实际应用建议
在完成基础驱动后,建议构建一个图形抽象层,方便上层应用开发:
c复制// 图形上下文结构体
typedef struct {
uint16_t width;
uint16_t height;
ST7789_Rotation rotation;
uint16_t foregroundColor;
uint16_t backgroundColor;
} GraphicsContext;
// 初始化图形上下文
void Graphics_Init(GraphicsContext *ctx, uint16_t width, uint16_t height) {
ctx->width = width;
ctx->height = height;
ctx->rotation = ST7789_ROTATION_0;
ctx->foregroundColor = 0xFFFF; // 白色
ctx->backgroundColor = 0x0000; // 黑色
}
// 绘制像素
void Graphics_DrawPixel(GraphicsContext *ctx, int16_t x, int16_t y) {
if(x >= 0 && x < ctx->width && y >= 0 && y < ctx->height) {
ST7789_DrawPixel(x, y, ctx->foregroundColor);
}
}
// 绘制字符串(简化版)
void Graphics_DrawString(GraphicsContext *ctx, int16_t x, int16_t y, const char *str) {
while(*str) {
Graphics_DrawChar(x, y, *str);
x += 8; // 假设字符宽度为8像素
str++;
}
}
这种分层设计使得更换显示驱动或移植到其他平台时,只需修改底层驱动实现,而上层图形代码可以保持不变。