一个跟内存分配有关的DMA异常话题
某日,有STM32客户反馈如下问题:
客户使用STM32F429芯片开发产品,使用到UART外设,并开启了针对UART通信的DMA传输。利用KEIL MDK调试产品,调试中发现一个比较令人困惑的问题。如果通过#if 0 条件编译语句屏蔽某部分代码,uart通信及DMA传输正常。当通过#if 1 开关让相关代码参与编译后,uart无法正常发送,经查是因为DMA工作异常。
事实上,#If条件下面的代码跟UART及DMA并没有直接关系。怀疑到是否跟堆栈方面的配置问题,首先怀疑是否栈溢出。经过检查发现,运行测试代码时栈的耗用量并不大,离启动文件里设置的还有很大裕量。不过,#if 1条件下的代码里有内存动态分配函数,是否因为堆溢出呢?调整堆的大小,问题依旧。
反复做了各种调整及尝试,问题依然没能解决。打开编译器生成的map文件,无意中发现当使用#if 1时,用来做DMA传输的数据缓冲区被编译器分配到了0x10000000地址以后的区域;当使用#if 0时,该数据缓冲区又被分配到了0x20000000地址以后的区域。
难道原因是出在这里?结合STM32F4的参考手册不难得知,0x20000000地址及以后的一段区域为芯片内部的SRAM区,可被DMA或ARM内核访问。0x10000000地址及以后的一段区域为芯片内部的CCM区,即ARM内核紧耦合高速RAM区,该区域只能被内核CPU访问。
问题到这里基本算是有个眉目了。
对于STM32F4来说,如果参与DMA传输的数据缓冲区被安排在CCM区,此时DMA是访问不了该区域的。因为CCM区只能被内核CPU访问。
结合到本案例,当使用#if 0条件编译时,此时的数据变量相对较少,编译器将参与DMA传输的数据缓冲区安排在芯片内部普通SRAM区,具体到这里,就是IDE环境下默认的IRAM1区域。此时DMA访问该缓冲区不会有问题。
当使用#if 1之后,由于代码量变大,尤其是数据RAM需求量变大了,编译器自动将参与DMA传输的数据缓冲区分配到CCM存储区了,即IDE环境下默认的IRAM2区域。此时,DMA访问该数据缓冲区将失败,导致传输错误。因此,我们就有必要将用于DMA访问的数据缓冲区的内存地址做调整,把它安排到DMA可以访问到的普通RAM区。
说实在的,这个问题还是算比较隐蔽的。一般来讲,我们做开发时,对于变量或数据缓冲区的内存安排往往就直接交给了编译器,在IDE配置界面做些简单配置而已。
但是,如果你的应用涉及到DMA传输,对于参与DMA传输的数据缓冲区的内存安排就要小心点。若是让编译器自动安排的话,编译完成完后不妨打开MAP文件看看相关的变量数据的内存安排情况。如果发现某些将被DMA访问的数据变量被被编译器自动安排到了CCM区,我们有必要手动指定相关数据变量或数据缓冲区的内存位置。
在ARM MDK环境下,可以把DMA要访问的缓冲区通过Attribute方式指定到IRAM1区域,比如uint16_t DataBuf[1024]__attribute__((at(0x20008000))) 。
或者我们可以使用IDE的Option属性图形化配置工具将定义于某C模块的数据变量分配到指定的内存区域。比方IRAM1或IRAM2甚至外部RAM区。
当然,我们还可以手动修改基于MDK IDE的分散加载文件,按自己的想法调整程序的加载地址、执行地址和内存分配,相关细节就不拓展了。这里主要就本案例做个分享,避免其他人就同样问题走弯路,浪费时间和精力。
==========================
往期话题链接:
扫描或长按二维码可关注公众号