STM32 SPI刷屏太慢?试试DMA+中断优化,实测刷屏时间从XXms降到387ms

张开发
2026/4/20 23:30:47 15 分钟阅读

分享文章

STM32 SPI刷屏太慢?试试DMA+中断优化,实测刷屏时间从XXms降到387ms
STM32 SPI刷屏性能优化实战从阻塞传输到DMA中断的蜕变之路当你在嵌入式项目中首次点亮那块TFT屏幕时那种成就感往往会被随后出现的刷屏延迟所冲淡。我清楚地记得第一次用STM32标准库的SPI驱动240x320分辨率LCD时完成全屏刷新需要近2秒——这种肉眼可见的卡顿对任何交互界面都是灾难性的。经过一系列优化最终将刷屏时间压缩到387ms这中间的每一步优化都值得细细道来。1. 问题定位与基准测试在开始任何优化前我们必须建立可量化的性能基准。使用逻辑分析仪捕获SPI波形时发现了三个关键现象字节间隔异常每个8位数据包之间出现了约4.5μs的间隔18MHz时钟下本应只需0.44μsCPU利用率峰值逻辑分析仪显示SCK信号停止期间CPU正在疯狂轮询状态寄存器带宽利用率不足实际有效数据传输仅占用了15%的总时间通过示波器测量到的原始传输参数如下参数数值说明时钟频率18MHzAPB1二分频单字节传输时间0.44μs理论值实际字节间隔5μs包含软件延迟有效带宽利用率8.2%实际/理论传输时间比问题根源直指标准库的SPI_I2S_SendData()函数实现void SPI_SendByte(uint8_t byte) { while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) RESET); // 等待发送缓冲区空 SPI_I2S_SendData(SPI2, byte); // 写入数据寄存器 while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) RESET); // 等待接收完成 SPI_I2S_ReceiveData(SPI2); // 清除RXNE标志 }这段代码存在两个致命缺陷轮询等待消耗大量时钟周期冗余的接收流程即使不需要接收数据2. DMA基础优化方案直接内存访问(DMA)技术允许外设与内存间直接传输数据无需CPU介入。针对SPI刷屏场景我们只需要单向传输模式。以下是关键配置步骤2.1 硬件初始化首先调整SPI为单工发送模式SPI_InitTypeDef SPI_InitStruct {0}; SPI_InitStruct.SPI_Direction SPI_Direction_1Line_Tx; // 单工发送 SPI_InitStruct.SPI_Mode SPI_Mode_Master; SPI_InitStruct.SPI_DataSize SPI_DataSize_8b; SPI_InitStruct.SPI_CPOL SPI_CPOL_High; SPI_InitStruct.SPI_CPHA SPI_CPHA_2Edge; SPI_InitStruct.SPI_NSS SPI_NSS_Soft; SPI_InitStruct.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_2; SPI_InitStruct.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI2, SPI_InitStruct);2.2 DMA通道配置使用DMA1通道5SPI2_TX的典型配置DMA_InitTypeDef DMA_InitStruct {0}; DMA_InitStruct.DMA_PeripheralBaseAddr (uint32_t)(SPI2-DR); DMA_InitStruct.DMA_MemoryBaseAddr (uint32_t)frameBuffer; DMA_InitStruct.DMA_DIR DMA_DIR_PeripheralDST; DMA_InitStruct.DMA_BufferSize SCREEN_BUFFER_SIZE; DMA_InitStruct.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStruct.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStruct.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; DMA_InitStruct.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; DMA_InitStruct.DMA_Mode DMA_Mode_Normal; DMA_InitStruct.DMA_Priority DMA_Priority_High; DMA_InitStruct.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel5, DMA_InitStruct);2.3 性能对比优化前后的关键指标变化指标阻塞传输DMA基础版提升幅度刷屏时间(240x320)1980ms850ms57%CPU占用率98%12%86%↓字节间隔5μs1.2μs76%↓注意DMA初始化后需要使能SPI的DMA发送请求SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);3. 中断驱动的高级优化基础DMA方案仍有优化空间——传输完成仍需要轮询DMA标志位。引入传输完成中断可进一步释放CPU资源。3.1 中断配置在DMA初始化代码后添加NVIC_InitTypeDef NVIC_InitStruct {0}; NVIC_InitStruct.NVIC_IRQChannel DMA1_Channel5_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStruct.NVIC_IRQChannelSubPriority 0; NVIC_InitStruct.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStruct); DMA_ITConfig(DMA1_Channel5, DMA_IT_TC, ENABLE);3.2 中断服务例程精简的中断处理函数void DMA1_Channel5_IRQHandler(void) { if(DMA_GetITStatus(DMA1_IT_TC5)) { DMA_ClearITPendingBit(DMA1_IT_TC5); SPI_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, DISABLE); DMA_Cmd(DMA1_Channel5, DISABLE); // 这里可以设置传输完成标志 } }3.3 优化效果引入中断机制后带来新的提升单次传输延迟从1.2μs降至0.4μs接近理论极限刷屏时间从850ms降至580ms系统响应性主循环不再被传输状态检查阻塞4. 实战中的陷阱与解决方案在实际部署中我遇到了两个典型问题4.1 中断优先级冲突当在按键中断中触发刷屏操作时出现了花屏现象。根本原因是按键中断(EXTI)和DMA中断共享相同的抢占优先级DMA传输被按键中断打断SPI时序出现错乱解决方案是调整中断优先级分组NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 2位抢占优先级 // 设置DMA中断优先级高于EXTI NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority 0; // DMA // ... NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority 1; // EXTI4.2 内存对齐问题当使用32位MCU时未对齐的内存访问会导致DMA传输效率下降。优化方案确保帧缓冲区4字节对齐__align(4) uint8_t frameBuffer[SCREEN_BUFFER_SIZE];改用32位传输模式DMA_InitStruct.DMA_PeripheralDataSize DMA_PeripheralDataSize_Word; DMA_InitStruct.DMA_MemoryDataSize DMA_MemoryDataSize_Word; SPI_InitStruct.SPI_DataSize SPI_DataSize_16b; // 对应调整SPI5. 极限优化技巧对于需要极致性能的场景还有以下进阶手段5.1 双缓冲技术建立两个帧缓冲区交替使用void DMA_StartTransfer(uint8_t *buffer) { while(DMA_GetCurrDataCounter(DMA1_Channel5)); // 等待前次传输完成 DMA_SetCurrDataCounter(DMA1_Channel5, BUFFER_SIZE); DMA_SetMemoryAddress(DMA1_Channel5, (uint32_t)buffer); DMA_Cmd(DMA1_Channel5, ENABLE); }5.2 SPI时钟超频在保证信号完整性的前提下可以尝试将APB1时钟从36MHz提升至72MHz移除SPI分频器SPI_BaudRatePrescaler_2→SPI_BaudRatePrescaler_15.3 硬件优化硬件层面的改进建议缩短SPI走线长度10cm增加适当的终端电阻通常33-100Ω使用质量更好的信号线双绞线/屏蔽线最终通过组合应用这些技术在240x320 16位色屏幕上实现了387ms的全屏刷新时间比初始方案提升了80%的性能。这个过程中最重要的收获是优化永无止境但必须基于精确测量而非盲目猜测。每次改动后都用示波器验证波形这才是嵌入式开发的真谛。

更多文章