有人使用STM32H7系列芯片,用到UART做字符串输出时遇到点小问题。这里一起聊聊该问题,并分析问题原因。【注:下面所用IDE乃ARM keil MDK】
事情是这样的,他基于DMA方式通过UART对外发送“Hello”字符串,可无意中发现当该字符串以不同的变量形式提供给函数时结果却不一样。不妨以下面示意的功能代码为例来看。
上图中的主要功能就是基于DMA方式通过UART发送两次字符串,一个字符串是“Hello,STM32-1!”,另一个是“Hello,STM32-2!”,两字符串一字之差以示区别,便于调试。
可是,按照上面写法和默认编译条件,测试发现只有第一个UART发送函数有效,第二个UART发送函数没有相应输出。见下面输出截图:
那是为什么呢?两个函数的实现及变量几乎一模一样。如果不使用DMA方式做UART发送会怎么样呢?即将UART的发送改成查询方式的API函数,见下面截图:
结果发现,如果按照上面的写法,两行功能代码的输出又是正常的,见下面输出结果截图。
看来,问题的出现跟使用DMA有关。既然问题跟DMA有关,为什么同样使用基于DMA方式的UART发送函数,一行可以另一行却无效呢?这两行的唯一差别就是在提供发送字符串的形式上的差异。一行是基于字符串常量提供给函数,一行是基于内存变量提供给函数。
现在的情况是,基于字符串常量提供给函数的可行,基于内存变量提供给函数的不可行。
这就可能涉及到DMA访问地址空间的可行性问题,一般来讲,每个DMA往往有其可访问的地址范围问题,并非无所不达。具体到STM32H7系列,其通用DMA是没法访问TCM区域的。当然,对于内核CUP而言,它自然没有访问不到的地址空间问题,所以刚才基于查询式做发送时两行函数功能都正常。
具体到这里,第一个字符串以常量形式提供给UART发送函数时,DMA可以访问到,第二个字符串以内存变量形式提供给UART发送函数时,DMA则访问不到。我们可以知道,字符串以常量形式提供给UART发送函数时,字符串是存放在FLASH里的。我们可以借助调试工具找到此时第一个字符串的具体存放地址,显然是片内Flash地址。见下图:
在STM32H7系列里,通用DMA方式片内Flash是没有问题的。所以解释了第一行代码输出正常。
那么,当第二个字符串以内存变量形式提供给UART发送函数时,该字符串到底存放在哪里呢?其实,其地址如果没有刻意通过用户指定的话,是编译器根据当前的编译配置自动安排的。我们借助调试工具,不难看到第二个字符串变量【TX_Buffer】就是存放下面的地址,即0x20000008开始的一段地址空间:
结合STM32H7系列手册相关地址布局说明,不难得知第二个字符串存放在DTCM区域,正是通用DMA访问不到的地址空间。
既然知道了原因,解决起来就就简单了。我们可以在代码里直接将字符串存放地址指定到通用DMA可以访问到的地方,比方AXIRAM,SRAM1/SRAM2/SRAM3任意区间。
我在下面使用AXI RAM作为默认的内存空间,也无须通过用户代码做地址的指定,只是稍微调整了ARM MDK IDE的链接配置选项,见下面示意图。【注:不同的IDE,这个地方的操作会略有差异。】
然后基于之前的代码重新编译、运行,结果正常,不再有发不出去的Hello了。
DMA作为CPU的强力帮手,也有鞭长莫及的时候,我们在STM32应用中要加以留意。