☰
STM32F103RCT6驱动1.44寸TFT屏:从点灯到UI底板实战
2026/10/26 2:48:57 网站建设 项目流程

简介:这份资源面向嵌入式初学者与需要快速验证显示方案的开发者,围绕STM32F103RCT6驱动1.44英寸128×128彩色TFT屏展开,解决从引脚定义、SPI初始化到屏幕刷新这一完整链路的落地问题。压缩包共86个文件、约289KB,以38个h头文件与32个c源文件为主体,涵盖LCD驱动、GUI绘制、字库与图片数据、延时与LED等模块,另含4个s启动文件、Keil工程文件、hex固件及readme说明,工程结构完整可直接编译参考。目前已有4053人学习下载,说明该方案在同类屏幕调试中具有较高参考价值。读者可从中获得SPI通信配置、DC/RST/BL控制线处理、初始化序列与显存缓冲刷新等关键实现思路,并借助现成工程对照排查花屏、无显示等常见问题,适合作为小型仪表盘、迷你显示屏等图形界面项目的起步模板。

1. 一块 1.44 寸屏点亮之后,我才敢说这颗 MCU 入门了

很多人第一次碰 STM32F103RCT6,都是从点灯开始的,灯亮那一刻挺爽,但真正让人心里有底的,是把一块 1.44 寸 TFT 屏点亮、刷出第一行字。这个项目干的就是这件事:用 STM32F103RCT6 驱动 1.44 寸 TFT 液晶屏,完成初始化、清屏、画点画线、显示字符和简单图形。它不花哨,却是嵌入式入门最扎实的一块试金石——SPI 时序、GPIO 配置、显存管理、字模取模,全在这一块小屏上过一遍。适合刚上手 STM32、想搞懂屏幕驱动链路的人,也适合拿它当 UI 底板做二次开发的老手。下面我按自己拆项目的顺序,把这块屏从硬件接线到代码改造讲透。

2. 硬件与工程结构:先看清这块屏和这颗 MCU 怎么搭

2.1 为什么选 STM32F103RCT6 配 1.44 寸 TFT

STM32F103RCT6 是 Cortex-M3 内核,主频 72MHz,256KB Flash、48KB SRAM,LQFP64 封装,GPIO 数量对驱动一块小屏绰绰有余。1.44 寸 TFT 常见分辨率是 128×128,驱动芯片多为 ST7735 系列,走 SPI 接口,只需要 SCK、MOSI、CS、DC、RESET 几根线,接线简单、占用引脚少,非常适合入门和中小型项目。

选这套组合的现实原因是:SPI 刷 128×128 的屏,72MHz 主频下全屏刷新在几十毫秒量级,做菜单、仪表盘、简单动画都够用;而 F103RCT6 的 SRAM 有 48KB,开一块 128×128×2 字节的显存缓冲(约 32KB)也放得下,这点很关键——有显存缓冲,画图才不会闪。

常见做法是把屏幕挂在 SPI1 上,用软件控制 CS 和 DC,复位脚单独接一个 GPIO。下面这张表是我一般会先确认的接线关系:

屏幕引脚接到 MCU作用
VCC / GND3.3V / GND供电,注意别接 5V
SCL(SCK)PA5SPI 时钟
SDA(MOSI)PA7SPI 数据
CSPA4片选,低有效
DCPA3数据/命令选择
RESPA2硬件复位
BLK3.3V 或 GPIO背光控制

提示:不同厂家的 1.44 寸模块引脚命名不统一,有的把 DC 写成 A0,有的把 MOSI 写成 SDA,接线前一定对照模块丝印,别照抄网上图。

2.2 工程目录怎么分层

这类项目我一般不会把所有代码堆在 main.c 里,而是按「硬件抽象—驱动—应用」三层拆开,后面换屏、换 MCU 都好改。典型目录结构如下:

Project/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── stm32f1xx_hal_conf.h │ │ └── lcd_st7735.h // 屏幕驱动对外接口 │ └── Src/ │ ├── main.c // 应用入口 │ ├── stm32f1xx_it.c │ ├── lcd_st7735.c // 屏幕底层驱动 │ ├── lcd_font.c // 字模数据 │ └── spi.c / gpio.c // 外设初始化 ├── Drivers/ │ └── STM32F1xx_HAL_Driver/ // HAL 库 └── MDK-ARM/ 或 STM32CubeIDE/ // 工程文件

分层的好处很直接:lcd_st7735.c只关心「怎么把命令和数据发出去」,main.c只关心「显示什么」。哪天把 ST7735 换成 ST7789,只动驱动层,应用层几乎不用改。这也是我后面讲二次开发时反复强调的一点。

2.3 时钟与 SPI 初始化

SPI 的速率直接决定刷屏快慢。F103 的 SPI1 挂在 APB2 上,72MHz,分频系数选 2 就是 36MHz,但 ST7735 手册一般标称写周期最快 66ns 左右,实际跑 18MHz 更稳。我一般先用 4 分频(18MHz)跑通,再往上试。

// spi.c 片段:SPI1 初始化,主机模式,8 位数据 void MX_SPI1_Init(void) { hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; // 主机模式 hspi1.Init.Direction = SPI_DIRECTION_2LINES;// 全双工 hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // 8 位 hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // 空闲低电平 hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // 第一个边沿采样 hspi1.Init.NSS = SPI_NSS_SOFT; // 软件片选 hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // 18MHz hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // 高位先发 HAL_SPI_Init(&hspi1); }

参数说明:CLKPolarity和CLKPhase合起来决定 SPI 模式,ST7735 通常用模式 0(CPOL=0、CPHA=0),也就是空闲低、第一个边沿采样;NSS设成软件,是因为片选要由驱动代码在发命令和发数据之间手动拉低拉高;FirstBit必须是 MSB,屏幕驱动芯片按高位在前解析。这几项配错,屏幕要么全白要么花屏,是新手最常见的翻车点。

3. 驱动核心拆解:一条命令从 MCU 到屏幕像素

3.1 命令与数据的分流:DC 引脚才是关键

SPI 本身只负责搬字节,屏幕怎么知道这一字节是命令还是数据?靠 DC 引脚。DC 拉低表示接下来发的是命令(寄存器地址),拉高表示发的是数据(参数或像素)。所以驱动层最核心的就是两个函数:写命令、写数据。

// lcd_st7735.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(); }

逻辑说明:每次传输前先设好 DC,再拉低 CS,发完立刻拉高 CS。有人图省事把 CS 一直拉低,单设备时确实能跑,但一旦总线上还有别的 SPI 器件就会互相干扰。HAL_SPI_Transmit的超时参数给 100ms 足够,实际单字节传输微秒级。批量发像素时不要一个字节调一次,要拼成缓冲一次发,否则刷屏慢得肉眼可见。

3.2 初始化序列:照着手册一条条喂

ST7735 上电后必须按手册的初始化序列配置,顺序错了屏幕不亮或颜色异常。核心几条是软件复位、退出睡眠、设置像素格式为 16 位 RGB565、设置显示窗口、打开显示。

void LCD_Init(void) { LCD_RES_LOW(); HAL_Delay(20); LCD_RES_HIGH(); HAL_Delay(120); // 硬件复位,等内部稳定 LCD_WriteCmd(0x01); HAL_Delay(120); // SWRESET 软件复位 LCD_WriteCmd(0x11); HAL_Delay(120); // SLPOUT 退出睡眠 LCD_WriteCmd(0x3A); // COLMOD 像素格式 LCD_WriteData(0x05); // 16 位/像素 RGB565 LCD_WriteCmd(0x36); // MADCTL 扫描方向 LCD_WriteData(0x00); // 根据屏幕朝向调整 LCD_WriteCmd(0x21); // INVON 反显,部分屏需要 LCD_WriteCmd(0x29); // DISPON 开显示 LCD_Fill(0x0000); // 清屏为黑 }

参数说明:0x3A的参数据0x05表示 16 位色,这是 RGB565 的标准配置;0x36控制行列扫描方向和 RGB/BGR 顺序,屏幕显示镜像、颜色红蓝颠倒,基本都是这一条参数不对,改0x00/0x60/0xA0/0xC0几个值试;0x21反显命令不是所有批次都需要,如果发现颜色整体反了,加或去掉它。初始化里每个延时都不能省,尤其是复位后那 120ms,省了就是玄学不亮。

3.3 设置显示窗口与刷像素

要在指定区域画图,先发0x2A(列地址)和0x2B(行地址)设定窗口,再发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_Fill(uint16_t color) { LCD_SetWindow(0, 0, 127, 127); LCD_DC_HIGH(); LCD_CS_LOW(); for (uint32_t i = 0; i < 128 * 128; i++) { uint8_t buf[2] = { color >> 8, color & 0xFF }; // RGB565 高字节在前 HAL_SPI_Transmit(&hspi1, buf, 2, 100); } LCD_CS_HIGH(); }

逻辑说明:地址是 16 位,拆成高低字节发;颜色是 RGB565,两个字节高字节在前。上面这个LCD_Fill是能跑但不够快的写法,逐像素调HAL_SPI_Transmit开销大。优化思路是开一块行缓冲,一次发一整行,或者直接用 DMA。128×128 全屏 16384 个像素,优化后刷新能压到十几毫秒。

3.4 字符显示:字模从哪来

显示字符本质是把字模点阵按位展开成像素。常用 8×16 或 16×16 字模,用取模软件生成 C 数组。显示时逐行判断每一位,为 1 画前景色,为 0 画背景色。

// 显示一个 8x16 ASCII 字符 void LCD_ShowChar(uint16_t x, uint16_t y, char ch, uint16_t fc, uint16_t bc) { const uint8_t *p = &font8x16[(ch - ' ') * 16]; // 每个字符 16 字节 LCD_SetWindow(x, y, x + 7, y + 15); LCD_DC_HIGH(); LCD_CS_LOW(); for (uint8_t row = 0; row < 16; row++) { uint8_t bits = p[row]; for (uint8_t col = 0; col < 8; col++) { uint16_t c = (bits & (0x80 >> col)) ? fc : bc; uint8_t buf[2] = { c >> 8, c & 0xFF }; HAL_SPI_Transmit(&hspi1, buf, 2, 100); } } LCD_CS_HIGH(); }

参数说明:font8x16是字模数组,按 ASCII 从空格开始排列,每个字符 16 字节、每字节一行 8 位;0x80 >> col是从左到右取位,取模软件设置成「阴码、逐行、高位在前」才能对上。字模方向搞反,显示出来就是镜像或乱码,这是取模配置和代码不匹配的典型问题。

4. 跑起来与避坑:从编译到点亮的那几道坎

4.1 编译下载与首次点亮流程

工程用 Keil MDK 或 STM32CubeIDE 都能开,HAL 库版本要和工程里stm32f1xx_hal_conf.h对得上。流程是:确认时钟树配到 72MHz、SPI1 和 GPIO 初始化被调用、LCD_Init()在main里while(1)之前执行、下载后按复位。第一次点亮建议先只做「清屏为红」,能整屏变红说明 SPI 通路、初始化序列、DC/CS 控制全对,再往上加字符和图形,逐层验证比一上来全功能好排查得多。

4.2 常见问题排查:白屏、花屏、颜色反

现象一:上电全白,什么都不显示。原因通常是初始化没跑完或背光/复位没处理好。先量 RES 脚有没有复位脉冲,再看LCD_Init是否真的被调用;有的模块 BLK 不接就不亮,把背光脚接 3.3V 试。还有一种情况是 SPI 模式配错,CPOL/CPHA 不对,命令根本没被识别。

现象二:花屏、雪花点。多半是 SPI 速率太高或接线过长。把分频从 2 改到 4 甚至 8 试,杜邦线尽量短;另外检查LCD_SetWindow的行列范围有没有越界,越界写显存会串到别的区域。

现象三:颜色红蓝颠倒。这是 RGB/BGR 顺序问题,改0x36(MADCTL)参数里的 RGB 位,或者初始化时加/去0x21反显命令。别急着改代码逻辑,先在这两个寄存器上找。

现象四:显示镜像或旋转 90 度不对。还是0x36的扫描方向位,四个方向对应不同参数值,配合LCD_SetWindow的坐标映射一起调,改完记得同步改画点函数的坐标换算。

现象五:刷屏慢、有撕裂感。逐字节HAL_SPI_Transmit是主因,改成行缓冲批量发或 DMA;如果开了显存缓冲,注意先写缓冲再整块刷,别边算边刷。

注意:屏幕供电一定确认是 3.3V,部分模块标 5V 兼容但逻辑电平仍是 3.3V,直接接 5V 数据线长期会伤 IO。

5. 二次开发:把这块屏改成你自己的 UI 底板

5.1 加显存缓冲,消灭闪烁

直接写屏最大的问题是画图过程中屏幕可见地闪。改造思路是开一块uint16_t frame[128*128](32KB,F103RCT6 的 48KB SRAM 放得下),所有画点、画线、画字符先写进内存,最后一次性LCD_Flush()推给屏幕。

static uint16_t frame[128 * 128]; void LCD_DrawPixelBuf(uint16_t x, uint16_t y, uint16_t color) { if (x < 128 && y < 128) frame[y * 128 + x] = color; // 先写缓冲 } void LCD_Flush(void) { LCD_SetWindow(0, 0, 127, 127); LCD_DC_HIGH(); LCD_CS_LOW(); HAL_SPI_Transmit(&hspi1, (uint8_t *)frame, sizeof(frame), 1000); // 整块推 LCD_CS_HIGH(); }

这样刷屏从「逐像素发」变成「一次 DMA 级传输」,配合 DMA 还能在传输时继续算下一帧。注意frame是 16 位数组,传给HAL_SPI_Transmit要强转成uint8_t*,长度是字节数不是元素数,这里写错就是只刷半屏。

5.2 封装图形接口,方便换屏换 MCU

把画点、画线、画矩形、画圆、显示字符串统一成一组接口,底层实现藏在驱动里。换 ST7789 时只改LCD_WriteCmd/Data和初始化序列,上层 UI 代码一行不动。我一般会定义一个lcd_ops结构体放函数指针,或者干脆用同名函数加条件编译,小项目后者更省事。

5.3 验证改造是否成功

改完别只看「能显示」,要验证三件事:全屏填充耗时(用定时器打点,优化后应在 20ms 内)、连续刷新 100 帧有没有撕裂、旋转方向切换后坐标是否仍然正确。我习惯在main里写一个自检函数,依次刷红绿蓝白黑五色各 200ms,再画一个边框和十字线,肉眼一遍就能确认颜色、方向、边界都对。

5.4 一个具体技巧:字模按需裁剪

全 ASCII 8×16 字模约 1.5KB,不算大,但如果只显示数字和少量字母,可以只保留用到的字符,把字模表压到几百字节,给 SRAM 紧张的场合腾空间。做法是建一个字符到索引的映射表,取模时只导出需要的字符,代码里查表偏移。这个技巧在 F103 这种 SRAM 有限的片子上很实用。

血泪经验是:每次改完初始化序列或0x36参数,我一定先跑一遍五色自检再往下写业务代码,不然等 UI 堆上去再回头查颜色问题,成本翻几倍。希望这块 1.44 寸屏也能成为你 STM32 路上第一块真正点亮的屏。

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

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

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

立即咨询