1. STM32驱动ST7735 LCD显示文字全流程解析
作为一名嵌入式开发工程师,我最近在项目中需要使用STM32H743驱动一块160×80分辨率的ST7735 LCD屏幕显示文字。经过完整实践后,我将整个开发过程整理成这篇技术笔记,重点分享CubeMX配置和驱动开发的实战经验。
2. 硬件环境搭建
2.1 开发板与屏幕选型
我使用的是STM32H743VIT6开发板,搭配一块1.14寸IPS液晶屏,驱动芯片为ST7735S,分辨率160×80,采用SPI接口通信。这种小型LCD在物联网设备中非常常见,具有以下特点:
- 低功耗(典型工作电流15mA)
- 支持RGB565色彩模式(16位色深)
- 内置显存,减轻MCU负担
- 4线SPI接口,节省IO资源
2.2 硬件连接说明
根据开发板原理图,LCD与MCU的连接方式如下:
| LCD引脚 | MCU引脚 | 功能说明 |
|---|---|---|
| SDA | PE12 | SPI4_MOSI |
| SCL | PE14 | SPI4_SCK |
| CS | PE11 | 片选信号 |
| DC | PE13 | 数据/命令选择 |
| BLK | PE10 | 背光控制(PWM) |
| VCC | 3.3V | 电源正极 |
| GND | GND | 电源地 |
注意:不同厂家的LCD模块引脚定义可能不同,务必对照 datasheet 确认。我曾因误接VCC导致屏幕损坏,教训深刻。
3. CubeMX工程配置
3.1 创建基础工程
打开CubeMX后,我按照以下步骤创建工程:
- 点击"New Project"新建项目
- 在芯片选择界面输入"STM32H743VIT6"
- 设置工程名称为"lcd_str"
- 选择MDK-ARM作为Toolchain/IDE
- 将最小堆栈大小调整为2000字节(防止显示缓冲区溢出)
3.2 SPI接口配置
ST7735通过SPI4接口通信,关键配置参数如下:
c复制// SPI4配置参数
Mode: Half-Duplex Master
Data Size: 8 bits
First Bit: MSB First
Baud Rate: 15MHz (Prescaler=8)
CPOL: Low
CPHA: 1 Edge
由于开发板PCB设计使用了重定向引脚,需要手动调整引脚映射:
- 按住Ctrl键拖动PE2到PE12(MOSI)
- 同样方法将PE6重定向到PE14(SCK)
- 在GPIO设置中将PE11和PE13配置为GPIO_Output
3.3 背光PWM配置
背光控制使用TIM1的通道2:
c复制TIM1 Channel2: PWM Generation CH2N
Prescaler: 119
Counter Period: 999
Pulse: 500 (初始亮度50%)
这里时钟配置很关键:
- APB2时钟为240MHz
- TIM1挂在APB2总线上
- 实际PWM频率 = 240MHz / (119+1) / (999+1) = 2kHz
3.4 时钟树配置
为确保SPI工作稳定,需要正确配置时钟:
- 开启HSE外部时钟(25MHz晶振)
- 配置PLL1输出480MHz
- 系统时钟设为480MHz
- APB4总线时钟设为120MHz(SPI4挂在此总线)
调试心得:我曾因时钟配置错误导致SPI通信失败,后来用逻辑分析仪抓取波形才发现时钟频率不对。建议新手务必检查时钟树配置。
4. 驱动开发实战
4.1 BSP驱动移植
ST官方提供了ST7735的BSP驱动,包含以下关键文件:
- st7735.c:底层寄存器操作
- st7735_reg.h:寄存器定义
- lcd.c:应用层API
- font.h:字库数据
移植步骤:
- 将BSP文件夹复制到工程目录
- 在MDK中添加文件到项目
- 设置头文件包含路径
- 在main.c中包含lcd.h头文件
4.2 显示缓冲区设计
我设计了双缓冲机制来避免闪烁:
c复制#define LCD_WIDTH 160
#define LCD_HEIGHT 80
// RGB565格式的双缓冲
uint16_t lcd_buffer[2][LCD_HEIGHT][LCD_WIDTH];
// 当前显示页和绘制页
uint8_t display_page = 0;
uint8_t draw_page = 1;
切换缓冲区的函数:
c复制void LCD_SwapBuffer(void) {
ST7735_FillRGBRect(&st7735_pObj, 0, 0,
(uint8_t*)lcd_buffer[draw_page],
LCD_WIDTH, LCD_HEIGHT);
// 交换页索引
uint8_t temp = display_page;
display_page = draw_page;
draw_page = temp;
}
4.3 文字显示实现
ST7735本身不带字库,需要自行实现字符显示。我采用点阵字模方式:
- 定义字模结构体:
c复制typedef struct {
uint8_t width;
uint8_t height;
const uint8_t *data;
} FontDef;
- 实现字符显示函数:
c复制void LCD_DrawChar(uint16_t x, uint16_t y, char ch, FontDef font, uint16_t color) {
uint32_t i, j, b;
for(i=0; i<font.height; i++) {
for(j=0; j<font.width; j++) {
b = font.data[(ch-32)*font.height + i] & (1<<(font.width-1-j));
if(b) {
lcd_buffer[draw_page][y+i][x+j] = color;
}
}
}
}
- 字符串显示函数:
c复制void LCD_DrawString(uint16_t x, uint16_t y, const char *str, FontDef font, uint16_t color) {
while(*str) {
LCD_DrawChar(x, y, *str, font, color);
x += font.width;
str++;
}
}
4.4 颜色处理技巧
ST7735使用RGB565格式,转换函数如下:
c复制uint16_t RGB888_to_RGB565(uint8_t r, uint8_t g, uint8_t b) {
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
常用颜色预定义:
c复制#define BLACK 0x0000
#define BLUE 0x001F
#define RED 0xF800
#define GREEN 0x07E0
#define CYAN 0x07FF
#define YELLOW 0xFFE0
#define WHITE 0xFFFF
5. 性能优化技巧
5.1 SPI DMA传输优化
直接使用SPI发送显示数据会阻塞CPU,我改用DMA传输:
c复制// 初始化DMA
hdma_spi4_tx.Instance = DMA1_Stream0;
hdma_spi4_tx.Init.Request = DMA_REQUEST_SPI4_TX;
HAL_DMA_Init(&hdma_spi4_tx);
__HAL_LINKDMA(&hspi4, hdmatx, hdma_spi4_tx);
// DMA方式刷新屏幕
void LCD_Refresh_DMA(void) {
HAL_SPI_Transmit_DMA(&hspi4, (uint8_t*)lcd_buffer[draw_page],
LCD_WIDTH*LCD_HEIGHT*2);
}
5.2 局部刷新策略
全屏刷新耗时长(约30ms),可采用脏矩形技术:
c复制typedef struct {
uint16_t x1, y1;
uint16_t x2, y2;
bool need_update;
} DirtyArea;
DirtyArea dirty_area = {0};
void LCD_MarkDirty(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
if(!dirty_area.need_update) {
dirty_area.x1 = x;
dirty_area.y1 = y;
dirty_area.x2 = x + w;
dirty_area.y2 = y + h;
dirty_area.need_update = true;
} else {
// 合并脏区域
dirty_area.x1 = MIN(dirty_area.x1, x);
dirty_area.y1 = MIN(dirty_area.y1, y);
dirty_area.x2 = MAX(dirty_area.x2, x + w);
dirty_area.y2 = MAX(dirty_area.y2, y + h);
}
}
void LCD_UpdateDirty(void) {
if(dirty_area.need_update) {
uint16_t width = dirty_area.x2 - dirty_area.x1;
uint16_t height = dirty_area.y2 - dirty_area.y1;
ST7735_SetWindow(dirty_area.x1, dirty_area.y1,
dirty_area.x2, dirty_area.y2);
HAL_SPI_Transmit_DMA(&hspi4,
(uint8_t*)&lcd_buffer[draw_page][dirty_area.y1][dirty_area.x1],
width * height * 2);
dirty_area.need_update = false;
}
}
6. 常见问题排查
6.1 屏幕无显示
检查步骤:
- 测量背光电压(应为3.3V)
- 检查复位信号(开机应有低脉冲)
- 用逻辑分析仪抓取SPI波形
- 确认CS/DC信号时序
6.2 显示花屏
可能原因:
- 时钟频率过高(建议不超过20MHz)
- SPI模式不匹配(CPOL/CPHA设置错误)
- 显存数据格式不对(RGB565 vs RGB888)
- 电源不稳定(建议增加100uF电容)
6.3 字符显示错位
调试方法:
- 检查字模数据是否正确
- 确认显示方向设置(LANDSCAPE/PORTRAIT)
- 验证坐标计算逻辑
- 检查字体宽度是否为8的倍数
7. 进阶功能实现
7.1 中文字库支持
英文字库占用空间小(约2KB),而中文字库较大。解决方案:
- 使用GB2312编码字库
- 按需加载部分字库到外部Flash
- 实现字库索引快速查找
c复制typedef struct {
uint16_t gb_code; // GB2312编码
uint8_t width;
uint8_t height;
const uint8_t *data;
} ChineseChar;
const ChineseChar chinese_font[] = {
{0xB0A1, 16, 16, ...}, // "啊"
{0xB0A2, 16, 16, ...}, // "阿"
// 其他汉字...
};
7.2 多语言支持
通过结构体封装不同语言的字库:
c复制typedef struct {
const char *lang;
FontDef ascii_font;
const ChineseChar *chinese_font;
uint16_t chinese_count;
} LanguagePack;
const LanguagePack languages[] = {
{"zh", font8x8, chinese_font, 1000},
{"en", font8x8, NULL, 0},
// 其他语言...
};
7.3 触摸屏集成
ST7735常与XPT2046触摸芯片搭配使用。集成步骤:
- 配置SPI接口(与LCD共用)
- 实现触摸坐标读取
- 添加校准算法
- 设计UI事件系统
c复制typedef struct {
uint16_t x;
uint16_t y;
bool pressed;
} TouchPoint;
TouchPoint LCD_GetTouch(void) {
// 读取触摸原始数据
uint16_t x_raw = XPT2046_Read(XPT2046_X);
uint16_t y_raw = XPT2046_Read(XPT2046_Y);
// 转换为屏幕坐标
TouchPoint p;
p.x = (x_raw - CALIB_X_MIN) * LCD_WIDTH / (CALIB_X_MAX - CALIB_X_MIN);
p.y = (y_raw - CALIB_Y_MIN) * LCD_HEIGHT / (CALIB_Y_MAX - CALIB_Y_MIN);
p.pressed = (XPT2046_Read(XPT2046_Z1) > TOUCH_THRESHOLD);
return p;
}
经过完整开发流程后,我的STM32H743已经可以流畅驱动ST7735显示中英文混合内容。实际项目中,我还添加了界面动画、触摸交互等功能,这些内容将在后续文章中分享。