SPI-DMA与轮询冲突问题总结

📅 2026/6/27 10:44:28
SPI-DMA与轮询冲突问题总结
HC32F072 SPI同时开 DMA 接收和轮询发送程序卡死结论先行Spi_FuncEnable(M0P_SPI1, SpiMskDmaRxEn)使能 DMA 接收后不要再在 CPU 侧轮询STAT 0x02RXNE。DMA 会抢在 CPU 之前读走 DATA、清零 RXNECPU 永远等不到这个标志。正确做法CPU 只等 TXE 写 DATARXNE 完全交给 DMA。一、现象SPI 主机 3MHzDMA Ch1 配置为 RX。程序调用Spi_SendBuf()发送时卡死在内部循环while(!(SPIx-STAT_f.RXNE));// 永远等不到调试器看寄存器STAT 0x0004TXE1, RXNE0, BUSY0CR 配置正确。二、根因两条通路在抢同一个硬件标志CPU 轮询: while(RXNE 0) ; // 死等 DMA 自动: RXNE → 读 DATA → 清 RXNE // 极快几个时钟周期Spi_FuncEnable(SpiMskDmaRxEn)使能后每次移位寄存器完成一个字节 → RXNE 置位 → DMA 立即抢走 → CPU 轮询时 RXNE 早已被清零。可以做一个简单验证临时注释掉Spi_FuncEnable(SpiMskDmaRxEn)纯轮询模式下的Spi_SendBuf立刻恢复正常——因为 RXNE 没有竞争者了。三、解法CPU 只管发DMA 专职收if(spiid1){Spi_SetCS(M0P_SPI1,FALSE);// 拉低 SSN使能 SCK// CPU 只等 TXE 写 DATA不碰 RXNEfor(i0;iSPI_BUFFER_SIZE;i){while(!(M0P_SPI1-STAT0x04));// 等 TXEM0P_SPI1-DATASPI1_TxBuffer[i];// 写不等 RXNE}while((M0P_SPI1-STAT0x08));// 等最后一字节 BUSY 清Spi_SetCS(M0P_SPI1,TRUE);// 拉高 SSN}// 发完后DMA 已将全部 RX 数据自动搬运到内存应用层检测 DMA 计数变化即可取走三个要点SSN 管理Spi_SetCS(FALSE/TRUE)——HC32F072 主机在内部 NSS高时禁止 SCKCPU 不碰读原库函数每发一个字节后会读一次 DATA 寄存器来清 RXNE这行必须删掉避免和 DMA 抢去掉 RXNE 等待CPU 不等 RXNE全权交给 DMA四、DDL 库为什么不行库函数能只发不收原因Spi_SendData❌单字节无 TXE 保护Spi_RWByte❌必等 RXNESpi_SendBuf❌循环内必等 RXNESpi_ReceiveBuf❌发哑字节收数据DDL 库的设计假设是纯轮询或纯 DMA不支持CPU 发 DMA 收这种分工。要实现就必须直接写寄存器。五、最终架构SPI_Write() 发送: CPU → 等 TXE → 写 DATA仅发 DMA → RXNE → 读 DATA → 存 RxBuffer仅收 SPI_Read() 接收主循环: DMA 计数检测 → 超时断帧 → 拷 SPI_Data[2] → SPI_ResetDMA各管各的互不干扰。