完整指南)
前阵子帮朋友调一个基于STM32的小项目本来只是打算加一块1.8寸TFT-LCD显示状态数据结果一头扎进软件SPI的坑里出不来。这块屏的驱动芯片绝大多数是ST7735S分辨率128x160接口走SPI网上例程一抓一大把但真到自己接线、写驱动、调时序的时候各种问题全冒出来了。尤其是资料里几乎都是硬件SPI的Demo真正讲清楚软件SPI怎么选引脚、时序怎么写、初始化序列那些命令到底在干什么的少之又少。这篇文章就把我用软件SPI驱动1.8寸TFT-LCD的完整过程整理出来从选型理由到底层代码到调试踩坑给准备在STM32上接这块屏的朋友做个参考。内容不挑具体型号F103和F4系列都适用重点是思路和实现不是光抄代码。1. 为什么用软件SPI驱动1.8寸屏接口选型的真实考虑1.1 这块屏的基本参数与典型应用场景1.8寸TFT-LCD是DIY圈子里非常常见的小屏很多模块上印着ST7735S少部分是ST7735R或者ST7735V。分辨率为128x160用RGB565格式表示一个像素需要2字节所以一帧画面数据量是128×160×2 40960字节大约40KB。这个体量放上世纪你可能觉得很大但对现在的STM32来说压力不大最普通的STM32F103C8T6都有20KB RAM只要不整帧缓存在内存里用串行方式一字节一字节发资源占用很低。它在实际项目里常出现在这几个位置温湿度计界面、电压电流显示面板、小型菜单系统、简易波形屏甚至有些人拿它当调试信息的替代串口屏。前两种场景基本是静态或低频刷新软件SPI完全能扛住就算要刷一个动画帧率也就每秒几帧做做状态切换动画还能接受。1.2 硬件SPI和软件SPI怎么选我的判断依据凡是接触过STM32的开发板都知道STM32自带多个硬件SPI外设理论上速度能达到18MHz甚至更高刷屏会快得多。但硬件SPI有一个绕不开的问题引脚通常是固定的。比如F103的SPI1固定走PA5、PA6、PA7SPI2固定走PB13、PB14、PB15。一旦项目里其他传感器或者模块已经占用了这些引脚或者PCB布线上这几个引脚被堵死了硬件SPI这条路就断了。软件SPI的本质是用GPIO根据SPI时序协议手动翻转电平好处就是引脚完全自由——你可以把SCLK放PA1把MOSI放PA4把CS放PB0只要代码里改一下宏定义随时能重新分配。对于产品原型阶段、多传感器复用项目、以及不想为显示功能专门重画PCB的情况这是一个极大的灵活性优势。对比项硬件SPI软件SPI通讯速度最高可达18MHz/36MHz实测约0.5~2MHz取决于代码CPU占用低发送由外设接管高每bit都由CPU翻转引脚限制固定在特定GPIO任意GPIO可指定实现难度需配置SPI外设寄存器掌握GPIO操作即可稳定性时序稳定不易受干扰受中断和主频影响适用场景刷图、动画、视频静态显示、低频界面刷新我的建议是如果你的项目引脚不冲突优先用硬件SPI省心省力但如果引脚紧张、或者你正在做验证板不想被硬件束缚软件SPI是完全值得掌握的方案。128x160的分辨率决定了它不像320x240的屏那么依赖速度软件SPI刷一屏不过几百毫秒对绝大多数显示任务来说够用了。2. 引脚与接线除了SCLK/MOSI之外还要处理的细节2.1 模块引脚定义速查1.8寸TFT模块的引脚数量一般在7到9个之间不同卖家接口顺序可能不同但信号本质是一样的。常见的8引脚版本定义如下引脚名方向作用VCC电源3.3V供电严禁接5VGND电源地与STM32共地CS输入片选信号低电平有效RESET输入复位信号低电平有效DC输入命令/数据选择低电平发命令高电平发数据SDA / MOSI输入串行数据输入主机发数据到屏幕SCLK输入串行时钟上升沿采样数据BLK / LED输入背光控制高电平点亮注意这个屏没有MISO引脚因为它是单工写操作ST7735S基本只能由主机写入数据不需要读回。看到有的模块引出了MISO或NC那个通常没用悬空即可。2.2 供电、背光和电平匹配的几个雷区先说供电。这些1.8寸模块绝大多数额定工作电压是3.3V直接把VCC接到STM32板的3.3V输出上就行。如果你图省事接到5V短时间内可能没烧但长期跑下来DC-DC电路或者面板驱动芯片发热严重轻则显示错乱重则芯片报废。背光BLK引脚要留意。有的模块在板子上已经放了限流电阻和驱动三极管这时BLK直接接3.3V就能点亮有的模块BLK只是一路LED阳极需要你自己串一个几十欧姆的限流电阻再把另一端接3.3V。怎么判断看模块丝印如果旁边有标注“BLK”且外观像一个小电阻网络一般可以直接接如果不确定先用万用表量一下BLK到VCC之间的阻值如果有几百欧姆说明板载电阻有了如果没有就要外接。电平方面STM32的GPIO输出3.3V直接驱动没问题。如果你的主控是5V单片机如STC或Arduino UNO就要确认模块上是否有电平转换电路。很多淘宝模块自带AMS1117或者分压电阻来兼容5V但也有一些精简模块没有这种情况下SCLK/SDA上最好串1kΩ电阻限流避免5V电平倒灌损伤ST7735S。2.3 软件SPI引脚分配建议与初始化既然用软件SPI引脚就可以自己挑。我习惯遵守三条原则第一绕开已经用于调试串口的引脚第二尽量不占用JTAG/SWD引脚除非你刻意禁用第三把5根信号线尽量安排在同一个GPIO端口这样写代码和查错都方便。以下是我这次用的引脚分配信号GPIO说明CSPA4片选RESETPA5复位DCPA6命令/数据切换SDAPA7数据线SCLKPA1时钟线BLKPA2背光控制GPIO初始化代码如下用的是标准外设库HAL库同理void TFT_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 上电默认释放片选、复位拉高、背光点亮 GPIO_SetBits(GPIOA, GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_2); GPIO_ResetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_6 | GPIO_Pin_7); }这里有个小细节GPIO的模式要选推挽输出不要选开漏输出。开漏输出在不接上拉电阻的情况下无法主动输出高电平SPI时序会直接废掉。3. 软件SPI底层时序GPIO翻转模拟标准SPI3.1 SPI通信时序到底在说什么先用一个生活化的类比。想象两个人约定用一根数据线传消息一根时钟线保证同步。数据线上的电平什么时候有效由时钟线上的上升沿或者下降沿来“盖章确认”。SPI有四种模式Mode 0到Mode 3区别在于空闲时时钟是高还是低、数据在哪个边沿被采样。对于ST7735S这类屏绝大多数模块初始化代码用的都是Mode 0时钟空闲为低电平数据在上升沿被采样。也就是说主机必须在SCLK产生上升沿之前把SDA上的数据设置好从机在上升沿读取SDA的电平确定为0还是1。基于这个原理软件SPI发送一个bit的顺序就固定了拉低SCLK保证时钟先回到低电平根据当前bit设置SDA的电平拉高SCLK产生上升沿重复8次发送一个字节3.2 发送字节的核心函数先定义几个宏方便后面代码阅读#define TFT_CS_L() GPIO_ResetBits(GPIOA, GPIO_Pin_4) #define TFT_CS_H() GPIO_SetBits(GPIOA, GPIO_Pin_4) #define TFT_DC_L() GPIO_ResetBits(GPIOA, GPIO_Pin_6) #define TFT_DC_H() GPIO_SetBits(GPIOA, GPIO_Pin_6) #define TFT_RST_L() GPIO_ResetBits(GPIOA, GPIO_Pin_5) #define TFT_RST_H() GPIO_SetBits(GPIOA, GPIO_Pin_5) #define TFT_SDA_L() GPIO_ResetBits(GPIOA, GPIO_Pin_7) #define TFT_SDA_H() GPIO_SetBits(GPIOA, GPIO_Pin_7) #define TFT_SCLK_L() GPIO_ResetBits(GPIOA, GPIO_Pin_1) #define TFT_SCLK_H() GPIO_SetBits(GPIOA, GPIO_Pin_1)接着是发送字节的函数void TFT_WriteByte(uint8_t dat) { uint8_t i; for (i 0; i 8; i) { TFT_SCLK_L(); // 时钟拉低 if (dat 0x80) TFT_SDA_H(); // 数据位为1SDA拉高 else TFT_SDA_L(); // 数据位为0SDA拉低 TFT_SCLK_H(); // 时钟拉高产生上升沿 dat 1; } }这段代码里有一个常见疑问为什么先拉低SCLK再设置SDA最后拉高SCLK因为从机是在上升沿采样的SDA必须在上升沿到来之前稳定。如果你反过来先拉高SCLK再改SDA从机上升沿采到的数据很可能还是上一个bit导致整个字节错乱。另外要注意我这里是高位先发MSB first因为ST7735S的SPI协议默认就是高位先发。如果你的某个模块要求低位先发把dat 1改成dat 1并把判断位从0x80改成0x01即可。3.3 命令和数据切换DC引脚的“武功心法”发送字节的逻辑清楚了但屏幕如何区分一个字节是命令还是数据全靠DC引脚的电平DC为低接下来的字节是命令DC为高接下来的字节是数据。所以每个写操作都分为两个基本函数void TFT_WriteCmd(uint8_t cmd) { TFT_CS_L(); TFT_DC_L(); TFT_WriteByte(cmd); TFT_CS_H(); } void TFT_WriteData(uint8_t dat) { TFT_CS_L(); TFT_DC_H(); TFT_WriteByte(dat); TFT_CS_H(); }CS片选的逻辑也值得解释一下。CS拉低表示“选中这块屏”SPI总线上可能挂了多个设备只有被CS拉低的设备才会响应时钟和数据。所以每次传输开始时拉低CS传输完一个字节后立刻拉高CS。这样做的好处是每个字节都是完整事务虽然慢一点但结构清晰后续改造成批量发送也很容易。有朋友问过DC电平必须在CS拉低之前设置吗严谨来说ST7735S在CS为高时忽略所有引脚状态所以先拉低CS再设置DC还是先设置DC再拉低CS实际效果一样。但我习惯先CS拉低再设置DC逻辑上更顺也避免CS拉低瞬间DC抖动产生误判。3.4 软件SPI要不要加延时先给结论STM32F103这种72MHz主频的单片机用标准库GPIO操作跑软件SPI通常不需要额外延时。原因很简单标准库函数本身封装开销就不小GPIO翻转一次大约要零点几微秒一个bit周期在1微秒左右对应SPI频率约1MHz。而ST7735S数据手册里的SCLK写周期下限通常是几十纳秒1MHz的速率绰绰有余。但你要是在主频特别高的平台比如M7核心跑400MHz寄存器的直接翻转速度可以快到几百纳秒一个bit这时候可能接近甚至快于芯片时序规格就需要在循环里加几个空指令或者调用DelayUs(1)。判断标准只有一个屏幕显示稳定不出错那时序就是合格的。4. ST7735S初始化序列花屏和白屏的分水岭4.1 上电复位时序一上来就翻车的高发区很多人在第3步还在正常到了初始化这里屏幕就是不亮原因往往不是代码逻辑多复杂而是一开始没做正确的复位。ST7735S的复位时序可以这样理解芯片上电后内部默认状态不确定必须由外部引脚给它一个“从头开始”的信号。实现方法很固定复位引脚拉低保持一段时间拉高然后等待芯片完成内部复位。TFT_RST_H(); Delay_ms(10); TFT_RST_L(); Delay_ms(10); TFT_RST_H(); Delay_ms(120);这里的120ms不是随便写的。数据手册里Sleep Out命令之后时长要求是120ms硬件复位之后给足120ms等待会让芯片把内部寄存器完全准备好。很多例程写成几十毫秒也能跑但有时就是莫名其妙白屏加长到120ms后问题就消失了。这类经验说不上科学但非常实用复位后的等待时间宁可多给不要省。4.2 完整初始化命令序列及关键命令解析网上流传的ST7735S初始化代码大同小异但有一个重要区别有些是针对ST7735R老芯片的有些是针对ST7735S的两者通用性不是百分之百。这里给出一个在几个不同卖家模块上都验证过能正常显示的序列void TFT_LCD_Init(void) { // 1. 复位 TFT_RST_H(); Delay_ms(10); TFT_RST_L(); Delay_ms(10); TFT_RST_H(); Delay_ms(120); // 2. 退出睡眠 TFT_WriteCmd(0x11); // Sleep Out Delay_ms(120); // 3. 帧率设置 TFT_WriteCmd(0xB1); TFT_WriteData(0x01); TFT_WriteData(0x2C); TFT_WriteData(0x2D); TFT_WriteCmd(0xB2); TFT_WriteData(0x01); TFT_WriteData(0x2C); TFT_WriteData(0x2D); TFT_WriteCmd(0xB3); TFT_WriteData(0x01); TFT_WriteData(0x2C); TFT_WriteData(0x2D); TFT_WriteData(0x01); TFT_WriteData(0x2C); TFT_WriteData(0x2D); // 4. 电源控制 TFT_WriteCmd(0xC0); TFT_WriteData(0xA2); TFT_WriteData(0x02); TFT_WriteData(0x84); TFT_WriteCmd(0xC1); TFT_WriteData(0xC5); TFT_WriteCmd(0xC2); TFT_WriteData(0x0A); TFT_WriteData(0x00); TFT_WriteCmd(0xC3); TFT_WriteData(0x8A); TFT_WriteData(0x2A); TFT_WriteCmd(0xC4); TFT_WriteData(0x8A); TFT_WriteData(0xEE); TFT_WriteCmd(0xC5); TFT_WriteData(0x0E); // 5. 显示相关 TFT_WriteCmd(0x20); // Inversion Off TFT_WriteCmd(0x36); // MADCTL TFT_WriteData(0xC8); TFT_WriteCmd(0x3A); // 像素格式 TFT_WriteData(0x05); // 16bit RGB565 // 6. 设置显示区域 TFT_WriteCmd(0x2A); // CASET TFT_WriteData(0x00); TFT_WriteData(0x00); TFT_WriteData(0x00); TFT_WriteData(0x7F); TFT_WriteCmd(0x2B); // RASET TFT_WriteData(0x00); TFT_WriteData(0x00); TFT_WriteData(0x00); TFT_WriteData(0x9F); // 7. 开显示 TFT_WriteCmd(0x29); // Display ON Delay_ms(50); // 8. 清屏为黑色 TFT_FillRect(0, 0, 127, 159, 0x0000); }逐个说下关键命令的作用0x11 Sleep Out让面板从睡眠模式进入正常模式。注意这条之后必须延时至少120ms。0xB1/B2/B3设置扫描帧率。不同模块对这些值不敏感但缺了有时会出现屏幕刷新不均或水波纹。0xC0~0xC5内部电源电荷泵、VCOM等电压设置。这些值直接影响显示对比度和色彩纯度。如果一个模块花屏且偏白、偏暗很有可能是C类寄存器配置不对。0x36 MADCTL数据方向控制后面单独讲。0x3A 设置0x05像素格式设为16位真彩RGB565。如果漏掉这一条芯片可能工作在12位或18位格式下发送的数据会被解析成错误颜色。0x2A/0x2B设置显示区域为整个128x160面板范围。0x29 Display ON开显示之后屏幕就处于可写状态。有朋友拿到某些模块后发现初始化序列带了一大串以0xE0、0xE1开头的Gamma校正命令这很正常。Gamma命令主要影响灰阶和色彩线性度如果你只是显示文字和色块不调整Gamma区别不大。如果追求更准确的色彩可以把这部分命令从卖家demo里原封不动搬过来。4.3 ST7735S和ST7735R的初始化差异两块芯片功能定位很接近但寄存器细节有差异。ST7735R是最早的一代ST7735S在后来的低功耗面板上更常见。很多Adafruit的开源库是基于ST7735R写的直接套在ST7735S上有时会出现颜色反转或显示区域不对的问题。判断芯片型号最简单的办法是看模块背面的丝印或者直接问卖家要规格书。使用上如果发现初始化后能亮但颜色不对可以先尝试把0x20改成0x21反过来开Inversion On一半的情况下问题就解决了。这个Inversion命令本质上是在告诉面板要不要对像素数据取反不同面板的默认极性不一样。5. 坐标映射与颜色填充从画一个点到刷一屏5.1 窗口设置CASET/RASET/RAMWR的关系ST7735S内部的显存结构是一个矩阵外部写入数据时通过三个命令协同工作CASET (0x2A)设置列地址范围RASET (0x2B)设置行地址范围RAMWR (0x2C)开始写显存数据窗口可以理解成一个矩形区域。先告诉屏幕“我要从哪一列写到哪一列、从哪一行写到哪一行”然后连续发像素数据数据会按从左到右、从上到下的顺序自动填充这个区域。当然这里的方向受MADCTL影响后面会说。窗口设置函数如下void TFT_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { TFT_WriteCmd(0x2A); TFT_WriteData(x0 8); TFT_WriteData(x0 0xFF); TFT_WriteData(x1 8); TFT_WriteData(x1 0xFF); TFT_WriteCmd(0x2B); TFT_WriteData(y0 8); TFT_WriteData(y0 0xFF); TFT_WriteData(y1 8); TFT_WriteData(y1 0xFF); TFT_WriteCmd(0x2C); }这里每个坐标都要拆成高字节和低字节两个部分发送因为CASET和RASET的参数都是16位。很多新手容易犯的错是直接只写一个坐标字节结果显示区域错乱成奇怪图案。5.2 画点、区域填充与清屏有了窗口函数画点就很简单void TFT_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { if (x 127) x 127; if (y 159) y 159; TFT_SetWindow(x, y, x, y); TFT_WriteData(color 8); TFT_WriteData(color 0xFF); }颜色数据是RGB565格式16位高字节是红绿高比特低字节是蓝高比特和绿低比特。发送时先高字节再低字节。区域填充是刷屏性能的关键。高效做法是一次性设置整个矩形窗口然后连续发送所有像素数据而不是每画一个点就发送一次CASET/RASET/RAMWR。后者每次要发9个字节命令10个点就要90个字节浪费太多时间。void TFT_FillRect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { uint32_t count; uint32_t width x1 - x0 1; uint32_t height y1 - y0 1; count width * height; TFT_SetWindow(x0, y0, x1, y1); while (count--) { TFT_WriteData(color 8); TFT_WriteData(color 0xFF); } } void TFT_Clear(uint16_t color) { TFT_FillRect(0, 0, 127, 159, color); }就这么一个简单的改动全屏填充的效率比逐点画矩形高出一个数量级。因为省掉的不仅仅是命令字节CS引脚的每次拉低拉高开销也一并省掉了。5.3 方向控制MADCTL寄存器决定横屏竖屏MADCTL (0x36) 是TFT方向控制的核心寄存器。它内部几个bit分别控制列地址方向、行地址方向和RGB/BGR顺序。我整理了一份常用方向对照表MADCTL值显示效果适用场景0x00竖屏左上角为原点默认竖屏布局0xC0竖屏某些面板默认模块面板方向调整0xA0横屏左上角原点横屏界面0x60横屏左下角原点横屏镜像调整0x08任意方向强制RGB颜色顺序修正这个寄存器的值不是一个固定“标准答案”因为不同厂家模块的软排线连接方向不同同样0x00可能在一个模块上正常在另一个模块上就上下颠倒。我的调法是先设置成0x00刷一个白色全屏再在左上角画一个红色方块看看实际出现在哪。如果出现在右上角或者左下角再查表调整MADCTL值通常试两三次就能锁定方向。5.4 在main函数中整体跑起来下面的main函数演示了完整的调用流程int main(void) { Delay_Init(); TFT_GPIO_Init(); TFT_LCD_Init(); // 画一个红色区域验证方向和颜色 TFT_Clear(0x0000); // 清屏为黑色 TFT_FillRect(0, 0, 63, 79, 0xF800); // 画红色矩形 TFT_FillRect(64, 0, 127, 79, 0x07E0); // 画绿色矩形 TFT_FillRect(0, 80, 127, 159, 0x001F); // 画蓝色矩形 while (1) { // 实际项目中在这里更新显示内容 } }跑起来后如果屏幕上出现红、绿、蓝三个色块说明初始化、引脚、时序和窗口设置全部正确这块屏已经握在手里了。如果颜色顺序互换了比如红变蓝往0x36寄存器加一个0x08试试。6. 调试实录与性能优化那些手册里没写的事6.1 白屏问题的排查链路白屏是新手遇到最多的问题表现就是屏幕背光亮了但没有任何内容。我调试时的顺序很固定第一步查供电。用万用表量模块VCC和GND两端电压确认是3.3V左右而不是0V或者被拉低的2V。有些稳压芯片输出能力弱背光点亮后会把电压拉垮这种情况换独立供电或者减小背光电流就解决了。第二步查复位。用示波器看RESET引脚有没有先低后高的脉冲如果没有检查代码有没有真的调用TFT_GPIO_Init和TFT_LCD_Init别只初始化了GPIO忘了调LCD初始化函数。第三步查背光。BLK引脚是高电平点亮确认BLK电压不是悬空或者为低。有些人用的是PA2但被其他初始化代码又配成了别的模式导致GPIO配置被覆盖。第四步查CS和DC。如果这两根信号线电平混乱屏幕收不到正确的命令字节自然白屏。直接把CS接GND、DC接3.3V强行拉死再上电看看是否出现随机花点用来验证这两根线有没有接反。6.2 花屏和偏色的根因分析花屏大致分两类一类是完全随机噪点一类是规律性错位。完全随机噪点多出现在发送数据过程中SCLK和SDA时序不干净或者供电纹波大。常见解决办法是降低软件SPI速度比如在每bit循环里加两个空操作指令。规律性错位则多半是窗口设置错误。比如你设置CASET时只发了2个字节而不是4个字节会让后续所有像素数据错位。偏色问题需要单独说因为它最隐蔽。画一个纯红色0xF800屏幕上如果显示纯绿或纯蓝那基本可以锁定是RGB/BGR顺序问题在MADCTL里加一个0x08标志位就能翻转过来。还有一种情况是你发送的16位像素高低字节反了也就是先发低字节后发高字节表现为颜色整体偏色但不规则把TFT_WriteData16里的两条语句换一下顺序即可。6.3 软件SPI的性能上限一屏到底要多久很多人关心软件SPI到底有多快。我用标准外设库、72MHz主频的F103实测发送一个字节大约需要10~12微秒一屏40KB数据算下来耗时大约0.4秒。听起来慢但你要知道这是标准库每次CS都拉低拉高的写法。刷静态页面0.4秒完全可以接受。如果你的应用需要更快的刷新可以用以下三个优化方向第一把GPIO操作从标准库函数改成直接操作BSRR寄存器。BSRR寄存器可以在一条指令里把一组引脚全部置高或置低比函数调用快得多。改进后的宏定义长这样#define TFT_SCLK_H() (GPIOA-BSRR GPIO_Pin_1) #define TFT_SCLK_L() (GPIOA-BSRR GPIO_Pin_1 16) #define TFT_SDA_H() (GPIOA-BSRR GPIO_Pin_7) #define TFT_SDA_L() (GPIOA-BSRR GPIO_Pin_7 16)第二展开循环。软件SPI发送一字节的循环每次都做位判断和位移8次循环不可避免但可以把判断逻辑改成查表法或者直接用硬件移位加判断的写法减少边界跳转。第三把批量写数据和CS控制分开。比如在填充矩形时CS只在开始和结束各拉低拉高一次中间连续发送所有像素字节避免每发一个字节就切换一次CS带来的额外开销。具体操作就是写一个单独的函数发数据过程中不操作CS。void TFT_FillRect_Fast(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { uint32_t count (uint32_t)(x1 - x0 1) * (y1 - y0 1); uint8_t hi color 8, lo color 0xFF; TFT_SetWindow(x0, y0, x1, y1); TFT_CS_L(); while (count--) { TFT_DC_H(); TFT_WriteByte(hi); TFT_DC_H(); TFT_WriteByte(lo); } TFT_CS_H(); }这样优化后全屏刷新时间能从0.4秒降到0.15秒左右。如果你想再进一步就该换硬件SPI加DMA了那是另一个话题。6.4 如何快速验证新模块的参数最后分享一个我拿到陌生模块后的标准做法先跑一个极简测试程序只做三件事——复位、初始化、刷红绿蓝三个色块。不需要写任何文本或画图库函数这样能最快确认基本链路是否通畅。如果色块正常再进入下一步测试方向、偏色、渐变、灰阶。这样一层层拆下去问题能被快速定位到具体环节不会像无头苍蝇一样到处改代码。写在最后一点实际操作中的体会如果你也准备在STM32上用软件SPI驱动这块1.8寸屏我的建议很简单先点亮再优化。第一步看清引脚定义第二步跑通复位、初始化和纯色填充第三步再去调方向和颜色。很多朋友一上来就想把完整界面刷出来结果卡在白屏上无从下手其实把底层时序调稳了后面都是水到渠成的事。软件SPI最大的价值不是省掉一个外设而是让你彻底理解SPI协议本身——当你亲手用GPIO翻出一个上升沿并且让屏幕准确响应时硬件SPI对你就没有什么神秘感了。这块屏作为学习SPI协议和理解LCD驱动机制的入门对象性价比真的很高。