公司动态

DMA技术详解:从原理到嵌入式开发实战应用

📅 2026/8/5 1:38:22
DMA技术详解:从原理到嵌入式开发实战应用
1. 从“搬运工”到“效率革命”DMA到底是什么如果你玩过单片机或者嵌入式开发肯定对“CPU占用率”这个词不陌生。想象一个场景你的MCU微控制器正在驱动一块屏幕显示动画同时还要通过串口不停地向外发送传感器数据。最直接的做法是CPU亲自上阵从内存里一个字节一个字节地把屏幕数据搬到显示控制器再把传感器数据搬到串口发送寄存器。这个过程我们称之为“程序控制的数据传输”。问题来了CPU这个“大管家”被这些简单重复的“搬运”工作缠住了手脚它宝贵的计算能力无法用于处理更复杂的逻辑比如解算姿态、运行用户界面导致系统整体响应变慢甚至动画卡顿。DMADirect Memory Access直接存储器访问就是为了解放CPU而生的“专职搬运队”。它是一种允许某些硬件子系统外设直接读写系统内存而无需中央处理器CPU全程介入的技术。你可以把它理解为一个高度专业化、自带“导航系统”的快递员。你只需要告诉DMA快递员货在哪里源地址送到哪去目标地址有多少货传输数量它就能自己开着车通过系统总线完成全部运输工作。在此期间CPU可以去处理其他更重要的任务只在快递员“送货完成”或“遇到问题”时过来签收一下处理中断即可。这项技术带来的效率提升是颠覆性的。在需要高速、大批量数据搬运的场景下比如音频播放数据从Flash搬到I2S接口、图像采集数据从摄像头接口搬到内存、网络通信数据在网卡和内存间交换使用DMA几乎是不二之选。它不仅降低了CPU负载还因为其专业化的传输路径往往能达到比CPU操作更高的速度和更稳定的带宽。因此从简单的8位MCU到复杂的多核SoCDMA都是一个核心的基础外设。2. DMA控制器架构、通道与仲裁机制理解了DMA的“快递员”角色我们来看看这个“快递公司”——DMA控制器——是如何运作的。它并非一个简单的模块而是一套精密的系统。2.1 核心架构与数据通路一个典型的DMA控制器包含以下几个关键部分DMA控制逻辑这是“快递公司”的大脑负责解析CPU发来的指令配置信息并指挥整个传输过程。地址寄存器包括源地址寄存器和目标地址寄存器。CPU需要事先填好这两个地址告诉DMA数据从哪里来、到哪里去。这些地址可以是内存地址如SRAM中的数组也可以是外设的数据寄存器地址如USART-DR。传输数量寄存器指定本次要传输的数据单元数量。单位可以是字节、半字16位或字32位具体取决于总线宽度和配置。控制/状态寄存器用于配置传输方向内存到外设、外设到内存、内存到内存、数据宽度、地址递增模式等并标志传输状态是否完成、是否有错误。数据是如何流动的呢当传输启动后DMA控制器会向系统总线仲裁器申请总线使用权。获得授权后它便接管总线直接从源地址读取数据然后写入目标地址整个过程完全绕过CPU的运算单元。一次典型的传输流程如下外设触发外设如USART接收缓冲区满向DMA控制器发送一个请求信号。DMA响应DMA控制器根据优先级仲裁响应请求。总线仲裁DMA控制器向总线仲裁器申请总线。数据传输获得总线后DMA执行一次数据传输读-写。更新指针根据配置自动递减传输数量计数器并递增或递减地址指针。循环判断检查传输数量是否为零。若非零等待下一次外设请求如果是外设触发模式或直接进行下一次传输如果是存储器到存储器模式若为零则传输完成。完成中断传输完成后DMA控制器产生一个中断信号通知CPU“任务已完成”。2.2 通道、优先级与仲裁一个DMA控制器通常有多个独立的“通道”。每个通道都可以看作一个独立的“快递员”可以配置为服务于一个特定的外设例如通道1分配给USART1发送通道2分配给ADC1。这种设计允许多个外设同时进行DMA传输并发地提升系统效率。当多个通道同时发出传输请求时就需要“仲裁机制”来决定谁先谁后。仲裁规则一般是固定的硬件优先级例如通道0高于通道1和可编程的软件优先级可配置某个通道为高优先级相结合。理解你所用芯片的仲裁规则对于设计实时性要求高的系统很重要避免低优先级的数据流被长期阻塞。2.3 传输模式单次、循环与双缓冲单次模式DMA配置好传输数量N启动后严格传输N个数据单元然后停止并关闭通道。需要CPU重新配置才能再次启动。适用于一次性、已知长度的数据传输。循环模式传输完N个数据后DMA控制器自动将地址指针和传输计数器重置为初始值然后等待下一次请求周而复始。这是最常用的模式之一特别适合用于持续的数据流例如为DAC数模转换器提供连续的音频数据流。你只需要在内存中开辟一个缓冲区数组DMA就会像播放循环列表一样不断地将这个缓冲区的数据搬送给外设。双缓冲模式这是循环模式的高级应用也是解决“数据竞争”问题的利器。它需要两个大小相同的缓冲区Buffer0和Buffer1。DMA正在从Buffer0向外设传输数据时CPU可以安全地向Buffer1填充新的数据。当Buffer0传输完成DMA会自动切换到从Buffer1传输同时产生一个中断通知CPU“Buffer0用完了你可以去处理它里面的数据如果是接收或者给它填充新数据如果是发送了”。如此交替实现了数据传输和数据处理的无缝衔接避免了CPU和DMA同时操作同一块内存区域的风险。很多高级的DMA控制器直接支持硬件双缓冲配置相应的标志位即可。3. 实战聚焦嵌入式开发中的DMA典型应用与配置理论说得再多不如一行代码。我们结合STM32等常见MCU看看DMA在具体外设上的应用。这里不会贴出完整的代码而是聚焦于配置的逻辑和关键点。3.1 USART的DMA收发告别等待循环串口通信是嵌入式中最基础的调试和数据交换手段。传统的查询或中断方式每个字节的收发都需要CPU介入效率低下。使用DMA可以成块地处理数据。发送场景CPU需要发送一段字符串char msg[] Hello, DMA!\r\n;传统方式CPU循环调用USART_SendData()每次发送一个字节并等待发送完成标志。期间CPU被完全阻塞。DMA方式配置DMA通道源地址为msg数组的首地址目标地址为USART的数据寄存器地址如USART1-DR。传输方向内存到外设。传输数量strlen(msg)。启动DMA传输。关键点启动后CPU立即被释放可以去干别的事。DMA会自动将整个数组的内容“搬运”到串口发送出去。如何知道发完了使能DMA传输完成中断。在中断服务函数里你可以进行后续操作比如关闭发送DMA或者准备下一包数据。接收场景CPU需要接收不定长的数据。传统中断方式每收到一个字节进一次中断CPU将其存入缓冲区。高频数据下中断开销巨大。DMA方式循环模式开辟一个固定大小的接收缓冲区rx_buffer[256]。配置DMA通道源地址为USART数据寄存器目标地址为rx_buffer。传输方向外设到内存。传输数量256。模式循环模式。启动DMA。核心难题与解决方案DMA在后台默默工作CPU如何知道收到了新数据以及数据有多长这里有几个经典策略空闲中断IDLE这是最优雅的方案之一。使能串口的空闲线路中断。当串口检测到数据线在至少一帧数据的时间内保持空闲高电平就会产生空闲中断。在空闲中断服务函数中我们可以计算数据长度 缓冲区总大小 - DMA未传输数量。这个“未传输数量”寄存器如DMA_CNDTR会随着DMA的自动搬运而递减。通过计算就能得到本次接收到的数据包长度然后进行处理。注意处理完数据后通常需要重新设置DMA的传输计数器重新装载为缓冲区大小并可能调整内存地址指针为下一次接收做准备。有些库如HAL的DMA空闲中断处理逻辑需要仔细阅读确保缓冲区管理和指针重置正确否则容易导致后续数据覆盖错误。定时器查询如果没有空闲中断可以开启一个定时器中断定期例如每10ms去检查DMA的传输计数器是否发生了变化。如果变了说明有新数据到来。这种方法实时性稍差且有额外的定时器开销。双缓冲半传输/传输完成中断如果DMA支持双缓冲可以开启“半传输完成中断”和“传输完成中断”。当DMA填满半个缓冲区Buffer0时产生半传输中断CPU处理Buffer0当填满整个缓冲区Buffer1时产生传输完成中断CPU处理Buffer1。这种方式非常适合处理高速、连续的数据流。避坑指南串口DMA发送的“完成”陷阱一个常见的困惑是“我启动了DMA发送也收到了DMA传输完成中断为什么用逻辑分析仪看到串口线上数据还没发完” 这是因为存在两个“完成”DMA传输完成仅表示DMA已经把内存中最后一个数据搬运到了USART的数据寄存器DR中。此时数据还在USART内部的发送移位寄存器里排队呢。串口发送完成表示USART的发送移位寄存器里的所有位包括停止位都已经在TX线上移出完毕。 因此安全的做法是在DMA传输完成中断里不要立即关闭串口或进行破坏性的操作如修改缓冲区。应该等待USART的“发送完成”标志如TC, Transmission Complete置位。可以查询该标志或者使能TC中断。确保TC标志置位后才认为这包数据真正发送完毕。3.2 ADC的DMA采集构建高速数据流ADC模数转换器采集多个通道或者进行连续采样时DMA是必备工具。没有DMACPU要么在循环中等待每次转换完成查询要么被频繁的ADC转换完成中断淹没。典型配置多通道扫描连续转换配置ADC为扫描模式Scan、连续转换模式Continuous。配置规则通道组顺序加入需要采集的多个通道如Ch0, Ch1, Ch2。使能ADC的DMA请求。配置DMA通道源地址为ADC的通用数据寄存器如ADC1-DR目标地址为一个内存数组adc_values[3]。传输方向外设到内存。传输数量3通道数。模式循环模式。外设地址不递增内存地址递增。启动后ADC会自动按顺序转换Ch0, Ch1, Ch2每转换完一个数据寄存器DR中就有新值并触发一次DMA请求。DMA则依次将DR的值搬运到adc_values[0],adc_values[1],adc_values[2]。三个通道都转换完一轮后ADC立即开始下一轮转换DMA也会在搬运完三个数据后由于是循环模式自动将目标地址指针重置回数组开头开始下一轮搬运。于是adc_values这个数组里就始终保持着这三个通道最新的采样值CPU在任何时候都可以直接读取无需等待或中断。3.3 内存到内存传输高效的数据搬运工DMA不仅可以服务外设也可以进行纯粹的内存间数据搬运。这在需要处理大量数据如图像缓冲区拷贝、数据块搬移、初始化大块内存时非常高效。配置要点源地址和目标地址都是内存地址。通常使用软件触发即通过设置某个寄存器位来启动传输而不是等待外设请求。传输模式可以是单次搬完即停或循环用于实现类似“内存填充”的操作。性能关键内存到内存传输会占用系统总线带宽。在进行此类操作时需注意它可能会暂时影响到CPU或其他总线主设备如另一个DMA访问内存的速度。在实时性要求极高的系统中需要规划好总线访问的优先级和时间窗口。4. 进阶话题双缓冲、链表传输与性能调优掌握了基础应用后我们来看看如何让DMA发挥更强大的威力。4.1 双缓冲模式的深入应用前文简述了双缓冲。在实际编程中一个经典的音频播放应用如下定义两个音频数据缓冲区audio_buf0[1024]和audio_buf1[1024]。配置I2S或DAC的DMA为双缓冲模式初始目标地址指向audio_buf0和audio_buf1。启动DMA它开始从audio_buf0读取数据播放。在DMA的“半传输完成”即audio_buf0播完一半或“传输完成”即audio_buf0播完中断中CPU将下一段音频数据填充到那个已经播放完的缓冲区比如audio_buf0。DMA播放完audio_buf0后会自动无缝切换到播放audio_buf1同时产生中断通知CPU去填充audio_buf0。 如此循环只要CPU填充数据的速度能跟上DMA播放的速度就能实现无卡顿的连续音频输出。关键在于中断服务函数中填充数据的操作必须高效不能耗时过长否则会导致缓冲区欠载产生爆音。4.2 链表传输与复杂事务一些高端的DMA控制器如STM32的DMA2D或一些SoC中的DMA支持“链表模式”或“描述符”模式。在这种模式下CPU不是在寄存器中直接配置一次传输的参数而是在内存中创建一个“传输描述符”链表。每个描述符节点包含了源地址、目标地址、传输数量、下一个描述符的地址等信息。 DMA完成当前节点的传输后会自动根据链表指针跳到下一个节点继续传输直到遇到一个标识结束的节点。这种方式极其强大实现复杂传输序列可以一次性设置好一个包含多种不同源/目标、不同长度的传输序列DMA自动按序执行。动态任务调度CPU可以在DMA运行时动态修改链表后未执行的任务实现灵活的传输调度。节省CPU干预对于一系列复杂的搬运任务CPU只需初始化好链表并启动DMA之后便可完全放手。4.3 DMA使用中的性能陷阱与调优总线竞争与带宽DMA是总线上的一个主设备它与CPU、其他DMA控制器、甚至GPU如果存在共享总线带宽。当多个主设备同时发起高带宽请求时总线仲裁会成为瓶颈。在设计系统时需要评估关键数据流的带宽需求并合理设置优先级。例如显示刷新、音频输出这类对实时性和连续性要求极高的数据流应赋予最高的DMA优先级。数据对齐与突发传输现代总线通常支持突发传输即一次操作连续传输多个连续地址的数据这比单次传输效率高得多。为了充分利用突发传输源地址和目标地址最好与总线宽度对齐例如32位系统上地址是4的倍数。同时配置DMA时选择正确的数据宽度字、半字、字节也能提升效率。不恰当的对齐可能导致总线拆分多次传输降低性能。缓存一致性Cache Coherency问题在带有数据缓存D-Cache的处理器如Cortex-M7, A系列上使用DMA必须高度警惕缓存一致性问题。如果CPU修改了内存中某块数据写操作这个修改可能只停留在CPU的缓存里并未写回实际内存。此时如果DMA从内存中读取该数据发送出去读到的就是旧数据。反之如果DMA将外设数据写入内存而CPU的缓存里可能还保留着该内存地址的旧数据CPU读到的就不是DMA刚写入的新数据。解决方案对于DMA要读取的内存区域源缓冲区在启动DMA前需要执行缓存清理Clean操作强制将CPU缓存中的数据写回内存。对于DMA要写入的内存区域目标缓冲区在DMA传输完成后、CPU读取之前需要执行缓存无效Invalidate操作丢弃CPU缓存中的旧数据确保从内存重新加载。许多MCU的库函数如STM32的HAL库提供了SCB_CleanDCache_by_Addr等函数来处理此问题。忽略缓存一致性是导致DMA数据传输出现“灵异”错误的最常见原因之一。中断风暴与处理延迟如果为每一次DMA传输完成都使能中断在高速数据传输下如SPI读写SD卡可能会产生非常频繁的中断消耗大量CPU资源甚至导致中断丢失。对于这类场景可以考虑使用循环模式定时查询不开启传输完成中断而是由主循环或一个低频定时器定期去检查DMA的传输进度通过读取当前传输计数器并进行批量处理。使用双缓冲模式将中断频率降低一半半传输中断和传输完成中断。增大缓冲区单次传输的数据量越大完成一次传输所需的时间越长中断频率自然就降低了。但这会带来更大的内存开销和数据处理延迟需要权衡。