1. ST7735S屏幕与SPI驱动的那些事儿第一次拿到ST7735S这块1.44寸TFT屏幕时我完全被它小巧的尺寸和丰富的显示效果吸引了。但真正开始驱动它时才发现SPI模式的选择竟然有这么多门道。记得当时用软件SPI调试屏幕刷新慢得像幻灯片改硬件SPI后终于流畅了些直到加上DMA才真正体会到什么叫丝般顺滑。SPI通信就像快餐店的取餐窗口软件SPI相当于服务员手动打包每个汉堡硬件SPI升级成了自动打包机而DMA则是给打包机加上了传送带。在STM32F103平台上这三种方式我都亲自试了个遍实测数据差异惊人软件SPI全屏刷新约8-12FPS硬件SPI全屏刷新约25-35FPS硬件SPIDMA全屏刷新可达50FPS2. 软件SPI灵活但低效的手动挡2.1 GPIO模拟的底层实现软件SPI最让人头大的就是得手动操作GPIO电平变化。下面这段代码我调了整整两天才稳定void spi_write_place(u8 data) { SPI_DATA_OUT data 1 ? SPI_DATA_VALID : !SPI_DATA_VALID; SPI_SCLK !SPI_EDGE_TRIGGERED; delay_us(SPI_SCLK_LOW_KEEP); // 关键延时 SPI_SCLK SPI_EDGE_TRIGGERED; delay_us(SPI_SCLK_HIGH_KEEP); // 另一个关键延时 }每个时钟边沿都要手动控制就像用筷子一粒粒夹米饭。ST7735S的SPI时序要求严格延时参数不对就会花屏。我最后用的是0.5μs延时在72MHz主频下这是能稳定工作的最小值。2.2 性能瓶颈分析用逻辑分析仪抓取的波形显示发送一个字节要花费约20μs。全屏128x128像素每个像素16bit2字节算下来完整刷屏要128 x 128 x 2 x 20μs ≈ 655ms → 约1.5FPS实际通过优化循环结构和减少函数调用我能做到8FPS左右但这已经是STM32F103的软件SPI极限了。提示如果非要使用软件SPI建议将GPIO操作全部改成寄存器直接操作能提升约30%速度3. 硬件SPI解放CPU的自动挡3.1 硬件外设配置要点切换到硬件SPI后代码清爽多了SPI_Initure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_2; // 36MHz SPI_Initure.SPI_CPHA SPI_CPHA_1Edge; SPI_Initure.SPI_CPOL SPI_CPOL_Low; SPI_Init(SPI1, SPI_Initure);这里有几个坑我踩过预分频设为2时SPI时钟36MHz72MHz/2这是F103的极限CPOL和CPHA必须与ST7735S手册一致模式0或模式3硬件NSS引脚最好禁用用普通GPIO手动控制CS3.2 实测性能提升同样的全屏刷新测试单字节传输时间缩短到约0.5μs理论刷屏速度128x128x2x0.5μs ≈ 16ms → 约60FPS实际由于函数调用等开销稳定在35FPS左右瓶颈主要来自等待SPI发送完成的while循环频繁的CS引脚电平切换数据准备时间4. 硬件SPIDMA性能天花板4.1 DMA配置核心代码加上DMA后代码复杂度陡升但效果惊人void dma1_init(DMA_Channel_TypeDef* dma_chanel, u32 mem_addr, u32 per_addr, u32 buf_size) { DMA_Initure.DMA_BufferSize buf_size; DMA_Initure.DMA_DIR DMA_DIR_PeripheralDST; // 内存到外设 DMA_Initure.DMA_MemoryBaseAddr mem_addr; DMA_Init(dma_chanel, DMA_Initure); } void dma1_to_spi1(DMA_Channel_TypeDef* dma_chanel, u32 buf_size, uint16_t spi_dma_req, uint32_t dma_flag) { DMA_Cmd(dma_chanel, DISABLE); DMA_SetCurrDataCounter(dma_chanel, buf_size); DMA_Cmd(dma_chanel, ENABLE); SPI_I2S_DMACmd(SPI1, spi_dma_req, ENABLE); }4.2 性能对比数据测试方法连续100次全屏刷新取平均驱动方式耗时(ms)帧率(FPS)CPU占用率软件SPI125898%硬件SPI283575%硬件SPIDMA185515%DMA模式下CPU基本被解放出来可以同时处理其他任务。实测发现将显示数据放在CCM RAM64KB中还能再提升约5%性能。5. 三种模式的实战选择建议5.1 何时用软件SPI虽然性能最差但在以下场景仍有用武之地硬件SPI引脚被其他外设占用需要非标准SPI时序如特殊延时作为学习SPI协议的实验手段5.2 硬件SPI的适用场景绝大多数项目的首选方案需要中等刷新率30FPS左右硬件资源充足项目周期紧张开发简单5.3 DMA方案的终极选择以下情况必须上DMA需要动画或视频播放系统有实时性要求如工业HMI需要低CPU占用多任务系统有个项目我同时驱动两块屏幕主屏用DMA刷新副屏用硬件SPICPU占用仍能控制在30%以下。6. 关键源码解析6.1 软件SPI的数据发送最底层的位操作函数每个时钟周期都要手动控制void spi_write(u8 data) { u8 i 0, place; for(; i 8; i) { place data 1 (7 - i); spi_write_place(place ! 0 ? 1 : 0); } }6.2 硬件SPI的优化技巧使用状态标志位而非固定延时void spi_write(u8 data) { u16 next 0; while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) RESET) { if(next 500) return; // 超时保护 } SPI_I2S_SendData(SPI1, data); }6.3 DMA传输的核心机制双缓冲技术大幅提升效率u8 dma_data[2][128*2]; // 双缓冲 void DispPic(u8 x, u8 y, u8 w, u8 h, const u8 *p) { // 填充缓冲区1 while(DMA1_Channel3-CNDTR); // 等待传输完成 // 填充缓冲区2 DMA1_Channel3-CMAR (u32)dma_data[0]; // 切换缓冲区 }7. 调试过程中踩过的坑SPI模式配置错误一开始把CPHA设成了第二边沿导致屏幕显示错乱。后来用逻辑分析仪抓波形才发现问题。DMA缓冲区溢出忘记设置正确的BufferSize导致传输到最后几个字节时数据错位。现在都会在代码里加这个检查assert(sizeof(dma_data) % 2 0); // 必须为偶数屏幕初始化序列错误ST7735S的初始化命令非常讲究有次漏掉了MADCTL命令导致颜色显示完全不对。后来把厂家提供的初始化代码封装成了函数void lcd_config() { lcd_write(TFT_CMD, LCD_SLPOUT); delay_ms(120); // 必须的延时 // ...其他初始化命令 }DMA与SPI时钟不同步有次DMA配置完了但SPI没启用数据全乱套。现在都会严格按照这个顺序初始化1. GPIO初始化 2. SPI外设初始化 3. DMA初始化 4. 使能SPI 5. 最后使能DMA8. 性能优化实战技巧SPI时钟极值测试F103的SPI理论上最高18MHz但实测发现ST7735S可以跑到24MHzSPI_BaudRatePrescaler_3不过稳定性会下降。DMA缓冲区大小选择经过测试128字节的缓冲区性价比最高小于64字节DMA频繁中断反而降低效率大于256字节内存占用过高内存布局优化将显示缓冲区放在CCM RAM后DMA传输速度提升约15%__attribute__((section(.ccmram))) u8 dma_data[128*2];指令预取优化开启预取缓冲区并设置正确的等待周期FLASH-ACR | FLASH_ACR_PRFTBE; // 开启预取 FLASH-ACR ~FLASH_ACR_LATENCY; FLASH-ACR | FLASH_ACR_LATENCY_2; // 2等待周期9. 三种方案的完整工程建议对于想要快速上手的开发者我的项目结构建议如下/Drivers /STM32F1xx_HAL_Driver # 标准库 /ST7735S st7735s.c # 屏幕驱动 /Config software_spi.c # 软件SPI实现 hardware_spi.c # 硬件SPI实现 dma_spi.c # DMA实现 /Application /Demo software_spi_demo.c hardware_spi_demo.c dma_spi_demo.c在st7735s.h中使用宏定义切换模式#define USE_SOFTWARE_SPI 0 #define USE_HARDWARE_SPI 1 #define USE_DMA_SPI 010. 终极选择指南最后给个直白的建议表需求场景推荐方案预期帧率开发难度学习SPI原理软件SPI10FPS★★☆☆☆简单状态显示硬件SPI30-40FPS★★★☆☆动态图表/简单动画硬件SPI40-50FPS★★★★☆视频播放/复杂UIDMA SPI50FPS★★★★★多屏驱动/低功耗应用DMA SPI自定义★★★★★最近在一个智能家居项目中我同时驱动了ST7735S屏幕和WIFI模块DMA方案让CPU仍有足够资源处理网络数据。当屏幕刷新和网络通信同时进行时帧率只下降了约5FPS这要是用软件SPI早就卡成PPT了。