资讯动态

ST7789V STM32驱动实战:三种SPI接口与初始化序列避坑指南

发布时间:2026/10/14 21:03:07 来源:尧图企业网站定制
简介ST7789V 驱动例程以 STM32F429 为主控面向需要在小尺寸 TFT-LCD 上实现图形显示的嵌入式开发者。例程覆盖 SPI 三线/四线和 GPIO 模拟 SPI 等通讯方式初始化序列包含屏幕方向、像素格式、时序参数等关键命令并提供点绘、画线、填充、文本显示等基础 LCD 操作代码适合作为驱动移植与学习模板。压缩包共 250 个文件大小 10.09MB其中含 88 个 h 头文件与 77 个 c 源文件以 STM32 HAL 库、字体与显示驱动为主其余为工程配置、编译产物及说明文档。整个驱动过程详细展示了 SPI 通信时序、寄存器命令设置以及不同模式切换的实现思路工程已进行过测试多数问题已解决可根据具体硬件微调。已有 6688 人学习对从事嵌入式图形显示开发的工程师具有直接参考价值。获取后可在 STM32F429 工程中对照使用借助源码理解初始化命令流与屏幕控制逻辑同时通过编译产物快速验证驱动效果有效降低从零移植的调试工作量。1. 先别急着接屏ST7789V 例程里那三种 SPI 接口够你用一阵子A同学照着网上的例程点一块 240x320 的 TFTLCD线都按图接好了上电却只有白屏折腾两天才发现是初始化序列里忘了在 Sleep Out 后面加延时。这是很多人第一次用 ST7789V 的真实经历。好消息是这屏不缺资料真正的问题出在 SPI 接口的三种接法4线标准 SPI 带 DC 控制脚、3线 9bit 格式省一根 DC 线、以及不依赖 SPI 外设的模拟 GPIO 方式。这份 ST7789V STM32 驱动例程把三种方式都写全了拿到手就能对照自己的板子接线再把初始化序列一灌白屏花屏镜像这类问题大多能在半小时内定位。适合正在点亮某块未知模组的人以及想把驱动和 UI 分离的嵌入式入门者。2. 看懂三种 SPI 接法3线 9bit、4线 DC 脚和纯 GPIO 模拟怎么选接手任何一块带 ST7789V 的屏第一件事不是写代码而是确认模组引出了几根信号线。屏厂为了适配不同主板会把接口做成 4 线、3 线甚至带触摸的排线而同一颗驱动 IC 在这几种接法下对时序的要求完全不同。2.1 三种接法的本质区别DC 信号去了哪里ST7789V 的串行接口信号其实只有三根是必须的CS片选、SCL时钟、SDA数据。所谓 4 线模式是在这三根之外再加一根 DC 线用 DC 的电平来区分当前字节是命令还是数据DC 拉低表示命令拉高表示数据。这种方式最简单单片机裸机程序里只需要两个函数各管一个电平属于大部分例程默认的做法。3 线模式则把 DC 这根物理线省掉。片上虽然还是三根信号但 SCL 上跑的帧格式变成了 9 位最高位是 D/C 标志低 8 位才是真正的命令或数据。发送命令 0x11 时实际要输出的是 0x011 这 9 个 bit发送数据 0x00 时实际要输出的是 0x100。这带来的直接后果是STM32 的硬件 SPI 数据位宽只支持 8 位或 16 位没法直接发一个 9 位帧必须用 GPIO 模拟或者做特殊拼接。模拟 SPI 跟前两种不是并列关系而是实现方式上的区别。它用普通 GPIO 逐 bit 翻转来模仿 SCL 和 SDA理论上既可以跑 4 线时序也可以跑 3 线 9bit 时序。选择模拟方式的理由通常只有一个硬件 SPI 的引脚刚好被占用或者用户板子上的 SCL/SDA 接在了非复用引脚上。代价是刷屏速度明显下降但作为驱动调试和功能验证完全够用。判断手里的模组是哪种模式最靠谱的办法是看排线丝印。排线上有 DC、RS 或者 D/C 标注的就是 4 线只有 CS/SCL/SDA 三根且屏上没有任何标注跳线的基本就是 3 线 9bit 模式。有些转接板上焊了电阻来选择模式这时候需要看模组数据手册不能靠猜。2.2 时序参数ST7789V 吃的是模式 0 还是模式 3SPI 通信的时序由 CPOL时钟极性和 CPHA时钟相位共同决定。ST7789V 数据手册里明确支持模式 0 和模式 3两者的差异在于 SCL 空闲电平和数据采样边沿。绝大多数现有例程用的是模式 0也就是 CPOL0、CPHA0SCL 空闲为低数据在 SCL 上升沿被采样。模式CPOLCPHASCL 空闲电平数据采样边沿对应 HAL 配置模式 000低上升沿SPI_POLARITY_LOW SPI_PHASE_1EDGE模式 311高上升沿SPI_POLARITY_HIGH SPI_PHASE_2EDGE我一般建议先用模式 0 调通因为网上流传的例程十有八九是模式 0出了问题容易对照。使用 HAL 库时在 SPI 初始化结构体里把 ClockPolarity 设为 SPI_POLARITY_LOW把 ClockPhase 设为 SPI_PHASE_1EDGEDataSize 设为 SPI_DATASIZE_8BITFirstBit 设为 SPI_FIRSTBIT_MSB。如果屏幕全白且 SCL 波形看着正常再检查这两项是否写反了。时钟频率方面首次调试不要直接拉到最大。很多模组的 PCB 走线很短40MHz 也能跑但一旦用杜邦线连接20MHz 以上就会出现偶发花屏。我习惯从 4MHz 起步确认初始化正常后再逐步往上提每一步刷一张纯色图验证这种排查方式比一次到位稳定得多。2.3 初始化序列为什么顺序错了就白屏ST7789V 不是上电就能直接显示的必须按顺序写完一串寄存器。这个序列是驱动的地基三种 SPI 方式共用同一份命令表区别只在于发送函数不同。下面这份序列能点亮绝大多数 240x320 的 ST7789V 模组用到的是数据手册里的标准指令步骤命令参数说明10x01无软复位20x11无退出睡眠模式之后延时 120ms30x360x00扫描方向0x00 为默认40x3A0x55像素格式0x55 表示 RGB565 16bit50x2A0x00 0x00 0x00 0xEF列地址范围240 像素减 160x2B0x00 0x00 0x01 0x3F行地址范围320 像素减 170x29无开启显示步骤 2 后面的延时不是可选项。0x11 之后芯片内部电源和时钟需要时间稳定延时不够就会出现上电正常、复位后白屏的怪现象。步骤 5 和 6 的 0xEF 与 0x3F 都是按 240x320 计算出来的换不同分辨率的屏时这两个参数必须同步改否则显示区域会错位。3. 硬件 SPI 驱动实现4线发指令数据、3线拼 9bit 帧的代码细节把时序搞清楚后代码部分反而简单。这一章直接给可用的发送函数分别对应 4 线硬件 SPI 和 3 线 9bit 帧。工程里用哪个函数取决于你的屏是哪种接口。3.1 4线硬件 SPIDC 脚加两个发送函数搞定命令与数据4 线模式的核心是两个函数发送命令和发送数据。发送命令时先拉低 DC发送数据时先拉高 DCCS 在整个字节传输期间保持低电平。用 HAL 库实现如下void ST7789_WriteCmd(uint8_t cmd) { DC_LOW(); // 命令模式 CS_LOW(); // 选中屏 HAL_SPI_Transmit(hspi1, cmd, 1, HAL_MAX_DELAY); CS_HIGH(); // 释放片选 } void ST7789_WriteData(uint8_t *buf, uint16_t len) { DC_HIGH(); // 数据模式 CS_LOW(); HAL_SPI_Transmit(hspi1, buf, len, HAL_MAX_DELAY); CS_HIGH(); }这段代码里有几个细节决定成败。DC 电平必须在 CS 拉低之前设置好因为屏在 CS 有效边沿就会开始识别首个 bit 的状态顺序反了会让第一个字节被误判成命令或数据。CS 拉高的位置放在传输结束后不要提前释放否则最后一个字节可能没有被完整锁存。HAL_SPI_Transmit 的最后一个参数是超时时间HAL_MAX_DELAY 表示无限等待用在初始化阶段没问题但后续如果刷大块图像建议换成具体的超时值免得 SPI 异常时整个任务卡死。初始化 SPI 时把 DataSize 配成 8bitFirstBit 配成 MSB这两个在 HAL 初始化结构体里直接设置即可。发送纯色帧时可以先把一整行颜色数据填进 buffer 再调用一次 ST7789_WriteData减少函数调用开销。比如刷一块 240x320 的红色背景可以这样组织数据uint8_t line_buf[240 * 2]; // 一行为 480 字节 for (int i 0; i 240 * 2; i 2) { line_buf[i] 0xF8; // RGB565 红色高字节 line_buf[i 1] 0x00; // RGB565 红色低字节 } for (int y 0; y 320; y) { ST7789_WriteData(line_buf, sizeof(line_buf)); }3.2 3线 9bit 帧硬件 SPI 发不了只能拼位或模拟3 线模式的难点在于帧格式变成了 9 bit。STM32 的硬件 SPI 配置成 8 位时发不了 9 位配置成 16 位时又会在 SCL 上多产生 7 个时钟边沿屏根本不会认。常见做法是把 9 bit 拆成两段用 GPIO 逐位输出实现起来并不复杂static void ST7789_Send9Bit(uint16_t frame) { // frame bit8 为 D/C1 表示数据0 表示命令bit7:0 为有效内容 for (uint8_t i 0; i 9; i) { SCL_LOW(); if (frame (0x100 i)) { SDA_HIGH(); } else { SDA_LOW(); } SCL_HIGH(); } SCL_LOW(); } void ST7789_WriteCmd_3Line(uint8_t cmd) { ST7789_Send9Bit(cmd 0xFF); // 高位置 0命令 } void ST7789_WriteData_3Line(uint8_t *buf, uint16_t len) { for (uint16_t i 0; i len; i) { ST7789_Send9Bit(0x100 | buf[i]); // 高位置 1数据 } }0x100 这个掩码是 3 线模式的灵魂。发送命令 0x11 时frame 实际为 0x011bit8 为 0屏收到后知道这是命令发送数据 0x05 时frame 实际为 0x105bit8 为 1屏知道这是数据。逐位输出的顺序是 MSB first也就是先从 bit8 开始发发完 9 位后再把 SCL 拉低恢复空闲电平。这种做法本质上是模拟时序只不过借用 GPIO 直接操作所以时钟频率取决于 GPIO 翻转速度。STM32 的 GPIO 在高速翻转模式下通常能跑到几 MHz 到十几 MHz对初始化指令和显示静态画面来说足够。需要注意的点是3 线模式下每次写一整块图像都会逐 bit 循环CPU 占用率会比较高如果要刷动态内容建议换用 4 线模式加硬件 SPI。3.3 初始化主流程复位、写序列、开显示无论哪种接口上电后的驱动流程是一致的。先拉复位脚再执行初始化序列最后设置窗口并写入显示数据。代码结构如下void ST7789_Init(void) { // 复位引脚由 GPIO 控制 RST_HIGH(); HAL_Delay(10); RST_LOW(); HAL_Delay(10); RST_HIGH(); HAL_Delay(120); ST7789_WriteCmd(0x01); // 软复位 HAL_Delay(10); ST7789_WriteCmd(0x11); // 退出睡眠 HAL_Delay(120); ST7789_WriteCmd(0x36); // 扫描方向 ST7789_WriteData(0x00); ST7789_WriteCmd(0x3A); // 像素格式 ST7789_WriteData(0x55); // RGB565 ST7789_WriteCmd(0x29); // 开启显示 }复位脚的脉冲宽度是很多白屏问题的根源。有些例程里 RST 低电平只维持几十微秒这在某些模组上能点亮但换一块屏就失效。我习惯固定保持 10ms 以上复位完成后延时 120ms 等内部稳压器稳定。初始化序列里 0x11 之后的 120ms 延时不能省这是芯片从睡眠模式切换回正常模式的恢复时间。4. 模拟 SPI 驱动实现GPIO 逐 bit 时序与三组参数调优模拟 SPI 是调试阶段的救火队员。当 SPI 外设被其他设备占用或者屏的 SCL/SDA 接到了非复用引脚上时用 GPIO 模拟是唯一不需要改板子的方案。代价是速度慢但写寄存器、刷静态图都没问题。4.1 GPIO 模拟代码模式 0 时序拆开看模拟 SPI 的核心是按模式 0 的时序逐位输出。SCL 空闲为低先拉低 SCL再改变 SDA 电平最后拉高 SCL 产生上升沿屏在这个上升沿采样。整个循环跑 8 次就是发送一个字节。#define SIM_SCL_LOW() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_RESET) #define SIM_SCL_HIGH() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, GPIO_PIN_SET) #define SIM_SDA_LOW() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, GPIO_PIN_RESET) #define SIM_SDA_HIGH() HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, GPIO_PIN_SET) void SIM_SPI_WriteByte(uint8_t dat) { for (uint8_t i 0; i 8; i) { SIM_SCL_LOW(); if (dat 0x80) { SIM_SDA_HIGH(); } else { SIM_SDA_LOW(); } SIM_SCL_HIGH(); dat 1; } SIM_SCL_LOW(); }这段代码的节奏是SCL 拉低 - SDA 设置 - SCL 拉高。数据必须在 SCL 上升沿之前稳定所以 SDA 的变化要放在 SCL 拉高之前。dat 1 把下一个 bit 移到最高位保证每次循环都是先发 MSB。初始化 GPIO 时SCL 和 SDA 都要配置成推挽输出、速度选 GPIO_SPEED_FREQ_VERY_HIGH否则翻转速度跟不上显示会出现雪花。命令函数和数据函数跟 4 线硬件 SPI 的区别只在于底层字节发送方式。模拟方式同样遵循 DC 低命令、DC 高数据的规则把 SIM_SPI_WriteByte 替换掉 HAL_SPI_Transmit 即可。刷整屏时建议一次写一行和硬件 SPI 的方式一致减少反复调用命令函数的次数。4.2 三组调优参数速度、延时、缓冲长度模拟 SPI 的调优参数比硬件 SPI 更直观就三个地方第 1 个是 SDA 提前建立时间。SCL 拉低后不要立刻修改 SDA至少留几十纳秒让电平稳定下来。代码里的做法是 SCL 拉低后加一个空循环或 __NOP()时间大约一两个时钟周期对 72MHz 主频的 MCU 来说已经足够。第 2 个是 SCL 高电平持续时间。屏的采样发生在升沿SCL 拉高后至少要维持一个最小高电平时间否则屏端采样不稳定。对于 ST7789V这个时间在几十纳秒量级GPIO 翻转的开销本身就超过这个值所以不加延时也能工作。但如果用的是极慢速的模拟 IO 或者主频很低就需要在 SCL 拉高后加同样的小延时。第 3 个是单次写入的缓冲长度。模拟 SPI 发送一个字节要 8 次循环一次写 480 字节就要循环 3840 次。如果每次调用都带上函数调用和循环开销整体速度会更慢。我一般把一行的颜色数据拼好后用一次 for 循环连续调用发送函数实测效率比逐像素发送高很多。选型建议也很简单如果只是点亮屏、验证模块好坏模拟 SPI 完全够用初始化命令不到 100 字节几毫秒就跑完了。如果要显示动态波形或做简易 UI建议切换回硬件 SPI速度差距在一个数量级以上。我的经验是模拟方式刷一帧纯色大约在几十毫秒量级硬件 SPI 配合 DMA 能到几毫秒具体数值跟主频和 GPIO 速度有关。5. ST7789V 避坑手册白屏、花屏、偏移五个高频翻车记录写 ST7789V 驱动的过程中翻车最集中的就是下面这五个问题。它们不涉及复杂的电气原理全是时序、参数、格式这三类低级错误但每一个都足够让人排查一晚上。5.1 白屏复位脉宽不够加 0x11 延时不到位现象上电后屏幕全白背光亮命令发出去没有任何反应。用示波器量 SCL 和 SDA 波形能看到信号在跳但屏就是不起来。原因两个高频诱因。一是 RST 低电平脉宽太短有些例程为了省时间只拉低几微秒屏内部的复位逻辑没有完全复位寄存器状态处于未知。二是 0x11 退出睡眠命令之后没有给足延时就继续发后续命令芯片还在恢复电源内部状态后续写入的寄存器被丢弃。解决把复位流程改成固定顺序RST 拉高等 10ms拉低等 10ms再拉高等 120ms。0x11 之后强制延时 120ms0x29 开启显示后再延时 10ms。这套时序我试过多种模组没有一次白屏。5.2 花屏且颜色错乱0x3A 像素格式和填充数据不一致现象屏幕能亮但显示红色时偏紫显示白色时带条纹整个画面像被打乱了。原因0x3A 命令设置的像素格式和程序里填充的数据格式不一致。0x55 是 RGB565 16bit0x66 是 RGB666 18bit。如果把 0x3A 配成了 0x66却用 16bit 数据填充屏会把每 18 个 bit 当成一个像素来解析两个字节的数据被拆得七零八落。解决初始化里 0x3A 统一配 0x55同时保证颜色数组是 2 字节一个像素。检查填充代码时注意有些例程会把颜色值直接按 16bit 写入这在 8bit SPI 上没问题但如果填充函数内部做了高低字节交换颜色就会变成 BGR 顺序看起来像红蓝对调。5.3 显示内容偏移CASET/RASET 没有按模组坐标修正现象屏幕能正常显示但第一行像素出现在屏幕中间或者左边有十几列被截掉右边出现黑边。原因ST7789V 虽然是 240x320 的驱动能力但具体模组在设计时可能只在面板的某个窗口区域引出了可见物理像素。有些屏需要从第 0 行开始写有些则需要偏移 40 行才能对准。这里的“偏移”来自屏厂的面板设计不是驱动芯片的事。解决把 0x2A 和 0x2B 的参数分别改成实际窗口。比如屏幕出现垂直偏移 40 像素就把 0x2B 的起始行从 0x00 0x00 改成 0x00 0x28结束行相应加上 40。注意偏移只影响窗口设置不影响像素颜色数据本身。5.4 图像镜像或倒置MADCTL 的扫描方向位没调对现象初始化能过但显示的文字是镜像的或者上下颠倒。原因0x36 命令里的 MADCTL 寄存器位控制行列扫描方向和 RGB/BGR 顺序。默认值 0x00 是左上到右下但不同模组的 FPC 绑定时引脚顺序可能不同导致实际显示方向相反。解决直接改 0x36 参数。0x40 是行反向0x80 是列反向两个组合起来可以转 180 度。如果发现颜色又对调了再看 bit3 的 BGR 位置 1 相当于把 RGB 换成 BGR。一般镜像问题就是这三个 bit 的组合试四次就能找到正确值。5.5 刷大图时偶发雪花时钟频率超过走线承受能力现象初始化正常小面积更新正常一刷全屏就出现随机雪花点位置不固定。原因硬件 SPI 时钟频率过高时SCL 和 SDA 信号在长走线上发生振铃屏端采样到错误的电平。用杜邦线连接时尤其明显因为杜邦线本身就是个天线高频边沿会互相干扰。解决把 SPI 时钟分频降下来。我习惯从 4MHz 开始测试稳定后再逐步升到 8MHz、12MHz、18MHz。如果是批量产品直接用 PCB 短走线18MHz 以上通常没问题开发板调试阶段建议 4MHz 到 8MHz 之间换取稳定性。6. 点亮之后DMA 刷帧与 FPS 自检的落地姿势初始化稳定后驱动工作才完成一半。刷屏速度决定了这块屏能不能用于实际显示而 DMA 是 STM32 刷 ST7789V 性价比最高的优化手段。用 DMA 代替阻塞式 SPI 发送只需要把 HAL_SPI_Transmit 换成 HAL_SPI_Transmit_DMA再开启 SPI 的发送完成中断。关键点是把整行或整帧数据交给 DMA 控制器搬运CPU 不用等每个字节发送完成可以继续准备下一行数据。void ST7789_WriteData_DMA(uint16_t *buf, uint16_t len) { DC_HIGH(); CS_LOW(); HAL_SPI_Transmit_DMA(hspi1, (uint8_t *)buf, len); // 在 SPI 发送完成中断里拉高 CS进入下一帧 }用 DMA 时要注意一个细节CS 拉高的时机不能放在 HAL_SPI_Transmit_DMA 函数之后立即执行因为 DMA 是异步的函数返回时数据可能还没发完。正确做法是在 SPI 的 TXE 中断或 DMA 传输完成回调里拉高 CS。HAL 库里对应的是 SPI_TxCpltCallback在这里补上 CS 释放逻辑最稳妥。帧率验证用最简单的定时器计数就行。记录刷一帧前后的系统 tick算出耗时再换算 FPS。配合一个滚动显示函数可以直观看到不同方案的速度差异。uint32_t tick_start HAL_GetTick(); for (int y 0; y 320; y) { ST7789_WriteData_DMA(line_buf, 240 * 2); } uint32_t tick_cost HAL_GetTick() - tick_start; printf(frame cost %d ms, FPS %d\n, tick_cost, 1000 / tick_cost);我一开始总觉得 DMA 麻烦一直用阻塞发送刷图直到有一次需要刷动态图表CPU 占用率飙到 90% 才发现这条路必须走。从那以后每次点亮新屏都强制走一遍“模拟点亮 - 硬件 SPI 提速 - DMA 刷帧”的流程先验证时序再追求速度。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑