手把手教你用STM32CubeMX点亮ST7789屏幕:从配置到显示全解析
你有没有遇到过这种情况?买了一块漂亮的1.3寸TFT彩屏,接上STM32后却死活不亮——黑屏、花屏、白屏轮番上演。别急,问题大概率不在硬件,而是初始化时序和接口配置没踩对点。
今天我们就来彻底解决这个“嵌入式入门拦路虎”:如何使用STM32CubeMX + HAL库快速、稳定地驱动ST7789显示屏。整个过程无需手写一行底层寄存器代码,也能做到一次点亮、长期稳定运行。
为什么是ST7789?
在琳琅满目的TFT驱动IC中(ILI9341、SSD1351、GC9A01…),ST7789近年来迅速走红,尤其是在国产小尺寸彩屏市场几乎成了标配。它到底强在哪?
核心优势一句话总结:
高分辨率 + 低成本 + 接口灵活 + 启动快
我们来看几个关键参数:
| 特性 | 参数值 |
|---|---|
| 最大分辨率 | 240×320(也有135×240等裁剪版) |
| 色深 | 16位 RGB565,65K色真彩 |
| 支持接口 | 四线SPI / 8080并行总线 |
| 内置GRAM | 是,无需外部显存 |
| 显示方向控制 | 通过MADCTL寄存器自由旋转 |
相比老将 ILI9341,ST7789 更适合现代小型化设备,比如智能手表、迷你仪表盘、WiFi配网界面等场景。而且它的初始化流程更短,唤醒响应更快,非常适合低功耗应用。
硬件怎么连?先搞清这五根线
ST7789模块通常引出以下关键引脚:
| 屏端引脚 | 功能说明 | 推荐连接MCU |
|---|---|---|
| VCC | 电源(3.3V) | STM32 3.3V输出或LDO |
| GND | 地 | 共地 |
| SCL / SCK | SPI时钟 | PA5 或任意SPI_SCK |
| SDA / SI / MOSI | 数据输入 | PA7 或任意SPI_MOSI |
| CS | 片选(低有效) | 任意GPIO(如PB6) |
| DC / RS | 命令/数据选择 | 任意GPIO(如PB7) |
| RST | 复位信号(低有效) | 任意GPIO(如PB8) |
| BLK / LED | 背光控制 | 可接PWM调光或直接拉高 |
📌重点提醒:
-DC和CS极容易接反!记住:DC决定传的是命令还是像素数据,而CS只是片选使能。
- 如果发现屏幕完全无反应,请优先检查RST是否正常拉低再释放,并确保电源干净稳定。
STM32CubeMX 图形化配置实战
接下来进入正题。我们将以最常见的SPI + GPIO控制模式为例,带你一步步完成配置。
第一步:创建工程 & 选择芯片
打开 STM32CubeMX,新建项目,选择你的MCU型号(例如STM32F103C8T6、F407ZGT6等均可)。
第二步:启用SPI外设
找到 Pinout 视图,启用一个SPI主设备(推荐SPI1或SPI2)。设置如下:
- Mode: Full Duplex Master
- Frame Format: Motorola
- Data Size: 8 bits
- Clock Polarity (CPOL): Low
- Clock Phase (CPHA): 1 Edge → 实际对应SPI MODE 0,0
- NSS: Software(禁用硬件NSS)
- Baud Rate Prescaler: fpclk / 16(初始调试建议不要太快)
✅ 为什么选 MODE 0,0?
大多数国产ST7789模组出厂默认使用 CPOL=0, CPHA=0,即空闲时SCK为低,在第一个上升沿采样。虽然手册说支持多种模式,但实际要以模块厂提供的例程为准。
第三步:配置控制引脚(CS/DC/RST)
这些不是SPI标准引脚,需要用普通GPIO模拟:
| 引脚名 | GPIO端口 | 模式 | 用户标签(User Label) |
|---|---|---|---|
| CS | PB6 | Output Push Pull | LCD_CS |
| DC | PB7 | Output Push Pull | LCD_DC |
| RST | PB8 | Output Push Pull | LCD_RST |
在GPIO配置中给它们加上清晰的标签,生成代码后会自动定义对应的宏。
第四步:开启时钟 & 生成代码
进入 Clock Configuration 页面,确认SPI时钟来源合理(如APB2=72MHz,则SPI速率约为4.5MHz)。
最后点击Project Manager,设置工程名称、路径、工具链(Keil/IAR/CubeIDE),然后生成代码。
添加驱动代码:让屏幕真正“活起来”
CubeMX只帮你搭好架子,真正的灵魂还得靠我们自己加进去。
1. 新建lcd_st7789.h
#ifndef __LCD_ST7789_H #define __LCD_ST7789_H #include "main.h" #include "spi.h" // 分辨率定义(根据你的屏幕调整) #define LCD_WIDTH 240 #define LCD_HEIGHT 320 // 控制引脚宏定义(自动生成的变量名可能不同,请核对) #define LCD_CS_LOW() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET) #define LCD_CS_HIGH() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET) #define LCD_DC_CMD() HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_RESET) #define LCD_DC_DATA() HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET) #define LCD_RST_LOW() HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET) #define LCD_RST_HIGH() HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_SET) // 函数声明 void LCD_Init(void); void LCD_Write_Cmd(uint8_t cmd); void LCD_Write_Data(uint8_t data); void LCD_Write_Buffer(uint8_t *buf, uint16_t len); void LCD_Set_Address(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2); void LCD_Fill_Color(uint16_t color); #endif2. 实现lcd_st7789.c
#include "lcd_st7789.h" #include <string.h> // 发送单个命令 void LCD_Write_Cmd(uint8_t cmd) { LCD_CS_LOW(); LCD_DC_CMD(); HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY); LCD_CS_HIGH(); } // 发送单个数据字节 void LCD_Write_Data(uint8_t data) { LCD_CS_LOW(); LCD_DC_DATA(); HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY); LCD_CS_HIGH(); } // 批量发送数据(用于填充像素) void LCD_Write_Buffer(uint8_t *buf, uint16_t len) { LCD_CS_LOW(); LCD_DC_DATA(); HAL_SPI_Transmit(&hspi1, buf, len, HAL_MAX_DELAY); LCD_CS_HIGH(); } // 初始化函数 —— 关键中的关键! void LCD_Init(void) { // 硬件复位 LCD_RST_LOW(); HAL_Delay(10); LCD_RST_HIGH(); HAL_Delay(120); // 退出睡眠模式 LCD_Write_Cmd(0x11); HAL_Delay(120); // 设置颜色格式为RGB565 LCD_Write_Cmd(0x3A); LCD_Write_Data(0x05); // 16-bit/pixel // Porch Control(厂商推荐值) LCD_Write_Cmd(0xB2); LCD_Write_Data(0x0C); LCD_Write_Data(0x0C); LCD_Write_Data(0x00); LCD_Write_Data(0x33); LCD_Write_Data(0x33); // Gate Control LCD_Write_Cmd(0xB7); LCD_Write_Data(0x35); // VCOM Setting LCD_Write_Cmd(0xBB); LCD_Write_Data(0x19); // LCM Control LCD_Write_Cmd(0xC0); LCD_Write_Data(0x2C); // Enable porch LCD_Write_Cmd(0xC2); LCD_Write_Data(0x01); // Vref trim LCD_Write_Cmd(0xC3); LCD_Write_Data(0x12); // VDV and VRH enable LCD_Write_Cmd(0xC4); LCD_Write_Data(0x20); // 帧率设置(正常模式下60Hz左右) LCD_Write_Cmd(0xC6); LCD_Write_Data(0x0F); // 电源控制1 LCD_Write_Cmd(0xD0); LCD_Write_Data(0xA4); LCD_Write_Data(0xA1); // 正向Gamma校正 LCD_Write_Cmd(0xE0); uint8_t posGamma[] = { 0xD0,0x04,0x0D,0x11,0x13,0x2B,0x3F,0x54, 0x4C,0x18,0x0D,0x0B,0x1F,0x23 }; LCD_Write_Buffer(posGamma, sizeof(posGamma)); // 负向Gamma校正 LCD_Write_Cmd(0xE1); uint8_t negGamma[] = { 0xD0,0x04,0x0C,0x11,0x13,0x2C,0x3F,0x44, 0x51,0x2F,0x1F,0x1F,0x20,0x23 }; LCD_Write_Buffer(negGamma, sizeof(negGamma)); // 开启显示反转(可选) LCD_Write_Cmd(0x21); // 进入正常显示模式 LCD_Write_Cmd(0x13); // 最终开启显示 LCD_Write_Cmd(0x29); HAL_Delay(10); }💡注意细节:
- Gamma表是厂家调好的色彩优化参数,不要随意修改;
-0x21表示开启显示反转,如果你的屏上下颠倒,可以尝试去掉或替换为0x20;
- 初始化完成后必须发0x29才能真正点亮屏幕。
让屏幕动起来:画个彩色方块试试
添加一个简单的测试函数:
// 在 lcd_st7789.c 中添加 void LCD_Draw_Filled_Rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) { uint16_t x_end = x + w - 1; uint16_t y_end = y + h - 1; LCD_Set_Address(x, y, x_end, y_end); uint8_t color_buf[2]; color_buf[0] = color >> 8; color_buf[1] = color; uint8_t pixel[2] = {color_buf[0], color_buf[1]}; uint32_t total_pixels = w * h; LCD_CS_LOW(); LCD_DC_DATA(); for (uint32_t i = 0; i < total_pixels; i++) { HAL_SPI_Transmit(&hspi1, pixel, 2, HAL_MAX_DELAY); } LCD_CS_HIGH(); } // 设置显示区域 void LCD_Set_Address(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { LCD_Write_Cmd(0x2A); // Column Address Set LCD_Write_Data(x1 >> 8); LCD_Write_Data(x1 & 0xFF); LCD_Write_Data(x2 >> 8); LCD_Write_Data(x2 & 0xFF); LCD_Write_Cmd(0x2B); // Row Address Set LCD_Write_Data(y1 >> 8); LCD_Write_Data(y1 & 0xFF); LCD_Write_Data(y2 >> 8); LCD_Write_Data(y2 & 0xFF); LCD_Write_Cmd(0x2C); // Memory Write }然后在main.c的while循环前加入测试代码:
LCD_Init(); LCD_Draw_Filled_Rect(50, 50, 100, 100, 0xF800); // 红色矩形(RGB565: 11111 000000 00000)编译下载,如果一切顺利,你应该能看到屏幕上出现一块醒目的红色区域!
常见坑点与调试秘籍
即使按步骤操作,也难免遇到问题。以下是我在多个项目中总结的“排雷清单”:
🔴 黑屏无反应?
- ✅ 检查供电是否达到3.3V且无压降;
- ✅ RST是否真的完成了“拉低→延时→拉高”全过程;
- ✅ 使用示波器抓SCK和MOSI,看是否有数据发出。
🟡 花屏、乱码?
- ✅ 确认SPI模式是否为 MODE 0,0(CPOL=0, CPHA=0);
- ✅ 尝试降低SPI速率(把Prescaler从÷4改成÷16);
- ✅ 检查DC引脚电平是否正确切换。
🟢 白屏但无内容?
- ✅ 初始化命令未成功发送,重点排查CS和DC控制逻辑;
- ✅ 可临时将所有写操作强制设为DATA模式,观察是否变为全彩噪点(验证通信通路)。
🔄 显示倒置或镜像?
加一句即可修复:
LCD_Write_Cmd(0x36); LCD_Write_Data(0x70); // 旋转90度(其他值见下表)| 数据值 | 显示方向 |
|---|---|
| 0x00 | 0° |
| 0x60 | 90° |
| 0xC0 | 180° |
| 0xA0 | 270° |
性能优化建议(进阶必看)
当你已经能稳定显示内容后,下一步就是提升体验:
✅ 启用DMA传输
对于大量像素数据(如图片刷屏),使用DMA可极大减轻CPU负担:
HAL_SPI_Transmit_DMA(&hspi1, buffer, size);记得开启DMA中断并在回调中释放CS。
✅ 局部刷新替代全屏重绘
只更新变化区域,显著提高帧率和降低功耗。
✅ 结合FreeRTOS做异步渲染
把UI绘制放在独立任务中,避免阻塞主逻辑。
✅ 预留多分辨率支持
通过宏定义切换不同屏幕尺寸:
#ifdef ST7789_135_240 #define LCD_WIDTH 135 #define LCD_HEIGHT 240 #elif defined(ST7789_240_320) #define LCD_WIDTH 240 #define LCD_HEIGHT 320 #endif写在最后:不只是点亮,更是起点
当那个小小的彩屏第一次亮起,你会发现——这不仅仅是一次成功的硬件对接,更是通往图形化人机交互世界的大门被推开。
掌握了 ST7789 的驱动方法,你就拥有了构建完整GUI系统的基石。下一步,你可以轻松集成:
-LVGL:轻量级开源GUI框架,支持按钮、滑条、动画;
-TouchGFX:ST官方高级图形引擎,媲美手机UI;
- 自定义菜单系统、实时曲线绘制、二维码生成……
而这一切的起点,就是你现在看到的这几行初始化代码。
所以,别再让屏幕躺在角落吃灰了。打开CubeMX,接好线,跑一遍代码——下一秒,属于你的彩色界面就要诞生了。
如果你在实现过程中遇到了其他挑战,欢迎在评论区分享讨论。我们一起把每一块屏幕都点亮。