3个月没STM32写程序,写SPI+DMA又遇到两个神人问题......
第一个问题是在我配置HAL库的时候,DMA不开global interrupt SPI不能传输,开了反而可以传输。 原因如下:
HAL库的DMA传输流程依赖中断回调
HAL库的DMA传输函数(如HAL_SPI_Transmit_DMA)在设计时通常依赖中断来更新传输状态(如标记传输完成)。即使你不需要在代码中显式处理中断,HAL库内部仍可能通过中断回调函数(如HAL_SPI_TxCpltCallback)来清除传输标志或释放资源。如果未开启DMA全局中断,这些内部状态机可能无法正确运行,导致传输卡死。
DMA传输完成标志(TCIF)的自动清除问题
当DMA传输完成时,硬件会置位传输完成标志(TCIF)。在HAL库中,该标志通常在中断服务程序(ISR)中自动清除。如果未启用全局中断,TCIF标志无法被清除,HAL库可能会误判DMA状态(如认为传输仍在进行),从而阻塞后续操作。
HAL库的超时机制与中断依赖
部分HAL函数(如HAL_SPI_Transmit)在DMA模式下会启动一个隐式的超时等待逻辑。如果未启用中断,库可能无法正确检测传输完成,导致函数永远等待,表现为“传输失败”。
第二个问题就是缓存一致性的问题
在 STM32 微控制器中,SPI 缓存一致性(Cache Coherency)是指在使用 DMA(直接内存访问) 进行 SPI 数据传输时,确保 CPU 缓存(Cache) 与 物理内存(RAM) 之间的数据一致性。这是基于 ARM Cortex-M 内核(尤其是带有 Cache 的 Cortex-M7/M33/M55 等)的一个重要概念。
STM32 的 CPU 内核可能包含 数据缓存(D-Cache),用于加速对内存的访问。当 CPU 修改数据时,数据可能暂时存储在缓存中,而不是立即写入物理内存。同样,当 CPU 读取数据时,可能直接从缓存中获取旧数据,而不是从物理内存中读取最新数据。
而 DMA 控制器在传输数据时,会直接访问物理内存(RAM),绕过 CPU 缓存。如果缓存和内存中的数据不一致,可能导致以下问题:
发送数据错误
如果 CPU 将数据写入缓存(但未刷新到物理内存),而 DMA 直接从物理内存读取旧数据发送,会导致 SPI 发送错误数据。
接收数据错误
如果 DMA 将接收到的数据写入物理内存,但 CPU 缓存中仍然保留旧数据,CPU 读取时会得到错误的旧数据。
典型场景举例
SPI 发送数据(CPU → DMA → SPI)
问题:CPU 将待发送数据写入内存缓冲区,但数据可能缓存在 CPU 的 D-Cache 中,未实际写入物理内存。
结果:DMA 从物理内存读取到未更新的旧数据,导致 SPI 发送错误。
SPI 接收数据(SPI → DMA → CPU)
问题:DMA 将接收到的数据写入物理内存,但 CPU 缓存中可能仍然保留该内存区域的旧数据。
结果:CPU 直接从缓存读取旧数据,而非最新的接收数据。
如何解决缓存一致性问题?
在 STM32 中,需通过 手动维护缓存一致性,确保缓存和物理内存的数据同步。常用方法如下:
1. 发送数据前的缓存清理(Clean)
在启动 DMA 传输前,强制将 CPU 缓存中的数据刷新到物理内存,确保 DMA 读取的是最新数据。
SCB_CleanDCache_by_Addr((uint32_t*)tx_buffer, buffer_size);
2. 接收数据后的缓存无效化(Invalidate)
在 DMA 传输完成后,标记 CPU 缓存中的旧数据为无效,强制 CPU 下次读取时从物理内存获取最新数据。
SCB_InvalidateDCache_by_Addr((uint32_t*)rx_buffer, buffer_size);
3. 使用非缓存内存(Non-Cacheable Memory)
通过 MPU(内存保护单元)或链接脚本,将 DMA 缓冲区配置为非缓存内存,避免缓存一致性问题。
__attribute__((section(".non_cacheable"))) uint8_t dma_buffer[256];
4. 对齐和长度要求
缓存维护函数(如 SCB_CleanDCache_by_Addr)要求内存地址和长度按 32 字节对齐(Cortex-M7 的缓存行大小)。
确保缓冲区地址和大小是 32 字节的整数倍。
值得注意的是,仅在使用 带 Cache 的 Cortex-M 内核(如 M7/M33/M55)时需要处理缓存一致性,Cortex-M3/M4 等无 Cache 的型号无需此操作。