拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32H7实战:HPDMA无法写入DTCM的排查与解决

1. 现象直击SAI数据进了HPDMA却没进DTCM最近调一块板子主控是STM32H7A3场景不复杂SAI1的Block A接了一颗音频编解码芯片以I2S主机模式接收PCM数据用HPDMA把数据搬运到内存。我一开始图省事想把缓冲区直接放在DTCM里理由很直观——DTCM是零等待的高速RAMCPU后续做音频处理时读写最快而且Cortex-M7内核访问DTCM时不需要经过L1 Cache省去了一堆缓存一致性开销。于是我在链接脚本里划了一块DTCM区域用section属性把环形缓冲区放了进去HPDMA通道配好SAI中断、DMA中断全部正常触发。结果呢缓冲区一直是空的。不是那种“偶尔丢几个字节”的空而是从头到尾完全没有数据进来读出来的全是0或者上一次启动时留下的残留值。HPDMA的完成中断确实产生了SAI的FIFO状态也很健康RXDR寄存器里能读到正确的音频数据。这说明外设在正常出声DMA通道也确实被触发并执行了传输但数据就是没有落到DTCM的目标地址上。这个现象特别有迷惑性因为很多资料里的DMA描述是“不需要CPU参与直接在系统总线之间搬运数据”给人的第一感觉就是只要是能寻址的内存DMA都应该能写进去。实际上在Cortex-M7为内核的MCU上DMA的内存可达范围并不是简单地和地址表一一对应而是由芯片内部总线拓扑决定的。TCM这类紧耦合内存恰恰是最容易被“DMA不可达”坑到的一类。这篇文章把我整个排查过程捋一遍从硬件总线结构讲起到HPDMA通道配置、SAI请求机制、链接脚本映射最后落在解决方案上。如果你也遇到类似的问题或者正准备把某个DMA缓冲区放到DTCM建议先看完这篇能省下好几天排查时间。2. 先疏通逻辑SAI、HPDMA、DTCM各自的角色和限制2.1 SAI的DMA出口FIFO阈值和请求时机SAI在STM32H7上是一个相当灵活的数字音频接口支持I2S、LSB/MSB对齐、PCM/DSP等多种格式。接收方向上串行数据经过移位寄存器进入SAI的接收FIFOFIFO深度是8个字每个字最大32位。当FIFO中的数据量超过编程设定的阈值时SAI就会向DMA控制器发出一个请求信号等待DMA把FIFO中的数据搬走。这里要明确一点DMA请求是“事件/电平”性质的不是“独立的数据包”。HPDMA收到SAI的请求后会按照配置的突发长度和数据宽度连续搬走若干个数据字。以16位I2S为例如果配置HPDMA的源数据宽度是16位一次突发4个节拍那SAI的FIFO请求一次就会触发HPDMA搬运4个半字。在调试初期我通过读取SAI_RXDR寄存器手工轮询确认FIFO里持续有正确的PCM数据进来。这基本排除了SAI本身配置错误、引脚连错、时钟出问题等前置条件。而且HPDMA的传输完成中断正常触发说明FIFO请求信号、HPDMA通道选择、传输长度这些环节都是通的。问题几乎可以锁定在“HPDMA把数据写到了哪里或者写没写进去”。2.2 HPDMA的传输路径总线矩阵和主设备身份HPDMA的全称是High-Performance DMA在STM32H7A3/H7B3这一类芯片上它替代了之前的DMA1/DMA2和MDMA成为统一的高性能DMA控制器。HPDMA的通道数量多、支持内存到内存搬运、支持链表模式还可以为不同的请求源分配优先级。但它终究是一个“总线主设备”。DMA从外设读取数据然后再写入目标地址它自己并不拥有像CPU那样的完整系统地址空间访问权。它访问什么位置的内存取决于这颗芯片内部总线矩阵把哪些从设备挂在了HPDMA主设备能访问的路径上。这句话听着有点绕但它是理解这个问题的关键。CPU在Cortex-M7内核里执行指令、访存、读外设寄存器地址是统一编址的0x24000000是AXI SRAM0x20000000是DTCM0x40000000附近是外设。这是“CPU视角”的地址。而HPDMA是另一套主设备它的地址总线连接到芯片内部的AXI/AHB互连网络。HPDMA发出一个地址总线矩阵负责译码并路由到对应的从设备。如果总线矩阵的译码表里根本没配“这个地址段对应DTCM”这个条目HPDMA的访问请求就找不到目标事务要么挂起要么被静默丢弃。2.3 DTCM的真实地位不是普通内存它是CPU的后花园DTCMData Tightly Coupled Memory直译叫数据紧耦合存储器。紧耦合指的是它紧贴着CPU的运算核心通过Cortex-M7内核专用的一组TCM接口直接连接不需要经过系统总线矩阵也不需要经过L1缓存。所以CPU访问DTCM的延迟极低可以做到零等待、一个周期内完成访问这比访问AXI SRAM还要稳定可预期。但这个“紧耦合”是有代价的它不属于系统总线矩阵的普通从节点。你可以把DTCM想象成CPU自己的“后花园”只有从CPU内核这个门进得去。外部的主设备走的是“城市主干道”系统总线矩阵而主干道上没有一个叫“DTCM”的路口自然开不到后花园门口。这一点我后来在ST参考手册的总线矩阵章节里得到了印证。主从设备连接表清楚地标注了哪些主设备可以访问哪些从设备TCM区域对应的连接上标记的就是CPUCortex-M7和几个调试组件DMA主设备那一栏没有连线。3. 排查链路外设请求、DMA通道、缓冲区三线并查这块内容我打算写得非常详细因为我实际操作时也踩过不少自以为是的坑。我的排错路线是先怀疑HPDMA配置再怀疑SAI配置最后才想到DTCM的可达性。3.1 第一查HPDMA通道配置和中断状态HPDMA配置错误的概率最高。用CubeMX生成代码时通道分配、请求映射这些看似自动完成但有一个地方特别容易被忽视——HPDMA的数据宽度、突发模式还有源地址和目的地址是否自增。我把HPDMA的配置情况贴出来简化后的关键部分hpdma_handle.Init.Request HPDMA_REQUEST_SAI1_A; hpdma_handle.Init.BlkHWReq HPDMA_BREQ_1_BURST; hpdma_handle.Init.Dir HPDMA_PERIPH_TO_MEM; hpdma_handle.Init.SrcInc HPDMA_SRC_NOINC; hpdma_handle.Init.DestInc HPDMA_DEST_INC; hpdma_handle.Init.SrcDataWidth HPDMA_SRC_DATA_WIDTH_32BIT; hpdma_handle.Init.DestDataWidth HPDMA_DEST_DATA_WIDTH_32BIT; hpdma_handle.Init.SrcBurstLength 4; hpdma_handle.Init.DestBurstLength 4; hpdma_handle.Init.TransferMode HPDMA_BLOCK_TRANSFER;这份配置如果目标地址换到AXI SRAM完全没问题。源地址是SAI1_A的RXDR外设寄存器源地址不自增目标地址自增32位宽度单次突发4个节拍。我检查了HPDMA的中断状态传输完成标志TCIF置位说明一次块传输已经完成。接着又特意去看了错误中断标志比如传输错误、总线错误、超时错误这些标志位都是干净的没有任何错误记录。这正是最挠人的地方DMA说自己传输完成了但内存里没有数据。你调DMA配置调到头它也不会给你任何警告因为从HPDMA的角度来说它可能已经把数据发到了总线上只不过没人接。3.2 第二查SAI实际发出的数据和FIFO状态我一度怀疑是SAI的FIFO阈值配置和HPDMA的突发长度不匹配导致DMA去读的时候FIFO是空的读到的全是默认值。这个怀疑很快被排除了。在HPDMA搬运过程中我通过调试器打了一个断点直接读SAI的状态寄存器SAI_SR看FLVL字段。结果显示FIFO中的数据量始终在正常范围内波动。我又用逻辑分析仪抓了I2S的SCK、WS和DATA引脚看到的PCM数据流完全正常。一句话SAI发给HPDMA的请求信号和实际读到的数据都没有问题。为了彻底切开HPDMA这个变量我还做了一次更原始的验证在SAI的接收中断里把RXDR寄存器读出来填到一个临时数组然后通过串口打印出来。结果是我收到了正确的PCM数据临时数组放在普通SRAM里一切正常。这就说明整个SAI到SRAM的链路完全OK问题一定出在目标地址为DTCM的环节上。3.3 第三查缓冲区地址和链接脚本的映射既然外设和DMA都疑似正常我开始怀疑是不是缓冲区地址根本没放到DTCM。我用调试器查看音频缓冲区的实际地址结果还真在DTCM的范围0x20000000附近。链接脚本的section定义也没有毛病MEMORY { FLASH (rx) : ORIGIN 0x08000000, LENGTH 2048K DTCM_RAM (rw) : ORIGIN 0x20000000, LENGTH 64K AXI_SRAM (rw) : ORIGIN 0x24000000, LENGTH 512K } .dtcm_audio : { . ALIGN(32); *(.audio_buffer) . ALIGN(32); } DTCM_RAMC代码里也对应着__attribute__((section(.audio_buffer), aligned(32))) static uint32_t audio_rx_buf[4096];地址没错section名字匹配也没错对齐是32字节。CPU自己写这个缓冲区没有任何问题我后来用memset给它填数再读回来数值完全一致。这说明DTCM本身工作正常只是HPDMA写不进来。这里可以分享一个小技巧在链接生成map文件后用文本搜索音频缓冲区的符号名可以直接看到它落在内存的哪个地址区间。排查类似问题的时候这一步比在调试器里看变量地址更高效因为你还能一眼确认它有没有被链接器优化到别的地方。3.4 第四查D-Cache、I-Cache和TCM的互动到这一步我还剩一个怀疑方向——缓存一致性。Cortex-M7有L1 D-Cache如果CPU在DMA传输之前通过D-Cache访问过这块缓冲区D-Cache里可能还保留着旧数据当DMA把新数据写到内存时D-Cache没有感知CPU再去读地址时命中缓存读到的还是旧值。这个怀疑很快也被排除了。首先DTCM本身是TCMCortex-M7对TCM空间的访问不经过L1 CacheTCM地址区域也不参与D-Cache的正常缓存行为。其次我在测试中用memset写过DTCM缓冲区这是CPU写后续再用D-Cache Clean操作也没有改变现象。D-Cache相关的脏数据问题在AXI SRAM场景里是家常便饭但在这里并没有起主导作用。我还顺手验证了MPU的配置如果给DTCM区域配置了cacheable属性会不会影响DMA结果是毫无影响因为DMA的访问根本不经过CPU的MPU和Cache单元这个角度可以直接排除。4. 破案为什么“HPDMA能访问DTCM”这句话是个坑4.1 数据手册给出的连接表HPDMA在ST的架构图里是作为系统总线的主设备出现的位于芯片内部互联总线的两端。而DTCM在ST的内存组织图里属于Cortex-M7内核私有存储地址是0x20000000开头。很多人看到内存映射图里DTCM的地址范围和普通SRAM挨得很近就默认认为“只要地址能访问到的内存DMA都能访问”。其实这是对嵌入式系统最常见的误解之一。我重新翻开参考手册找到“System architecture”那一章里的主
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门