
这个标题是我在项目中反复踩坑后最想写的一篇。市面上1.8寸TFT屏模块大多用ST7735S这颗驱动芯片网上资料多但零散标准库版本的完整驱动更少不少人一上来就被初始化序列、偏移地址、花屏问题劝退。这篇文章我会把从接线到出图的完整流程拆开讲包括标准库SPI配置、初始化寄存器逐条说明、取模和显示原理以及我实际调试中遇到的坑和排查方法全程附可复现的代码希望能帮你少走弯路。1. 项目概述与硬件准备1.1 弄懂ST7735S这颗屏的核心参数1.8寸TFT屏模块常见的分辨率为128x160驱动IC是ST7735S。这里有个重点ST7735S和ST7735R在市场上经常混着卖它们指令基本兼容但初始化序列有细微差别主要体现在电源设置和伽马校正上。如果代码按ST7735R写点不亮或颜色偏色是常有的事。我一般拿到屏先看背面的丝印或排线标签确认是S还是R不确定就两种初始化序列都试。屏的接口方式也要分清。市面上1.8寸TFT模块绝大多数是4线SPI即SPI数据线时钟线加上DC、CS、RST、BL控制线也有些是8位并口或3线SPI。3线SPI没有单独的DC引脚命令和数据的区分靠第9位bit一般新手板子很少用所以我这里以最常见的4线SPI为例。接线表如下模块引脚功能说明常见接法STM32F103C8T6VCC逻辑电源/背光电源3.3V或5V看模块上有无稳压GND地线GNDSCL / SCKSPI时钟PA5SPI1_SCKSDA / MOSISPI数据PA7SPI1_MOSIRES / RST复位控制PB0普通GPIODC / RS数据/命令选择PB1普通GPIOCS片选PA4普通GPIOBL / LED背光控制接3.3V或PB10做PWM控制需要注意模块上的VCC和BL有的是分开的。如果BL脚悬空屏会非常暗甚至像是没点亮。我第一次接的时候只接了VCC没接BL屏幕背光几乎不可见搞得我以为屏坏了。另外模块上如果自带稳压芯片和电平转换电路VCC可以接5V如果是裸屏无稳压必须接3.3V否则有烧毁风险。1.2 为什么选标准库而不是HAL库我用的是STM32F103C8T6蓝色Pill板开发环境是Keil5固件包用标准外设库3.5版本。关于标准库和HAL库的选择网上吵得厉害我的看法是标准库对寄存器的封装粒度适中不像寄存器操作那样繁琐也不像HAL库那样在初始化结构体内藏了太多自动逻辑。对于SPI这种外设标准库的SPI_InitTypeDef结构体一眼就能看全时钟极性、相位、分频系数、数据长度一目了然。标准库资料多尤其是江科大、正点原子、野火等教程的例程几乎全是用标准库写的遇到问题找参考容易。标准库在F1系列已经到了稳定状态不存在维护迭代问题适合做项目原型验证。如果你用HAL库最大的区别在于SPI发送等函数命名和超时机制但底层寄存器操作逻辑一致。这篇文章的核心代码用标准库实现HAL库用户也可以对照寄存器说明自行移植。2. 标准库SPI驱动方案设计与初始化2.1 硬件SPI还是软件模拟SPI驱动ST7735S有两种常见的SPI实现方式硬件SPI和GPIO软件模拟SPI。我先说结论优先用硬件SPI原因是传输速率高、CPU占用低、时序稳定。软件模拟SPI的好处是引脚任意、代码直观但传输速度有限且容易受中断影响导致时钟抖动。画点、清屏这种大量数据操作软件模拟在8MHz主频下会明显卡顿。硬件SPI也不是没有坑。最典型的是STM32的SPI主机模式在发送数据时MOSI和SCK的相位由CPOL和CPHA决定如果配置不对屏的控制器采不到正确的数据。ST7735S的SPI模式支持Mode 0和Mode 3我实测用Mode 0CPOL0CPHA0稳定。初始化时SPI_InitStructure里的SPI_CPOL和SPI_CPHA要配对SPI_BAUDRATEPRESCALER_2时在36MHz系统时钟下能达到18MHz的SCK频偏大部分ST7735S模块能跑但为了稳妥我习惯用SPI_BAUDRATEPRESCALER_49MHz或SPI_BAUDRATEPRESCALER_84.5MHz。2.2 标准库SPI初始化代码解析下面这段是完整可用的SPI1初始化代码我在F103C8T6上验证过void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // 配置SPI1_SCK PA5、SPI1_MOSI PA7为复用推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // CS PA4 和 DC/RST等控制引脚配置为通用推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_4; SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial 7; SPI_Init(SPI1, SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }代码里有几个细节值得展开GPIO模式必须是GPIO_Mode_AF_PP复用推挽输出由SPI外设控制器接管。如果把SCK和MOSI配成普通推挽输出SPI外设无法控制引脚数据发不出去。SPI_NSS必须设成Soft由软件控制CS引脚。如果启用硬件NSSCS会由硬件自动控制配合多个SPI从设备容易出问题。SPI_BAUDRATEPRESCALER_4在72MHz主频下对应18MHz分频后实际为4.5MHz这个我重新算一下F103最高主频72MHzAPB2总线时钟为72MHzSPI1挂在APB2上所以预分频系数4对应18MHz。我的板子主频默认是72MHz这里我用的是RCC_APB2PeriphClockCmd默认系统时钟就是72MHz所以预分频4就是18MHz。之前写9MHz的地方是笔误——预分频8才是9MHz。实测ST7735S在18MHz下工作稳定但如果你的布线较长或屏是山寨模块建议降到预分频8。屏幕的控制引脚初始化也要做我这里是一个通用写法#define TFT_CS_PORT GPIOA #define TFT_CS_PIN GPIO_Pin_4 #define TFT_DC_PORT GPIOB #define TFT_DC_PIN GPIO_Pin_1 #define TFT_RST_PORT GPIOB #define TFT_RST_PIN GPIO_Pin_0 #define TFT_BL_PORT GPIOB #define TFT_BL_PIN GPIO_Pin_10 #define TFT_CS_L() GPIO_ResetBits(TFT_CS_PORT, TFT_CS_PIN) #define TFT_CS_H() GPIO_SetBits(TFT_CS_PORT, TFT_CS_PIN) #define TFT_DC_L() GPIO_ResetBits(TFT_DC_PORT, TFT_DC_PIN) #define TFT_DC_H() GPIO_SetBits(TFT_DC_PORT, TFT_DC_PIN) #define TFT_RST_L() GPIO_ResetBits(TFT_RST_PORT, TFT_RST_PIN) #define TFT_RST_H() GPIO_SetBits(TFT_RST_PORT, TFT_RST_PIN) #define TFT_BL_L() GPIO_ResetBits(TFT_BL_PORT, TFT_BL_PIN) #define TFT_BL_H() GPIO_SetBits(TFT_BL_PORT, TFT_BL_PIN)用宏封装GPIO操作后续写驱动函数时代码会非常干净。CS和DC引脚需要设置为GPIO_Mode_Out_PP速度设为50MHz。RST和BL同理但BL如果要做PWM调光需要复用定时器通道这里先直接用高低电平控制。2.3 SPI发送函数与读写注意事项标准库的SPI_I2S_SendData一次只能发送一个字节发送前要检查发送缓冲区空标志SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)发送后等待SPI_I2S_FLAG_BSY清空。裸写法如下void LCD_WriteData_8Bit(uint8_t data) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, data); }如果你要连续发送多个字节可以优化成void LCD_WriteData_Buffer(uint8_t *buffer, uint16_t len) { for (uint16_t i 0; i len; i) { while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET); SPI_I2S_SendData(SPI1, buffer[i]); } while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) SET); }注意SPI_I2S_SendData本质上只写数据寄存器如果不等TXE标志直接写有可能覆盖上一个还没移出去的数据。这个坑在高速连续发送时极易出现表现为屏幕显示随机噪点。ST7735S的数据格式是8位SPI也就是每次传1个字节。如果要写16位颜色值RGB565需要拆成两个字节依次发送高字节在前。驱动里封装一个LCD_WriteData_16Bit会更方便void LCD_WriteData_16Bit(uint16_t data) { uint8_t buf[2]; buf[0] data 8; buf[1] data 0xFF; LCD_WriteData_Buffer(buf, 2); }3. ST7735S驱动核心代码与初始化序列3.1 初始化序列逐条解读ST7735S初始化序列是整个驱动中最容易翻车的环节。网上的初始化代码版本很多有的用的是初始化R的版本有的初始化S的版本但少了延迟还有的用了非官方寄存器导致屏幕显示异常但看不出原因。我的做法是把初始化序列按功能块拆开逐个解释每条指令的作用这样以后遇到问题能自己改。void LCD_Init(void) { // 硬件复位 TFT_RST_H(); delay_ms(10); TFT_RST_L(); delay_ms(10); TFT_RST_H(); delay_ms(120); // 进入正常模式 LCD_WriteCommand(0x01); // SWRESET 软件复位 delay_ms(150); LCD_WriteCommand(0x11); // SLPOUT 退出睡眠模式 delay_ms(200); LCD_WriteCommand(0x36); // MADCTL 内存数据访问控制 LCD_WriteData_8Bit(0x00); // 默认方向行扫描从左上到右下 // 如果需要旋转90度可以改成 0x60 或 0xC0后面会细说 LCD_WriteCommand(0x3A); // COLMOD 接口像素格式 LCD_WriteData_8Bit(0x05); // 16位/pixelRGB565 LCD_WriteCommand(0x20); // INVOFF 反演关闭 // 如果屏幕颜色显示异常反色可尝试 0x21 打开反演 LCD_WriteCommand(0x2A); // CASET 列地址设置 LCD_WriteData_8Bit(0x00); LCD_WriteData_8Bit(0x00); LCD_WriteData_8Bit(0x00); LCD_WriteData_8Bit(0x7F); // 列范围 0~127 LCD_WriteCommand(0x2B); // RASET 行地址设置 LCD_WriteData_8Bit(0x00); LCD_WriteData_8Bit(0x00); LCD_WriteData_8Bit(0x00); LCD_WriteData_8Bit(0x9F); // 行范围 0~159 LCD_WriteCommand(0x21); // INVON 开启反演部分屏需要 delay_ms(10); LCD_WriteCommand(0x13); // NORON 正常显示模式 delay_ms(10); LCD_WriteCommand(0x29); // DISPON 开启显示 delay_ms(50); // 背光点亮 TFT_BL_H(); }这段代码里我写了INVOFF但后面又写了INVON实际上这是两种常见情况的兼容处理。不同批次的ST7735S有的默认显示反色需要0x21打开反演才正常有的默认正常打开反演反而偏色。实操做法是先不写0x21用白底黑字测试如果底是黑色、字是白色就该补上0x21。代码里我先0x20再0x21效果等同于直接0x21但保守起见这两条中间没有延迟芯片能正常响应。另外要注意延迟时间。硬件复位后的延迟、SLPOUT后的延迟、DISPON前的延迟这三个地方不能省。网上有些代码为追求快速启动把延迟压缩到几十毫秒在部分屏上会出现首次上电花屏、过一会才正常的情况。我的经验是SLPOUT后至少等120ms最好200ms。3.2 MADCTL寄存器与屏幕方向控制MADCTL0x36是控制扫描方向和颜色顺序的关键寄存器每一位含义如下Bit含义MY行地址扫描方向1为从下到上MX列地址扫描方向1为从右到左MV行/列交换1为行列交换用于横屏ML垂直刷新方向BGR颜色顺序1为BGR0为RGBMH水平刷新方向常见方向配置值值显示效果0x00竖屏默认方向左上角起始0xA0竖屏上下翻转0xC0竖屏左右翻转0x60横屏左转90度宽128高160变宽160高1280xA0横屏右转90度这里有个很常见的坑当你设置横屏后CASET和RASET的坐标范围也要相应调整。竖屏时列范围0~127、行范围0~159横屏时列范围必须改成0~159、行范围改成0~127。如果你的代码只改MADCTL不改CASET/RASET屏幕显示区域会偏移或出现彩条。有些模块的显示偏移正好是2个像素。比如ST7735S在驱动1.8寸屏时有的模组需要把CASET和RASET的起始坐标设为2即0x02因为物理像素和驱动IC内部坐标之间存在偏移。这个偏移量通常可以在厂家提供的初始化代码里看到我见过0x00、0x01、0x02三种都在用。如果屏显示内容偏左或偏上2像素把初始坐标改为2即可。3.3 画点、画矩形与清屏函数初始化完成后核心操作就是画点。画点的流程是设置坐标窗口CASETRASET然后写入像素颜色。ST7735S支持窗口模式可以先设置一个矩形区域再连续写入多个像素数据效率远高于逐点设置坐标。以画一个点为例void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { LCD_WriteCommand(0x2A); LCD_WriteData_8Bit(x0 8); LCD_WriteData_8Bit(x0 0xFF); LCD_WriteData_8Bit(x1 8); LCD_WriteData_8Bit(x1 0xFF); LCD_WriteCommand(0x2B); LCD_WriteData_8Bit(y0 8); LCD_WriteData_8Bit(y0 0xFF); LCD_WriteData_8Bit(y1 8); LCD_WriteData_8Bit(y1 0xFF); LCD_WriteCommand(0x2C); // RAMWR 写内存 } void LCD_DrawPoint(uint16_t x, uint16_t y, uint16_t color) { if (x 128 || y 160) return; LCD_SetWindow(x, y, x, y); LCD_WriteData_16Bit(color); }清屏就是设置全窗口然后连续填充同一个颜色void LCD_Clear(uint16_t color) { LCD_SetWindow(0, 0, 127, 159); uint32_t total 128 * 160; for (uint32_t i 0; i total; i) { LCD_WriteData_16Bit(color); } }这里有个性能优化点清屏如果用画点函数每次设置窗口的4条命令加1次RAMWR128x160个点就要执行20000多次命令SPI上浪费的时间非常多。用窗口模式连续写入只需要设置一次窗口然后连续传送20480个像素数据速度提升几十倍。同理填充矩形、显示图片都应该用窗口模式。有人会问为什么清屏不写成缓冲区数组一次性发送比如定义一个uint16_t buffer[128*160]然后LCD_WriteData_Buffer((uint8_t*)buffer, 128*160*2)。理论上可行但F103C8T6只有20KB RAM128x160x2字节40KB放不下数组。即使是F103ZE这种64KB RAM的芯片40KB缓冲区也会挤压堆栈空间。所以清屏用循环发送是更务实的做法。4. 图片取模与显示4.1 图片转RGB565数组TFT屏显示图片本质上是把图片像素数据按RGB565格式写入显存。你需要先把图片处理成C语言数组工具常用的是Image2Lcd。Image2Lcd的设置要点输出数据类型C语言数组扫描方式水平扫描从上到下、从左到右输出灰度16位真彩色最大宽度和高度128、160包含头文件勾选或手动生成数组生成的数组大概是这个样子const uint16_t gImage_logo[128 * 160] { 0x0000, 0x0000, 0x0000, ... };把数组放到驱动文件里然后显示函数如下void LCD_ShowImage(uint16_t x0, uint16_t y0, uint16_t width, uint16_t height, const uint16_t *image) { LCD_SetWindow(x0, y0, x0 width - 1, y0 height - 1); uint32_t total width * height; for (uint32_t i 0; i total; i) { LCD_WriteData_16Bit(image[i]); } }注意如果图片尺寸小于屏幕比如只显示一个80x80的图标窗口设置不能超边界否则会写溢出。稳妥做法是在函数入口加边界裁剪但为了简单我这里的代码默认使用者确保坐标和尺寸合法。4.2 颜色格式与字节序的坑ST7735S的RGB565颜色格式一个像素由2字节组成。16位颜色值中高5位是红色中间6位是绿色低5位是蓝色。比如纯红色是0xF800纯绿色是0x07E0纯蓝色是0x001F。这里有一个非常容易踩的坑MADCTL寄存器中的BGR位会影响颜色通道的物理顺序。如果MADCTL中的BGR0写入的数据按照RGB565解释如果BGR1则按照BGR565解释。也就是说同样的0xF800在BGR模式下显示的是蓝色而不是红色。所以当你发现颜色“红蓝互换”时优先查MADCTL的BGR位而不是怀疑代码数据传错。另外有些标准库驱动里会加一个宏定义#define RGB565_RED 0xF800 #define RGB565_GREEN 0x07E0 #define RGB565_BLUE 0x001F测试的时候用这几个宏填满全屏可以快速判断是RGB顺序问题还是BGR位问题。5. 字符显示与简单GUI5.1 英文字符取模与显示原理显示字符和显示图片原理一样本质也是向指定窗口填充像素数据。字符取模用PCtoLCD2002软件取模方式选择“逐行式”或“列行式”方向要和你的TFT扫描方向一致。我一般选择取模模式C51格式每行显示数据点阵行数/88x16字体就是2字节一行取模走向顺向高位在前下面是一个8x16字体显示一个字符的函数示例void LCD_ShowChar(uint16_t x, uint16_t y, uint8_t ch, uint16_t color, uint16_t bgcolor) { ch - 0x20; // 字模数组按ASCII从空格开始 for (uint8_t row 0; row 16; row) { uint8_t data font8x16[ch * 16 row]; for (uint8_t col 0; col 8; col) { if (data (0x80 col)) { LCD_DrawPoint(x col, y row, color); } else { LCD_DrawPoint(x col, y row, bgcolor); } } } }这个函数简单易懂但逐点调用性能一般适合字符数量不多的场景。更高效的做法是把一行8个点打包成一个字节用1/8的绘图调用次数完成显示。在刷新整屏文字时性能差异会非常明显。5.2 中文字符显示要点中文显示比英文麻烦因为中文字模是16x16点阵占用32字节。如果要在屏幕上显示多个中文建议把常用字模表放到一个数组里做一个简单的查表函数typedef struct { uint8_t index[2]; uint8_t data[32]; } ChineseChar; ChineseChar font_hz[] { {温, {0x10, 0x10, ...}}, {度, {0x10, 0x10, ...}}, };中文GBK编码是2字节需要把你的字符串编码方式通常是GB2312/GBK和字模数组索引对齐。注意Keil5默认源文件编码可能是UTF-8而数组里存的是GBK编码的相对偏移如果不做转换会出现乱码。我习惯将源文件编码改成GB2312或者直接用Unicode转义字符省去编码混乱的坑。6. 常见问题排查与避坑技巧6.1 白屏、黑屏、花屏的原因白屏是最常见的故障我会按以下顺序排查首先确认供电和接线。模块的VCC和BL是否都接好BL不接会导致屏幕看起来“没亮”。然后用万用表量一下模块VCC引脚电压确认在3.3V或5V范围。如果模块上有稳压芯片输入5V也正常但裸屏模块接5V可能会烧这时候量不出电压就说明屏损坏。其次是复位时序。RST引脚必须正确拉低再拉高有些模块的RST引脚与STM32的复位电路连接不当导致上电后一直处于复位状态。我用示波器看过RST波形发现有些开发板的复位电路和TFT模块的RST引脚有冲突解决方法是把模块的RST引脚接到一个普通GPIO由代码控制而不是接到STM32的NRST。再次是初始化序列是否执行完整。ST7735S初始化序列虽然不像老式LCD那么漫长但SWRESET和SLPOUT命令后的延迟不能太短。我遇到过一种情况去掉SLPOUT后的延迟屏幕显示全白加上200ms延迟后正常。这个问题在某些山寨屏上尤其明显所以初始化函数里延迟宁可多不可少。还有SPI配置问题。检查CPOL和CPHA是否配置正确、数据长度是不是8位、MSB是否在前。用逻辑分析仪看SCK和MOSI波形如果SCK空闲电平不对基本就是CPOL错误。最后是CS和DC的控制逻辑。命令模式和数据模式的切换完全依赖DC引脚如果DC引脚接错或是没有及时切换屏幕上的内容会是一堆乱码。现象优先级排查白屏背光亮初始化序列、延迟、DC控制白屏背光不亮BL接线、背光供电黑屏有一点点影子对比度/伽马设置、背光电压不足花屏/噪点SPI波特率过高、TXE等待缺失、接线过长颜色反转/偏色0x21反演指令、MADCTL的BGR位内容偏移CASET/RASET起始坐标偏移6.2 屏幕显示异常偏移的修正方法有一类问题是屏幕可以点亮、有内容但显示区域不在正中央或者内容整体向左/向上偏移几个像素。这种问题通常不是初始化序列错了而是模块的偏移量设计问题。ST7735S驱动128x160的屏但芯片本身还支持128x128、128x160、132x162等不同分辨率。一些模块设计时屏幕物理像素与驱动IC的GRAM之间存在行列偏移需要你在CASET和RASET中把起始坐标设置成偏移值。排查方法先用纯白底清屏然后设置一个坐标窗口比如(0,0,127,159)填充红色。观察红色区域是否完整覆盖整个屏幕。如果左边有一条白边或者右边超出微调CASET的x1和x0。同理竖方向微调RASET。这个调整通常以1像素为步进我见过的偏移量从0到4都有。6.3 SPI速率与信号完整性的权衡F103的SPI1最大可配置到18MHzAPB272MHz分频4。但18MHz并不是所有场景都合适。如果你用的是杜邦线连接线长10cm以上或者模块引脚焊接不牢18MHz下很容易出现花屏或者偶发显示错误。我的经验是板内连接或短排线18MHz分频4杜邦线10~15cm9MHz分频8长线或者干扰环境4.5MHz分频16在项目调试初期先把SPI速度降到9MHz或4.5MHz等代码全部调通后再提高速度这样可以避免把SPI时序问题和逻辑问题混在一起。我早期就在18MHz下遇到花屏一开始以为代码逻辑有bug排查了很久才发现是杜邦线太长导致的信号反射。6.4 花屏和闪烁的隐蔽原因缓冲区与DMA如果你使用了DMA传输像素数据还有一个隐蔽坑DMA缓冲区必须是连续的且字节长度要正确。RGB565一个像素2字节如果你的数组是uint16_t类型DMA长度参数必须乘以2。很多人在这里写错表现为传送一半数据后乱码。另外DMA传输过程中如果CPU同时修改缓冲区也会导致图像撕裂或花屏。解决办法是等DMA传输完成后再修改缓冲区或者使用双缓冲切换。6.5 屏幕闪烁和残影的软件处理技巧最后一个常见问题是屏幕闪烁和残影。ST7735S的刷新率由内部振荡器决定不能直接调整但可以通过合理设计绘图流程来减少闪烁感。一个很实用的技巧是——利用窗口模式做局部刷新。不要每次都整屏清空再重画只更新变化的区域。例如做时钟显示时只需要每秒刷新数字所在的矩形区域而不是全屏重绘。这样可以明显减少视觉闪烁。另外如果你做的是动态波形或者动画尽量避免在SPI传输期间打断传输。比如在画线循环中夹着delay会让像素一帧一帧地显示看起来就像闪烁。正确做法是先计算好一帧所有像素然后一次性窗口连续写入。7. 从ST7735S驱动出发的扩展思路7.1 驱动其他SPI屏的通用方法学会ST7735S之后ST7789、ILI9341、ILI9488这些SPI屏的驱动思路完全一样差别只在初始化序列和分辨率。ST7789是240x240分辨率初始化时CASET和RASET要设置成239并且对1.3寸和1.54寸屏有偏移差异ILI9341是240x320初始化还需要设置VCOM和伽马。核心的SPI读写、窗口设置、颜色填充逻辑完全一致。我的建议是手头有一个成熟的SPI屏驱动框架把LCD_WriteCommand和LCD_WriteData作为最底层接口上层所有绘图逻辑与具体驱动芯片解耦。以后换屏只需要替换初始化序列和窗口设置函数就能快速适配。7.2 对接常用GUI库的可行方案U8g2、LVGL、LittlevGL这些GUI库都支持ST7735S。LVGL在F103上驱动128x160的小屏有点吃力但只做简单界面还是能跑的。U8g2则专门为小屏做了优化内存占用小使用简单。如果对接U8g2需要实现它的SPI回调函数把u8x8_byte_hw_spi等函数替换成你自己的SPI发送函数。U8g2默认使用软件SPI你可以查看它的源码改成硬件SPI发送性能会提高不少。LVGL对接则更复杂一些需要实现disp_flush回调并在其中调用你的LCD_ShowImage或窗口写像素函数。LVGL会传入一个颜色缓冲区一般是RGB565格式你需要把这个缓冲区通过SPI发送到屏幕。注意LVGL的缓冲区大小可以配置建议设置成屏幕的10%行数大小平衡内存和刷新速度。7.3 OLED与TFT如何选择最后聊一个很多新手纠结的问题0.96寸OLEDSSD1306和1.8寸TFTST7735S该选哪个。OLED对比度高、视角广、功耗低但分辨率只有128x64单色或双色适合显示少量文字和简单图形。TFT能显示彩色图片但功耗大、刷新率受限、响应速度略慢适合需要彩色数据和复杂界面的场景。如果只是做个温湿度计显示数字OLED足够如果想显示波形、图片或者彩色趋势图选TFT。我个人的习惯是项目原型阶段两个屏都备一块先用OLED快速验证逻辑再做彩色界面时换成TFT。两者的驱动代码结构几乎一样从OLED切换到TFT主要改初始化序列和坐标范围SPI框架可以复用。8. 写在最后这套ST7735S标准库驱动前前后后用了大概一个多星期才真正稳定下来。最大的体会是LCD驱动不复杂但细节极多。从SPI极性和速率到复位时序再到MADCTL的BGR位任何一个环节不对屏幕都能给出看似“玄学”的现象但用逻辑分析仪和示波器逐一排查后会发现所有问题都有明确原因。如果你也正在调这块屏建议先跑通清屏和单色填充再上字符和图片这样能快速缩小问题范围。比如初始化后先用红色刷满屏红色不对就查MADCTL和反演指令红色对了再显示图片图片不对就查取模方向和数据格式。调试步骤划分得越细定位问题越快。