news 2026/10/26 2:48:57

STM32F103RCT6驱动1.44寸TFT屏:从点亮到UI实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32F103RCT6驱动1.44寸TFT屏:从点亮到UI实战

简介:这份资源面向嵌入式初学者与需要快速上手彩屏显示的开发者,围绕STM32F103RCT6驱动1.44英寸TFT屏展开,解决从引脚定义、SPI初始化到屏幕刷新这一完整链路的实现问题。包内共86个文件,以38个h头文件与32个c源文件为主体,涵盖LCD驱动、GUI绘制、字库与图片数据、延时与LED等模块,另有4个s启动文件、Keil工程文件及一个可直接烧录的hex,压缩包约289KB,结构清晰便于按模块查阅。资源中已包含屏幕初始化序列、命令与数据发送、图像缓冲区刷新等关键代码,读者可据此理解SPI通信协议与显示控制的配合方式,并在此基础上扩展仪表盘、小型游戏机等彩色显示应用。目前已有4053人学习下载,适合作为STM32外设驱动入门的参考工程。

1. 一块 1.44 寸屏点亮之后,才算真正摸到 STM32 的门

很多人第一次拿到 STM32F103RCT6 开发板,点亮 LED 之后就开始犯难:串口打印太抽象,OLED 又太小,想做个能看见波形、能显示中文、能画进度条的小界面,手头正好有一块 1.44 寸 TFT 彩屏,却卡在“接线对不对、驱动怎么写、字库怎么放”这三道坎上。这个项目就是围绕 STM32F103RCT6 加 1.44 寸 TFT 屏做的一套完整显示方案,从 GPIO 模拟 SPI 到屏幕初始化、从画点画线到显示字符和数字,把一块彩屏从黑屏点亮到能显示业务数据的全过程都摊开在源码里。它适合刚学完外设、想做一个带界面的小设备原型的开发者,也适合需要给传感器数据找一个可视化出口的工程场景。屏幕不大,但足够把 STM32 的 SPI、定时器、GPIO 和字库组织这几件事串起来,跑通之后你对“驱动”两个字的理解会完全不一样。

2. 项目定位与技术选型:为什么是 F103RCT6 配 1.44 寸 TFT

2.1 硬件组合的合理性

STM32F103RCT6 属于 F103 系列里资源比较宽裕的一颗,256KB Flash、48KB RAM,LQFP64 封装,GPIO 数量足够同时挂屏幕、按键和几个传感器。1.44 寸 TFT 常见分辨率是 128×128,驱动芯片多为 ST7735 系列,采用 SPI 接口,只需要 SCK、MOSI、CS、DC、RESET 五根线就能工作,对 GPIO 的压力很小。这个组合的好处是:主控不用上外部总线,屏幕不占并口,剩下的引脚还能接编码器、温湿度传感器或者无线模块,适合做手持小终端、调试监视器、教学实验板这类产品原型。

从工程角度看,F103RCT6 的 SPI1 最高可以跑到 18MHz 左右,刷 128×128 的整屏理论上几十毫秒级别,做界面切换和波形刷新完全够用。RAM 48KB 意味着可以开一块 128×128×2 字节的显存缓冲(约 32KB),也可以选择不開缓冲、直接写屏,两种策略在源码里通常都会留出切换开关。选型上没有花哨的地方,就是“够用、便宜、资料多”,这也是大量小屏项目仍然停留在 F103 平台的原因。

2.2 软件分层与目录结构

这类项目的源码一般不会写成一个大文件,而是按“硬件抽象—屏幕驱动—图形绘制—业务显示”四层来组织。下面是一个典型的目录结构,实际源码包基本遵循这个骨架:

project/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── stm32f1xx_hal_conf.h │ │ └── stm32f1xx_it.h │ └── Src/ │ ├── main.c │ ├── stm32f1xx_it.c │ └── system_stm32f1xx.c ├── Drivers/ │ ├── CMSIS/ │ └── STM32F1xx_HAL_Driver/ ├── BSP/ │ ├── lcd_init.c // 屏幕初始化序列 │ ├── lcd_init.h │ ├── lcd.c // 画点、填充、字符 │ └── lcd.h ├── Fonts/ │ ├── font_8x16.h // ASCII 字库 │ └── font_16x16.h // 中文点阵字库(按需) └── App/ ├── ui.c // 界面布局与刷新逻辑 └── ui.h

BSP层只关心“怎么把像素写到屏幕上”,App层只关心“屏幕上显示什么内容”,中间通过LCD_ShowString、LCD_DrawLine这类函数衔接。这种分法在二次开发时非常关键:换屏幕型号只动BSP,改界面只动App,两边不会互相牵连。我见过不少新手把初始化序列和业务逻辑混在main.c里,结果换一块屏就要重写半个工程,血泪经验就是分层要趁早。

2.3 通信方式:硬件 SPI 与软件模拟的取舍

1.44 寸 TFT 的驱动芯片对时序要求不算苛刻,所以源码里常见两种写法:一种用 STM32 的硬件 SPI 外设,速度快、CPU 占用低;另一种用 GPIO 翻转模拟 SPI,接线自由、便于移植到任意引脚。项目里通常会提供一个宏来切换,比如#define LCD_USE_HARDWARE_SPI 1。硬件 SPI 适合追求刷新率的场景,软件模拟适合引脚被其他外设占用、或者想快速验证屏幕好坏的情况。

硬件 SPI 的配置要点是:主机模式、8 位数据帧、CPOL=0、CPHA=0(模式 0)、波特率预分频先设大一点保证稳定,调通后再往上提。软件模拟则要注意每根线的方向切换,尤其是 MOSI 在写命令和写数据时都要保持输出,DC 线用来区分命令和数据,CS 可以常拉低也可以按字节拉低,前者速度快但多设备共用总线时要小心。

3. 从零跑起来:接线、初始化与第一个像素

3.1 引脚连接与供电检查

在写任何代码之前,先把线接对。以常见的 ST7735 方案为例,屏幕模块一般有 8 个引脚:VCC、GND、CS、RESET、DC、MOSI、SCK、LED。对应到 STM32F103RCT6 上,可以这样分配:

屏幕引脚STM32 引脚说明
VCC3.3V不要接 5V,多数模块不带电平转换
GNDGND共地
CSPA4片选,低有效
RESETPA3复位,低有效
DCPA2命令/数据选择
MOSIPA7SPI1_MOSI
SCKPA5SPI1_SCK
LED3.3V 或 PA1背光,可接 GPIO 做调光

接线时最容易翻车的是供电:有些模块标称 3.3V~5V,但逻辑电平其实是 3.3V,直接接 5V 可能烧掉驱动芯片。另外 RESET 和 DC 不要接反,接反的典型现象是屏幕全白或者花屏,初始化命令全部被当成数据写进显存。上电前用万用表量一下 VCC 和 GND 是否短路,这个动作能省掉很多“为什么屏幕不亮”的排查时间。

3.2 初始化序列:一段不能随便改的“咒语”

屏幕能点亮的关键在于初始化序列,它由驱动芯片厂商给定,顺序和参数都不能随意调整。下面是一段典型的 ST7735 初始化代码,用 HAL 库的 SPI 发送函数实现:

// lcd_init.c static void LCD_WriteCmd(uint8_t cmd) { LCD_DC_LOW(); // DC=0 表示命令 LCD_CS_LOW(); HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); LCD_CS_HIGH(); } static void LCD_WriteData(uint8_t data) { LCD_DC_HIGH(); // DC=1 表示数据 LCD_CS_LOW(); HAL_SPI_Transmit(&hspi1, &data, 1, 100); LCD_CS_HIGH(); } void LCD_Init(void) { LCD_RESET_LOW(); HAL_Delay(20); LCD_RESET_HIGH(); HAL_Delay(120); // 等待内部复位完成 LCD_WriteCmd(0x11); // Sleep Out HAL_Delay(120); LCD_WriteCmd(0x36); // MADCTL,扫描方向 LCD_WriteData(0x00); // 根据屏幕方向调整 LCD_WriteCmd(0x3A); // 像素格式 LCD_WriteData(0x05); // 16 位色,RGB565 LCD_WriteCmd(0x29); // Display On HAL_Delay(20); }

这段代码里,LCD_WriteCmd和LCD_WriteData的区别只在 DC 电平,但正是这个电平决定了驱动芯片把收到的字节解释成命令还是显存数据。0x11是退出睡眠,必须等 120ms 左右;0x36控制扫描方向,取值不同会导致画面旋转或镜像;0x3A设为0x05表示每个像素用 16 位表示,也就是 RGB565,红色 5 位、绿色 6 位、蓝色 5 位。初始化之后如果屏幕还是白的,优先检查复位时序和0x29是否发出,而不是去怀疑 SPI 速率。

3.3 设置显示窗口与写像素

初始化完成后,要告诉屏幕“接下来往哪个矩形区域写数据”,这就是设置窗口。128×128 的屏幕,列地址范围是 0~127,行地址范围也是 0~127,但有些模块内部有偏移,比如 ST7735 的 1.44 寸屏常见列偏移 2、行偏移 1,需要在代码里补偿:

void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { LCD_WriteCmd(0x2A); // 列地址设置 LCD_WriteData(0x00); LCD_WriteData(x0 + 2); // 列偏移补偿 LCD_WriteData(0x00); LCD_WriteData(x1 + 2); LCD_WriteCmd(0x2B); // 行地址设置 LCD_WriteData(0x00); LCD_WriteData(y0 + 1); // 行偏移补偿 LCD_WriteData(0x00); LCD_WriteData(y1 + 1); LCD_WriteCmd(0x2C); // 开始写显存 } void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { LCD_SetWindow(x, y, x, y); LCD_WriteData(color >> 8); // 高字节先发 LCD_WriteData(color & 0xFF); }

LCD_SetWindow里的偏移量不是所有模块都一样,如果发现画点位置整体偏了几个像素,就是这里没对上。颜色采用 RGB565,比如纯红是0xF800,纯绿是0x07E0,纯蓝是0x001F。写像素时先发高字节再发低字节,这是 SPI 大端顺序决定的,发反了颜色会变成“蓝红互换”的怪色。调通LCD_DrawPixel之后,先画一个 10×10 的红色方块在屏幕左上角,能稳定显示就说明整条链路通了。

4. 核心模块拆解:画点、字符与界面刷新

4.1 图形绘制:从画点到画线再到填充

有了画点函数,画线、画矩形、填充都可以在此基础上叠加。项目里通常会用 Bresenham 算法画线,避免浮点运算:

void LCD_DrawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { int16_t dx = (x1 > x0) ? (x1 - x0) : (x0 - x1); int16_t dy = (y1 > y0) ? (y1 - y0) : (y0 - y1); int16_t sx = (x0 < x1) ? 1 : -1; int16_t sy = (y0 < y1) ? 1 : -1; int16_t err = dx - dy; while (1) { LCD_DrawPixel(x0, y0, color); if (x0 == x1 && y0 == y1) break; int16_t e2 = err << 1; if (e2 > -dy) { err -= dy; x0 += sx; } if (e2 < dx) { err += dx; y0 += sy; } } }

这段代码的err是误差累积项,e2是它的两倍,用来判断下一步该往 x 方向走还是往 y 方向走。相比逐点计算斜率,它只用整数加减和移位,在 F103 这种没有 FPU 的芯片上非常合适。填充矩形则可以直接调用LCD_SetWindow设定区域,然后连续写width * height个颜色值,比逐点画快一个数量级。实际项目里,进度条、边框、波形背景都靠这几个基础函数拼出来。

4.2 字符显示:字库取模与坐标计算

显示字符的核心是“把字模数据按位展开成像素”。以 8×16 的 ASCII 字库为例,每个字符占 16 个字节,每字节代表一行中的 8 个像素,高位在左。显示时逐行判断每一位,是 1 就画前景色,是 0 就画背景色(或者跳过):

void LCD_ShowChar(uint16_t x, uint16_t y, char ch, uint16_t fc, uint16_t bc, uint8_t size) { uint8_t i, j; uint8_t temp; const uint8_t *pfont = &font_8x16[(ch - ' ') * 16]; // 字库起始偏移 for (i = 0; i < 16; i++) { temp = pfont[i]; for (j = 0; j < 8; j++) { if (temp & (0x80 >> j)) LCD_DrawPixel(x + j, y + i, fc); else LCD_DrawPixel(x + j, y + i, bc); } } }

ch - ' '是因为字库通常从空格开始存,空格 ASCII 是 32,所以偏移量要减掉。0x80 >> j是从最高位往最低位取,对应字模“左高右低”的排列。如果显示出来是乱码或者上下颠倒,多半是取模软件的设置和代码里的扫描方向不一致,常见的有“阴码/阳码”“逐行/逐列”几个选项,改一个再试。中文显示则需要 16×16 或 24×24 的点阵字库,原理相同,只是每个字占 32 或 72 个字节,代码里把内层循环改成 16 位即可。

4.3 界面刷新策略:全屏重绘还是局部更新

屏幕刷新是这类项目里最影响体验的部分。全屏重绘就是每次清屏再画所有元素,代码简单但闪烁明显,128×128×2 字节约 32KB,SPI 速率不够时肉眼能看到刷屏过程。局部更新只重绘发生变化的区域,比如数字从 25 变成 26,只刷新那两位数字所在的矩形,速度快、无闪烁,但需要维护“哪些区域脏了”的状态。

常见做法是在App层维护一个脏矩形列表,或者更简单一点:把会变化的数值区域单独封装成UI_UpdateValue(x, y, old, new),内部先填背景色再画新值。对于波形显示,则用“滚动窗口”思路,新数据从右侧进入,旧数据左移,只重绘一列或几列。我一般会先上全屏重绘把功能跑通,再针对数字和波形做局部优化,这样调试阶段不会被刷新逻辑干扰。

5. 避坑与排查:屏幕不亮、花屏、颜色不对的常见原因

5.1 现象:上电后屏幕全白,没有任何显示

原因通常是初始化序列没有正确执行,或者复位时序不对。解决方法是先用逻辑分析仪或示波器看 RESET 引脚是否有低电平脉冲,再看 SPI 的 SCK 和 MOSI 上有没有数据波形。如果没有波形,检查 SPI 外设是否使能、引脚是否配置为复用推挽;如果有波形但屏幕仍白,检查0x11之后是否等待足够时间,以及0x29是否发出。另一个容易忽略的点是背光:LED 引脚没接或者限流电阻过大,屏幕其实是黑的但看起来像白屏,用手电筒斜照能看到隐约图像。

5.2 现象:画面花屏,颜色块错位

原因多半是窗口设置和实际分辨率不匹配,或者偏移量没补偿。ST7735 系列在不同尺寸模块上的 RAM 起始地址不一样,1.44 寸常见偏移是列 2、行 1,但也有的模块是列 0、行 0。解决方法是写一个测试函数,从 (0,0) 开始逐行填充不同颜色,观察第一行第一列是否从屏幕左上角开始,偏了就调整LCD_SetWindow里的偏移常量。花屏还有一种可能是 SPI 速率过高导致数据错位,把预分频调大一档再试。

5.3 现象:颜色显示不对,红色变蓝色

原因是 RGB565 的字节顺序或者颜色通道顺序搞反了。有的屏幕模块默认是 BGR 排列,需要在 MADCTL 命令里设置 RGB/BGR 位,比如0x36的参数从0x00改成0x08。另外,如果写像素时先发低字节再发高字节,颜色也会错乱。解决方法是先画三个已知颜色的色块(红、绿、蓝),对照实际显示调整字节顺序和 MADCTL 参数,直到三个色块颜色正确。

5.4 现象:显示字符时出现残影或拖尾

原因是画字符时背景色没有正确填充,或者局部刷新时旧内容没擦除。如果字符区域背景是动态的,必须先填背景再画字;如果用了局部更新,要确保更新区域的坐标和上次完全一致。还有一种情况是 SPI 发送没有等待完成就修改了 DC 或 CS,导致最后一个字节被截断,表现为字符边缘缺像素。解决方法是每次HAL_SPI_Transmit后确认返回HAL_OK,必要时加短暂延时。

5.5 现象:程序跑一段时间后屏幕闪烁或死机

原因可能是栈溢出或者 SPI 传输阻塞过久。F103 的默认栈不大,如果在中断里调用画屏函数,很容易把栈撑爆。解决方法是把屏幕刷新放到主循环,中断里只置标志位;同时检查HAL_SPI_Transmit的超时参数,不要设成无限等待。另外,如果使用了 DMA 刷屏,要注意 DMA 传输完成前不能修改显存缓冲,否则会出现撕裂。

6. 二次开发:把这块屏变成你自己的数据面板

6.1 封装一个轻量 UI 层

跑通显示之后,下一步是把“画点画线”包装成业务能用的组件。我一般会在App层定义几个结构体:Label(位置、颜色、文本缓冲)、Bar(位置、宽高、当前值、最大值)、Wave(采样缓冲、绘制区域)。每个组件提供Init和Update两个函数,Update内部只重绘自己占用的矩形。这样主循环里只需要根据传感器数据调用对应组件的Update,界面逻辑和驱动彻底解耦。

typedef struct { uint16_t x, y, w, h; uint16_t fg, bg; int16_t value; int16_t max; } UI_Bar; void UI_Bar_Update(UI_Bar *bar) { uint16_t fill = (uint32_t)bar->w * bar->value / bar->max; LCD_Fill(bar->x, bar->y, bar->x + fill, bar->y + bar->h, bar->fg); LCD_Fill(bar->x + fill, bar->y, bar->x + bar->w, bar->y + bar->h, bar->bg); }

这个进度条组件只重绘自己的区域,fill用 32 位中间变量避免乘法溢出。实际使用时,把value更新后调用一次UI_Bar_Update即可,不需要清整屏。

6.2 字库裁剪与 Flash 占用控制

F103RCT6 有 256KB Flash,ASCII 字库 8×16 只占几 KB,但完整的中文字库动辄几百 KB,放不下。常见做法是按项目实际用到的汉字做裁剪,只保留界面文案里出现的字,用取模软件导出成 C 数组。另一种做法是把字库放到外部 SPI Flash,屏幕刷新时按需读取,但这会增加读取延迟,适合字数多、刷新不频繁的场景。裁剪时注意保留标点和数字,否则运行到某个界面会显示空白。

6.3 验证方法与调试习惯

改完驱动或 UI 后,不要直接上业务代码,先跑一个自检界面:全屏红、绿、蓝交替,四角画标记点,中间显示一行 ASCII 和一个中文,再画一条对角线。这个界面能在几秒内暴露偏移、颜色、字库、刷新四类问题。我习惯在main.c里留一个#define LCD_SELFTEST 1开关,量产前关掉,调试时打开。从那以后我每次换屏幕型号或者改初始化序列,都强制先走一遍自检界面,确认基础显示无误再动业务逻辑,省下了大量“到底是屏的问题还是代码的问题”的来回折腾。希望这套思路能帮到你,把手里那块 1.44 寸屏真正用起来。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 2:48:46

扩散模型知识蒸馏的悖论:为何越教学生模型越退化?

1. 项目概述&#xff1a;当“教学生”反而让“老师”变笨了&#xff1f;“扩散蒸馏的悖论”——光看这个词组&#xff0c;很多人第一反应是&#xff1a;“这又是个论文里造出来的玄学术语吧&#xff1f;”但如果你真在图像生成、大模型轻量化或AI推理优化一线干过两三年&#x…

作者头像 李华
网站建设 2026/10/26 2:48:44

AI编程工具如何重塑开发流程与团队协作

1. 行业变革的临界点&#xff1a;AI编程工具如何重塑开发流程三年前我第一次接触GitHub Copilot时&#xff0c;它还是个经常给出荒谬代码建议的"玩具"。但当我最近在为一个金融项目编写Python数据处理脚本时&#xff0c;这个AI助手在10分钟内就完成了原本需要半天的工…

作者头像 李华
网站建设 2026/10/26 2:48:31

电动汽车销量预测:时间序列模型实战与优化

1. 项目概述&#xff1a;电动汽车销量预测的行业价值去年参与某新能源车企的数字化项目时&#xff0c;我亲眼见证了销售预测偏差带来的连锁反应——生产线紧急调整导致300万物料浪费&#xff0c;经销商库存积压造成现金流紧张。这正是时间序列预测在电动汽车行业的典型应用场景…

作者头像 李华
网站建设 2026/10/26 2:46:30

全国天气数据采集源码包:可复跑链路与断点续采实践

简介&#xff1a;面向数据分析学习者、爬虫初学者与需要长时序气象数据的科研人员&#xff0c;这套资料提供全国多城市2011—2024年天气数据及配套采集源代码&#xff0c;可用于气温、风向风力等气象指标的趋势分析、可视化与建模练习。包内共300个文件&#xff0c;以261个CSV分…

作者头像 李华
网站建设 2026/10/26 2:45:30

教育数据分析:动态分层评价体系与黑马识别

1. 项目概述&#xff1a;京北中学期中排名分析的价值与意义每次期中考试后的排名公布&#xff0c;都是检验教学成果与学习成效的重要时刻。2026年京北中学的期中排名尤其值得关注&#xff0c;因为这所百年名校正在经历教学改革的关键时期。作为长期跟踪教育数据的从业者&#x…

作者头像 李华
网站建设 2026/10/26 2:45:18

Chrome插件开发入门:从Manifest到发布全流程

1. 项目概述&#xff1a;Chrome插件开发入门指南"chromeplugin途"这个标题直指Chrome浏览器插件开发的学习路径。作为浏览器扩展开发的热门领域&#xff0c;Chrome插件因其跨平台特性和庞大的用户基础&#xff0c;成为开发者实现功能扩展的首选方案。我在2016年第一次…

作者头像 李华