ARM Cortex-M7(STM32F7)DMA操作数据损坏问题咨询
使用STM32F767ZG(ARM Cortex-M7内核)微控制器,通过4个异步USART结合DMA与外部设备通信,裸机测试时出现数据损坏问题:
测试代码片段:
volatile char buffer[3]; // USART & DMA初始化代码 // ... buffer[0] = 0x11; // 初始值 buffer[1] = 0x22; buffer[2] = 0x33; // 其他代码 // ... buffer[0] = 0xAA; // 更新后的值 buffer[1] = 0xBB; buffer[2] = 0xCC; // DMA流启动 // ...
执行后USART输出异常:
0x11 (buffer[0]旧值) 0x22 (buffer[1]旧值) 0xCC (buffer[2]新值)
推测原因:Cortex-M7的数据缓存(D-Cache)未及时将CPU写入的新值同步到物理RAM,DMA直接读取RAM时拿到旧值。前两个字节因USART内部缓冲被DMA立即读取,第三个字节因9600bps传输延迟(约1ms),CPU有足够时间完成缓存回写,因此读取到新值。添加1微秒延迟可临时解决问题,但该方法不可靠。
要确保DMA读取的RAM内容与CPU设置的buffer值一致,需从缓存同步、内存配置层面入手,以下是几种可靠方案:
1. 手动执行缓存维护指令
Cortex-M7提供缓存维护指令,可强制将缓存中的脏数据写回物理RAM,再通过内存屏障确保操作完成。
通用缓存清理函数(C语言嵌入汇编)
#include <stdint.h> void Clean_DCache_Range(void *addr, uint32_t length) { // 按32字节缓存行对齐起始地址(Cortex-M7缓存行大小为32B) uint32_t start_addr = (uint32_t)addr & ~(31UL); uint32_t end_addr = (uint32_t)addr + length; // 逐缓存行执行清理操作(将缓存内容写回RAM) while (start_addr < end_addr) { __asm volatile ("DC CVAC, %0" :: "r"(start_addr)); start_addr += 32; } // 数据同步屏障:确保所有缓存操作完成 __asm volatile ("DSB"); // 指令同步屏障:确保后续指令看到最新内存状态 __asm volatile ("ISB"); }
使用方式
在启动DMA前调用该函数:
// 更新buffer值后 buffer[0] = 0xAA; buffer[1] = 0xBB; buffer[2] = 0xCC; // 清理buffer所在缓存行,同步到RAM Clean_DCache_Range(buffer, sizeof(buffer)); // 启动DMA流 // ...
2. 利用CMSIS/STM32 HAL库现成函数
STM32的CMSIS库提供了封装好的缓存维护函数,无需手动写汇编:
#include "stm32f7xx_hal.h" // ... // 更新buffer后,清理指定地址范围的DCache SCB_CleanDCache_by_Addr((uint32_t *)buffer, sizeof(buffer)); // 启动DMA流 // ...
SCB_CleanDCache_by_Addr()内部会自动处理缓存行对齐、执行清理指令及内存屏障,使用更简便。
3. 将buffer配置为非缓存内存
如果buffer专门用于DMA传输,可将其放到非缓存RAM区域,或通过MPU(内存保护单元)配置该区域为非缓存属性,从根源避免缓存同步问题。
MPU配置示例
void MPU_Config_DMA_Buffer(void) { MPU_Region_InitTypeDef MPU_InitStruct = {0}; // 先禁用MPU HAL_MPU_Disable(); // 配置buffer所在区域为非缓存、可共享(CPU和DMA都访问) MPU_InitStruct.Enable = MPU_REGION_ENABLE; MPU_InitStruct.Number = MPU_REGION_NUMBER0; MPU_InitStruct.BaseAddress = (uint32_t)buffer; MPU_InitStruct.Size = MPU_REGION_SIZE_32B; // 根据buffer实际大小选择(最小32B) MPU_InitStruct.SubRegionDisable = 0x00; MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE; MPU_InitStruct.IsShareable = MPU_ACCESS_SHAREABLE; MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE; HAL_MPU_ConfigRegion(&MPU_InitStruct); // 启用MPU,默认特权模式访问 HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT); }
在系统初始化阶段(如main()开头)调用该函数,后续对buffer的读写会直接操作物理RAM,无需缓存同步。
4. 编译器辅助优化
- 用
__attribute__((aligned(32)))声明buffer,确保其按缓存行对齐,避免缓存清理操作影响其他内存区域:volatile char buffer[3] __attribute__((aligned(32))); volatile关键字仅能防止编译器优化变量读写,无法解决硬件缓存同步问题,需配合上述缓存维护或内存配置方案使用。
添加延迟能临时解决问题,是因为延迟期间CPU可能因其他操作触发缓存自动回写,但该行为是硬件自发的,无明确时序保证,无法在量产代码中依赖。只有通过主动缓存维护或非缓存内存配置,才能确保DMA与CPU的数据一致性。
内容的提问来源于stack exchange,提问作者Costas Vlachos

