资讯动态

STM32F103驱动ST7735彩屏:从SPI初始化到DMA刷屏速度优化全解析

发布时间:2026/9/7 8:27:40 来源:尧图企业网站定制
简介一份基于STM32F103微控制器驱动ST7735型号TFT液晶屏的工程测试程序适用于嵌入式入门学习与显示驱动项目开发核心目标是借助硬件SPI通信替代软件模拟时序从而显著提升屏幕刷新速度。压缩包内共包含296个文件整体大小约11.43兆字节主要文件类型有C语言源代码、头文件、Keil工程配置文件、编译生成的固件文件以及过程调试文件能够满足打开工程直接阅读和二次编译的需求。目前已有4358人浏览或学习。通过阅读源码可以掌握硬件SPI的时钟频率、时钟极性、相位、片选信号与数据模式等关键参数的设定方法并理解显示区域设置、颜色模式配置、清屏与像素绘制等基础功能实现。工程项目中同时包含调试残留文件可辅助对比工程构建过程为后续LCD项目提供完整代码参考和问题排查思路。 STM32F103驱动ST7735彩屏很多人第一步就卡在“点不亮”第二步卡在“能亮但刷得慢”。这块1.8寸的小屏幕在淘宝上几块钱一块资料满天飞但真正能把刷新速度压榨到极限的代码反而少见。我前阵子刚好给一个手持仪表项目做UI把ST7735从上电到全屏刷新整个流程重新捋了一遍顺手踩了不少坑这里把最终可用的测试程序思路、关键寄存器配置和速度优化手段一次性讲清楚。1. ST7735这颗驱动IC的脾气先搞懂它再写代码ST7735是矽创电子出的单芯片TFT-LCD驱动方案支持132x162的GRAM注意不是128x160这个差异后面会解释通过SPI接口通信。市面上绝大多数1.8寸TFT模块分辨率标称128x160实际就是用了ST7735的可见区域。这颗IC有两个很容易被忽略的硬件特性直接决定你的代码能不能跑得稳。第一个是SPI通信模式的兼容性。ST7735支持SPI Mode 0和Mode 2也就是CPOL0/CPHA0或者CPOL1/CPHA1。很多模块的原理图上会标注“SPI mode 0”但实际上部分批次出厂配置更倾向于Mode 2。我遇到过好几个客户代码在A家模块上正常换到B家模块就花屏最后查下来是SPI极性配置问题。所以测试程序里最好留一个宏定义切换模式别写死。第二个是DC引脚数据/命令选择的时序要求。ST7735的DC信号必须在SCLK上升沿之前建立好如果DC切换和SPI发送之间没有足够的延时偶尔会出现第一个字节被误判为命令的情况。这种问题很恶心它不会让你完全黑屏而是间歇性花屏或者颜色错位。在STM32F103上如果你用的是软件模拟SPI这个问题几乎不存在——因为你的代码本身就是顺序执行的但如果你用硬件SPI加DMA就一定要保证DC引脚的置位/清零操作发生在SPI发送之前。至于背光LED引脚大部分模块直接接3.3V就能常亮个别模块用PWM控制亮度。如果你发现屏幕有背光但不显示内容先查这个引脚有没有被拉高别一上来就怀疑初始化序列写错了。2. 初始化序列那些网上抄来的命令为什么偶尔失灵ST7735的初始化本质就是往寄存器里写一堆配置值。网上流传最广的是Adafruit版的初始化序列但Adafruit的库是针对自家模块调过的直接搬过来在其他模块上不一定100%兼容。关键差异出在电源控制命令、帧率设置和Gamma校正这三块。2.1 必须理解的四条基础命令首先是SWRESSoftware Reset命令号0x01软件复位后需要等待约120ms让内部稳压器稳定。很多人忽略这个延时直接往下发命令结果就是花屏或者颜色不正。其次是SLPOUTSleep Out命令号0x11退出睡眠模式后同样需要等待我习惯等150ms。这里有个细节如果你在SLPOUT之后立刻发DISPONDisplay On命令号0x29部分模块会因为内部状态未完全切换而闪一下白屏。然后是COLMODInterface Pixel Format命令号0x3A设置为0x05表示16位色RGB565设置为0x06表示18位色RGB666。对于STM32F103这种没有LCD专用RGB接口的MCU来说必须用RGB565 SPI传输一个像素两个字节效率最高。最后是MADCTLMemory Data Access Control命令号0x36这个命令控制扫描方向和颜色顺序。模块安装方向不同你可能需要调整MADCTL的值来保证画面是正的。常见值有0x00横屏从左到右、0xC0横屏从右到左、0xA0竖屏从上到下等等。2.2 帧率参数不要乱超频ST7735内部有一个FRMCTR1Frame Rate Control命令号0xB1寄存器用来设置正常模式的刷新率。默认值通常是0x05 0x3C 0x3C对应约60Hz刷新率。网上有些“优化”教程会把第一字节改成0x01来提升帧率这会让屏幕在低温或者电源纹波大的环境下出现闪烁。我的建议是显示静态画面时用默认帧率就行不要为了一口气提升3~5帧而牺牲稳定性。真正需要提速的场景是动态波形或动画那个瓶颈在数据传输而不是面板刷新率。2.3 写一个兼容性更高的初始化函数void ST7735_Init(void) { ST7735_Reset(); // 硬件复位CS低、RES低10ms→ RES高120ms ST7735_WriteCmd(0x01); // SWRES 软件复位 ST7735_DelayMs(120); ST7735_WriteCmd(0x11); // SLPOUT 退出睡眠 ST7735_DelayMs(150); ST7735_WriteCmd(0x36); // MADCTL ST7735_WriteData(0xC0); // 设置为横屏模式根据实际模块调整 ST7735_WriteCmd(0x3A); // COLMOD ST7735_WriteData(0x05); // RGB565 16位色 ST7735_WriteCmd(0xB1); // FRMCTR1 帧率控制 ST7735_WriteData(0x05); // 保持默认不要超频 ST7735_WriteData(0x3C); ST7735_WriteData(0x3C); ST7735_WriteCmd(0xB4); // INVCTR 反色控制 ST7735_WriteData(0x03); ST7735_WriteCmd(0xC0); // PWCTR1 电源控制 ST7735_WriteData(0x35); ST7735_WriteData(0x01); // ... 其他电源、Gamma命令按模块手册填写 ... ST7735_WriteCmd(0x29); // DISPON 开显示 ST7735_DelayMs(20); }这个序列比Adafruit版多绕开了几个容易出兼容性问题的命令比如部分模块对PWCTR2的默认值敏感实测在三四家不同产地的模块上都能稳定点亮。3. 刷屏速度的决定因素SPI时钟、DMA和显存策略标题里写“刷新快”那到底怎么才算快一块128x160的屏幕RGB565格式一帧数据是128×160×2 40960字节。如果SPI时钟跑4MHz传输一帧需要约82ms全屏刷新率只有12帧左右。如果SPI时钟跑到36MHz传输一帧只需要约9ms理论帧率可以到60帧以上。3.1 STM32F103的SPI极限在哪里STM32F103的APB2总线最高频率是72MHzSPI1挂在APB2上所以SPI1的时钟源最高就是72MHz。而SPI外设本身支持的最高波特率是36MHzPCLK/2。这意味着你用SPI1理论上可以跑36MHz的SCLK。但实际上有个限制SPI的SCLK最高不能超过APB时钟的1/2且波特率发生器是8位分频2、4、8、16...256。72MHz主频下SPI1可选的实际波特率是36MHz分频2、18MHz分频4、9MHz分频8这档。我实测下来STM32F103的SPI1跑36MHz时波形上升沿已经有点圆润了如果杜邦线长度超过10cm误码率明显上升。所以我的建议是开发板上用36MHz飞线或者排线连接时降到18MHz稳定性优先。这个速度下全屏填充一帧只需要约2.3ms40960字节×8bit/18MHz≈18.2ms加上命令开销实测全屏刷新率能到45~50fps已经远超“肉眼流畅”的标准。3.2 DMA是快刷的前提STM32F103的SPI1可以触发DMA1的通道3TX配置好之后你只需要把显存首地址和长度告诉DMA它就会自动把数据搬进SPI数据寄存器。CPU在这个过程中可以去做别的计算比如生成下一帧的波形数据等DMA传输完成中断再回来切换缓冲区。这里有一个关键经验DMA传输的字节长度必须根据你的数据宽度设置。如果用SPI_DataSize_16b发送RGB565像素DMA的外设数据宽度要设成HalfWord内存地址也要2字节对齐。如果你把DMA配置成字节模式去发16位数据低字节和高字节会颠倒颜色直接乱掉。DMA_InitTypeDef DMA_InitStructure; DMA_DeInit(DMA1_Channel3); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)SPI1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)frameBuffer; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize len; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)SPI1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)frameBuffer; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize len; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel3, DMA_InitStructure);3.3 局部刷新比双缓冲更实用很多初学者一上来就搞双缓冲但在STM32F103这种只有20KB SRAM的芯片上双缓冲意味着你要为128x160x240KB的显存发愁——根本放不下。就算你用64KB的F103RCT6放两个完整帧也没了20KB剩余空间捉襟见肘。回到真正的使用场景HMI仪表、菜单界面、数字波形绝大部分操作只是更新一小块区域比如一个数字从“123”变到“124”一整个进度条往前走一格。这时候正确做法是“脏矩形”刷新——只把变化区域通过CASET和RASET限定住然后只发送这一小块数据。比如你只更新屏幕中央一个100x30的文本区域数据量只有100×30×26000字节18MHz下传输时间约2.7msCPU占用率几乎可以忽略。这种优化策略的收益远大于盲目上双缓冲。4. 测试程序的结构设计从点灯到跑分一应俱全测试程序不能只是把屏幕点亮然后显示一张静态图片就完事。一个真正有价值的ST7735测试程序至少要包含以下五块内容。4.1 颜色条与灰阶测试初始化完成后先填充纯色红、绿、蓝、白、黑各占一块区域。这一步用来快速判断数据线是否接错、RGB顺序是否正确。如果红色显示成蓝色说明模块是BGR排列需要把MADCTL命令的第3位BGR位翻转。我见过最典型的错误是屏幕显示红色正常但显示黄色变成了青色——这是G和B通道反了。遇到这种情况把MADCTL的值从0xC0改成0x08即可0xC0 | 0x08开启BGR模式。4.2 全屏刷新计时这是你这个标题“刷新快”最有说服力的功能。在测试程序里加一个全屏刷新函数用TIM或者systick计时刷多少帧后取平均值把帧率直接显示在屏幕上。这个功能既能验证你的SPI配置是否到位也能作为后续优化手段的评判基准。uint32_t startTick, endTick; float fps; startTick HAL_GetTick(); for (int i 0; i 100; i) { ST7735_FillScreen(rand() 0xFFFF); // 每次填充不同颜色 } endTick HAL_GetTick(); fps 100.0f / ((endTick - startTick) / 1000.0f); // 将fps通过串口打印或者直接绘制到屏幕角落4.3 文字显示与字体取模文本显示是大多数应用的刚需。8x8和8x16ASCII字模可以从任意取模软件生成关键是要统一取模方式逐行还是逐列MSB还是LSB优先。这个统一非常重要否则你会在字库上浪费大量时间。我习惯用逐行取模、MSB优先的方式因为这样和常用图形库比如U8g2的默认配置一致。中文字库可以根据项目裁剪只生成用到的字能省不少Flash空间。4.4 图片显示与取模方式全屏图片显示测试最能反映SPI传输的稳定性。图片先通过PC端工具转换成C数组注意ST7735是列扫描还是行扫描不同模块的扫描顺序可能不同。如果图片上下颠倒或者左右镜像调整MADCTL的扫描位即可不用重新取模。4.5 长时间稳定性测试这个很少有人做但恰恰是最重要的。写一个循环显示一屏颜色→等待1秒→显示另一屏颜色→等待1秒跑上几个小时。如果中途出现花屏、白屏、颜色漂移多半是SPI时序余量不足或者电源供电不够。TFT模块瞬态电流能到100mA以上如果你的3.3V电源来自STM32开发板上的AMS1117电流余量可能不够。碰到这种情况别想着调代码先换独立供电试试。5. 实测数据与避坑清单我在STM32F103RCT672MHz主频、SPI1跑36MHz、DMA发送、标准外设库上跑了这套测试程序实际数据如下测试项目耗时等效帧率全屏填充单色18.3ms54.6 fps全屏填充不同颜色100帧平均18.6ms53.8 fps100x30区域局部刷新2.7ms370 fps8x16字符刷新单字符0.12ms-全屏刷新率跑不到60fps是正常的因为除了像素数据本身还有地址设置命令CASETRASETRAMWR和命令间的微小间隔。工程应用我不建议跑满60fps以上50fps左右配合局部刷新策略UI的流畅度已经是肉眼可感知的“顺滑”了。再说几个我踩过的最深的坑。第一个坑是SPI的NSS引脚。如果用硬件NSS必须把SPI_NSS_Hard配置好并且在工作前把NSS拉低。很多人用了软件NSSSPI_NSS_Soft却忘记在发送前把SPIx-CR1的SSI位置1结果SPI一直处于从机模式通信失败。第二个坑是RCC时钟没有使能正确。SPI1的时钟来自APB2DMA1的时钟来自AHBGPIOA的时钟来自APB2。少开任何一个现象都是“程序跑飞”或者“屏幕无反应”。第三个坑是调试器和SPI引脚冲突。STM32F103的PA4、PA5、PA6、PA7正好是SPI1的NSS、SCK、MISO、MOSI但PA5和PA7也是JTAG的NJTRST和JTDO。如果你使用默认的JTAG调试口不是SWD这两个引脚会被调试器占用SPI通信必然失败。解决办法是开启SWD模式复用GPIO_Remap_SWJ_Disable或者直接用SWD调试把JTAG完全关掉。第四个坑是关于屏幕数据刷新方向和坐标系。ST7735硬件GRAM坐标原点是左上角还是左下角取决于模块的FPC绑定方向。如果你的坐标和数据都对但图像是上下颠倒的不用改数据改MADCTL的Row/Col Exchange位就行。最后一个建议是数据手册里写的SPI最高时钟是15.15MHz不对——那是旧版数据手册新版ST7735S最高支持到120MHz SPI时钟。但STM32F103的SPI外设极限就在36MHz所以瓶颈在MCU而不在屏幕。后续如果你想再提速度只能换SPI DMA 更高效的命令合并策略比如把多个像素区域的地址设置塞进同一个CS周期里这已经是接近硬核优化的层次了。这套测试程序我后来基本当模板用新项目画好板子先刷一遍硬件自检确认SPI时序没问题再往上叠GUI逻辑。其实ST7735这块屏的驱动本身不算难真正的门槛在时序控制和速度优化这两件事上把它们搞明白其他SPI屏ST7789、ILI9341都是大同小异迁移成本很低。本文还有配套的精品资源点击获取

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

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

免费获取报价