☰
STM32驱动1.44寸TFT屏:ST7735接线与SPI时序避坑指南
2026/10/17 4:28:24 网站建设 项目流程

简介:基于STM32F103RCT6驱动1.44英寸TFT彩色屏幕的完整嵌入式工程,适用于正在学习STM32 SPI通信、LCD驱动或需要快速搭建小型显示界面的开发者。压缩包共86个文件,以C源文件和H头文件为主,包含TFT_demo.c、Lcd_Driver.c、GUI.c等核心驱动代码,以及Keil工程文件、烧录hex文件和调试配置,整体仅289KB,结构精简便于阅读。目前已有4051人学习下载,方案对同类需求具有较高参考价值。通过这份工程,读者可以直接打开Keil工程编译烧录到STM32F103RCT6开发板,观察屏幕显示效果,同时结合源码学习屏幕初始化序列、SPI数据传输、汉字与图片显示以及LED控制等关键知识点,适合作为从入门到进阶的实践模板。

1. 先别急着接线:STM32 1.44TFT 适配边界比你想象的窄

同样是 1.44 寸 SPI 屏,接口定义和驱动 IC 版本五花八门,STM32F103RCT6 配 1.44TFT 屏幕显示的项目,翻车点往往不在代码,而在接线和初始化序列。很多开发者从网上下了一段通用例程,改几个引脚就烧录,结果白屏、花屏、颜色错乱轮着来,最后发现是 PS 引脚电平、驱动 IC 版本、SPI 采样沿三者不匹配。这份资源适合做课设、小仪表、温湿度显示器的人,核心就三件事:把 ST7735 控制器的时序吃透、把引脚和电平配好、把字库和刷新策略从“能亮”做到“能看”。

2. 硬件接线与驱动原理:先分清 ST7735 的引脚职责和电平边界

2.1 引脚对照:SPI 四线、PS 电平、背光分压一个都不能错

拿到 1.44TFT 屏幕,排针上常见丝印是 VCC、GND、SCL、SDA、RES、A0(或 DC)、CS、BLK(或 LEDA)这 8 个引脚。我一般把 A0 当作寄存器/数据选择脚,高电平写命令,低电平写数据,这个脚接错或者悬空,初始化序列会全部错位。以下是常见接法,基于 STM32F103RCT6 的 SPI1 外设。

屏幕引脚功能STM32F103RCT6 引脚说明
VCC逻辑电源 3.3V3.3V不要接 5V,ST7735 逻辑耐压有限
GND地GND与 MCU 共地
SCLSPI 时钟PA5(SPI1_SCK)空闲电平为低,模式 0
SDASPI 数据PA7(SPI1_MOSI)屏幕侧多数是 MOSI 单线
RES复位PB0低电平复位,复位后拉高
A0/DC命令/数据选择PB1低电平命令,高电平数据
CS片选PA4(软件控制)拉低选中,空闲拉高
BLK/LEDA背光PA2 或直接 3.3V直接接 3.3V 也行,加三极管可控

关键点是 BLK 脚。很多屏的背光电路已经焊好限流电阻,直接接 3.3V 能亮,但亮度不可调;有些屏的 LEDA 脚是 LED 阳极,串联电阻值偏小,长时间高亮会发热。我一般会用一个 100 欧电阻串在背光脚和 3.3V 之间,或者在 PA2 上做一个 PWM 背光控制,频率设 1kHz 以上,避免人眼可见闪烁。

还有一个容易被忽略的引脚是 PS,部分 1.44 寸屏模块会引出 PS 脚用于切换 SPI 和并行接口模式。如果模块上有 PS 引脚,必须把它接低电平或接 GND,选择 SPI 模式;一旦悬空或拉高,屏幕会进入并行接口状态,SPI 命令全部无效。这是白屏问题里最玄学的一种。

2.2 驱动时序与初始化序列:SPI 模式 0、采样沿和命令顺序

ST7735 控制器本质上是一个带 RAM 的液晶驱动芯片,MCU 通过 SPI 往它的显存里写颜色数据。SPI 通信参数必须匹配:时钟极性 CPOL=0,时钟相位 CPHA=0,即空闲时钟为低电平,在第一个边沿采样数据。很多例程用硬件 SPI 却忘了配置 CPOL 和 CPHA,或者用了模式 3,导致数据错位半拍,表现出来就是花屏而不是完全白屏。

SPI 速率也不能盲目拉高。1.44TFT 这类小屏的 SPI 时钟上限通常在 15MHz 左右,但我习惯保守地设在 8MHz 或 4MHz。速率太高时,加上杜邦线过长带来的寄生电容,信号沿会变缓,屏幕出现随机噪点。如果你用飞线连接,长度超过 10cm,建议降到 2MHz 起步,点亮后再逐步加速。

初始化序列是另一个黑匣子。ST7735 有多个子版本,比如 ST7735R 和 ST7735S,它们的 Gamma 校正值和偏压设置不完全一致。通用初始化序列一般包含以下命令段:

阶段命令作用常见参数
软件复位0x01重启控制器无参数,延时 120ms
退出睡眠0x11进入正常模式无参数,延时 120ms
帧率设置0xB1设置行周期典型值 0x01, 0x2C, 0x2D
电源控制0xB4设置电压能力0x07
显存访问控制0x36设置扫描方向和 RGB/BGR0xC0 或 0x08
像素格式0x3A设置 RGB 位数0x05 表示 16bit
Gamma 校正0xE0、0xE1正负极性 Gamma一组固定参数
退出睡眠0x29开启显示无参数

这里最容易踩坑的是 0x36 命令。它的低三位控制 RGB/BGR 顺序,如果你的屏实际是 BGR 排列,却按 RGB 初始化,颜色会偏色到没法看,比如红色变成蓝色。网上下载的驱动里,0x36 参数从 0x00 到 0xC8 都有,我建议先按 0xC0 试,看到颜色反转再改 0x08,这是最快定位偏色问题的做法。

3. 代码移植与手工驱动:从零点亮 1.44TFT 的可抄作业

3.1 底层 SPI 初始化:寄存器操作还是标准库,关键在于采样沿

STM32F103RCT6 有 SPI1、SPI2、SPI3 三个外设,驱动 1.44TFT 用硬件 SPI 通信即可。先给出基于标准外设库的 SPI1 初始化代码,这段配置了 PA5、PA6、PA7 的复用功能,并设置了 CPOL 和 CPHA。

void SPI1_Init(void) { GPIO_InitTypeDef gpio; SPI_InitTypeDef spi; // 打开 GPIOA 和 SPI1 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // PA5 SCK, PA7 MOSI, PA6 MISO(MISO 本设计未用,但可配置) gpio.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; gpio.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出 gpio.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &gpio); // 软件控制 CS 与 A0,配置为通用推挽输出 gpio.GPIO_Pin = GPIO_Pin_4; // CS gpio.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &gpio); // SPI1 主机模式,模式 0 spi.SPI_Direction = SPI_Direction_2Lines_FullDuplex; spi.SPI_Mode = SPI_Mode_Master; spi.SPI_DataSize = SPI_DataSize_8b; spi.SPI_CPOL = SPI_CPOL_Low; // 空闲时钟低电平 spi.SPI_CPHA = SPI_CPHA_1Edge; // 第一个边沿采样 spi.SPI_NSS = SPI_NSS_Soft; spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8; // 72MHz / 8 = 9MHz spi.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Init(SPI1, &spi); SPI_Cmd(SPI1, ENABLE); }

这段代码里,SPI_BaudRatePrescaler_8 在 72MHz 系统时钟下得到 9MHz 的 SCK,对 ST7735 和短飞线来说是安全的。如果屏幕出现随机噪点,优先把这个分频系数改为 16,也就是降到 4.5MHz,再确认是否好转。SPI_CPOL_Low 和 SPI_CPHA_1Edge 是模式 0 的核心,很多移植失败就是这两个参数没配对。

实际发送时,CS 要拉低,A0 要根据命令/数据切换,然后调用 SPI_I2S_SendData。注意 SPI 发送要等待 TXE 标志,库函数本身会做,但如果用 DMA 方式发送,必须等上一次传输结束再改 A0,否则旧的发送缓冲还残留数据,屏幕会读到错位内容。

3.2 初始化序列与显存访问控制:把 ST7735 从睡眠唤到可写入状态

上电后第一步是复位。RES 引脚拉低至少 10ms,再拉高,等待 120ms 让内部电路稳定。之后按顺序发送命令。以下是核心命令发送函数和初始化序列的开头部分。

void LCD_WriteCmd(uint8_t cmd) { LCD_CS_LOW(); LCD_A0_LOW(); // A0 低电平表示命令 SPI1_SendByte(cmd); LCD_CS_HIGH(); } void LCD_WriteData(uint8_t data) { LCD_CS_LOW(); LCD_A0_HIGH(); // A0 高电平表示数据 SPI1_SendByte(data); LCD_CS_HIGH(); } void LCD_Init(void) { // 复位时序 LCD_RES_LOW(); delay_ms(50); LCD_RES_HIGH(); delay_ms(120); // 软件复位与退出睡眠 LCD_WriteCmd(0x01); delay_ms(120); LCD_WriteCmd(0x11); delay_ms(120); // 设置显存访问控制:扫描方向 + RGB/BGR LCD_WriteCmd(0x36); LCD_WriteData(0xC0); // 16bit 像素格式 LCD_WriteCmd(0x3A); LCD_WriteData(0x05); // 开启显示 LCD_WriteCmd(0x29); }

注意 0x36 的 0xC0 这个值。高两位控制行扫描和列扫描方向,0xC0 表示从左上角开始,先从左到右再从上到下,适合竖屏 128x128。如果你的屏幕显示内容是镜像的,或者被旋转了 90 度,调整 0x36 比在代码里做坐标映射要省事得多。

初始化序列不能随意精简。有些开发者跳过了 0x01 软件复位,直接用 0x11 退出睡眠,屏也能亮,但 Gamma 相关寄存器还是默认值,颜色会明显偏淡。完整的初始化序列还包括 0xB1、0xB4、0xE0、0xE1 等电源和 Gamma 命令,建议保留厂商 Bootloader 里那份完整序列,不要为了省几十行代码去删。

3.3 打点与颜色填充:显存地址窗口是局部刷新的基础

点亮屏幕的关键函数是设置地址窗口,然后连续写入颜色数据。ST7735 支持 MADCTL(0x36)、CASET(0x2A)、RASET(0x2B)、RAMWR(0x2C)这一套,显存地址窗口可以小于整个屏幕范围,这是局部刷新的基础。下面这段代码实现了坐标窗口设置和全屏填充。

void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { // 列地址范围 LCD_WriteCmd(0x2A); LCD_WriteData(x0 >> 8); LCD_WriteData(x0 & 0xFF); LCD_WriteData(x1 >> 8); LCD_WriteData(x1 & 0xFF); // 行地址范围 LCD_WriteCmd(0x2B); LCD_WriteData(y0 >> 8); LCD_WriteData(y0 & 0xFF); LCD_WriteData(y1 >> 8); LCD_WriteData(y1 & 0xFF); // 准备好写显存 LCD_WriteCmd(0x2C); }
void LCD_FillRect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { uint32_t count = (uint32_t)(x1 - x0 + 1) * (y1 - y0 + 1); LCD_SetWindow(x0, y0, x1, y1); LCD_CS_LOW(); LCD_A0_HIGH(); // 数据模式 while (count--) { SPI1_SendByte(color >> 8); // 565 格式高字节 SPI1_SendByte(color & 0xFF); // 565 格式低字节 } LCD_CS_HIGH(); }

颜色值用的是 RGB565 格式,高 5 位红色、中间 6 位绿色、低 5 位蓝色。常见颜色:红色 0xF800,绿色 0x07E0,蓝色 0x001F,白色 0xFFFF,黑色 0x0000。写颜色数据时要注意 16bit 颜色的字节序,ST7735 默认先接收高字节。如果整个屏幕颜色发灰、像是颜色值和背景混在一起,多半是高低字节写反了,把代码里两个 send 的顺序换一下就能验证。

单点打点函数不需要重新设置整个地址窗口,直接调 LCD_SetWindow 再写两个字节即可,但频繁调用会比较慢。实际绘制文字、波形时,应当批量填充,尽量减少 SetWindow 调用次数。

4. 显示内容与字体处理:中英文混排、滚动刷新和颜色反转

4.1 字模格式:GB2312 内码与取模方向是如何影响字形显示的

1.44TFT 屏幕要显示中文,最常见的方式是使用 16x16 点阵字库,按 GB2312 内码索引。每个汉字占 32 字节,每行 2 字节,共 16 行。取模工具我在用的有 PCtoLCD2002 和 Image2Lcd,前者取模方向和扫描方式可控,后者适合做图片和图标。选择取模参数时,核心要关注“逐行式”还是“逐列式”,以及高位在前还是低位在前。

常见的取模格式是按行扫描、字节高位在前。一个 16x16 汉字的字节序列是这样的:

// 以“温”字为例,取模结果前 8 字节 0x00, 0x00, 0x3F, 0xF8, 0x20, 0x08, 0x20, 0x08

每两个字节对应一行,第 1 字节是左 8 点,第 2 字节是右 8 点,其中最低位在左还是最高位在左,取决于取模设置。把取模方向设置为“横向取模、字节倒序”之后,可以用下面的函数显示一个 16x16 汉字:

void LCD_ShowChar16(uint16_t x, uint16_t y, uint16_t color, uint16_t bgColor, uint16_t index) { const uint8_t *p = font16 + index * 32; // 字模起始地址 uint16_t i, j; for (i = 0; i < 16; i++) { for (j = 0; j < 16; j++) { if (p[i * 2 + j / 8] & (0x01 << (7 - j % 8))) LCD_DrawPoint(x + j, y + i, color); else LCD_DrawPoint(x + j, y + i, bgColor); } } }

字体发虚、字迹像被横向拉伸,大多是取模方向与显示循环不匹配。比如取模设置是“逐列式”,代码却按“逐行式”解析,字形就会出现 90 度旋转或左右镜像。另一个常见问题是背景色填充和前景色分开画,导致残影。小屏刷新一整行 ASCII 字符的速度并不慢,但频繁清屏再绘制会造成视觉闪烁,建议在局部区域先填背景色,再一次性画字。

ASCII 字符通常用 8x16 点阵,每个字符 16 字节,一字节一行。显示英文时要注意等宽字体和非等宽字体的区别。多数嵌入式字库把 ASCII 做成等宽,但如果你用的是从 PC 系统字体提取的字库,可能包含间隙宽度信息,那就要额外处理字间距。

4.2 清屏策略与重绘开销:一次性写全屏还是局部刷新

在 128x128 分辨率下,整屏填充一次要写 128x128x2 字节,也就是 32KB,在 9MHz SPI 下约需 29ms 左右。看起来很快,但如果做秒级刷新的仪表盘,每次全屏填充会明显卡顿。更严重的是,当数据写入一半又去执行别的任务时,屏幕会出现撕裂,上半屏和下半屏内容不是同一帧。

常见的优化方向是局部刷新。比如只更新时间区域的数字,先用背景色填充一个小矩形,再绘制新数字:

void LCD_RefreshNumber(uint16_t x, uint16_t y, uint16_t newValue) { // 先擦除 32x16 区域 LCD_FillRect(x, y, x + 31, y + 15, BG_COLOR); // 再绘制新数值 LCD_ShowNum(x, y, newValue, 5, FONT_COLOR); }

这样每次只刷新 32x16 的区域,约 1KB 数据,刷新耗时不到 1ms,肉眼几乎看不出闪烁。如果页面元素多且变化频繁,可以先把整帧绘制到一个内存缓冲区,再一次性把整个缓冲区写到屏幕。F103RCT6 有 256KB Flash 和 48KB RAM,128x128 的 RGB565 帧缓冲需要 32KB,RAM 勉强够用,但这个方案会压缩用户程序可用的堆栈空间,必须权衡。

如果你做的是波形滚动显示,不要整屏重绘。裁剪窗口后,把旧波形左移一列,再在新列填充数据,效果更平滑,也避免了全屏刷新导致的闪烁。

4.3 常用显示效果:滑动条、波形刷新与颜色反转的常见做法

做滑动条时,全量重绘整个滑块区域是低效的。更合理的方案是记录上一次滑块位置,只更新旧位置和新位置之间的区域。假设滑块是竖直方向,上下两个矩形即可覆盖变化区域,代码量不多但刷新面积小很多。

颜色反转通常用于菜单选中态,ST7735 没有硬件取反指令,需要在绘制时把前景色和背景色对调。可以把绘制函数封装成带 invert 参数的版本:

void LCD_ShowMenuText(uint16_t x, uint16_t y, char *str, uint16_t color, uint16_t bgColor, uint8_t invert) { if (invert) { // 前景背景互换 uint16_t tmp = color; color = bgColor; bgColor = tmp; } // 先填充背景,再绘制字符 LCD_ShowString(x, y, str, color, bgColor); }

如果屏幕做时钟显示,秒钟刷新时只更新时间区域的两位数,不要整个时间都重绘。分和时的数字没变,重绘属于无效开销,时间久了还会烧屏留残影。虽然 LCD 烧屏比 OLED 轻,但长时间固定显示同一图案,仍然可能出现轻微残影,所以显示内容长期不变时,可以做一个“漂移”策略,每隔一段时间把画面整体平移几个像素再移回来,能有效缓解。

5. 避坑记录:这四类翻车现象先看一遍,能省好几个晚上

5.1 白屏:复位信号和 SPI 采样沿的问题

现象:复位后屏幕全白,背光亮,写任何命令都没有反应。

原因:最常见的是 RES 复位时间不足,或者 SPI 工作在模式 3。ST7735 的复位信号至少保持 10ms 低电平,上电时序要求更严格。另一个原因是 0x36 命令里显存访问控制设置错误,导致数据写到屏幕外区域。

解决:RES 拉低后延时 50ms 再拉高,随后延时 120ms。检查 SPI_CPOL 是否为 Low,SPI_CPHA 是否为 1Edge。如果硬件 SPI 怎么调都不行,临时改用 GPIO 模拟 SPI,代码里用延时翻转 SCL,可以直接确认屏幕本身是否正常。用模拟 SPI 点亮后再逐步替换回硬件 SPI。

5.2 花屏或颜色错乱:RGB/BGR 顺序和列行地址映射

现象:初始化后屏幕能显示,但颜色混乱,红色显示成蓝色,或者画面呈现“斜条纹”状。

原因:ST7735 的 0x36 命令里包含 RGB/BGR 控制位。屏幕硬件是 BGR 排列,初始化却设置为 RGB,颜色就会对调。横向斜条纹通常不是颜色问题,而是列地址扫描方向不对,数据一列一列错位。

解决:先固定扫描方向,把 0x36 设为 0xC0。如果颜色仍偏,把参数改成 0x08 再试,这一步能排除 90% 的颜色问题。斜条纹则把 0x36 的 MX/MY 位反转,比如从 0xC0 改 0xA0,看条纹是否消失。不要试图在打点函数里做坐标换算来弥补,治标不治本,后续画图会出现更多偏移问题。

5.3 字体发虚、左右镜像:取模方向与扫描方式不匹配

现象:英文和数字显示正常,中文显示出来像镜像,或者笔画粗细不均。

原因:ASCII 字库和中文汉字字库用了不同的取模参数。很多工程从网上找的字库,ASCII 是按行扫描取的模,汉字却是按列扫描取模,放在同一套显示代码里自然出事。还有一种情况是字模高低位顺序不同,汉字显示成“反π”形状。

解决:统一所有字库的取模参数。推荐大家养成一个习惯,取模时固定选择“横向取模、字节高位在前、行扫描”,并在代码里用函数名或注释标注这个约定。更换字库时,先显示一个全 0xFF 的字模做测试,比如“测”字,正常显示应该是 16x16 全黑方块,如果不是,取模方向还需要调整。

5.4 上电瞬间闪屏、通信不稳定:电源去耦和飞线长度

现象:屏幕在 MCU 上电或复位瞬间闪白,运行时偶尔出现横向噪点。

原因:逻辑电源和背光共用同一路 3.3V,若走线很长且没有去耦电容,瞬间大电流会让电压跌落,ST7735 内部逻辑电压不足,画面闪断。SPI 信号线用长杜邦线飞线时,信号沿过冲或反射也会导致采样误判。

解决:在屏幕 VCC 和 GND 之间就近并联一个 10uF 钽电容和一个 0.1uF 陶瓷电容,背光供电单独走一路。SCL 和 SDA 线尽量缩短,如果能用双绞线或排线最好。SPI 时钟降到 2MHz 再观察,如果噪点消失,问题就在信号完整性。还可以在 SCL 上串联一个 33 欧电阻抑制过冲,不影响速率却有明显效果。

6. 进阶验证与优化:测一测真实刷新率,再做局部 DMA 传输

把屏幕点亮只是第一步,能不能稳定跑在项目里才是关键。我习惯在显示驱动完成以后,写一个满屏刷新计时测试,用来量化当前 SPI 配置下的真实吞吐量:

uint32_t startTick = GetTick(); for (int i = 0; i < 100; i++) { LCD_FillRect(0, 0, 127, 127, i % 2 ? RED : BLUE); } uint32_t elapsed = GetTick() - startTick; // 单帧耗时约为 elapsed / 100 毫秒

F103RCT6 在 72MHz 主频下,如果 SPI 分频为 8,满屏 32KB 的理论传输时间约 28ms,实测加上函数调用开销,单帧应该在 30ms 上下。如果实测超过 50ms,多半是 SPI 发送函数里每发一个字节都做了过多判断,此时可以用循环展开或用 SPI_I2S_SendData 配合 TXE 等待,减少库函数调用层次。

更进一步,开启 SPI DMA 能显著降低 CPU 占用。初始化 DMA 通道,把显存数据从内存缓冲区搬运到 SPI1_DR,传输完成产生中断。这样 CPU 可以在 DMA 搬运期间去处理传感器读取或按键扫描。注意 DMA 搬运期间不能修改源缓冲区,否则画面会出现撕裂。常见做法是双缓冲,两块缓冲区轮流给 DMA 用。

还有一个验证点:局部刷新的坐标边界。很多开发者写 LCD_SetWindow 时,x0、y0、x1、y1 四者的加减关系容易出错,比如把 x0 和 x1 的赋值对调,屏幕显示区域就会出现“偏移剪切”效果。我建议写一个测试函数,分别在四个角落绘制 8x8 方块,然后检查方位是否正确,这比肉眼判断整屏颜色更可靠。

颜色格式也不要忽略。如果项目要从摄像头或图像传感器取图,传感器输出的可能是 RGB565 但字节序相反,或者直接是 RGB888。在 F103RCT6 上处理 RGB888 转 RGB565 时,用移位和掩码操作,注意平台的字序,转换后的数据直接进 SPI 发送缓冲区,不要在发送循环里现场计算,否则会拖慢刷新速度。最后推荐做一次老化测试:让屏幕连续运行 30 分钟,交替显示全红、全绿、全蓝画面,确认无闪屏、无竖线、无颜色漂移,再交付给项目使用。从那次以后,我每次点亮新的 SPI 屏,都会强制走一遍“查模式 0、查 0x36、查取模方向、测满屏刷新耗时”这四步,省下的时间比什么都值,希望帮到你。

本文还有配套的精品资源,点击获取

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

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

立即咨询