汕头市网站建设_网站建设公司_在线商城_seo优化
2025/12/31 7:43:04 网站建设 项目流程

点亮第一块彩屏:STM32驱动ST7789的完整实战指南

你有没有过这样的经历?手头有一块1.3英寸的小彩屏,引脚标着SCL、SDA、RST、DC……却迟迟不敢上电——生怕接错线烧了屏幕,或者代码跑起来后只看到一片白屏、花屏,毫无头绪。

别担心。今天我们就来彻底拆解 STM32 驱动 ST7789 彩色屏的全过程,从最基础的硬件连接到软件初始化、再到画出第一个像素点,全程“零依赖”、“无黑盒”,让你真正掌握底层逻辑,不再靠复制粘贴别人代码蒙混过关。

这不仅是一次“点亮屏幕”的实践,更是一场深入嵌入式显示系统的硬核训练营。


为什么是 ST7789 + STM32?

在众多TFT驱动IC中,ST7789凭借其高集成度和对小尺寸圆角屏的良好支持,近年来迅速走红。它常见于1.3~2.0英寸的IPS彩屏模组,分辨率多为240×240或240×320,采用标准SPI接口通信,非常适合资源有限但追求视觉效果的嵌入式项目。

STM32—— 尤其是F1系列中的“蓝 pill”开发板(STM32F103C8T6)—— 因其价格低廉、生态成熟、资料丰富,成为无数初学者入门嵌入式的首选平台。

两者结合,既能满足低成本需求,又能实现不错的图形表现力。更重要的是:

你可以不用RTOS、不用LVGL、甚至不带帧缓冲,也能让这块彩屏正常工作。

这种“裸机直驱”的方式,虽然看起来原始,却是理解整个显示机制的关键一步。


核心特性速览:ST7789 到底强在哪?

参数指标
接口类型支持 SPI / I2C / 8080并行 / RGB模式
最大分辨率240×320
常见模组分辨率240×240(圆形/圆角裁剪)
色彩格式RGB565(16位真彩色,约65K色)
工作电压3.3V(IO兼容5V需注意)
内部升压集成VCOM和Gamma调节电路
特色功能支持MADCTL控制显示方向与镜像

相比老牌选手如 ILI9341,ST7789 的一大优势在于原生支持异形屏布局。比如很多1.3寸“类Apple Watch”风格的圆角屏,就是通过配置MADCTL寄存器实现可视区域裁剪和坐标旋转完成的。

这意味着你不需要额外处理边界裁剪问题,只需要告诉它:“我要用左上角(0,0)到右下角(239,239)这个矩形区域”,它就能自动适配。


它是怎么工作的?一文讲清ST7789的工作流程

想象一下,你要给一块没有大脑的屏幕下达指令:

  • “先清空记忆”
  • “准备好接收数据”
  • “接下来我要写颜色了”
  • “把这部分区域刷成红色”

这些操作,全靠一个核心机制:命令-数据分离

关键信号线解析

引脚作用控制方
SCKSPI时钟线STM32输出
MOSI(SDA)数据输入STM32输出
CS片选使能(低有效)STM32控制
DCData/Command选择STM32控制
RST复位信号(低有效)STM32控制
BLK/VCC背光与电源外部供电

其中最关键的,就是DC 引脚。它的状态决定了当前传输的是“命令”还是“数据”:

  • DC = 0 → 当前发送的是命令(例如0x2A表示设置列地址)
  • DC = 1 → 当前发送的是参数或显存内容

这就像是你在跟芯片对话:

“喂,听好了!我现在要说的是‘命令’!”
“请执行:设置列地址范围。”
“好,现在我说的是‘数据’!”
“起始列=0,结束列=239。”

整个过程由SPI串行发送,配合CS片选拉低激活设备。


硬件怎么接?一张图说清楚

典型的四线SPI接法如下(以STM32F103为例):

ST7789模块 ↔ STM32 ------------------------------- VCC → 3.3V GND → GND SCL(SCK) → PA5 (SPI1_SCK) SDA(MOSI) → PA7 (SPI1_MOSI) CS → PA4 (普通GPIO) DC → PA6 (普通GPIO) RST → PB0 (普通GPIO) BLK → 3.3V 或 PWM调光

⚠️ 注意事项:
- 所有IO必须为3.3V电平,若使用5V单片机需加电平转换
- RST可接MCU也可直接拉高,但建议由MCU控制以便软复位
- BLK背光可接固定电源或PWM引脚用于亮度调节

建议使用杜邦线或排针可靠连接,并确保电源稳定。劣质USB线导致供电不足是“白屏”的常见元凶。


软件驱动怎么做?从SPI到底层函数封装

我们使用STM32 HAL库进行开发,假设已通过CubeMX配置好SPI1为主机模式,时钟设为18MHz(F1最高APB2频率),CPOL=1, CPHA=1(即SPI Mode 3)。

第一步:定义关键引脚宏

#define ST7789_CS_LOW() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET) #define ST7789_CS_HIGH() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET) #define ST7789_DC_CMD() HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, GPIO_PIN_RESET) #define ST7789_DC_DATA() HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, GPIO_PIN_SET) #define ST7789_RST_LOW() HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_RESET) #define ST7789_RST_HIGH() HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_SET)

这些宏将GPIO操作抽象出来,便于移植到不同引脚或MCU平台。

第二步:实现SPI字节发送

static void st7789_spi_write(uint8_t data) { HAL_SPI_Transmit(&hspi1, &data, 1, 10); }

简单直接。注意超时时间不要设太短,避免阻塞异常。

第三步:封装命令与数据写入

void st7789_write_command(uint8_t cmd) { ST7789_CS_LOW(); ST7789_DC_CMD(); st7789_spi_write(cmd); ST7789_CS_HIGH(); } void st7789_write_data(uint8_t *data, size_t len) { ST7789_CS_LOW(); ST7789_DC_DATA(); HAL_SPI_Transmit(&hspi1, data, len, 100); ST7789_CS_HIGH(); }

这两个函数是你和ST7789“对话”的基本语法单元。所有的高级功能都建立在此之上。


初始化序列:让屏幕“醒过来”的关键步骤

这是最容易出错的部分。很多开发者照搬网上的初始化代码却发现屏幕没反应,原因往往是模组差异时序不当

以下是适用于大多数240×240 ST7789模组的精简初始化流程:

void st7789_init(void) { // 硬件复位 ST7789_RST_LOW(); HAL_Delay(10); ST7789_RST_HIGH(); HAL_Delay(120); // 必须等待足够长时间! // 退出睡眠模式 st7789_write_command(0x11); HAL_Delay(120); // 设置存储访问方向(MADCTL) st7789_write_command(0x36); uint8_t madctl = 0x00; // 正常方向:从左到右,从上到下 st7789_write_data(&madctl, 1); // 设置颜色格式为16位RGB565 st7789_write_command(0x3A); uint8_t colmod = 0x05; st7789_write_data(&colmod, 1); // 配置Porch参数(影响刷新稳定性) st7789_write_command(0xB2); uint8_t porch[] = {0x0C, 0x0C, 0x00, 0x33, 0x33}; st7789_write_data(porch, 5); // 其他电源与伽马校正设置... st7789_write_command(0xB7); st7789_write_data(&(uint8_t){0x35}, 1); st7789_write_command(0xBB); st7789_write_data(&(uint8_t){0x19}, 1); st7789_write_command(0xC0); st7789_write_data(&(uint8_t){0x2C}, 1); st7789_write_command(0xC2); st7789_write_data(&(uint8_t){0x01}, 1); st7789_write_command(0xC3); st7789_write_data(&(uint8_t){0x12}, 1); st7789_write_command(0xC4); st7789_write_data(&(uint8_t){0x20}, 1); st7789_write_command(0xC6); st7789_write_data(&(uint8_t){0x0F}, 1); st7789_write_command(0xD0); uint8_t pw2[] = {0xA4, 0xA1}; st7789_write_data(pw2, 2); // 正负伽马校正(可根据实际显示效果微调) st7789_write_command(0xE0); uint8_t gammaP[] = {0xD0,0x04,0x0D,0x11,0x13,0x2B,0x3F,0x54,0x4C,0x18,0x0D,0x0B,0x1F,0x23}; st7789_write_data(gammaP, 14); st7789_write_command(0xE1); uint8_t gammaN[] = {0xD0,0x04,0x0C,0x11,0x13,0x2C,0x3F,0x44,0x51,0x2F,0x1F,0x1F,0x20,0x23}; st7789_write_data(gammaN, 14); // 开启显示反转(部分屏需要) st7789_write_command(0x21); // 或0x20关闭 // 进入正常显示模式 st7789_write_command(0x13); HAL_Delay(10); // 最终开启显示 st7789_write_command(0x29); HAL_Delay(100); }

📌重点提醒
-HAL_Delay(120)不可省略!这是为了让内部电路充分启动
- 不同厂商的模组可能有不同的初始化顺序,务必参考具体模块的手册
- 若显示偏色严重,优先检查伽马值是否匹配你的屏幕批次


如何画图?从设置GRAM窗口说起

ST7789内部有一个叫 GRAM(Graphic RAM)的显存区域,你写入的数据最终会映射到屏幕上。

但你不能直接往GRAM乱写,必须先告诉它:“我要更新哪一块区域”。

这就是Set Address Window的作用。

void st7789_set_address_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { st7789_write_command(0x2A); // Column Address Set uint8_t col_addr[4] = { (x0 >> 8) & 0xFF, x0 & 0xFF, (x1 >> 8) & 0xFF, x1 & 0xFF }; st7789_write_data(col_addr, 4); st7789_write_command(0x2B); // Row Address Set uint8_t row_addr[4] = { (y0 >> 8) & 0xFF, y0 & 0xFF, (y1 >> 8) & 0xFF, y1 & 0xFF }; st7789_write_data(row_addr, 4); st7789_write_command(0x2C); // Memory Write Start }

调用此函数后,后续所有数据都将被当作该矩形区域的颜色数据依次填充。


实战:填充一个红色矩形

void st7789_fill_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; st7789_set_address_window(x, y, x_end, y_end); uint32_t total_pixels = w * h; uint8_t color_bytes[2] = { (color >> 8), color & 0xFF }; ST7789_CS_LOW(); ST7789_DC_DATA(); for (uint32_t i = 0; i < total_pixels; i++) { HAL_SPI_Transmit(&hspi1, color_bytes, 2, 10); } ST7789_CS_HIGH(); }

调用示例:

// 清屏为黑色 st7789_fill_rect(0, 0, 240, 240, 0x0000); // 在中间画一个红色矩形 st7789_fill_rect(100, 100, 40, 40, 0xF800); // 红色 (RGB565: R=11111...)

虽然这种方式效率不高(逐个发送2字节),但对于小区域更新完全够用。进阶方案可用DMA批量传输提升速度。


常见坑点与调试秘籍

❌ 屏幕全白或全黑?

  • ✅ 检查RST是否有效触发
  • ✅ 确保初始化前有足够延时(至少120ms)
  • ✅ 查看是否遗漏0x11(Sleep Out)或0x29(Display ON)

❌ 显示花屏、错位、倒置?

  • ✅ 检查SPI模式是否为Mode 3 (CPOL=1, CPHA=1)
  • ✅ 查看MADCTL设置是否正确(0x36命令后的值)
  • ✅ 确认GRAM地址范围未越界(如写了240却只有240列)

❌ 刷新极慢?

  • ✅ 使用连续SPI写入而非循环调用单字节
  • ✅ 启用DMA传输(可提速5倍以上)
  • ✅ 避免频繁调用HAL_Delay

❌ 花屏且伴随程序卡死?

  • ✅ 检查SPI是否被其他外设占用
  • ✅ 添加CS片选保护,防止干扰
  • ✅ 使用逻辑分析仪抓包查看实际波形

🔧推荐工具:Saleae Logic Analyzer + PulseView,可以直观看到SPI时钟、数据、DC变化是否符合预期。


性能优化思路(进阶)

如果你希望实现动画、滚动文本或UI交互,以下几点值得考虑:

优化方向实现方式
提高SPI速率F4/F7系列可达45~60MHz
使用DMA减少CPU参与,释放资源做其他事
双缓冲机制前后台交替刷新,避免撕裂
区域刷新只更新变动部分,降低带宽消耗
字模压缩使用RLE编码减少字体存储空间

未来接入LVGL等GUI框架时,这些优化将成为流畅体验的基础。


写在最后:这不仅仅是在点亮一块屏

当你亲手写下第一行st7789_write_command(0x11);并成功看到屏幕亮起时,那种成就感远超调用一句lv_label_set_text()

因为你知道:
- 每一次颜色变化背后,都有SPI信号在跳动
- 每一个坐标的计算,都源于对MADCTL寄存器的理解
- 每一次刷新,都是你与硬件之间的默契对话

这项技能的价值,远不止于驱动一块小彩屏。它是通往嵌入式图形世界的钥匙——
无论是做一块智能手表原型,还是设计工业仪表界面,亦或是打造属于自己的DIY掌机,你都已经迈出了最关键的一步。

所以,别再犹豫了。拿起你的STM32和那块积灰的ST7789,现在就开始吧。

如果你在实现过程中遇到了挑战,欢迎在评论区留言交流。我们一起把每一行代码,都变成看得见的成长痕迹。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询