尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32驱动0.96寸ST7735S TFT屏:从SPI接线到画点全流程

STM32驱动0.96寸ST7735S TFT屏:从SPI接线到画点全流程 玩嵌入式这些年屏幕项目算是新手最容易上手、也最容易出成就感的一类。最近后台好几个朋友都在问 0.96 寸 ST7735S TFT 屏怎么用 STM32 驱动这应该是目前小尺寸彩屏里性价比最高的一块了某宝几块钱一片SPI 接口省引脚分辨率虽然只有 80x160但显示个菜单、曲线、小动画完全够用。这篇文章我就把整个驱动过程从头到尾捋一遍从硬件接线到 CubeMX 配置再到初始化序列和画点画线代码最后把我踩过的坑也一并列出来保证你照着做能一次点亮。1. 整体设计与环境准备1.1 为什么选 ST7735S 这块屏0.96 寸这块小屏市面上的驱动芯片主要有 ST7735S、ST7735R、GC9106 等其中 ST7735S 占据了大半壁江山。这颗芯片是矽创Sitronix的 TFT 驱动 IC内部 GRAM 是 132x1620.96 寸模组一般只用其中 80x160 的区域支持 RGB565、RGB666 两种颜色格式SPI 接口最高可以跑到几十兆赫兹刷屏速度比 I2C 的 OLED 快一个数量级。选它的理由很简单便宜、资料多、引脚少。相比 1.8 寸的 ST7735 屏0.96 寸分辨率低一些但显示效果在这么小的面积上反而显得细腻。再加上模块厂商已经把外围电路升压、背光、电平转换基本都做好了我们只需要操心四根信号线这对刚接触屏驱动的朋友来说非常友好。1.2 硬件接线与引脚规划市面上常见的 0.96 寸 ST7735S 模块通常引出 8 个引脚VCC、GND、CS、RESET、DC有些丝印写 RS、MOSI或 SDA、SCLK或 SCK、LED背光。也有少部分模块把 LED 和 VCC 合并为一个引脚或者多引出几个 NC 引脚接线前一定要对照自己模块的丝印别想当然。我以 STM32F103C8T6 最小系统板为例用 SPI1 外设驱动接线表如下ST7735S 模块引脚STM32 引脚说明VCC3.3V模块电源必须 3.3VGNDGND共地CSPA4SPI 片选软件控制RESETPA3硬件复位低电平有效DCPA2数据/命令选择0命令1数据MOSIPA7SPI1_MOSISCLKPA5SPI1_SCKLED3.3V背光串一个 10Ω 电阻更稳这里做几个补充说明。VCC 千万不要接到 5V 上虽然很多模块上有稳压芯片能扛但默认稳压输出 3.3V 给屏幕长期 5V 供电发热明显容易伤屏。LED 背光引脚建议串一个小电阻限流直接接 3.3V 也行但串上更放心。CS 和 RESET、DC 这三个脚其实可以用任意普通 GPIO只要在代码里保持一致即可不强制绑定某个引脚。1.3 电平匹配注意事项STM32F103 的 IO 口是 5V 容忍的但 ST7735S 模块的 IO 电平标准是 3.3V。大多数模块板上已经做了电平转换或串了保护电阻直接接 STM32 的 3.3V 系统没问题。如果你用的是 5V 供电的 Arduino 或者 51 单片机最好加电平转换模块或者至少串 1kΩ 电阻降压否则长期使用有烧毁模块 IO 的风险。还有一个容易被忽视的点共地。模块和单片机必须接同一个 GND否则 SPI 信号的电平参考点不一致轻则花屏重则完全无显示。这点在飞线连接时尤其容易犯我见过好几个朋友折腾半天白屏最后发现是共地没接牢。2. CubeMX 初始化与工程配置2.1 新建工程与时钟配置我习惯用 STM32CubeMX 生成初始化代码再用 Keil 写业务逻辑这样既省时间又不容易漏配置。先选择芯片型号以 STM32F103C8T6 为例RCC 选择 HSE 外部晶振时钟树把 System Clock Mux 设为 PLLHCLK 拉到 72MHz。这里注意SPI 的时钟源挂在 APB2 总线上SPI1 最高可以跑到 36MHz72MHz/2SPI2/SPI3 挂在 APB1 上最高 36MHz 但实际受限于 36MHz 上限所以如果用 F103优先选 SPI1 能获得更高的刷屏速度。时钟这一块别偷懒直接用默认的 8MHz 内部时钟。虽然屏能点亮但后续刷屏速度、串口波特率都会受影响而且 SPI 分频计算也容易出问题。花 30 秒把外部晶振和 PLL 配置好后面会省很多事。2.2 SPI 参数配置的几个关键点CubeMX 里找到 SPI1Mode 选择 Transmit Only Master只发送主机模式因为我们只需要往屏幕写数据不需要读。硬件 NSS 关闭用软件控制 CS。参数配置如下参数项设置值说明Prescaler32SPI 时钟 72MHz / 32 2.25MHzClock Polarity (CPOL)LowSPI 模式 0Clock Phase (CPHA)1 EdgeSPI 模式 0Data Size8 Bits逐字节传输First BitMSB First高位在前CRCDisabled关闭Prescaler 我这里先设置为 32也就是 2.25MHz这是保守值确保第一版代码跑起来稳定。等驱动验证通过后再逐步把分频调小提高到 36MHz 分频 4 或分频 2实测 F103 的 SPI1 跑到 18MHz 时 ST7735S 基本已经到极限了再快会出现花屏。关于 SPI 模式ST7735S 数据手册明确支持 Mode 0 和 Mode 3我们代码里用 Mode 0 即可。这里有朋友遇到过明明配置没问题但显示花屏后来发现是 CPOL 和 CPHA 配错了所以这两个参数一定要检查两遍。2.3 GPIO 初始化细节在 CubeMX 里把 PA2、PA3、PA4 配置为 Output Push Pull推挽输出初始电平分别设置为PA2DC低电平、PA3RESET高电平、PA4CS高电平。这样做的目的是让屏幕在上电初始时刻处于非复位状态、片选释放状态避免误触发。注意到一个细节RESET 和 DC 引脚最好选择带 FT5V 容忍标识的引脚虽然我们工作在 3.3V 没有电平问题但后续如果换平台兼容性更好。另外 CS 引脚建议选一个离 SPI 引脚近的 GPIO减少飞线长度高速 SPI 信号线过长会引入串扰。工程生成后在main.c的main()函数里MX_GPIO_Init()和MX_SPI1_Init()已经自动完成我们只需要在初始化代码之后调用自己的 TFT 初始化函数即可。注意 CubeMX 生成的HAL_SPI_Transmit()在发送时会检测 BUSY 标志速度偏慢后续优化时我们会用 DMA 或者直接用寄存器操作这部分第 4 节细说。3. 核心代码实现从引脚操作到完整显示3.1 底层引脚封装与 SPI 发送驱动 ST7735S 本质上就是往 SPI 总线写数据但屏幕区分命令和数据用的是 DC 引脚所以在底层封装上我们要做三个基本的原子操作写命令、写数据、写一串数据。先定义引脚操作宏用宏而不是函数可以减少调用开销刷屏时能明显提升速度#define TFT_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define TFT_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) #define TFT_DC_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET) #define TFT_DC_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET) #define TFT_RES_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET) #define TFT_RES_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET)然后是 SPI 发送一个字节的函数static void TFT_WriteByte(uint8_t data) { HAL_SPI_Transmit(hspi1, data, 1, 100); }这里我用了 HAL 库的阻塞式发送超时时间给 100ms。实际测试中每个字节发送耗时在微秒级别完全够用。但如果你刷大面积的图片这个函数的调用次数会相当多后面我们会优化成连续发送缓冲区。写命令和写数据的接口如下static void TFT_WriteCmd(uint8_t cmd) { TFT_DC_LOW(); TFT_CS_LOW(); TFT_WriteByte(cmd); TFT_CS_HIGH(); } static void TFT_WriteData(uint8_t data) { TFT_DC_HIGH(); TFT_CS_LOW(); TFT_WriteByte(data); TFT_CS_HIGH(); }有的教材在写命令和写数据之间不操作 CS就让 CS 一直拉低这样也可以但每次操作都拉高拉低 CS 可以增强抗干扰能力代价是速度略慢。实际测试下来 CS 电平翻转的耗时在几百纳秒对一个命令一个数据地初始化屏幕来说影响可忽略。3.2 屏幕复位序列ST7735S 上电后需要一次硬件复位来确保芯片进入已知状态。复位序列很简单RESET 拉低至少 10us然后拉高再等待 120ms 左右让内部电路稳定。static void TFT_Reset(void) { TFT_RES_HIGH(); HAL_Delay(10); TFT_RES_LOW(); HAL_Delay(10); TFT_RES_HIGH(); HAL_Delay(120); }复位时间不能太短。ST7735S 数据手册要求 RESET 低电平持续时间至少 10us但建议上拉到高电平后等待 120ms 再执行后续命令因为芯片内部 DCDC 升压电路需要时间稳定。如果复位后立刻发命令偶尔会出现第一条命令丢失的情况表现就是屏幕颜色异常或者局部不亮。这里 HAL_Delay 的单位是毫秒120ms 看着长实际上只是上电瞬间的事不影响后续刷新速度。3.3 初始化序列ST7735S 的“开机流程”这部分是整个驱动最核心、最容易被坑的地方。ST7735S 虽然兼容 ST7735R 的多数命令但不同批次、不同厂商的模组在初始化参数上有差异。我整理了一套在我手头多块 0.96 寸屏上验证过的序列大家可以直接用static void TFT_InitSequence(void) { // 软件复位 TFT_WriteCmd(0x01); // SWRESET HAL_Delay(150); // 关闭睡眠 TFT_WriteCmd(0x11); // SLPOUT HAL_Delay(200); // 帧率设置 TFT_WriteCmd(0xB1); // FRMCTR1 TFT_WriteData(0x01); TFT_WriteData(0x2C); TFT_WriteData(0x2D); TFT_WriteCmd(0xB2); // FRMCTR2 TFT_WriteData(0x01); TFT_WriteData(0x2C); TFT_WriteData(0x2D); TFT_WriteCmd(0xB3); // FRMCTR3 TFT_WriteData(0x01); TFT_WriteData(0x2C); TFT_WriteData(0x2D); TFT_WriteData(0x01); TFT_WriteData(0x2C); TFT_WriteData(0x2D); // 电源控制 TFT_WriteCmd(0xC0); // PWCTR1 TFT_WriteData(0xA2); TFT_WriteData(0x02); TFT_WriteData(0x84); TFT_WriteCmd(0xC1); // PWCTR2 TFT_WriteData(0xC5); TFT_WriteCmd(0xC2); // PWCTR3 TFT_WriteData(0x0A); TFT_WriteData(0x00); TFT_WriteCmd(0xC3); // PWCTR4 TFT_WriteData(0x8A); TFT_WriteData(0x2A); TFT_WriteCmd(0xC4); // PWCTR5 TFT_WriteData(0x8A); TFT_WriteData(0xEE); // VCOM 设置 TFT_WriteCmd(0xC5); // VMCTR1 TFT_WriteData(0x0E); // 存储器访问控制 TFT_WriteCmd(0x36); // MADCTL TFT_WriteData(0x08); // RGB 顺序正常方向 // 像素格式 TFT_WriteCmd(0x3A); // COLMOD TFT_WriteData(0x05); // 16位色 RGB565 // 开启显示 TFT_WriteCmd(0x29); // DISPON HAL_Delay(100); }这套序列的核心逻辑是先软复位让芯片回到初始状态再唤醒屏幕SLPOUT接着设置帧率、电源、VCOM 这些模拟参数最后设置颜色格式并打开显示。有几个参数我要重点解释下。0x3A这个命令设置像素格式0x05表示 16 位色 RGB565这是最常用的格式一个像素占两个字节颜色信息丰富且传输效率高。如果设置成0x0618 位色每个像素要传三个字节刷屏速度会下降颜色显示也不会有肉眼可见的提升所以一般都用 RGB565。MADCTL命令控制显示方向寄存器低三位分别控制行列扫描方向、行地址递增方向、列地址递增方向和 RGB/BGR 顺序具体怎么组合我下一节详细展开。还有一点要注意初始化序列里我加了延时尤其是 SWRESET 之后必须延时 150msSLPOUT 之后延时 200ms。这些延时不是随便拍的ST7735S 内部状态机从复位到退出睡眠需要时间发太快芯片可能还没来得及响应就被下一个命令覆盖了。这部分我最早实现时为了追求“效率”删掉了延时结果屏幕 50% 概率白屏后来对照数据手册才知道原因。3.4 画点函数与颜色格式转换屏幕初始化的最终目的是画点而画点的核心是“设置窗口 写像素数据”。先讲窗口设置。ST7735S 的显示驱动机制是先把要操作的区域用 CASET列地址和 RASET行地址命令圈出来然后再往这个窗口里连续写像素数据写入的数据会自动填充窗口。这个机制类似一块画布上先用铅笔框出范围再往里面倒颜料。static void TFT_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { TFT_WriteCmd(0x2A); // CASET TFT_WriteData(x0 8); TFT_WriteData(x0 0xFF); TFT_WriteData(x1 8); TFT_WriteData(x1 0xFF); TFT_WriteCmd(0x2B); // RASET TFT_WriteData(y0 8); TFT_WriteData(y0 0xFF); TFT_WriteData(y1 8); TFT_WriteData(y1 0xFF); }注意这里地址是 16 位的所以要拆成高低字节分别发送且高位在前。虽然 0.96 寸屏只用到 80x160但 ST7735S 内部寄存器的地址范围是 0~131所以 CASET 和 RASET 必须传 16 位数据否则地址解析会出错。窗口设置好后发送 RAMWR0x2C命令即可进入写像素模式static void TFT_PushColor(uint16_t color) { TFT_WriteCmd(0x2C); // RAMWR TFT_WriteData(color 8); TFT_WriteData(color 0xFF); }画单个像素的完整函数就是先设窗口再写像素void TFT_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { if (x TFT_WIDTH || y TFT_HEIGHT) return; TFT_SetWindow(x, y, x, y); TFT_PushColor(color); }这里要注意 RGB565 的大小端问题。STM32 是小端处理器如果你直接把一个 uint16_t 的颜色值用(uint8_t *)color的方式发出去会发现颜色反了。比如红色应该是 0xF800但如果低字节先发出去屏上就会显示成蓝色。很多朋友第一次画颜色不对就是这个原因。上面的代码先发高字节再发低字节就是为了匹配 ST7735S 期望的传输顺序。常用的几个颜色值可以预定义成宏#define COLOR_BLACK 0x0000 #define COLOR_WHITE 0xFFFF #define COLOR_RED 0xF800 #define COLOR_GREEN 0x07E0 #define COLOR_BLUE 0x001F #define COLOR_YELLOW 0xFFE0 #define COLOR_CYAN 0x07FF #define COLOR_MAGENTA 0xF81F3.5 整屏填充与矩形绘制有了画点函数还不够实际项目里最常用的其实是填充大面积背景。如果用画点函数一格格刷80x160 的屏要调 12800 次函数光窗口设置就要发 3 条命令速度慢得感人。正确做法是设置一次窗口覆盖整个目标区域然后连续发送所有像素数据。void TFT_FillScreen(uint16_t color) { TFT_SetWindow(0, 0, TFT_WIDTH - 1, TFT_HEIGHT - 1); TFT_WriteCmd(0x2C); for (uint32_t i 0; i TFT_WIDTH * TFT_HEIGHT; i) { TFT_WriteData(color 8); TFT_WriteData(color 0xFF); } }这个函数的问题在于每次调用TFT_WriteData都要拉高拉低 CS效率还不够极致。更优的写法是准备一个缓冲区把颜色值填满缓冲区后一次性用 SPI 连续发送。对于内存紧张的 F103C8只有 20KB RAM80x160x2 25.6KB 的缓冲区放不下所以一般用每行缓冲的方式申请一个 160x2 320 字节的行缓冲区填充一行发一行。矩形绘制本质上是多个水平线的组合画边框就是四条线画填充矩形就是逐行填充。这些封装代码网上很多关键是要理解背后的原理窗口设置得越小刷新越快所以绘制小控件时一定要精确到目标区域避免整屏刷新。3.6 方向控制MADCTL 实现横竖屏切换ST7735S 的方向控制是通过 MADCTL0x36命令实现的。这个寄存器的每一位含义如下Bit功能bit7MY行地址翻转bit6MX列地址翻转bit5MV行列交换bit4ML行地址反向刷新bit3BGR0RGB1BGRbit2MH列地址反向刷新默认情况下发送0x00屏幕从左上角开始扫描颜色顺序是 RGB。如果发现颜色不对红色显示成蓝色就把 bit3 置 1发送0x08。如果屏幕是倒着的就调整 bit6 或 bit7。比如常见的竖屏转横屏需要设置 MVbit5为 1同时交换宽高定义。我自己测试下来0.96 寸模组最常用的几个方向配置// 竖屏正常方向RGB MADCTL 0x00 // 竖屏正常方向BGR MADCTL 0x08 // 横屏向右旋转90度 MADCTL 0x60 // 横屏向左旋转90度 MADCTL 0xA0方向变了以后宽高定义要跟着变。如果你在TFT_WIDTH和TFT_HEIGHT里写死的是竖屏 80x160切横屏后逻辑宽度变成了 160、高度变成 80如果不改会有一部分内容画在屏幕外面。建议定义一个TFT_InitEx(uint8_t direction)函数把方向和宽高一起设置好。3.7 字符显示从点阵到文本显示字符的原理和 OLED 类似用一个点阵字库每个字符用 NxM 个 0/1 表示像素状态。常用的是 8x16 点阵ASCII 字体即每个字符占 8 列 16 行用 16 个字节表示每个字节的每一位对应一列中的 8 个像素点。const uint8_t asc2_1616[][16] { // 空格 { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }, // ! (0x21) { 0x00,0x00,0x00,0x04,0x04,0x04,0x04,0x04, 0x04,0x04,0x00,0x00,0x04,0x04,0x00,0x00 }, // 其他字符省略... };显示一个字符的核心函数void TFT_ShowChar(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bgColor) { uint8_t i, j; uint16_t temp; for (i 0; i 16; i) // 16行 { temp asc2_1616[(c - 0x20)][i]; for (j 0; j 8; j) // 8列 { if (temp (0x80 j)) TFT_DrawPixel(x j, y i, color); else TFT_DrawPixel(x j, y i, bgColor); } } }这个函数逻辑很清晰但效率不高因为每个像素都调用一次TFT_DrawPixel而TFT_DrawPixel内部又设置了窗口。优化思路是先把一个字符 8x16128 个像素的颜色值生成好然后一次性设置窗口并写入。这样速度能提升好几倍刷一屏文字时差距非常明显。后续扩展字符串显示、数字显示、甚至 16x16 汉字点阵都是在这个基础上叠加原理不变。4. 性能优化与进阶玩法4.1 SPI 时钟频率怎么选第一版代码我保守地设置了 SPI 分频 32也就是 2.25MHz。这个速度刷整屏需要大约 25.6KB / 2.25MHz ≈ 90ms人眼能明显看到从上到下的刷新过程。把分频降到 89MHz后整屏刷新时间缩短到约 22ms基本看不出来刷新痕迹了。再往下压到 18MHzF103 的 SPI1 已经接近极限配合高质量的飞线和稳定的电源勉强可用。实际项目中建议这样调先用分频 32 确保显示正常然后逐级降分频32→16→8→4每降一级观察是否出现花屏或条纹。一旦出现回退到上一档即可。需要强调的是SPI 速率不是越高越好ST7735S 的极限速率取决于 PCB 走线长度、模块质量、供电稳定度甚至飞线的方式所以“实测为准”是唯一标准。4.2 用 DMA 提升刷屏速度对屏幕这类外设DMA 是提升性能最有效的手段。把一整块颜色数据通过 DMA 搬运到 SPI 数据寄存器CPU 只需要启动一次传输然后可以去做别的事传输完成后触发中断。刷屏期间 CPU 占用率从 100% 降到了几乎为 0。void TFT_FillScreen_DMA(uint16_t color) { uint16_t buffer[256]; for (int i 0; i 256; i) buffer[i] color; TFT_SetWindow(0, 0, TFT_WIDTH - 1, TFT_HEIGHT - 1); TFT_DC_HIGH(); TFT_CS_LOW(); for (uint32_t offset 0; offset TFT_WIDTH * TFT_HEIGHT; offset 256) { HAL_SPI_Transmit_DMA(hspi1, (uint8_t *)buffer, 512); while (hspi1.State ! HAL_SPI_STATE_READY); } TFT_CS_HIGH(); }这里注意一个细节HAL_SPI_Transmit_DMA要求发送缓冲区在传输期间不能被修改所以我们每传一块数据后要等传输完成再填下一块。另外 DMA 发送的数据宽度要和 SPI 配置一致这里是 8 位所以 256 个 uint16_t 要转换成 512 字节。用 DMA 后整屏刷新时间不只是快一半因为 DMATransmit 是硬件搬运不占用 CPU 指令周期实际刷屏速度更多受限于 SPI 时钟本身CPU 可以去处理其他任务。4.3 局部刷新实践很多时候我们不需要刷新整个屏幕比如显示一个温度值只需要更新数字所在的那块小区域。ST7735S 的窗口机制天然支持局部刷新设置一个精确到单个字符大小的窗口然后填充新的内容。void TFT_ShowString_Area(uint16_t x, uint16_t y, const char *str, uint16_t color, uint16_t bgColor) { uint16_t len strlen(str); for (uint16_t i 0; i len; i) { TFT_ShowChar(x i * 8, y, str[i], color, bgColor); } }这个函数比逐个设置窗口刷新整个屏幕高效得多但因为TFT_ShowChar内部还在每个像素上调用画点函数仍有优化空间。更进一步的方案是把字符点阵生成到一个小缓冲区一次性写入屏幕。具体实现思路是先调用TFT_SetWindow(x, y, x 8*len - 1, y 16 - 1)圈出字符显示区域然后对每个字符的每一行点阵字节做遍历遇到 1 写入 color遇到 0 写入 bgColor用 SPI 连续发送。局部刷新在制作菜单 UI、仪表盘、游戏界面时非常实用配合 STM32 的定时器中断可以实现无闪烁的动态画面。5. 常见问题与排查技巧实录这部分是我最想写的因为屏幕驱动最大的坑往往不在代码逻辑而在那些看似“不可能”的硬件问题上。我把折腾 ST7735S 过程中遇到的高频问题按现象分类整理出来方便大家对照查。5.1 白屏问题排查清单白屏是新手遇到最多的问题表现是背光亮了屏幕一片白或者背光不亮屏幕发暗。排查顺序如下现象可能原因排查方法屏幕全白背光亮初始化序列没执行或执行失败检查 DC/CS/RESET 引脚配置确认所有命令都发送成功屏幕全白背光不亮LED 引脚没接或电压不足确认背光引脚接 3.3V万用表测电压屏幕全白但偶尔正常复位时序问题检查 RESET 引脚初始电平确认复位低电平持续时间 10us屏幕部分白部分有显示初始化序列有误或窗口设置越界检查 CASET/RASET 参数确认坐标范围在 0~79 和 0~159 内我自己踩过的一个坑是TFT_WriteCmd函数里 DC 引脚没有在发送数据前重新拉高导致第一个像素数据被当成命令发出去屏幕上半部分颜色异常。这种问题肉眼很难看出来建议用逻辑分析仪抓 SPI 波形看 DC 信号是否在合适的位置翻转。5.2 花屏、鬼影、颜色错乱花屏现象很多样常见的有三种满屏雪花点、显示内容有横向条纹、颜色整体偏色。满屏雪花点通常是 SPI 速率过高导致的采样错误把分频调大一档即可解决。横向条纹或者某几行颜色异常多半是电源问题ST7735S 内部升压电路对电源纹波敏感飞线过长或电源线与信号线并行走线会引入干扰。这时候在模块 VCC 和 GND 之间并一个 10uF 电解电容再加一个 0.1uF 陶瓷电容往往立竿见影。颜色整体错乱红色变蓝色是 BGR 设置错误。把MADCTL寄存器的 bit3 取反例如原来是 0x00 就改成 0x08原来是 0x08 就改成 0x00。这个现象在有些屏上明显有些屏上几乎看不出来因为颜色相近但如果你的 UI 有明确的红绿蓝三色块就能一秒钟发现问题。还有一种特殊情况显示内容出现镜像或者旋转 90 度。这是 MADCTL 的 MY、MX、MV 位没配对0.96 寸屏的常见方向配置我在 3.6 节已经列出来了直接对照改即可。5.3 屏幕内容偏移或显示不完整有朋友遇到过这样的问题屏幕能显示但内容整体偏左或偏右有一部分被切掉了另一部分有黑边。这通常是 ST7735S 的偏移设置不对。虽然 0.96 寸屏只用 80x160 区域但 ST7735S 芯片的 GRAM 是 132x162不同厂商模组对 80x160 区域在 132x162 中的起始位置定义不同常见的偏移有 (0,0)、(1,1)、(2,1) 等。解决方法是修改初始化序列里 CASET 和 RASET 的默认偏移或者在创建窗口时加上偏移量#define TFT_OFFSET_X 0 #define TFT_OFFSET_Y 0 static void TFT_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) { x0 TFT_OFFSET_X; x1 TFT_OFFSET_X; y0 TFT_OFFSET_Y; y1 TFT_OFFSET_Y; // 后续命令不变 }需要特别注意的是偏移量只加在窗口设置上不要加到所有坐标运算里否则调用画点函数时逻辑坐标和物理坐标会乱套。调整偏移是个碰运气的过程每次改一点刷新一次屏幕观察直到边缘对齐。5.4 下载与调试环境相关的坑这部分虽然不是屏幕本身的问题但却是很多初学者卡住的第一道坎。STM32F103C8T6 的下载方式有 ST-Link 和串口UART两种。ST-Link 需要安装驱动Keil 里 Debug 设置选择 ST-Link Debugger 并正确配置 Flash Download 选项串口下载需要 USB 转 TTL 模块CH340、CP2102 等芯片BOOT0 引脚拉高进 ISP 模式才能烧录烧完记得把 BOOT0 拉低复位。我建议优先用 ST-Link因为支持在线仿真可以单步执行初始化序列观察每一条命令发送后屏幕的状态变化。调试驱动时这是最重要的工具没有之一。串口下载虽然也能烧进去但出了问题很难定位你也不知道是代码没有烧录成功还是屏幕没初始化好。5.5 飞线布局的一些体会最后分享几个布线上的经验。屏模块的 FPC 排线非常脆弱弯折一两次就可能断线飞线时尽量把杜邦线插牢不要频繁插拔。如果使用面包板注意 SPI 时钟线和 MOSI 线不要紧贴着并排走如果有条件在两根线之间隔一个地线能有效减少串扰。还有一个容易忽略的点电源线要粗。VCC 和 GND 是电流通路模块的背光电流加上芯片功耗瞬时峰值可能到几十毫安如果用细长的杜邦线接 5V 电源端子线阻上产生的压降会导致模块供电不稳。我吃过一次亏供电线长 20cm屏幕显示正常但刷屏时偶尔闪断缩短电源线后问题彻底消失。写在最后断断续续说了这么多核心想表达的其实就一句话驱动 ST7735S 这块屏本身不难SPI 接口加初始化序列就能点亮但要做到稳定、高速、可复用背后的注意点非常多。我个人在实际项目中的体会是屏幕驱动代码写一次能复用到很多项目里值得花点时间把它封装好建议按“底层 SPI 发送 → 画点 → 画线/矩形 → 字符 → 图片”这样的层次结构来组织代码以后任何 STM32 项目想加屏幕显示直接把文件拖过去改一下引脚就行。最后再分享一个小技巧如果你手头有逻辑分析仪强烈建议在首次驱动时抓一下 SPI 的波形重点关注 DC 信号的翻转时机和 CS 信号的有效性。很多看着莫名其妙的显示问题波形图上一眼就能看清原因。设备便宜几十块钱的 8 通道逻辑分析仪就完全够用绝对是嵌入式调试的“神器”。动手点亮第一块屏后后续玩 GUI、做小游戏、接传感器显示数据都会顺畅很多祝一次点亮。
返回列表