关于使用SPI+DMA的注意事项与缓存一致性问题
小善乄
2025年03月10日 00:57

3个月没STM32写程序,写SPI+DMA又遇到两个神人问题......


第一个问题是在我配置HAL库的时候,DMA不开global interrupt SPI不能传输,开了反而可以传输。 原因如下:

  1. HAL库的DMA传输流程依赖中断回调

  2. HAL库的DMA传输函数(如HAL_SPI_Transmit_DMA)在设计时通常依赖中断来更新传输状态(如标记传输完成)。即使你不需要在代码中显式处理中断,HAL库内部仍可能通过中断回调函数(如HAL_SPI_TxCpltCallback)来清除传输标志或释放资源。如果未开启DMA全局中断,这些内部状态机可能无法正确运行,导致传输卡死。

  3. DMA传输完成标志(TCIF)的自动清除问题

  4. 当DMA传输完成时,硬件会置位传输完成标志(TCIF)。在HAL库中,该标志通常在中断服务程序(ISR)中自动清除。如果未启用全局中断,TCIF标志无法被清除,HAL库可能会误判DMA状态(如认为传输仍在进行),从而阻塞后续操作。

  5. HAL库的超时机制与中断依赖

  6. 部分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 缓存。如果缓存和内存中的数据不一致,可能导致以下问题:

  1. 发送数据错误

    • 如果 CPU 将数据写入缓存(但未刷新到物理内存),而 DMA 直接从物理内存读取旧数据发送,会导致 SPI 发送错误数据。

  1. 接收数据错误

    • 如果 DMA 将接收到的数据写入物理内存,但 CPU 缓存中仍然保留旧数据,CPU 读取时会得到错误的旧数据。

典型场景举例

  1. SPI 发送数据(CPU → DMA → SPI)

    • 问题:CPU 将待发送数据写入内存缓冲区,但数据可能缓存在 CPU 的 D-Cache 中,未实际写入物理内存。

    • 结果:DMA 从物理内存读取到未更新的旧数据,导致 SPI 发送错误。

  1. 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 的型号无需此操作。