1. 项目概述
在嵌入式开发领域,显示模块的选择和驱动一直是工程师们经常需要面对的技术挑战。ST7735S驱动的TFT LCD屏幕因其性价比高、接口简单、显示效果良好,成为STM32开发中常用的显示解决方案。这个项目将带你从零开始,完整实现STM32与ST7735S TFT LCD的驱动对接。
我最初接触这个项目是在开发一个工业控制面板时,需要一个小尺寸但显示效果清晰的屏幕来展示实时数据。经过多次对比测试,最终选择了1.8寸的ST7735S驱动TFT屏幕,它不仅价格亲民,而且色彩表现和刷新率都能满足大多数嵌入式应用的需求。
2. 硬件准备与连接
2.1 所需硬件清单
- STM32开发板(本项目使用STM32F103C8T6最小系统板)
- ST7735S驱动的TFT LCD屏幕(常见尺寸有1.8寸、1.44寸)
- 杜邦线若干
- 3.3V稳压电源(部分屏幕需要独立供电)
- 10KΩ电阻(用于背光控制)
注意:不同厂商的ST7735S屏幕引脚定义可能略有差异,务必查阅你购买屏幕的具体规格书。
2.2 硬件连接详解
ST7735S通常支持SPI和8位并行接口,考虑到STM32F103的资源限制,我们选择4线SPI模式连接:
| TFT引脚 | STM32引脚 | 功能说明 |
|---|---|---|
| VCC | 3.3V | 电源正极 |
| GND | GND | 电源地 |
| SCL | PA5 | SPI时钟线 |
| SDA | PA7 | SPI数据线 |
| RES | PA1 | 复位信号 |
| DC | PA2 | 数据/命令选择 |
| CS | PA4 | 片选信号 |
| BLK | PA3 | 背光控制 |
在实际连接时,我发现有些屏幕的背光控制需要上拉电阻才能正常工作。如果你的屏幕背光不亮,可以尝试在BLK引脚和3.3V之间加一个10KΩ电阻。
3. 软件环境搭建
3.1 开发工具准备
我推荐使用以下工具链组合:
- Keil MDK-ARM 5.xx(或其他支持STM32的IDE)
- STM32CubeMX(用于外设初始化)
- ST-Link/V2调试器
3.2 库文件准备
根据个人偏好,你可以选择:
- 直接寄存器操作(适合对STM32熟悉的开发者)
- 标准外设库(Standard Peripheral Library)
- HAL库(当前ST主推的硬件抽象层)
我建议初学者从HAL库开始,它虽然效率略低但开发效率高。在CubeMX中配置SPI外设时,需要注意以下参数:
- SPI模式:Mode 3(CPOL=1, CPHA=1)
- 时钟分频:至少8分频(确保时钟不超过屏幕最大支持频率)
- 数据宽度:8位
- MSB先行
4. 驱动代码实现
4.1 底层通信函数
首先需要实现基本的SPI读写函数:
c复制// 发送命令
void ST7735_WriteCommand(uint8_t cmd) {
HAL_GPIO_WritePin(GPIOA, DC_Pin, GPIO_PIN_RESET); // DC低电平表示命令
HAL_GPIO_WritePin(GPIOA, CS_Pin, GPIO_PIN_RESET); // 片选使能
HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, CS_Pin, GPIO_PIN_SET); // 片选禁用
}
// 发送数据
void ST7735_WriteData(uint8_t data) {
HAL_GPIO_WritePin(GPIOA, DC_Pin, GPIO_PIN_SET); // DC高电平表示数据
HAL_GPIO_WritePin(GPIOA, CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA, CS_Pin, GPIO_PIN_SET);
}
4.2 屏幕初始化序列
ST7735S的初始化需要严格按照时序发送一系列命令和参数:
c复制void ST7735_Init(void) {
// 硬件复位
HAL_GPIO_WritePin(GPIOA, RES_Pin, GPIO_PIN_RESET);
HAL_Delay(100);
HAL_GPIO_WritePin(GPIOA, RES_Pin, GPIO_PIN_SET);
HAL_Delay(100);
// 发送初始化命令序列
ST7735_WriteCommand(0x11); // Sleep out
HAL_Delay(120);
ST7735_WriteCommand(0xB1); // 帧率控制
ST7735_WriteData(0x05);
ST7735_WriteData(0x3C);
ST7735_WriteData(0x3C);
// 更多初始化命令...
ST7735_WriteCommand(0x29); // 开启显示
}
经验分享:不同厂商的ST7735S模块可能需要不同的初始化序列。如果遇到显示异常,可以尝试调整初始化参数或联系厂商获取准确的初始化代码。
5. 图形显示功能实现
5.1 基本绘图函数
实现基本的像素点绘制函数是所有图形显示的基础:
c复制void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color) {
// 设置显示窗口
ST7735_SetWindow(x, y, x, y);
// 发送像素颜色数据
uint8_t data[2] = {color >> 8, color & 0xFF};
ST7735_WriteData(data[0]);
ST7735_WriteData(data[1]);
}
5.2 颜色格式处理
ST7735S使用RGB565颜色格式,我们需要一些辅助函数来处理颜色:
c复制// RGB888转RGB565
uint16_t RGB888_to_RGB565(uint8_t r, uint8_t g, uint8_t b) {
return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3);
}
// 常用颜色定义
#define ST7735_BLACK 0x0000
#define ST7735_BLUE 0x001F
#define ST7735_RED 0xF800
#define ST7735_GREEN 0x07E0
#define ST7735_WHITE 0xFFFF
5.3 高级图形功能
基于像素绘制,我们可以实现更高级的图形功能:
c复制// 绘制矩形
void ST7735_DrawRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
ST7735_SetWindow(x, y, x+w-1, y+h-1);
uint32_t pixels = w * h;
while(pixels--) {
ST7735_WriteData(color >> 8);
ST7735_WriteData(color & 0xFF);
}
}
// 绘制字符
void ST7735_DrawChar(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bg, uint8_t size) {
// 从字库中获取字符点阵数据
const uint8_t *charData = &font[c * FONT_WIDTH];
for(uint8_t i = 0; i < FONT_WIDTH; i++) {
uint8_t line = charData[i];
for(uint8_t j = 0; j < FONT_HEIGHT; j++) {
if(line & (1 << j)) {
ST7735_DrawPixel(x+i*size, y+j*size, color);
} else if(bg != color) {
ST7735_DrawPixel(x+i*size, y+j*size, bg);
}
}
}
}
6. 性能优化技巧
6.1 DMA传输优化
使用DMA可以显著提高图形刷新速度:
c复制void ST7735_FillScreen_DMA(uint16_t color) {
ST7735_SetWindow(0, 0, ST7735_WIDTH-1, ST7735_HEIGHT-1);
uint8_t colorData[2] = {color >> 8, color & 0xFF};
uint8_t buffer[ST7735_WIDTH * 2]; // 一行像素的缓冲区
// 填充缓冲区
for(int i = 0; i < sizeof(buffer); i += 2) {
buffer[i] = colorData[0];
buffer[i+1] = colorData[1];
}
// 使用DMA发送多行数据
HAL_GPIO_WritePin(GPIOA, DC_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, CS_Pin, GPIO_PIN_RESET);
for(int y = 0; y < ST7735_HEIGHT; y++) {
HAL_SPI_Transmit_DMA(&hspi1, buffer, sizeof(buffer));
while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);
}
HAL_GPIO_WritePin(GPIOA, CS_Pin, GPIO_PIN_SET);
}
6.2 双缓冲技术
对于动画或频繁更新的界面,可以考虑实现双缓冲:
- 在内存中创建一个与屏幕分辨率相同的缓冲区
- 所有绘图操作先在内存缓冲区中进行
- 完成一帧绘制后,一次性将整个缓冲区传输到屏幕
这种方法可以避免屏幕闪烁,但会消耗较多内存(对于160x128的屏幕,需要约40KB RAM)。
7. 常见问题与解决方案
7.1 屏幕无显示
排查步骤:
- 检查电源连接(3.3V和GND)
- 确认背光是否开启(尝试给BLK引脚高电平)
- 用示波器检查SPI信号是否正常
- 检查复位时序是否满足要求
7.2 显示颜色异常
可能原因:
- 颜色格式设置错误(确认是RGB565)
- 初始化命令参数不正确
- SPI时钟极性设置错误(尝试Mode 0和Mode 3)
7.3 屏幕闪烁或残影
解决方案:
- 降低SPI时钟频率
- 确保电源稳定(可在VCC和GND之间加100uF电容)
- 检查复位信号是否受到干扰
8. 实际应用案例
8.1 简易示波器显示
利用STM32的ADC采集信号,通过ST7735S实时显示波形:
c复制void DrawWaveform(uint16_t *adcValues, uint16_t count) {
// 清除上一帧
ST7735_DrawRect(0, 0, ST7735_WIDTH, ST7735_HEIGHT, ST7735_BLACK);
// 绘制坐标轴
ST7735_DrawLine(10, 10, 10, ST7735_HEIGHT-10, ST7735_WHITE);
ST7735_DrawLine(10, ST7735_HEIGHT/2, ST7735_WIDTH-10, ST7735_HEIGHT/2, ST7735_WHITE);
// 绘制波形
for(uint16_t i = 1; i < count; i++) {
uint16_t y1 = ST7735_HEIGHT - 10 - (adcValues[i-1] * (ST7735_HEIGHT-20) / 4095);
uint16_t y2 = ST7735_HEIGHT - 10 - (adcValues[i] * (ST7735_HEIGHT-20) / 4095);
ST7735_DrawLine(10+i-1, y1, 10+i, y2, ST7735_GREEN);
}
}
8.2 嵌入式UI界面
实现简单的按钮和菜单系统:
c复制typedef struct {
uint16_t x, y, width, height;
char *text;
void (*callback)(void);
} Button;
void DrawButton(Button *btn) {
// 绘制按钮背景
ST7735_DrawRect(btn->x, btn->y, btn->width, btn->height, ST7735_BLUE);
// 绘制按钮边框
ST7735_DrawRect(btn->x, btn->y, btn->width, 2, ST7735_WHITE);
ST7735_DrawRect(btn->x, btn->y+btn->height-2, btn->width, 2, ST7735_WHITE);
// 绘制按钮文字
uint16_t textX = btn->x + (btn->width - strlen(btn->text)*6)/2;
uint16_t textY = btn->y + (btn->height - 8)/2;
ST7735_DrawString(textX, textY, btn->text, ST7735_WHITE, ST7735_BLUE, 1);
}
9. 进阶开发建议
9.1 使用LVGL图形库
对于复杂的用户界面,可以考虑集成LVGL等开源图形库:
- 下载LVGL源码
- 实现LVGL所需的显示和输入驱动接口
- 配置LVGL的内存池和刷新率
- 使用LVGL提供的丰富控件构建UI
9.2 硬件加速优化
如果使用更高端的STM32系列(如STM32F4/F7/H7),可以利用硬件加速功能:
- 使用DMA2D加速图形填充和混合
- 利用Chrom-ART加速图形操作
- 使用硬件JPEG解码器显示图片
9.3 低功耗设计
对于电池供电设备:
- 动态调整屏幕刷新率
- 在空闲时降低背光亮度或关闭背光
- 利用ST7735S的睡眠模式
在完成这个项目的过程中,我发现ST7735S虽然是一款相对简单的显示驱动芯片,但只要充分利用其特性,配合STM32的各种外设功能,完全可以实现专业级的显示效果。特别是在理解了底层通信协议后,可以根据实际需求灵活调整驱动方式,平衡性能和资源消耗。