查看原文
其他

一个跟内存分配有关的DMA异常话题

miler 茶话MCU 2022-09-11

某日,有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的分散加载文件,按自己的想法调整程序的加载地址、执行地址和内存分配,相关细节就不拓展了。这里主要就本案例做个分享,避免其他人就同样问题走弯路,浪费时间和精力。


==========================

往期话题链接:

1、一个使用外部 SRAM 导致死机的案例

2、关于程序总跳回启动文件的话题

3、STM32开发调试受阻的二三个案例分享

4、获取ST MCU技术资料及相关支持的方式与途径

5、关于STM32 DMA重新使能的话题

6、利用STM32定时器输出指定脉冲个数的一种方法

7、MCU功能严重异常的几个常见原因

8、STM8 cosmic C 编译器无代码限制免费版面世


扫描或长按二维码可关注公众号

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存