news 2026/9/9 22:53:19

ST7789V2驱动详解:从零点亮1.3寸IPS屏,字符图片显示全记录

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ST7789V2驱动详解:从零点亮1.3寸IPS屏,字符图片显示全记录

简介:面向嵌入式开发者和电子制作爱好者,这份ST7789V2驱动示例代码展示了在SPI/I2C接口液晶屏上显示字符与图片的完整实现。压缩包一共包含2个文件,分别为C源文件与头文件,整体只有6KB,代码量非常精简。其中C源文件承担了控制器初始化、像素坐标设置、颜色数据转换等实际函数,头文件则声明了相关接口与常用常量,这样的分工便于快速整合到现有工程中。在功能方面,代码覆盖初始化时序、ASCII字符点阵映射、图片数据到RGB565格式的转换、命令与数据的区分发送,以及显示刷新等关键环节,并加入了基础错误处理逻辑。作者亲测可用,目前已有3880人学习下载;对于正在调试小尺寸LCD屏或希望了解ST7789V2底层驱动的开发者,这份资源提供了清晰的代码框架,可直接借鉴并适配到智能手表、微型显示屏等项目中,无论是学习LCD驱动原理还是进行快速原型验证,都能从中获得实用参考。 ST7789V2 这颗驱动芯片,玩嵌入式屏幕的人应该都不陌生。手里这块 1.3 寸 240x240 的 IPS 屏就是它,网上资料不少,但很多代码贴出来要么缺两行、要么初始化时序不对,真正能拿到手直接编译烧录就能跑的不多。我这次把字符显示和图片显示的代码都调通了,从零开始到全屏刷图,前后折腾了一个晚上,整理出来给大家一个可以直接抄作业的版本,同时把每一步背后的原理也讲清楚,免得你只是代码跑通了,下次换个屏幕换个 MCU 又抓瞎。

如果你手里刚好有 ST7789V2 的模块,或者正准备买一块来玩,这篇文章能帮你跳过大部分我踩过的坑。无论你用 STM32、ESP32 还是 GD32,驱动思路完全一样,只要我会把寄存器级别的代码给出来,你移植的时候照着改底层 SPI 接口就行。这里多说一句,我全程用的是标准 SPI 四线模式驱动,不是八线并口,这也是市面上大多数这类小板子常用的接法。

1. 屏能亮起来,事情就成了一半:硬件准备与接线

1.1 先确认你手里的屏确实是 ST7789V2

这听起来像废话,但真不是。淘宝上很多 1.3 寸 IPS 屏和 0.96 寸的屏,外观长得几乎一模一样,背面丝印乱七八糟,有的写的是 ST7789V2,有的写 V3,甚至有写 ST7735 的。我第一次拿到板子,看丝印写的是 TFT_V2,想当然以为就是 ST7789,结果读 ID 才发现是 ST7735S,初始化序列完全不是一回事,白折腾了一个多小时。

判断方式有两个。第一,直接看模块背面的 IC 丝印,ST7789V2 会在芯片上印一行很细的字,眼神好的拿放大镜能认出来。第二,更可靠的办法是写代码读 ID,ST7789 的读 ID 指令是 0x04,正常读回来三位数据,我这块屏读出来是 0x85,对应 ST7789V2。如果你代码烧进去,屏幕有反应但花屏严重,建议先做这一步确认,别在错误的驱动上浪费时间。

1.2 接线清单与电平匹配

标准 SPI 屏幕,一共八个引脚,分别是 VCC、GND、DIN(也叫 SDA/MOSI)、CLK(SCK)、CS(片选)、DC(数据/命令选择)、RST(复位)、BLK(背光)。

屏幕引脚STM32 示例ESP32 示例说明
VCC3.3V3.3V部分模块标 5V,但 IO 电平必须 3.3V
GNDGNDGND共地,必须连
DINPA7 (SPI1_MOSI)GPIO23主机输出
CLKPA5 (SPI1_SCK)GPIO18SPI 时钟
CSPA4GPIO5片选,低有效
DCPA3GPIO17高电平为数据,低电平为命令
RSTPA2GPIO16复位,低有效
BLKPA1GPIO4背光控制,高电平点亮

接线里面最容易被忽略的是 BLK 脚。有些模块的 BLK 引脚悬空时默认就是亮的,但更多模块必须拉高,否则整个屏幕是黑的,但你量电压又发现屏的电源、命令都正常,非常迷惑。我建议不管什么模块,BLK 脚直接接一个 GPIO 并初始化为高电平,顺手还能做背光调节。另外一个坑是 VCC 供电,1.3 寸这块屏满背光电流大概 30-50mA,单片机 3.3V 直接供电没问题,但如果同时给 ESP32 的 WiFi 和外设供电,建议单独用 LDO,避免背光导致的电压跌落。

再一个是 SPI 速率。ST7789V2 的极限 SPI 时钟在 30MHz 左右,理论上可以跑很高,但我实测在 STM32F103 上把 SPI 预分频设成 2(36MHz)时偶尔会出现花屏,降到 4(18MHz)就非常稳定。ESP32 上跑到 40MHz 也没事,这跟具体的 MCU 的 SPI 输出质量和杜邦线长度都有关系。如果你用杜邦线连接,建议不要超过 20MHz;用 PCB 短连线的话可以适当提高。

2. 初始化序列里没有一行是多余的

2.1 复位时序与基础初始化

ST7789V2 上电之后,第一件事是复位,而且时序有讲究:拉高 RST,等至少 10ms,拉低至少 10ms,再拉高,然后等 120ms。这个 120ms 是让内部 DCDC 和振荡器稳定下来的时间,一定不能省。我一开始图省事,延时写短到 50ms,屏幕偶发不亮或者显示错乱,后来老老实实恢复 120ms 就再没出过问题。

复位完成后,依次发送初始化命令序列。下面这个是我亲测可用的完整序列,每一步的寄存器作用我会在下一小节解释。

void ST7789_Init(void) { // 复位时序 ST7789_RST_HIGH(); delay_ms(10); ST7789_RST_LOW(); delay_ms(10); ST7789_RST_HIGH(); delay_ms(120); // 软件复位 ST7789_WriteCmd(0x01); delay_ms(150); // 退出睡眠模式 ST7789_WriteCmd(0x11); delay_ms(200); // 像素格式:RGB565 ST7789_WriteCmd(0x3A); ST7789_WriteData(0x05); // 扫描方向:左上角开始,默认 RGB 顺序 ST7789_WriteCmd(0x36); ST7789_WriteData(0x00); // 关闭颜色反转(部分模块需要 0x21 开启反转,否则偏色) ST7789_WriteCmd(0x20); // 电压相关设置,不同模块略有差异 ST7789_WriteCmd(0xB0); ST7789_WriteData(0x00); ST7789_WriteCmd(0xB1); ST7789_WriteData(0x00); ST7789_WriteCmd(0xB2); ST7789_WriteData(0x00); // 开启显示 ST7789_WriteCmd(0x29); delay_ms(50); }

这套序列发完之后,屏幕应该处于点亮状态,如果你这时候往显存里写数据,就能看到对应的色块。我用的是标准四线 SPI,命令和数据通过 DC 脚区分:DC 拉低发命令,拉高发数据。这个逻辑贯穿后面所有的显示操作。

2.2 三个关键寄存器:像素格式、扫描方向和颜色反转

很多人的屏幕能亮但显示不对,问题基本集中在三个寄存器上。 第一个是 0x3A,像素格式。我们传 0x05 表示 RGB565 格式,也就是每个像素 16 位,红 5 位、绿 6 位、蓝 5 位。有些屏默认是 18 位模式(0x06),你以 16 位数据去刷屏,颜色会很怪,最常见的是偏绿或偏紫。做字符和图片显示之前,先把这一步焊死。

第二个是 0x36,MADCTL 控制寄存器。这个寄存器的 bit7、bit6、bit5 分别控制行方向、列方向和行列交换,bit3 控制 RGB 还是 BGR 序。我传 0x00,表示从左上角开始正常扫描、RGB 颜色序。如果你的屏幕内容整体旋转了 90 度或者左右镜像,改的就是这个寄存器的值。比如你想旋转 180 度,可以试试 0xC0;如果显示出来的图片颜色中红色和蓝色互换了,就把 bit3 置 1,改成 0x08。这个值没有绝对标准,跟屏幕模组的排线接法有关,同一颗芯片封装成不同模块,可能也要调。

第三个是颜色反转控制,指令是 0x20 和 0x21。大部分 IPS 屏用 0x20(关闭反转)是正常的,但确实有一部分模组,内部电路设计得比较奇怪,必须用 0x21 打开反转,否则你往显存写白色,屏幕显示黑色,整屏颜色跟负片一样。这个没法从芯片规格书里直接判断,只能实测,我当时第一次用这块屏就是这种情况,后来加了一行 0x21 立刻正常。你调试的时候,如果其他都正常、颜色反了,优先试这个。

3. 字符显示:十六字节一个字的魔法

3.1 点阵字模是怎么来的

字符在屏幕上的本质就是一堆点的亮和灭。对于 ASCII 字符,最常见的是 8x16 的点阵,也就是每个字符用 8 列、16 行来表示。每一行用一个字节来存,这一字节的 8 个 bit 对应 8 列像素,bit 为 1 就点亮,为 0 就灭。这样一个字符正好 16 个字节,看起来是"十六字节一个字"。

平时我们用取模软件,比如 PCtoLCD2002 或者 Image2Lcd,把想要的字体生成数组。这里要特别注意一个地方:取模方向。不同的取模软件、不同设置,生成的数据排列顺序完全不同,常见的有"横向取模"和"纵向取模"两种。我在本文的代码里用的是每一行一个字节、从左到右排列的方式,也就是"横向取模,高位在前"。如果你从网上找的字模拷贝过来,发现字符左右颠倒或者整个倒过来,不要怀疑代码,先去确认取模方向是否和绘制函数匹配。

比如大写的字母 A,在 8x16 点阵里,前面若干行可能是 0x00,中间开始出现 0x18、0x24、0x42 这些值,把这些字节按顺序填进去,屏幕就会拼出一个 A。字模文件本质上是把人的字形翻译成机器能读的数字,理解这一点,你就算离开取模软件,自己手工构造一个字符也能办到。

3.2 绘制函数实现与取模方向匹配

下面这套代码是我在实际项目里用的,包含画点、画字符、画字符串三个层级。画点是最底层的操作,字符和图片最终都会拆成一个一个像素点来写入。

void ST7789_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { ST7789_WriteCmd(0x2A); ST7789_WriteData16(x0); ST7789_WriteData16(x1); ST7789_WriteCmd(0x2B); ST7789_WriteData16(y0); ST7789_WriteData16(y1); } void ST7789_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { ST7789_SetWindow(x, y, x, y); ST7789_WriteCmd(0x2C); ST7789_WriteData16(color); }

画字符的代码,假设字模数组是 font8x16[95][16],从空格符号(0x20)开始存:

void ST7789_DrawChar(uint16_t x, uint16_t y, char ch, uint16_t color, uint16_t bgColor) { const uint8_t *glyph = font8x16[(uint8_t)ch - 0x20]; for (uint16_t row = 0; row < 16; row++) { uint8_t data = glyph[row]; for (uint16_t col = 0; col < 8; col++) { if (data & (0x80 >> col)) { ST7789_DrawPixel(x + col, y + row, color); } else if (bgColor != color) { ST7789_DrawPixel(x + col, y + row, bgColor); } } } }

注意这里 data 是当前行的 8 个像素状态,从最高位开始判断。判断为 1 就画前景色,为 0 就画背景色。如果你不想画背景色,可以把背景色参数改成透明模式跳过,但注意屏幕刷新是有痕迹的,不填充背景色的话,字符移动时会留下残影。

字符串就是依次调用单个字符绘制函数,同时让 x 坐标偏移 8 像素:

void ST7789_DrawString(uint16_t x, uint16_t y, const char *str, uint16_t color, uint16_t bgColor) { while (*str) { ST7789_DrawChar(x, y, *str++, color, bgColor); x += 8; if (x >= 240 - 8) { x = 0; y += 16; } } }

你要显示中文字符,原理完全一样,只是字模不再只有 16 字节,而是 16x16 点阵占 32 字节甚至更大。中文字的取模参数需要对得更仔细,因为不同字库、不同编码取模出来的字节序差异更大。我建议把常用汉字做成字库数组,直接放进代码里,而不是临时从 SD 卡读,这样实时性高得多。当然,MCU 的 Flash 如果紧张,像 STM32F103C8T6 只有 64KB,塞几个中文汉字没问题,塞整套字库就够呛了。

4. 图片显示:RGB565 才是主战场

4.1 从图片到数组

单片机上显示图片,和电脑上完全不是一回事。电脑上你给个 PNG 路径就能读,单片机上你需要把图片转成一串裸的像素数组。这串数组在 FLASH 里占一个坑,显示的时候把位图数据按顺序写进屏幕显存即可。

拿 Image2Lcd 这个工具举例,它是做这个用的经典工具。打开一张图片后,有几项设置必须先确认。输出类型选"C 语言数组"。最大宽度和高度设为 240 和 240,和屏幕分辨率一致,也可以小于屏幕分辨率,显示的时候通过开窗让它居中。颜色格式,也就是像素格式,必须选 16 位真彩色(RGB565)。扫描方式是"水平扫描"。最后生成的是一个 uint16_t 数组,数组里每个元素就是一个像素的颜色值,比如 0xF800 是红色,0x07E0 是绿色,0x001F 是蓝色。

为什么强调 RGB565,因为 ST7789V2 初始化时如果设了 0x3A=0x05,屏幕就是按 16 位颜色读取数据,数据线的 D0-D15 对应颜色值的各位。你在取模时就转成 RGB565,后面 MCU 就不需要再做任何格式转换,直接写进显存就行。如果在 PC 上处理好格式,MCU 端只负责搬运,这能省下大量计算时间。如果交给 MCU 现算,比如从 SPI Flash 读一张 RGB888 的图再转 RGB565,性能会差非常多,不推荐。

4.2 开窗写数据的高效刷新

显示图片的核心在于"开窗"操作。前面 SetWindow 函数里的 0x2A 和 0x2B 就是设置 X 方向和 Y 方向的显示窗口。ST7789 的显存是按行列组织的,设置好窗口之后,连续写 0x2C,数据就会一行一行自动填充到这个窗口内。窗口开得越小,写的数据量越少,刷新越快。

void ST7789_ShowImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *img) { ST7789_SetWindow(x, y, x + w - 1, y + h - 1); ST7789_WriteCmd(0x2C); for (uint32_t i = 0; i < (uint32_t)w * h; i++) { ST7789_WriteData16(img[i]); } }

这段代码看起来很简单,但对小白有个隐藏的优化点:在 for 循环里逐次调用 WriteData16,每调用一次就要操作一次 GPIO 或 SPI 发送寄存器,开销非常大。更好的做法是打开 DMA,让 SPI 外设自动从数组搬数据。在 STM32 上可以这样改:先开启 SPI 的 DMA 发送通道,然后把数组首地址和长度告诉 DMA,之后就什么都不用管了。240x240 全屏一共 57600 个像素,每个像素 2 字节,也就是 115200 字节的数据量,对 STM32F103 的 Flash 来说直接放 const 数组已经很大了,如果放不下,后续再说解决办法。

要注意一个细节:如果你的图片数组是从 Image2Lcd 生成的,它默认包含了图片尺寸信息,甚至还可以设置"包括头文件",生成一个带 IMAGE_HEADER 的格式。我建议生成的时候去掉这些额外的结构,只要纯色数据数组,这样 MCU 代码里使用更自由。

4.3 大图跑不动的出路

很多玩 STM32 的朋友会卡在一个很现实的问题上:Flash 不够存图片。一块 240x240 的 RGB565 图片就要 115200 字节,STM32F103C8T6 的 Flash 才 64KB,一张图都放不下。有两条路可以走:

一是缩小图片显示区域。把图片预裁剪成 128x128 或 160x128 之类的尺寸,配合开窗函数,在有需要的位置显示。我实际项目里做菜单界面,常用图标都是 48x48 或者 64x64,一张图几 KB,放上几十个都没问题。

二是把图片放到外部 Flash,比如 W25Q64 这类 SPI NOR Flash。图片数据可以直接用 ST-Link 配合烧录软件写进去,MCU 上电后从 Flash 里周期性读出数据再送入屏幕。这个做法会多一倍的搬运开销,但可以让一张板子存几百张图片。如果你的屏幕分辨率更大,比如 320x240 的屏,一张图 150KB 起,外部 Flash 几乎是唯一出路。

全屏刷新耗时上可以做一个预估:SPI 时钟按 20MHz 算,每次时钟传 1 bit,传输 115200 字节 = 921600 bit,理论最快 46ms,实际加命令开销和 GPIO 翻转,整屏刷新大概 70-80ms。这个速度做静态界面完全够用,但如果要刷视频流畅度(要求 30fps 即每帧小于 33ms),就需要把 SPI 提到 40MHz 以上,并且配合 DMA 双缓冲才可能达到。

5. 实测记录与常见故障快速定位

5.1 本次实测环境

我是在 STM32F103C8T6 上做的验证,SPI1 驱动,时钟 18MHz,屏幕是淘宝几块钱一块的 1.3 寸 240x240 IPS 模组,背面丝印 TFT_V2。复位、初始化、画字符、显示全屏图片,整个过程一次跑通后连续运行了一周没出过问题,包括反复断电上电、复位以及温度变化的场景。字符显示用的是上面给的自绘字体方案,因为只用了数字和 26 个字母,16 字节一个字符,总共 95 个字符才 1520 字节,Flash 压力非常小。图片我放了 5 张 64x64 的图标加一张 240x240 全屏图,Flash 也够用。

另外我还顺手在 ESP32 上测试了同一套初始化序列,只把底层 SPI 发送函数替换成 ESP32 的 SPI 库,其他部分完全没动,同样正常。这说明这套驱动代码跨平台移植性没问题,问题只可能在 SPI 底层接口上。这个结论对想从 STM32 换到 ESP32 的朋友应该有用。

5.2 几个让我查了半天的坑

第一个坑是屏幕一直黑屏。现象很诡异,代码和接线都检查过,逻辑分析仪看 SPI 波形也有数据,但屏幕就是黑的。后来发现是 RST 引脚没初始化好,复位信号给的太随意。ST7789 上电时如果 RST 引脚悬空,内部上电复位有时候不可靠,必须由 MCU 主动拉一次低电平复位时序。

第二个坑是全屏刷新时出现水平条纹,就像刷新到一半卡住一样。我最初以为是代码问题,反复检查发现是 SPI 发送频率太高,36MHz 时钟下这种杜邦线连接很容易发生信号串扰,降到 18MHz 立刻好了。如果你用的是面包板加长杜邦线,尤其容易出现这个问题,建议先从低速开始排查。

第三个坑是字符左右镜像。取模软件生成的字模,有的默认是"纵向取模",有的默认是"横向取模",我在一个网上找的字库里没有确认方向,结果字母全是反的。这个问题最坑,因为它看起来像硬件扫描方向错乱了,实际只是数据位序不对。你把生成字模的软件设置改成"横向取模,高位在前",或者把绘制函数里的判断顺序反过来,问题就解决了。

第四个坑是图片颜色偏蓝偏紫。原因就是我前面讲的那个 0x36 寄存器的 bit3。不同模块这个值不一样,需要晚点试。你把 0x00 改成 0x08,颜色就正常了。另外如果整体颜色像负片,记得检查 0x21 和 0x20。

第五个坑,也是很多新手容易忽略的,就是数组溢出。你在 C 语言里定义一个 const uint16_t image_240x240[57600],编译会报 Flash 不够或者数据过大。这时候优先检查你有没有把 MCU 的 Flash 大小选对,我见过有人在 Keil 里芯片型号选成 STM32F103C6(32KB Flash),结果一张图放进去直接编译不过。换个思路,也可以把全屏图用 RLE 压缩算法处理一下,简单的图形界面图能压到原来的三分之一。

5.3 关于后续扩展的一点想法

代码跑通之后,你还可以做很多事:把最小系统移植到自己的 PCB 上,或者加上触摸芯片做一个完整的人机交互界面。我最近就在做一个小型环境监测站,屏幕上显示温湿度曲线和几个状态图标,字符和图片混排,这套驱动当底层渲染引擎非常合适。如果后续需要显示动态波形或者进度条,建议直接把画线、画矩形、画圆的函数也实现一遍,基础都是 DrawPixel,稍微封装一下就行。祝你也早日点亮自己的屏幕,有问题欢迎在评论区交流。

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

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

胡广书《数字信号处理》Matlab代码实战:FFT、滤波器与谱估计全解析

简介&#xff1a;配套胡广书《数字信号处理理论、算法与实现》的Matlab代码及参考文献合集&#xff0c;面向正在系统学习数字信号处理课程的高校学生与自学者。内容围绕经典数字信号处理与统计数字信号处理两大体系&#xff0c;覆盖离散时间信号与系统、Z变换、傅里叶变换、数字…

作者头像 李华
网站建设 2026/9/9 22:49:32

中大型企业全链路服务:2026主流智能客服系统盘点

中大型企业全链路服务&#xff1a;2026主流智能客服系统盘点 一、市场背景&#xff1a;全链路智能服务成为企业刚需 2026年&#xff0c;企业客户服务正经历从“成本中心”向“价值中心”的深刻转变。随着大模型技术的成熟与AI Agent&#xff08;智能体&#xff09;的落地&#…

作者头像 李华
网站建设 2026/9/9 22:46:07

ABAQUS竹材切削仿真模型搭建全流程:从材料本构到切屑分离

在竹材加工这个行业摸爬滚打久了&#xff0c;你会发现一个挺尴尬的事实&#xff1a;车间里的工艺参数&#xff0c;很大程度上是靠老师傅的手感调出来的。同一个竹片&#xff0c;换台机器、换个刀盘&#xff0c;切削力和刀具磨损完全两个样&#xff0c;谁也不太说得清到底差在哪…

作者头像 李华
网站建设 2026/9/9 22:46:01

Redis 服务启动与停止全攻略:从原理到实操的完整指南

Redis 服务启动与停止&#xff0c;你真的搞明白了吗做后端开发这些年&#xff0c;Redis 大概是除了 MySQL 之外用得最频繁的中间件了。但有意思的是&#xff0c;很多人在 Redis 装好之后&#xff0c;连“怎么正确启动、怎么优雅停止”都没完全搞明白。我见过不少同事在服务器上…

作者头像 李华
网站建设 2026/9/9 22:42:54

基于双层优化的电动汽车时空调度Matlab实现与KKT求解方法

做电动汽车时空调度这个方向的同行&#xff0c;应该都有同感&#xff1a;最让人头疼的往往不是问题本身的物理含义&#xff0c;而是模型建完之后要么算不动&#xff0c;要么算出来的结果根本没法指导落地。我前两年做大型电动汽车时空调度项目时&#xff0c;就踩过这个坑——第…

作者头像 李华