从零点亮一块1.8寸TFT屏:ST7735 + MCU的SPI实战全解析
你有没有过这样的经历?手里的STM32或ESP32开发板一切正常,传感器数据也读得出来,可一到驱动那块小小的1.8英寸TFT屏时,屏幕却死活不亮——要么白屏、要么花屏、甚至完全没反应。别急,这几乎是每个嵌入式工程师在初涉图形显示时都会踩的坑。
今天我们就来彻底拆解这个“拦路虎”:如何用MCU通过SPI接口正确驱动ST7735控制器的TFT显示屏。这不是一份照搬手册的参数说明书,而是一份来自真实项目调试经验的实战指南。我们将从硬件连接讲到软件初始化,再到常见问题排查,让你不仅能点亮屏幕,还能真正理解背后的每一个细节。
为什么是ST7735?小屏幕的大智慧
在物联网和便携设备盛行的当下,用户对“看得见”的交互需求越来越高。虽然OLED精致,但受限于尺寸与寿命;大尺寸LCD又太耗资源。而1.8英寸TFT彩屏恰好处于一个黄金平衡点:够小、够便宜、色彩表现还行。
其中,ST7735是这类屏幕中最常见的驱动IC之一。它不是简单的段码驱动器,而是一个集成了GRAM(图形内存)、时序控制器和电源管理的完整显示子系统。你可以把它看作是“微型显卡”,只等MCU发号施令。
它的核心优势很明确:
- 支持16位色(RGB565),能显示6万多种颜色;
- 分辨率128×160,足够展示图标、文字和简单曲线;
- 接口灵活,尤其适合使用4线SPI模式,仅需6个GPIO即可控制;
- 成本极低,在淘宝几块钱就能买到模块化成品。
更重要的是,社区支持丰富。无论是Arduino库还是STM32 HAL适配,都有大量开源代码可供参考。但正因如此,很多人直接复制粘贴初始化代码却不知其所以然,一旦换了个批次的屏幕就出问题。
所以我们必须搞清楚:它是怎么工作的?我们该怎么正确地“唤醒”它?
SPI通信的本质:不只是发数据那么简单
先抛开ST7735本身,我们来看看它依赖的通信协议——SPI。
很多初学者以为SPI就是“主控发数据,外设收数据”。但实际上,对于像ST7735这样的复杂外设,SPI传输的内容是有语义的:命令和数据要区分开。
关键引脚解析
| 引脚 | 功能 | 必须接吗? |
|---|---|---|
| SCLK | 时钟信号,由MCU产生 | ✅ |
| MOSI | 主发从收,传输命令/像素数据 | ✅ |
| CS | 片选,低电平选中设备 | ✅(可软控) |
| DC | 数据/命令选择:0=命令,1=数据 | ✅ |
| RST | 硬件复位,低电平有效 | ⚠️ 建议接 |
注意:这里多了一个DC引脚,这是ST7735这类显示驱动特有的设计。没有它,你就无法告诉芯片“我现在发的是‘打开显示’这条指令”还是“这是某个像素的颜色值”。
默认工作模式:SPI Mode 0
ST7735默认运行在SPI Mode 0:
-CPOL = 0:空闲时SCLK为低电平;
-CPHA = 0:在上升沿采样数据。
这意味着你在配置MCU的SPI外设时,一定要确保极性和相位匹配。否则即使接了线,也可能收到乱码。
另外,虽然理论上SPI可以高速传输(最高可达15~27MHz),但在实际布线中建议初次调试时先降到2~4MHz,排除信号完整性问题后再提速。
硬件连接:少一根线都不行
下面是一个推荐的标准连接方式(以STM32为例):
| ST7735引脚 | 连接到MCU | 备注 |
|---|---|---|
| VCC | 3.3V | 不要超过3.6V! |
| GND | GND | 共地必须可靠 |
| SCL/SCLK | PA5 (SPI1_SCK) | 主输出时钟 |
| SDA/MOSI | PA7 (SPI1_MOSI) | 数据输入 |
| CS | PA4 | 可任意IO模拟 |
| DC | PB0 | 通用IO即可 |
| RST | PB1 | 强烈建议外接 |
| LED/BLK | PC7 (PWM) | 背光控制,建议PWM调光 |
若使用ESP32或Arduino,只需将对应引脚映射修改即可,逻辑不变。
设计要点提醒
去耦电容不能省
在VCC与GND之间靠近芯片处加一个0.1μF陶瓷电容,防止上电瞬间电压波动导致初始化失败。背光要限流
LED引脚不是数字IO!必须串联一个10Ω~100Ω电阻再接到电源或PWM输出端,否则可能烧毁背光LED。RST要不要接?强烈建议接!
虽然有些模块内部有上电复位电路,但不同MCU启动速度不同,可能导致ST7735未完成自检就被操作。手动控制RST能保证每次复位行为一致。避免长导线和平行走线
SPI属于高速信号,尽量缩短连线长度,远离电源线和高频干扰源。
初始化流程:真正的“开机密码”
如果你已经按上述接好线,却发现屏幕依旧无反应——别怀疑人生,大概率是你没把“开机密码”输对。
ST7735不像GPIO那样上电即用,它需要一系列精确的寄存器配置才能进入正常工作状态。这些命令顺序不能乱、延时不能少、参数也不能错。
初始化流程四步走
硬件复位
- 拉低RST ≥ 10ms
- 延时50ms等待内部稳压建立
- 拉高RST退出睡眠模式(Sleep Out)
- 发送0x11
- 延时 ≥ 120ms配置关键参数
- 帧率控制(B1/B2/B3)
- 伽马校正(E0/E1)
- 接口格式(3A → RGB565)
- 显示方向(36)开启显示
- 发送0x29(Display On)
注意:不同厂商的ST7735模块(如红屏 vs 绿屏)初始化序列略有差异,务必确认你的模块类型!
核心代码实现:封装清晰,调用简单
以下是基于STM32 HAL库的基础驱动函数,结构清晰,便于移植:
// 控制引脚定义(根据实际硬件调整) #define TFT_CS_PORT GPIOA #define TFT_DC_PORT GPIOB #define TFT_RST_PORT GPIOB #define TFT_CS_PIN GPIO_PIN_4 #define TFT_DC_PIN GPIO_PIN_0 #define TFT_RST_PIN GPIO_PIN_1 // 宏简化操作 #define CS_LOW() HAL_GPIO_WritePin(TFT_CS_PORT, TFT_CS_PIN, GPIO_PIN_RESET) #define CS_HIGH() HAL_GPIO_WritePin(TFT_CS_PORT, TFT_CS_PIN, GPIO_PIN_SET) #define DC_CMD() HAL_GPIO_WritePin(TFT_DC_PORT, TFT_DC_PIN, GPIO_PIN_RESET) #define DC_DATA() HAL_GPIO_WritePin(TFT_DC_PORT, TFT_DC_PIN, GPIO_PIN_SET) #define RST_LOW() HAL_GPIO_WritePin(TFT_RST_PORT, TFT_RST_PIN, GPIO_PIN_RESET) #define RST_HIGH() HAL_GPIO_WritePin(TFT_RST_PORT, TFT_RST_PIN, GPIO_PIN_SET) // SPI发送单字节 void tft_write_byte(uint8_t byte) { HAL_SPI_Transmit(&hspi1, &byte, 1, 10); } // 发送命令 void tft_send_cmd(uint8_t cmd) { DC_CMD(); CS_LOW(); tft_write_byte(cmd); CS_HIGH(); } // 发送数据 void tft_send_data(uint8_t data) { DC_DATA(); CS_LOW(); tft_write_byte(data); CS_HIGH(); } // 批量发送数据(用于图像/缓冲区) void tft_send_buffer(uint8_t *buffer, uint16_t length) { DC_DATA(); CS_LOW(); HAL_SPI_Transmit(&hspi1, buffer, length, 100); CS_HIGH(); }有了这些基础函数,就可以编写初始化函数了:
void ST7735_Init(void) { // 硬件复位 RST_HIGH(); delay_ms(10); RST_LOW(); delay_ms(50); RST_HIGH(); delay_ms(150); // 退出睡眠 tft_send_cmd(0x11); delay_ms(120); // 开启反显(可选) tft_send_cmd(0x21); delay_ms(10); // 设置帧率(正常模式) tft_send_cmd(0xB1); tft_send_data(0x05); tft_send_data(0x3C); tft_send_data(0x3C); // 设置伽马曲线(关键!影响色彩表现) tft_send_cmd(0xE0); // 正向伽马 uint8_t pgamma[] = {0x10,0x0E,0x02,0x03,0x0E,0x07,0x02,0x07, 0x0A,0x12,0x27,0x37,0x00,0x0D,0x0E,0x10}; tft_send_buffer(pgamma, 16); tft_send_cmd(0xE1); // 负向伽马 uint8_t ngamma[] = {0x10,0x0E,0x03,0x03,0x0F,0x06,0x02,0x08, 0x0A,0x13,0x26,0x36,0x00,0x0D,0x0E,0x10}; tft_send_buffer(ngamma, 16); // 设置色彩格式为16位RGB565 tft_send_cmd(0x3A); tft_send_data(0x05); // 设置GRAM访问方向(例如旋转90度) tft_send_cmd(0x36); tft_send_data(0xC0); // MY=0, MX=1, MV=1 → 竖屏显示 // 开启显示 tft_send_cmd(0x29); delay_ms(100); }这段代码最关键的地方在于:
-延时准确:某些命令后必须等待足够时间让芯片响应;
-伽马校正不可跳过:直接影响色彩是否偏红/偏蓝;
-0x36命令决定显示方向:0xC0表示顺时针旋转90°,适配竖屏布局。
实战技巧:如何高效写入画面?
很多人初始化成功后兴奋不已,结果一试画图就卡成幻灯片。原因只有一个:逐像素写入效率太低。
ST7735的GRAM是连续地址空间,我们应该采用“区域刷新”策略。
高效刷新三步法
设置地址窗口
```c
void set_addr_window(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
tft_send_cmd(0x2A); // Column Address Set
tft_send_data(0x00); tft_send_data(x0 + 2); // XSHL, XSLL
tft_send_data(0x00); tft_send_data(x1 + 2);tft_send_cmd(0x2B); // Row Address Set
tft_send_data(0x00); tft_send_data(y0 + 1);
tft_send_data(0x00); tft_send_data(y1 + 1);tft_send_cmd(0x2C); // Memory Write
}
```批量写入颜色数据
c // 填充一片区域为红色(RGB565: 0xF800) uint8_t red[] = {0xF8, 0x00}; // 每个像素占2字节 set_addr_window(0, 0, 127, 159); for(int i = 0; i < 128*160; i++) { tft_send_buffer(red, 2); }使用DMA进一步提速(进阶)
若MCU支持DMA,可将SPI配置为DMA传输模式,解放CPU资源,实现流畅动画。
常见问题与调试秘籍
❌ 白屏?检查这几个点:
- RST是否真的拉低过?
- 是否发送了
0x11并等待 ≥120ms? - CS是否始终为低?(可能是片选拉错)
❌ 花屏/乱码?
- SPI模式是否为Mode 0(CPOL=0, CPHA=0)?
- 尝试降低SCLK频率至2MHz再测试;
- 检查MOSI与SCLK是否接反?
❌ 显示偏色(发红/发绿)?
- 伽马校正参数不对!不同批次屏幕需微调E0/E1数据;
- 检查是否设置了正确的RGB565格式(0x3A, 0x05)。
❌ 刷新慢如蜗牛?
- 避免逐像素写入;
- 使用
set_addr_window+ 批量发送; - 启用SPI DMA传输。
❌ 背光亮但无图像?
- DC引脚接错?导致所有数据被当作命令处理;
- 检查GRAM写入前是否发送了
0x2C(Memory Write)。
应用延伸:不止是“点亮”
一旦掌握了基本驱动方法,你可以轻松扩展更多功能:
- 绘制温度曲线:采集DS18B20数据,动态更新折线图;
- 菜单界面:结合按键实现多级UI导航;
- 加载图片:将BMP图像压缩后存入Flash,按需解码显示;
- 搭配触摸屏:接入XPT2046电阻屏,构建完整HMI系统;
- 集成LVGL:在RAM充足的MCU上跑轻量GUI框架,实现现代化交互体验。
写在最后:从“能用”到“好用”
驱动一块TFT屏,看似只是“点亮”,实则是对嵌入式系统能力的一次综合考验:
你要懂硬件设计、会调试通信协议、能分析时序、还要有耐心读数据手册。
但当你第一次看到自己写的代码在小屏幕上绘出一条平滑曲线时,那种成就感是无可替代的。
记住:
-初始化序列是钥匙,必须完整且有序;
-DC引脚是灵魂,区分命令与数据才能精准控制;
-批量传输是性能关键,别再一个像素一个像素地写了。
如果你正在做一个需要本地显示的小项目,ST7735 + SPI 绝对是一个性价比极高的选择。只要掌握方法,它就会成为你手中最趁手的工具之一。
如果你在实现过程中遇到了其他挑战,欢迎在评论区分享讨论。我们一起把这块小屏幕,玩出大花样。