公司动态
STM32H743硬件SPI驱动ST7789:DMA加速实践
简介本资源是一套面向嵌入式开发工程师与STM32进阶学习者的硬件SPI驱动实战方案聚焦STM32H743微控制器高效驱动ST7789彩色LCD显示屏的核心技术实现。资源完整覆盖CubeMX外设配置、HAL库SPI底层驱动、ST7789初始化序列、命令/数据双模式传输机制、帧缓冲管理及DMA加速优化等关键环节解决高速彩屏显示中时序精准控制与CPU负载均衡的实际难题。压缩包为ZIP格式共包含源码工程文件、配置生成代码、驱动接口函数及注释详尽的主控逻辑整体大小19.52MB结构清晰便于快速集成到自有项目中。目前已有4353人学习下载提供可直接编译运行的完整工程框架、关键寄存器配置说明及典型显示效果验证逻辑助开发者跳过协议调试陷阱快速实现稳定、高刷的SPI LCD显示功能。 先说结论这块板子是STM32H743屏是一块常见的240x320 TFT驱动IC是ST7789接口是标准的4线SPI。很多人拿到这种屏第一反应是用GPIO模拟SPI慢慢刷但既然都上了H743这种主频480MHz的M7芯片不去把硬件SPI用起来就太浪费了。这篇文章就把我从CubeMX配置到驱动代码、再到DMA加速和排错的全过程复盘一遍所有代码都是实测跑过的适合刚拿到ST7789模块、想用H743硬件SPI正式点亮它的人参考。要玩转这个组合核心就三件事一是搞懂ST7789这个驱动芯片的脾气二是把H743的硬件SPI配置到位三是处理好命令/数据和DMA传输的节奏。这三件事拆开看都不算复杂但合在一起就容易翻车。下面我按实际开发顺序从方案选型到问题排查一条线讲完。1. 项目背景与整体方案选型1.1 为什么是H743硬件SPIST7789先说为什么要用H743来驱动ST7789。很多人觉得STM32F103就可以跑这种小屏没错但F103的SPI1最高也就是18MHz刷一帧240x320的RGB565数据理论时间就是153600字节乘以8再除以18M大概68毫秒实际加上命令开销直接奔着100毫秒去了1秒只能刷10帧左右做简单菜单都勉强更别说动画和滑动效果。H743这边情况完全不一样。它的SPI外设最高可以跑到100MHz以上实际驱动ST7789这种屏的极限受限于屏幕本身ST7789的数据手册上SPI时钟典型上限在62MHz到80MHz这个区间取一个保守值50MHz一帧数据大约24.6毫秒就能传完配上DMA之后整个刷屏过程CPU几乎不用等。再加上H743有1MB以上的RAM可以直接开大块显存这就为后面跑LVGL这类GUI库留足了空间。再说为什么选SPI接口而不是8080并口或者FSMC。答案很简单模块引脚就那么多选它就是因为方便。现在市面上大量小尺寸TFT模组都做成了SPI接口6针或者7针或者8针SCK、MOSI、CS、DC、RES、BLK、VCC、GND一路SPI加几个GPIO就完事。好处是接线少、占IO少、布线压力小坏处是速度上限不如并口但在240x320这种分辨率下50MHz SPI配合DMA已经能跑出40帧左右的刷新率对绝大多数界面应用完全够用。1.2 方案选型背后的几个关键判断实际动手前我做了几个选择这里逐个说理由。第一硬件SPI还是软件模拟SPI如果你只是点亮一块屏显示个静态图片软件模拟完全可行。但如果要刷新动画、显示中文菜单、跑LVGL软件模拟是扛不住的。H743主频高但软件模拟SPI本质上就是GPIO翻转一个字节8个bit每个bit要经过拉高SCK、输出数据、拉低SCK这一串操作即使按每条指令2个周期算50MHz下模拟一个bit也得几十纳秒起步实际测速通常在1-2Mbps左右比硬件SPI慢几十倍。第二用不用DMA答案是必须用。H743的硬件SPI本身有发送FIFO但连续往DR寄存器里塞数据依然会占CPU刷一张240x320全屏图要153600次写操作每次写还要等待TXE标志这个等待时间累积起来非常可观。用DMA把数据从内存搬到SPI外设整帧传输期间CPU可以干别的等传输完成中断一下就行。这也是整个方案的性能核心。第三屏的SPI是4线还是3线ST7789支持两种SPI模式一种叫4线SPI在标准SPI基础上多一根DC线用来区分命令和数据另一种叫3线SPI没有DC线而是在每个传输字节前面多塞一个bit用来表示命令还是数据。绝大多数模块做成了4线SPI我的这块屏也是所以本文后面的代码都按4线SPI处理。如果你拿到的是3线模块写命令和写数据的方式会有区别这一点在购买模块时一定要先确认清楚。提示模块上的丝印如果写着“SPI TFT 240x320”大概率是ST7789或ILI9341两者初始化序列不一样拿到屏之后第一件事是确认驱动IC型号直接看模组背面的丝印或者问卖家要规格书不要凭猜。2. 硬件连接与电路设计要点2.1 引脚分配与接线实录我用的H743开发板引脚分配如下模块引脚功能连接H743引脚备注VCC电源3.3V3.3V需要并联电容GND地GND共地必须SCKSPI时钟PB13 (SPI1_SCK)串22Ω电阻MOSISPI主出从入PB15 (SPI1_MOSI)串22Ω电阻CS片选PB12 (SPI1_NSS/GPIO)本文用软件控制DC数据/命令选择PC6GPIO输出RES复位PC7GPIO输出BLK背光PC8GPIO输出/PWM注意H743的复用功能映射和F1系列不一样同样的SPI1_SCK在不同型号上可能映射到不同的引脚具体以参考手册的Alternate Function映射表为准。我这边用CubeMX图形化配置一遍确认IO复用为AF5之后再看生成的代码省得自己对着寄存器翻。SCK和MOSI线上我各串了一个22Ω电阻这个电阻不是可有可无的。SPI时钟线在信号跳变时如果走线较长会形成振铃严重时导致采样错误。串电阻能够抑制过冲同时不会明显影响信号完整性。实测在50MHz SPI时钟下不串电阻的波形边沿有明显振铃串了22Ω之后干净很多。CLK线串电阻一般取22Ω到33ΩMOSI线同样处理阻值不用太纠结22Ω是通用首选。2.2 电源、背光和复位设计电源这块很多人不在意但翻车率很高。ST7789正常运行时的电流在几十毫安级别如果背光亮起来、全屏刷白色图片瞬时电流能到100毫安甚至更高。H743开发板上的3.3V稳压器如果还供着其他外设容易掉压导致花屏或者复位。我给屏单独并联了一个100uF钽电容加0.1uF陶瓷电容尽量靠近模块的VCC引脚实测对花屏问题有明显改善。背光引脚BLK有些模块默认上拉会有背光但强烈建议用GPIO控制这样开机时先关闭背光等初始化完成之后再点亮避免上电瞬间屏上显示乱码。如果要做呼吸灯效果可以把BLK接到定时器PWM输出H743的PWM能力很强这就是顺手的事。RES复位引脚的典型接法是接到MCU的GPIO因为ST7789的复位时序要求拉低至少10us再拉高这个用RC电路做虽然也能行但不好控制。我建议直接软件控制初始化的时候先RES拉低20ms、拉高20ms稳定之后再发命令这样每次上电都能保证屏幕状态可预期。还有一个细节如果MCU的复位引脚没有接到屏的RES那么在MCU下载程序复位时屏不会跟着复位两次下载之间屏幕会一直保持上一次的状态看起来像“卡住”了这个问题后面会专门讲。2.3 硬件片选与软件片选的实战选择STM32H743的SPI支持硬件NSS片选也就是由SPI外设自动拉低CS理论上可以减少一条GPIO控制代码。我在这个项目里最终用的是软件片选原因有两个。第一LCD这类设备的CS控制有时候需要和数据流严格配合。比如DMA传输模式下如果硬件NSS在SPI关闭时自动拉高CS而DMA数据还没发完就可能出现CS提前释放导致最后几个字节丢失。软件片选虽然多写一行代码但时序完全可控。第二不少ST7789模块的CS引脚已经默认拉低或者直接接地如果你的模块是这种设计硬件片选反而没法用只能靠软件控制。实际操作中我的做法是初始化时先把CS拉高每次传输数据前拉低CS传输完成后拉高CS。这里有一个性能优化细节如果连续刷大量数据比如填充一块矩形区域CS不需要每字节都切换只需要在整个传输序列开始前拉低、全部发完后拉高。尤其是配合DMA时CS的持续拉低时间要覆盖整个DMA传输周期不能中间释放。提示如果你的板子上CS接到了SPI_NSS引脚且没接外部上下拉NSS引脚默认会有一个内部上拉这时候用硬件NSS是顺的。如果用了软件片选记得把NSS引脚的复用功能去掉配置成普通GPIO输出避免外部信号和GPIO输出打架。3. ST7789驱动原理与初始化时序3.1 ST7789到底是个什么芯片ST7789是一个单芯片TFT LCD驱动控制器最大支持240x320分辨率内部有GRAM显存每个像素用18位RGB数据也就是每个像素的R、G、B各6位总共262K色。SPI接口写进去的数据经过内部数据转换后在屏幕上显示。理解ST7789的SPI写时序有个关键点它区分命令和数据靠的是DC引脚电平。DC为低时SPI上的字节解释为命令DC为高时解释为数据。命令本身可以带参数比如设置窗口范围的命令需要跟上几个参数参数也是数据这个时候DC要拉高。还有一个容易混淆的点写色彩数据时ST7789支持RGB565和RGB666两种格式通过寄存器设置。SPI接口下最常见的做法是RGB565也就是每个像素两个字节。RGB565格式下第一个字节的高5位是R、中间6位是G、低5位是B发送顺序是先高字节后低字节。我在代码里定义了对应的颜色宏#define RGB565(r, g, b) (((r 0xF8) 8) | ((g 0xFC) 3) | ((b 0xF8) 3))这个宏的作用是把8位RGB转成16位RGB565。要注意的是如果你在LCD上看到颜色反了或者偏色严重大概率是高低字节顺序不对或者颜色格式配置错了后面排查章节详细说。3.2 初始化序列为什么不能随便抄ST7789的初始化序列网上代码成千上万但直接抄很危险。原因是ST7789有多个版本ST7789V和ST7789V2以及ST7789VW等版本初始化寄存器存在差异。尤其是小分辨率模块比如240x240的屏很多厂家做了裁剪某些寄存器的默认值不一样直接套用会白屏或者显示偏移。我用的初始化序列整理如下这组序列经过实测在240x320和240x240两种分辨率下都能正常显示static void ST7789_Init(void) { ST7789_RESET_LOW(); delay_ms(20); ST7789_RESET_HIGH(); delay_ms(20); ST7789_WriteCmd(0x01); // Software Reset delay_ms(20); ST7789_WriteCmd(0x11); // Sleep Out delay_ms(120); ST7789_WriteCmd(0x3A); // Interface Pixel Format ST7789_WriteData(0x55); // RGB565, 16bit ST7789_WriteCmd(0x36); // Memory Data Access Control ST7789_WriteData(0x00); // RGB方向, 默认 /* 根据屏幕安装方向需要调整 */ ST7789_WriteCmd(0x2A); // Column Address Set ST7789_WriteData(0x00); ST7789_WriteData(0x00); ST7789_WriteData(0x00); ST7789_WriteData(0xEF); // 240-1 ST7789_WriteCmd(0x2B); // Row Address Set ST7789_WriteData(0x00); ST7789_WriteData(0x00); ST7789_WriteData(0x01); ST7789_WriteData(0x3F); // 320-1 ST7789_WriteCmd(0x21); // Display Inversion On, 有些模块必须打开否则颜色反相 ST7789_WriteCmd(0x29); // Display On }这段代码中0x3A设置像素格式为0x55即RGB565这一条必须和后续代码的像素格式保持一致。0x36控制扫描方向默认0x00是从左上角开始如果你的屏显示镜像了改这个寄存器的bit5、bit6、bit7就能调整方向这比改坐标代码方便得多。3.3 窗口设置屏幕为什么能局部刷新ST7789的GRAM本质上是一块连续的显存它的写入行为像一个自动递增的指针。要往某个区域写像素必须先告诉它列的起始和结束地址、行的起始和结束地址这个操作叫设置窗口Set Window。窗口设置由两条命令完成0x2A设置列地址范围0x2B设置行地址范围。设置完成之后连续写入的像素数据会按从左到右、从上到下的顺序填充到窗口内填满窗口后地址自动回到起始位置。窗口设置的一个常见坑是ST7789芯片本身是320行但如果你用的是240x240的屏行地址范围必须设置成0到239。如果直接沿用320行的例程屏幕下半部分会显示乱码或者偏移。我手头这块屏是240x320但很多淘宝款是240x240两者的行地址设置不同购买时一定问清楚分辨率。为了封装好窗口操作我写了一个基础函数void ST7789_AddrWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { ST7789_WriteCmd(0x2A); ST7789_WriteData(x0 8); ST7789_WriteData(x0 0xFF); ST7789_WriteData(x1 8); ST7789_WriteData(x1 0xFF); ST7789_WriteCmd(0x2B); ST7789_WriteData(y0 8); ST7789_WriteData(y0 0xFF); ST7789_WriteData(y1 8); ST7789_WriteData(y1 0xFF); ST7789_WriteCmd(0x2C); // Memory Write }设定窗口后紧接着写入的像素数据会自动填充。这个函数是后面所有绘制操作的基础无论是画点、画矩形还是显示图片都要先调用它。4. H743硬件SPI配置实操4.1 CubeMX中的关键参数配置与解释用STM32CubeMX配置H743的SPI1核心参数就几个但每个都有讲究。我在CubeMX里的配置如下参数名称配置值说明ModeTransmit Only Master只发不收简化配置Hardware NSS SignalDisable用软件片选NSS不做硬件控制Data Size8 BitsST7789按字节收发First BitMSB FirstST7789要求先传高位Prescaler按需配置下面单独讲Clock Polarity (CPOL)Low空闲时SCK为低Clock Phase (CPHA)1 Edge第一个边沿采样CRC CalculationDisabledLCD用不到CRC关于SPI模式ST7789的数据手册上SPI时序图采样边沿默认是上升沿CPOL0、CPHA0即模式0可以工作CPOL0、CPHA1也就是模式1在某些模块上也能工作。实际测下来我用的这个模块在模式0下最稳定如果你照着我的配置发现数据错位尝试把CPHA改成1再试。网上不少IL9341例程用的是模式0两个芯片的SPI时序要求其实差别不大所以模式0是首选。波特率分频这里需要算一笔账。H743的SPI1挂在不同时钟树上在CubeMX里通过Clock Configuration可以直观看到SPI1时钟源来自PCLK2我这边配置出来PCLK2是100MHz。如果Prescaler选2SPI时钟就是50MHz正好在ST7789的安全范围内。如果选1就是100MHz理论更快但ST7789不一定扛得住而且SPI时钟过快时信号质量容易劣化实测在较长杜邦线下会花屏。所以我最终选择了50MHz。4.2 写命令和写数据底层函数硬件SPI配置好之后底层最关键的两个函数就是写命令和写数据。区别只在于DC引脚的电平。代码很简单void ST7789_WriteCmd(uint8_t cmd) { ST7789_DC_LOW(); ST7789_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, HAL_MAX_DELAY); ST7789_CS_HIGH(); } void ST7789_WriteData(uint8_t data) { ST7789_DC_HIGH(); ST7789_CS_LOW(); HAL_SPI_Transmit(hspi1, data, 1, HAL_MAX_DELAY); ST7789_CS_HIGH(); }这里有几个容易忽略的细节。第一CS的拉低和拉高要包围整个字节传输过程不要先拉高CS再等待传输完成。HAL_SPI_Transmit是阻塞发送返回时数据已经发送完毕所以CS在返回之后再拉高是安全的。第二DC电平必须在CS拉低之前设置好。因为ST7789在CS下降沿采样DC电平如果先拉CS再改DC可能被误判为命令或者数据。我一开始就是先CS拉低再DC切高结果写数据偶尔被识别成命令屏上出现随机线条排查了很久才发现是顺序问题。第三HAL_SPI_Transmit有超时参数我传的是HAL_MAX_DELAY因为硬件片选由软件控制不会出现总线被占的情况传个最大值省心。如果你用了操作系统建议改成有界超时避免极端情况下死等。4.3 DMA传输配置与FIFO的配合单个字节用HAL_SPI_Transmit没问题但刷全屏就不能这么干了。满屏240x320的RGB565数据是153600字节逐字节调用SPI发送函数每次都有函数调用开销和标志位等待实测大约要1.2秒这个速度完全不可接受。必须上DMA。H743的SPI1 DMA请求挂在DMA1或者DMA2上具体看CubeMX自动生成。配置方式是在CubeMX里面给SPI1添加TX DMA请求然后设置DMA方向为MemoryToPeripheral模式为Normal数据宽度设为Byte内存地址递增。这里有个关键点DMA的数据宽度要和SPI的数据宽度一致。如果SPI配置成8位DMA也必须是8位否则数据会错位。有些教程为了省DMA请求次数会把SPI配置成16位传输一次DMA传两个字节这个优化可以做但要先把SPI的数据帧格式改成16位底层的命令和数据发送函数也要相应调整不然整段代码全是bug。DMA发送的实现我封装了一个函数void ST7789_DMA_SendData(uint8_t *data, uint32_t len) { ST7789_DC_HIGH(); ST7789_CS_LOW(); HAL_SPI_Transmit_DMA(hspi1, data, len); // DMA传输完成中断里做 CS_HIGH }DMA传输完成中断里做收尾void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi hspi1) { ST7789_CS_HIGH(); } }注意CS拉高必须在DMA完全传输结束之后否则最后一个字节容易被吞掉。HAL库里SPI的DMA传输完成中断是在最后的字节移出移位寄存器之后触发这一点SPI和UART的DMA回调时机不太一样SPI相对安全可以直接在回调里操作CS。提示DMA模式下记得设置DMA的传输完成中断并且回调函数里不要再调用ST7789_WriteCmd之类的函数否则中断嵌套容易出问题。正确的做法是只设置一个标志位主循环检查到标志再处理后续绘制操作。4.4 帧率估算50MHz SPI到底能跑多快算一笔账240x320分辨率的屏RGB565一帧数据是240 * 320 * 2 153600字节50MHz SPI理论传输时间是153600 * 8 / 50000000 ≈ 24.6毫秒。加上命令开销、CS切换、DC切换、DMA启动等实测一帧全屏填充大约在30毫秒左右也就是33帧每秒。这个帧率对大多数GUI足够滑动菜单和动画效果都算流畅。如果刷屏时还需要同时处理触摸、按键、网络等任务DMA的好处就体现出来了。因为整帧数据传输期间CPU基本空闲可以做其他事情。不过要注意DMA占用的是内存带宽如果同时跑外部的SDRAM或者其他DMA可能出现带宽竞争H743的CPU和DMA总线架构比较复杂但240x320这种数据量级完全不用担心。5. 驱动代码实现与显示进阶5.1 画点、填充矩形和清屏有了窗口操作和DMA发送绘制基础图形就非常简单了。以填充矩形为例先设置窗口然后连续发送颜色数据void ST7789_FillRect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { uint32_t width x1 - x0 1; uint32_t height y1 - y0 1; uint32_t pixelCount width * height; ST7789_AddrWindow(x0, y0, x1, y1); ST7789_DC_HIGH(); ST7789_CS_LOW(); for (uint32_t i 0; i pixelCount; i) { uint8_t hi color 8; uint8_t lo color 0xFF; HAL_SPI_Transmit(hspi1, hi, 1, HAL_MAX_DELAY); HAL_SPI_Transmit(hspi1, lo, 1, HAL_MAX_DELAY); } ST7789_CS_HIGH(); }上面这段代码能跑但效率很低循环里做了两次HAL调用每一像素都检查SPI状态。更优的做法是先把颜色填充到一个缓冲区再一次性DMA发送。对于全屏填充可以开一个4KB的局部缓冲区先填满然后循环用DMA发送每次发4KB直到发完。实测这种方式比逐像素发送快好几倍。清屏其实就是填充整个屏幕为指定颜色代码上直接复用FillRect传全屏坐标。注意清屏颜色用黑色还是白色取决于场景如果界面是浅色主题白色0xFFFF更合适如果做深色OLED风格黑色0x0000。5.2 显示ASCII字符和中文的原理与实现显示字符的本质是“用像素拼字形”。最简单的做法是取模。ASCLL字符常用的英文5x8点阵中文常用16x16点阵。每个字符预先按行排列的位图保存成数组显示时逐字节解析有1的位置画前景色有0的位置画背景色。ASCII字符的存储方式通常是每个字符占一个固定大小的数组比如8x16字体就是每个字符16个字节每字节8个bit对应一行。显示时计算出每个像素在窗口中的坐标再用FillRect或者直接写GRAM。因为字符绘制通常是小区域用窗口设置逐像素写GRAM的方式就够了不一定要上DMA。中文显示稍微麻烦一点因为汉字数量太多不能随随便便全量放入单片机Flash。常用方案是只把需要用到的汉字取模比如“温度 湿度 时间 日期”这几个字每个字一个32字节数组存到一个常量数组里编译进Flash用的时候查表。如果要做完整的全字库就得外挂NorFlash或者SD卡加载字库文件到内存再查表这个思路在后面扩展LVGL时很有用。取模工具有很多我用过PCtoLCD2002和Image2Lcd。Image2Lcd更适合图片PCtoLCD2002取字符点阵更方便。取模时参数设置要注意点阵格式选阴码还是阳码、逐行还是逐列必须和代码的解析方式匹配。我习惯用“阴码、逐行、高位在前”这样显示函数只需要按bit7到bit0的顺序一一判断即可。5.3 显示图片RGB565数据的直接搬运显示16位BMP或者RGB565图片本质上就是把图片的数据数组直接通过SPI写到GRAM。前提是图片的数据格式和屏幕设置一致。用Image2Lcd把图片转成C语言数组时选择输出RGB565、16位真彩色、扫描方向保持默认生成的数组直接用DMA发送即可。需要注意一个方向问题如果图片显示出来是左右镜像或者上下颠倒不要急着改代码坐标。先调整0x36寄存器里的扫描方向位比如把0x00改成0xA0或0xC0通常是旋转90度或者镜像。这个比修改图片数据高效得多。图片体积方面240x320的RGB565全屏图片数组大小是153600字节H743的Flash足够放好几张但如果想放几十张就得考虑外扩Flash或者从SD卡读取。H743的SDMMC接口和高主频在这里又有优势读取速度比F103快很多。5.4 本地缓冲和区域刷新的取舍刷屏性能的终极优化方向是“局部刷新”和“双缓冲”。局部刷新的含义是界面变化只是某个区域比如一个数值从23变成24那么只需要重绘这个区域不需要全屏刷新。实现方式就是上面一直说的AddrWindow把窗口设置成数字所在的矩形区域只更新那部分。双缓冲则是开一块和屏幕分辨率一样大的RAM程序先在RAM中绘制所有内容再一次性把整块RAM刷到屏幕。H743的内存足够大240x320的16位色缓冲只要153600字节完全没问题。双缓冲的好处是界面不会出现残影、撕裂代价是多一次内存拷贝和刷屏时间。如果跑LVGLLVGL本身就要求提供显存缓冲区H743这种大内存芯片配套非常舒服。我实际测试过单纯用DMA刷一帧约30毫秒如果加上双缓冲绘制和拷贝整体大概40毫秒也就是25帧左右。对大多数界面应用完全足够。6. 常见问题与排查技巧实录6.1 白屏、黑屏和花屏的快速定位白屏是最常见的问题通常意味着屏幕没有收到任何有效数据或者复位不彻底。排查顺序我按优先级排先测供电量VCC是否稳定3.3V特别是刷亮色图片时电压会不会被拉低再看复位时序RES有没有拉低过拉低时间是否足够实在怀疑就手动在初始化代码开头强制RES拉低100ms再拉高最后检查SPI时钟有没有输出用示波器或者逻辑分析仪看SCK引脚初始化代码跑完应该能看到时钟脉冲。黑屏和白屏的原因略微不同。黑屏通常是背光没亮或者显示是纯黑色先量BLK引脚有没有高电平如果BLK接了PWM检查PWM有没有正常输出。还有一种黑屏是初始化序列走到一半卡住比如延时时间不够ST7789的Sleep Out命令之后要等待120ms以上这是芯片内部电源稳定和OSC起振的时间只等几毫秒很容易黑屏。花屏或者显示乱码的原因通常是数据格式不匹配。最常见的是SPI模式不对CPOL/CPHA配置成了模式2或者模式3导致采样边沿不对数据被错位读取。其次是RGB565高低字节顺序错误显示画面整体偏色且颜色混乱。最后是行地址范围不对比如屏幕实际只有240行代码却设置成320行屏幕下部会出现重复或者乱码。表格整理一下现象大概率原因排查方法白屏复位不到位/无数据/SPI无时钟量RES、SCK、VCC黑屏背光未开/初始化卡死量BLK、检查延时花屏/乱码SPI模式错误/颜色字节序反调整CPOL/CPHA、试RGB字节交换画面偏移窗口设置错误/分辨率不匹配检查行地址范围、0x36方向颜色反相缺少0x21反色命令加写0x21闪烁/残影刷新方式不当/电源不稳优化局部刷新、并联电容6.2 DMA刷新时CS操作与数据完整性的坑DMA刷新时CS和DC的时序配合是我踩得最深的一个坑。最初我是先调用DMA发送然后在回调里才去调ST7789_WriteCmd来拉高CS结果导致下一次传输时CS状态不对或者DMA还没发完CS就提前释放最后表现为刷新一次画面后屏幕出现一行错位。正确做法是在DMA发送开始前就把DC拉高、CS拉低让ST7789进入数据接收状态DMA发送完成回调里第一件事就是拉高CS告诉ST7789这一轮数据已经结束了。注意不要在回调里做其他复杂操作只是置标志位即可。否则中断服务函数太耗时会影响下一次DMA启动。另外一个坑是DMA传输的缓冲区生命周期。如果你给DMA传一个局部数组的地址而数组在DMA还没完成时就超出了作用域或者被其他代码修改了那么显示出来的数据就会出现随机花屏。解决办法是把待发送的数据放在静态数组或者全局数组里并且DMA完成之前不要修改这块内存。我写了一个带标志位的发送函数确保上一次DMA完成后再开始下一次发送。代码思路volatile uint8_t dma_busy 0; void ST7789_DMA_SendDataBlocking(uint8_t *data, uint32_t len) { while (dma_busy) { // 等待上一次DMA完成 } dma_busy 1; ST7789_DC_HIGH(); ST7789_CS_LOW(); HAL_SPI_Transmit_DMA(hspi1, data, len); } void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi hspi1) { ST7789_CS_HIGH(); dma_busy 0; } }6.3 刷新速度上不去的真正瓶颈50MHz SPI刷屏速度上不去常见瓶颈并不在SPI外设本身而在数据传输链路的前半段。第一个瓶颈是数据源。如果用SD卡或者外部Flash存储图片读取速度可能只有SPI Flash的几MB/s成为瓶颈。解决方法是用DMA双缓冲读下一块数据的同时发送当前数据把读取和发送流水线化。第二个瓶颈是内存到SPI外设的总线传输。H743的DMA传输速率很高理论上远超过50MHz SPI的需要但如果同时还有大量中断在抢占总线比如定时器中断、串口中断DMA的传输效率会被拉低。解决办法是给DMA设置高优先级或者调整中断优先级让DMA尽量不被干扰。第三个瓶颈是刷新策略。如果每次更新界面都是全屏刷新那就算SPI跑满也就30帧上下。如果只刷新变化区域比如只有一个小图标在动帧率可以轻松上百。所以从应用层控制刷新区域比在驱动层疯狂优化要有效得多。这也是“局部刷新”策略永远是嵌入式GUI性能第一要素的原因。6.4 关于“下载程序后屏幕不更新”的坑这个坑非常典型程序下载后屏幕还是上一次的内容看起来像屏幕死机了。原因很简单MCU复位了但ST7789没有复位屏幕还保持着上一次的GRAM内容。如果初始化代码没有用GPIO控制RES脚或者RES脚没接好ST7789就不知道MCU已经重新开始继续沿用旧状态初始化命令进去后可能被忽略或者执行到一半。解决方法是确认RES引脚确实由MCU控制并且在初始化开头做一次完整的复位时序。我顺便在代码里加了一个“全屏清屏”的步骤初始化完成后强制填充一次黑色这样无论屏幕之前是什么状态都会被恢复到已知状态不会出现旧画面残留的错觉。另外如果下载程序时调试器复位了芯片而ST7789的RES脚没有接MCU复位信号同样会碰到这个问题。最保险的做法是把屏的RES脚连接到MCU的复位电路上这样MCU复位时屏幕也跟着复位。但MCU复位线和RES之间不要直接用导线直连要看一下两者的电平要求最好通过一个10k电阻连接避免互相干扰。6.5 屏幕方向调整和触摸坐标的联动问题最后说一个和屏幕方向有关的细节这个在做成产品时很容易忽略。当你的屏幕旋转了90度比如竖屏改横屏不仅是显示内容需要旋转触摸屏的坐标轴也要跟着旋转。否则会出现“指东打西”的诡异效果。ST7789的0x36寄存器可以控制扫描方向它实际上控制了GRAM的写入顺序。不同的扫描方向下同一个像素坐标对应的屏幕物理位置不同。如果写了触摸驱动触摸坐标转换函数里的X、Y交换或者取反必须和这个寄存器保持一致否则触摸和显示对不上。我的一贯做法是先把0x36的方向固定下来然后在整个工程里统一使用一个坐标转换宏所有显示坐标和触摸坐标都经过这个宏统一转换不要在驱动层到处打补丁。这样后面换屏幕方向只改一个宏就全变了排查问题也容易得多。写在最后的实践体感这块H743驱动ST7789的项目做完之后我最大的感受是H743的硬件SPI加DMA驱动小尺寸SPI屏是性价比和开发效率都很高的组合。硬件SPI学起来不复杂一旦把DMA、片选时序、缓冲管理这几个关键点理顺后面无论是刷图、跑LVGL还是做小型HMI都非常顺手。ST7789本身也是一颗很经典的驱动IC它的初始化序列和绘制流程搞明白之后再去看ILI9341、GC9A01这些类似芯片会发现套路都差不多换型号的成本很低。如果你正在做类似的项目我建议动手前先把屏的规格书和对应例程的初始化序列对一遍确认分辨率、驱动IC版本、SPI模式这三点再开写代码。这三处只要错一个后面就得在排错上花好几倍的时间。我是真真切切走了这条弯路过来的把过程整理出来希望你能少踩几个坑。本文还有配套的精品资源点击获取