资讯动态

ST7789V2 TFT屏驱动开发:从点亮到字符图片显示

发布时间:2026/10/20 13:21:28 来源:尧图企业网站定制
简介面向嵌入式与单片机开发者的ST7789V2液晶显示示例代码适用于智能手表、微型显示屏等小尺寸屏幕项目帮助解决字符与图片显示的基础驱动问题代码采用C语言编写可直接嵌入常见MCU工程。整个压缩包仅2个文件——C源码与对应头文件大小6KB非常精简。源码包含完整的初始化流程覆盖时序参数、GPIO引脚、SPI/I2C接口配置字符显示部分实现了ASCII码到像素阵列的转换与字库映射图片显示部分则涉及BMP数据读取、颜色空间转换、RGB565像素封装、帧缓冲区更新与刷新命令同时支持RGB565颜色模式。代码还区分了命令与数据的发送机制并加入通信错误检查机制方便开发者直接对照硬件平台修改移植。目前已有3885人学习下载对于需要快速驱动ST7789V2屏幕、跳过寄存器调试环节的初中级工程师而言是一份可直接参考的轻量级基础框架对理解LCD驱动原理也有帮助。1. ST7789V2 字符图片显示代码一块屏从点亮到可用的最短路径当你想在一块 240×320 的 TFT 屏上同时显示字符和图片最先遇到的坑往往不是 SPI 时序而是数据手册里那些分散在各章节的初始化命令——漏一条屏就花给你看。ST7789V2 字符图片显示代码把复位、初始化、清屏、字符绘制和图片显示封装成可直接调用的接口烧录后立刻能亮屏。它适合两类人刚接触 TFT 屏、想绕过手册直接看到画面的入门者以及要把显示模块集成进自己固件、需要可移植底层驱动的开发者。这份代码不是点灯例程而是一套带帧缓冲与图片解码的小型图形骨架可以拿它当基座继续往上盖。2. 工程拆解从 SPI 引脚到渲染接口的模块分层2.1 三层架构谁在管寄存器谁在管像素打开代码包第一件事是看清它的分层方式。整套代码按驱动、渲染、应用三层组织每一层的边界非常清楚这也是我建议保留原始结构的原因。驱动层只负责跟 ST7789V2 芯片打交道SPI 时钟、数据线电平、片选拉高拉低、芯片寄存器读写。它对外暴露的接口很少就是Init、Clear、SetPixel这几个。渲染层在驱动层之上把“画一个字符”或“显示一张图片”翻译成一批像素坐标上的颜色写入。应用层就是 main.c负责业务逻辑——先初始化屏幕再画几行文字接着显示一张图片。这样分层的价值在于你要换一块不同分辨率的屏幕只需要改驱动层你要换 UI 风格只需要改渲染层应用层的调用关系几乎不用动。后面章节讲二次开发时所有改动都是围绕这三层的边界展开的。2.2 目录结构文件少但职责不重叠代码包的核心文件就这几个我按阅读顺序给你列一下project/ ├── inc/ │ ├── st7789v2.h │ ├── font.h │ └── image.h ├── src/ │ ├── st7789v2.c │ ├── font.c │ ├── image.c │ └── main.c └── Makefilest7789v2.c是整块屏最核心的代码SPI 写命令和写数据函数、初始化序列、像素设置、画矩形和画线这些基础图形。font.c处理字符渲染image.c处理图片渲染。main.c里的 demo 流程很直观就是教你把这些接口串起来跑通一遍。拿到代码后先别急着编译打开src/st7789v2.c顶端的宏定义区域那里是引脚分配。每个人的板子接线不一样这里必须改成自己实际的连接#define LCD_SCK_PIN 10 // SPI 时钟 #define LCD_SDA_PIN 11 // SPI 数据输出对应屏的 SDA/MOSI #define LCD_RES_PIN 12 // 复位引脚低电平有效 #define LCD_DC_PIN 13 // 数据/命令选择引脚 #define LCD_CS_PIN 14 // 片选引脚低电平有效 #define LCD_BL_PIN 15 // 背光控制可直接接高电平这些引脚定义决定了整个驱动能否跑起来。其中最容易接错的是 DC 引脚和 RES 引脚DC 必须接到 MCU 的 GPIO不能用硬件 SPI 自动控制RES 虽然也可以由 GPIO 控制但有些模块会在内部上拉这时你只需要初始化时给它一个低脉冲即可。背光 BL 如果不想用 PWM 调光直接接 3.3V 或通过三极管控制也行。2.3 初始化流程让屏幕点亮的命令序列每个 ST7789V2 驱动方案的核心都是一段初始化命令。这段代码最考验耐心因为一旦延时不足或命令顺序错误屏幕的表现就是黑屏或花屏。初始化分两步先做 GPIO 和 SPI 的准备再通过 SPI 命令完成屏幕内部状态的配置。void ST7789V2_Init(void) { // 1. GPIO 和 SPI 外设初始化 SPI_Init(SPI_SPEED_40MHz); // 硬件 SPI时钟 40MHz LCD_BL_H(); // 打开背光 // 2. 硬件复位RES 拉低再拉高低电平时间至少 20ms LCD_RES_H(); delay_ms(30); LCD_RES_L(); delay_ms(30); LCD_RES_H(); delay_ms(200); // 3. 软件复位与睡眠唤醒 ST7789V2_WriteCmd(0x01); // 软件复位 delay_ms(150); ST7789V2_WriteCmd(0x11); // 退出睡眠模式 delay_ms(120); // 4. 内存访问方向默认竖屏 RGB横屏常改成 0x60 或 0xA0 ST7789V2_WriteCmd(0x36); ST7789V2_WriteData(0x00); // 5. 像素格式0x05 表示 16bit RGB565每个像素两个字节 ST7789V2_WriteCmd(0x3A); ST7789V2_WriteData(0x05); // 6. 打开显示并清屏为白色 ST7789V2_WriteCmd(0x29); ST7789V2_Clear(0xFFFF); }这套序列里延时参数是最大的坑。复位后给到的 30ms 低电平时间0x01 之后的 150ms 等待0x11 之后的 120ms 等待都必须大于数据手册规定的最小值。很多翻车现场都是因为把 0x11 后的延时从 120ms 砍到 50ms 想提速结果屏幕开机随机花屏。0x36 的数值控制显示方向和 RGB/BGR 顺序改一个 bit 就能让横屏竖屏互相切换具体会在避坑章节展开。底层的数据传输是通过两个静态函数完成的它们用 DC 引脚区分“命令”和“数据”static void ST7789V2_WriteCmd(uint8_t cmd) { LCD_DC_L(); // 拉低 DC表示接下来发送的是命令 LCD_CS_L(); // 拉低片选选中屏幕 SPI_WriteByte(cmd); LCD_CS_H(); // 释放片选 } static void ST7789V2_WriteData(uint8_t data) { LCD_DC_H(); // 拉高 DC表示接下来是数据 LCD_CS_L(); SPI_WriteByte(data); LCD_CS_H(); }这里有个常见的低级错误有的初学者把命令和数据都写进同一个函数不控制 DC 引脚结果屏幕完全没反应。DC 的高低电平决定了 ST7789V2 把收到的字节解读成寄存器地址还是寄存器值这是驱动的命门。3. 核心实现走读字符和图片渲染的完整链路3.1 像素操作从 SetPixel 到窗口设置理解渲染前先看最底层的SetPixel。ST7789V2 内部有一块 240×320 的显存你不需要在 MCU 侧维护整张帧缓冲只需要把像素数据通过 SPI 送进控制器。标准做法是先设置一个显示窗口然后向窗口内连续写入像素颜色值。void ST7789V2_SetPixel(uint16_t x, uint16_t y, uint16_t color) { // 将显示窗口设置为单像素区域 ST7789V2_WriteCmd(0x2A); // 列地址设置 ST7789V2_WriteData(x 8); ST7789V2_WriteData(x 0xFF); ST7789V2_WriteCmd(0x2B); // 行地址设置 ST7789V2_WriteData(y 8); ST7789V2_WriteData(y 0xFF); // 写入 16bit 颜色先高 8 位再低 8 位 ST7789V2_WriteCmd(0x2C); ST7789V2_WriteData(color 8); ST7789V2_WriteData(color 0xFF); }这个函数的代价很高——每画一个点都要发送一次完整的窗口设置命令。以 240×320 全屏刷图为例如果逐点调用总共有 76800 次窗口设置每次多出 6 个字节的额外通信量。后面二次开发章节我会给出行缓冲方案把开销降到接近理论最小值。3.2 字符渲染字体表如何变成像素字符显示的逻辑和图片不同它不做任何解码而是查一张预先做好的 ASCII 字体表。字体表里每个字符保存了 8×16 点阵的位图数据每一行用一个 uint8 表示bit 为 1 的位置就是前景色。void ST7789V2_DrawChar(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bgcolor) { uint8_t idx c - 0x20; // 可打印字符从空格开始 const uint8_t *glyph font_table[idx]; for (int row 0; row 16; row) { uint8_t line glyph[row]; for (int col 0; col 8; col) { if (line (0x80 col)) { ST7789V2_SetPixel(x col, y row, color); } else if (bgcolor ! 0xFFFF) { ST7789V2_SetPixel(x col, y row, bgcolor); } } } }参数里idx c - 0x20是最容易踩坑的地方。ASCII 表中空格是 0x20所以把不可见的控制字符跳过用可打印字符的序号去索引字体数组。字体表默认覆盖 0x200x7F共 96 个字符每个 16 字节总共约 1.5KB 存储。bgcolor设置为0xFFFF时表示透明背景也就是不写背景像素这样两个字符叠加时不会互相遮盖。这段代码的运行逻辑可以拆成两层循环外层 row 决定纵向 16 行的顺序内层 col 用位运算0x80 col逐位判断当前像素是否为前景。外层慢、内层快和屏幕扫描方向一致所以视觉上字符不会变形。如果你用的是 12×24 或 16×32 的字体表只需要修改行列循环范围这个结构不用变。3.3 图片显示RGB565 数组与坐标映射图片显示比字符复杂在数据格式转换。屏幕需要的是 RGB565也就是每个像素两个字节红 5 位、绿 6 位、蓝 5 位。而普通 BMP 图片大多是 24 位真彩每像素三个字节。最省心的做法是先用图片转换工具把图片预处理成 RGB565 数组再放到代码里直接读取。void ST7789V2_DrawImage(uint16_t x, uint16_t y, const uint16_t *img, uint16_t w, uint16_t h) { uint32_t idx 0; // 图片数组的线性索引 for (uint16_t row 0; row h; row) { for (uint16_t col 0; col w; col) { ST7789V2_SetPixel(x col, y row, img[idx]); } } }这个函数的核心假设是传入的img数组已经按照“从左到右、从上到下”的扫描顺序排列好了 RGB565 数据。外层循环控制屏幕上的 y 坐标内层循环控制 x 坐标每取一个值就画一个点。idx是连续递增的保证数组读取顺序和屏幕坐标顺序一一对应。如果你拿到的是 24 位 BMP 原始数据需要自己做转换常见做法是#define RGB888_TO_RGB565(r, g, b) \ ((((r 3) 0x1F) 11) | \ (((g 2) 0x3F) 5) | \ ((b 3) 0x1F))这里有个 24bit 转 16bit 的精度损失红和蓝各取高 5 位绿取高 6 位因为人眼对绿色更敏感。 3和 2就是移除低位的多余数据。转换宏可以用在预处理脚本里也可以放在运行时逐个像素调用前者省时间后者省空间。4. 避坑指南四个高频显示问题的定位与修复4.1 花屏或雪花噪点现象屏幕能亮但满屏无规律噪点或者开机白屏持续几秒才恢复正常。原因初始化命令顺序不对或者复位后的延时时间不够。ST7789V2 对时序要求比较苛刻睡眠退出命令 0x11 之后必须等待至少 120ms某些批次的模块甚至需要 150ms 才稳定。复位引脚的低电平时间如果短于 20ms控制器可能没有完成内部状态清理上电后就会花屏。解决把硬件复位中的三段延时分别加大到 50ms、50ms、300ms软件复位 0x01 后的延时调整到 200ms。如果还花屏检查一下 SPI 时钟频率是否过高——我见过有人把时钟调到 80MHz初始化阶段直接黑屏降到 20MHz 后一切正常。初始化期间建议保守一点跑通后再逐步提速。4.2 字符乱码或字形错位现象屏幕能亮也能画点但打印字符时出现乱码、半个字形、位置偏移。原因字符索引算错了。上一章提到的c - 0x20依赖字体表按 ASCII 可打印字符排序。如果实际字体表是从 0x00 开始存的完整 128 个字符那索引就错位了你打印A时取到的可能是?的字形。解决先打印一个空格和一个A定位问题。如果空格正常但字母错位说明字体表本身就是从 0x00 开始的把索引改成idx c即可。如果所有字符都偏移检查是不是坐标算错了——比如 8×16 的字体字符间距计算应该用x 8而不是x 16。一个简单的验证方式在两个字符之间画一条竖线通过间距判断是不是每个字符占用的宽度算多了。4.3 颜色反色红色变蓝色现象图片和字符整体反色红色显示成蓝色绿色保持不变。原因0x36 寄存器的 BGR 位没有设置正确。ST7789V2 的 0x36 寄存器第 3 位控制 RGB 还是 BGR 顺序。很多初始化序列默认写 0x00如果你的模块内部排线是 BGR 顺序颜色就会整体对调。这是很典型的屏厂“相同型号不同接线”的坑同一家买的两块屏一块正常一块反色也不奇怪。解决将初始化里 0x36 的值改成 0x08 或 0x68然后重新编译烧录。如果改了反色状态变了但颜色仍不对再检查 RGB565 转换宏是不是把 R 和 B 的移位写反了。这个排查顺序很重要——先改寄存器再改代码不要把两件事混在一起试否则容易越改越乱。颜色不对的问题八成是硬件方向只有二成是软件转换方向判断错了会浪费大量时间。4.4 刷新慢、显示撕裂现象全屏刷一张图片需要几百毫秒拖动画面时出现横向撕裂或残影。原因逐像素方式下每个点都要发送 0x2A、0x2B、0x2C 三组命令传输效率被拉低了好几倍。240×320×2 字节的图片数据量约 150KB在 40MHz SPI 下理论传输只要 30ms但逐点发送实际耗时经常超过 300ms。再加上初始化没有配置 Tearing Effect 相关的同步参数控制器刷新和 MCU 写入不同步就容易出现撕裂。解决先把逐像素改成行缓冲一次 SPI 事务发送整行 480 字节。具体做法在下一章展开。撕裂问题则通过开启 ST7789V2 的 TE 输出同步或者让写入频率略高于面板刷新频率来缓解。对大多数应用来说先解决行缓冲撕裂现象会明显减轻因为连续传输模式下控制器内部 FIFO 利用率更高。5. 二次开发把这段驱动改造成自己的显示框架5.1 核心优化从逐像素升级到行缓冲加连续传输要在实际项目里用这块屏逐像素模式跑不了真业务。最简单的升级是把SetPixel从通信瓶颈中解放出来定义一个 240 宽的行缓冲先把一行的像素数据填满再一次 SPI 事务发出去。对 240×320 全屏刷新来说这能让耗时从数百毫秒降到几十毫秒级别。static uint16_t line_buf[240]; // 一行最多 240 个像素 void ST7789V2_FlushRow(uint16_t y, uint16_t len) { ST7789V2_WriteCmd(0x2A); // 列地址设为整行 ST7789V2_WriteData(0x00); ST7789V2_WriteData(0x00); ST7789V2_WriteData((len - 1) 8); ST7789V2_WriteData((len - 1) 0xFF); ST7789V2_WriteCmd(0x2B); // 行地址固定为 y ST7789V2_WriteData(y 8); ST7789V2_WriteData(y 0xFF); ST7789V2_WriteCmd(0x2C); // 连续写像素数据 LCD_DC_H(); LCD_CS_L(); for (uint16_t i 0; i len; i) { SPI_WriteByte(line_buf[i] 8); SPI_WriteByte(line_buf[i] 0xFF); } LCD_CS_H(); }这套写法把窗口设置从每像素做一次降为每行做一次整行数据通过循环连续发送同样的一屏数据量SPI 利用率高出一大截。如果 MCU 支持 DMA再把循环里的SPI_WriteByte替换成 DMA 链表发送CPU 几乎完全释放。改造时注意line_buf每个元素是 uint16_t写入时按照大端顺序——先高字节后低字节这对应 ST7789V2 对 RGB565 的接收顺序发反了颜色会偏。5.2 扩展方向中文显示和动态图片解码字符渲染的改造有两种思路。一是把字体表换成完整的 GB2312 或 UTF-8 编码的字库芯片通过 SPI 或 I2C 外挂读取二是用 PC 端工具把常用汉字预渲染成点阵数组和 ASCII 字体表合并。前者适合需要大量文字的场合后者适合内存紧张、只需要固定几个菜单词的场景。图片方面把 BMP 转换成 RGB565 数组的预处理思路可以扩展到 PNG 和 JPEG但运行时解压需要额外的解码库对 MCU 性能和内存都有要求一般放到带文件系统的方案里做。5.3 移植到新平台时的改动清单换平台时只需要改三个地方引脚宏定义、SPI_Init和SPI_WriteByte的实现、延时函数。这三处都在st7789v2.c的头部和底部渲染层和图片代码完全不用动。如果你要换的是另一块采用 ST7789V2 方案的屏幕保持 240×320 分辨率即可只改方向参数如果换成其他控制器则驱动层整体替换渲染层仍然可以复用。从那以后我每次在新板子上点屏都会强制先跑一遍初始化、写一行数据、刷一张测试图这三个步骤确认屏幕稳定了再动应用层逻辑。等你把这套代码在自己的板子上跑通了你会发现下一次遇到类似显示需求基本就是改引脚和改分辨率参数的事。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑