STM32CubeMX + HAL库搞定ST7735彩屏:从SPI配置到显示图片的保姆级避坑指南
ST32CubeMX + HAL库驱动ST7735彩屏全流程实战解析
在嵌入式开发中,图形显示界面往往是提升用户体验的关键要素。ST7735作为一款性价比极高的彩色TFT液晶驱动芯片,被广泛应用于各种嵌入式场景。本文将基于STM32CubeMX和HAL库,从硬件配置到软件实现,完整呈现ST7735屏幕的驱动过程,特别针对开发中常见的SPI配置、颜色模式、屏幕旋转等痛点问题提供解决方案。
1. 硬件准备与CubeMX工程创建
在开始编码之前,我们需要确保硬件连接正确。ST7735通常通过SPI接口与MCU通信,除了基本的SPI引脚(SCK、MOSI)外,还需要三个控制引脚:
- RST(复位信号)
- DC(数据/命令选择)
- CS(片选信号)
打开STM32CubeMX,按照以下步骤创建工程:
- 选择正确的STM32型号(任何带有SPI外设的型号均可)
- 在"Connectivity"选项卡中启用SPI1(或其他可用SPI接口)
- 配置SPI为"Transmit Only Master"模式
- 设置合适的Prescaler值,建议波特率在5-20MHz之间
提示:SPI时钟频率并非越高越好,过高的频率可能导致信号完整性问题。如果屏幕出现雪花噪点或显示不稳定,可尝试降低SPI速率。
对于GPIO引脚配置:
/* 在CubeMX中将三个GPIO配置为Output模式 */
/* 建议的引脚标签命名 */
#define SPI1_RST_GPIO_Port GPIOA
#define SPI1_RST_Pin GPIO_PIN_0
#define SPI1_DC_GPIO_Port GPIOA
#define SPI1_DC_Pin GPIO_PIN_1
#define SPI1_CS_GPIO_Port GPIOA
#define SPI1_CS_Pin GPIO_PIN_4
2. ST7735驱动实现与HAL库整合
创建好基础工程后,我们需要添加ST7735的驱动程序。建议将驱动代码分为头文件和源文件,便于维护和移植。
2.1 驱动头文件设计
st7735.h应包含以下核心内容:
#ifndef ST7735_H
#define ST7735_H
#include "main.h"
#include "spi.h"
/* 屏幕物理参数 */
#define ST7735_WIDTH 128
#define ST7735_HEIGHT 160
/* 控制引脚定义 - 需与CubeMX配置一致 */
#define ST7735_RST_Pin SPI1_RST_Pin
#define ST7735_RST_GPIO_Port SPI1_RST_GPIO_Port
#define ST7735_DC_Pin SPI1_DC_Pin
#define ST7735_DC_GPIO_Port SPI1_DC_GPIO_Port
#define ST7735_CS_Pin SPI1_CS_Pin
#define ST7735_CS_GPIO_Port SPI1_CS_GPIO_Port
/* SPI实例选择 */
#define ST7735_SPI_INSTANCE hspi1
/* 颜色模式配置 */
#define ST7735_MADCTL_RGB 0x00
#define ST7735_MADCTL_BGR 0x08
#define ST7735_MADCTL_MODE ST7735_MADCTL_RGB
/* 函数声明 */
void ST7735_Init(void);
void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color);
void ST7735_FillScreen(uint16_t color);
#endif
2.2 核心驱动函数实现
在st7735.c中,我们需要实现几个关键函数:
SPI数据传输函数:
void ST7735_WriteCommand(uint8_t cmd) {
HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&ST7735_SPI_INSTANCE, &cmd, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_SET);
}
void ST7735_WriteData(uint8_t *data, uint16_t length) {
HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&ST7735_SPI_INSTANCE, data, length, HAL_MAX_DELAY);
HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_SET);
}
初始化序列:
ST7735的初始化需要发送一系列配置命令,这些命令因屏幕型号而异。以下是一个典型的初始化流程:
void ST7735_Init(void) {
// 硬件复位
HAL_GPIO_WritePin(ST7735_RST_GPIO_Port, ST7735_RST_Pin, GPIO_PIN_RESET);
HAL_Delay(100);
HAL_GPIO_WritePin(ST7735_RST_GPIO_Port, ST7735_RST_Pin, GPIO_PIN_SET);
HAL_Delay(100);
// 发送初始化命令序列
ST7735_WriteCommand(0x11); // Sleep out
HAL_Delay(120);
ST7735_WriteCommand(0xB1); // Frame rate control
uint8_t data1[] = {0x01, 0x2C, 0x2D};
ST7735_WriteData(data1, sizeof(data1));
ST7735_WriteCommand(0xB2); // Frame rate control
uint8_t data2[] = {0x01, 0x2C, 0x2D};
ST7735_WriteData(data2, sizeof(data2));
// ... 更多初始化命令
}
3. 高级功能实现
3.1 屏幕旋转与显示方向控制
ST7735支持通过MADCTL命令设置显示方向。我们可以通过以下函数实现屏幕旋转:
void ST7735_SetRotation(uint8_t rotation) {
uint8_t madctl = 0;
switch(rotation % 4) {
case 0:
madctl = ST7735_MADCTL_MX | ST7735_MADCTL_MY | ST7735_MADCTL_MODE;
break;
case 1:
madctl = ST7735_MADCTL_MY | ST7735_MADCTL_MV | ST7735_MADCTL_MODE;
break;
case 2:
madctl = ST7735_MADCTL_MODE;
break;
case 3:
madctl = ST7735_MADCTL_MX | ST7735_MADCTL_MV | ST7735_MADCTL_MODE;
break;
}
ST7735_WriteCommand(0x36); // MADCTL
ST7735_WriteData(&madctl, 1);
}
3.2 颜色格式处理
ST7735使用RGB565颜色格式,即每个像素用16位表示(5位红色,6位绿色,5位蓝色)。我们可以提供方便的宏来生成颜色值:
#define ST7735_COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3))
// 常用颜色定义
#define ST7735_BLACK 0x0000
#define ST7735_BLUE 0x001F
#define ST7735_RED 0xF800
#define ST7735_GREEN 0x07E0
#define ST7735_WHITE 0xFFFF
3.3 图像显示优化
显示图像时,直接使用SPI传输每个像素效率较低。我们可以通过设置窗口地址然后批量传输来提高效率:
void ST7735_SetWindow(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
// 设置列地址
ST7735_WriteCommand(0x2A);
uint8_t data[] = {0x00, x0, 0x00, x1};
ST7735_WriteData(data, sizeof(data));
// 设置行地址
ST7735_WriteCommand(0x2B);
data[1] = y0;
data[3] = y1;
ST7735_WriteData(data, sizeof(data));
// 准备写入GRAM
ST7735_WriteCommand(0x2C);
}
void ST7735_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *img) {
ST7735_SetWindow(x, y, x + w - 1, y + h - 1);
ST7735_WriteData((uint8_t *)img, w * h * 2);
}
4. 常见问题排查与优化
4.1 显示异常排查指南
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 屏幕全白 | 背光正常但无显示 | 检查RST引脚时序,确保复位信号正确 |
| 显示错位 | 窗口设置不正确 | 调整ST7735_XSTART和ST7735_YSTART偏移量 |
| 颜色异常 | 颜色模式不匹配 | 切换ST7735_MADCTL_MODE为RGB或BGR |
| 显示反色 | 反色模式被启用 | 设置ST7735_INVERSE为0或1 |
| 雪花噪点 | SPI时钟速率过高 | 降低SPI波特率,检查PCB布线 |
4.2 性能优化技巧
- 双缓冲技术:在内存中维护两个帧缓冲区,交替更新和显示
- 局部刷新:只更新屏幕上发生变化的部分区域
- DMA传输:使用DMA代替CPU来搬运显示数据
- 指令预存:将常用显示命令预先存储在数组中,减少函数调用开销
// 使用DMA传输示例
void ST7735_WriteData_DMA(uint8_t *data, uint16_t length) {
HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit_DMA(&ST7735_SPI_INSTANCE, data, length);
// 注意:需要等待DMA传输完成才能拉高CS
}
4.3 字体显示实现
为了在屏幕上显示文字,我们需要实现字体系统。以下是基本字体结构的定义:
typedef struct {
uint8_t width; // 字符宽度
uint8_t height; // 字符高度
const uint32_t *data; // 字体数据指针
} FontDef;
// 7x10字体示例
const uint32_t Font7x10 [] = {
// 空格
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
// 其他字符数据...
};
const FontDef Font_7x10 = {7, 10, Font7x10};
字符显示函数实现:
void ST7735_DrawChar(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bg, const FontDef *font) {
uint32_t charData = font->data[(c - 32) * font->height];
for(uint8_t i = 0; i < font->height; i++) {
for(uint8_t j = 0; j < font->width; j++) {
if(charData & (1 << (font->width - 1 - j))) {
ST7735_DrawPixel(x + j, y + i, color);
} else {
ST7735_DrawPixel(x + j, y + i, bg);
}
}
charData = font->data[(c - 32) * font->height + i];
}
}
5. 项目实战:构建UI框架
基于以上基础功能,我们可以构建简单的UI框架。以下是一个状态机式的UI管理系统示例:
typedef enum {
UI_HOME,
UI_MENU,
UI_SETTINGS,
UI_ABOUT
} UI_State;
typedef struct {
UI_State current_state;
void (*draw_func)(void);
void (*input_handler)(uint8_t key);
} UI_Manager;
void UI_DrawHome(void) {
ST7735_FillScreen(ST7735_BLACK);
ST7735_DrawString(10, 10, "Home Screen", ST7735_WHITE, ST7735_BLACK, &Font_11x18);
// 绘制更多UI元素...
}
void UI_HandleHomeInput(uint8_t key) {
if(key == KEY_OK) {
ui_manager.current_state = UI_MENU;
ui_manager.draw_func = UI_DrawMenu;
ui_manager.input_handler = UI_HandleMenuInput;
}
}
UI_Manager ui_manager = {
.current_state = UI_HOME,
.draw_func = UI_DrawHome,
.input_handler = UI_HandleHomeInput
};
void UI_Update(void) {
ui_manager.draw_func();
}
void UI_HandleInput(uint8_t key) {
ui_manager.input_handler(key);
}
在实际项目中,这种架构可以方便地扩展各种界面和交互逻辑。
更多推荐
所有评论(0)