
1. 项目概述为什么我们需要DMA在嵌入式开发或者计算机系统设计里我们经常听到一个词“CPU负载”。当你用单片机通过串口发送一个1MB的文件或者用STM32驱动TFT屏幕刷新图像时如果让CPU一个字节一个字节地去搬运数据那它基本上就干不了别的了整个系统的实时性会大打折扣。这时候DMADirect Memory Access直接存储器访问控制器就像一位得力的“搬运工”它能在不打扰CPU的情况下高效地在内存与外设之间、或者内存与内存之间搬运数据。简单来说DMA的核心思想就是“解放CPU”。CPU只需要告诉DMA搬运工“把这堆砖头数据从A地点源地址搬到B地点目标地址一共N块砖数据长度”然后就可以去处理其他更复杂的计算任务了。等DMA搬完了再通知CPU一声“活儿干完了”。这个过程CPU只在开始和结束时介入中间的搬运工作完全由DMA硬件并行完成。我刚开始接触DMA时觉得它配置起来比轮询或中断复杂有点望而却步。但真正用起来后才发现在涉及大量、高速数据搬运的场景下比如音频流传输、图像采集、高速通信等DMA是提升系统性能和稳定性的利器。它能有效降低总线冲突减少中断延迟让系统响应更及时。这篇文章我就结合自己踩过的坑和实战经验把DMA控制器从原理到应用掰开揉碎了讲清楚无论你是刚入门的新手还是想深入理解的老鸟都能找到有用的东西。2. DMA控制器的工作原理与核心架构要玩转DMA不能只停留在调用HAL库函数的层面理解其内部工作原理和架构才能在出问题时快速定位在优化时有的放矢。2.1 DMA的基本工作模型三方参与者一个典型的DMA传输涉及三个核心参与者CPU作为“指挥官”负责初始化并启动DMA传输设置传输参数处理传输完成后的善后工作如中断服务程序。DMA控制器作为“搬运工”是执行数据搬运的专用硬件模块。它包含配置寄存器、地址寄存器、计数器等。系统总线与存储器作为“道路和仓库”包括内存SRAM、SDRAM、外设的数据寄存器如USART-DR、SPI-DR以及连接它们的数据总线、地址总线和控制总线。它们之间的关系可以用一个生活中的快递场景来类比CPU是“发货人/收货人”他写好快递单配置DMA叫来快递员启动DMA。DMA控制器是“快递员和快递车”按照快递单上的信息从发货地址源地址取件送到收货地址目标地址。系统总线是“城市道路”快递车在上面行驶。存储器和外设是“仓库”源仓库和目标仓库。2.2 DMA控制器的内部结构拆解虽然不同厂商如ST的STM32NXP的KinetisMicrochip的PIC32的DMA控制器实现有差异但其核心结构大同小异。我们以一个典型的、功能相对完整的DMA通道为例其内部通常包含以下几组关键寄存器通道控制寄存器 (CCR / CR)这是DMA通道的“大脑”。你在这里配置传输的方向内存到外设、外设到内存、内存到内存、数据宽度8位、16位、32位、地址增量模式每次传输后源/目标地址是否自动增加、循环模式、中断使能传输完成、半传输、传输错误等。这些配置决定了DMA搬运数据的具体“行为规范”。数据数量寄存器 (CNDTR / NBYTES)这个寄存器存放着“还要搬多少数据”。它是一个递减计数器你初始化时写入要传输的总数据量比如1000个字节每成功传输一个数据或一组数据取决于突发传输设置硬件会自动将其减1或减对应的数量。当它减到0时意味着传输完成可以触发传输完成中断。外设地址寄存器 (CPAR / SAR)存放“源仓库”或“目标仓库”中属于外设的那个地址。例如当你用DMA从串口接收数据到内存时这个寄存器就存放串口数据寄存器USART-DR的地址。这个地址在传输过程中通常是固定的外设寄存器地址不变。存储器地址寄存器 (CMAR / DAR)存放“源仓库”或“目标仓库”中属于内存的那个地址。例如上面那个例子中这个寄存器就存放你定义的接收缓冲区比如uint8_t rx_buf[100]的首地址。根据配置这个地址在每次传输后可以自动递增以指向缓冲区的下一个位置。状态寄存器 (ISR / INT)用于指示DMA通道的当前状态和中断标志。比如传输完成标志位TCIF、半传输完成标志位HTIF、传输错误标志位TEIF。CPU通过查询或中断的方式读取这些标志位来了解DMA的工作状态。注意这里有一个非常重要的概念叫“通道”。一个DMA控制器通常有多个独立的通道如STM32F1有7个通道F4有8个流每个流有8个通道。每个通道可以独立配置服务于一个特定的外设如通道1分配给USART1_TX通道2分配给ADC。多个通道共享DMA控制器的内部仲裁逻辑当多个通道同时请求时由仲裁器根据优先级软件可配置决定谁先使用总线。2.3 DMA传输的完整流程一次DMA传输的生命周期可以分为以下几个阶段阶段一初始化与配置CPU主导CPU上电或需要启动传输时执行以下步骤使能DMA控制器的时钟AHB总线时钟。配置具体的DMA通道控制寄存器CCR设置传输方向、数据宽度、地址增量、循环模式、优先级、中断。配置外设地址寄存器CPAR和存储器地址寄存器CMAR。配置数据数量寄存器CNDTR写入要传输的总数据量。使能外设的DMA请求功能。例如使能USART的DMA发送USART_CR3中的DMAT位或DMA接收USART_CR3中的DMAR位。最后使能DMA通道设置CCR中的EN位为1。此时DMA控制器就处于“待命”状态等待搬运指令。阶段二传输请求与启动外设或软件触发外设触发模式最常见当外设准备好发送或接收数据时会向DMA控制器发出一个硬件请求信号。例如串口发送数据寄存器空TXE时会请求DMA填充数据串口接收数据寄存器非空RXNE时会请求DMA取走数据。DMA控制器收到请求后如果该通道已使能且优先级最高则开始占用系统总线进行数据传输。软件触发模式内存到内存传输通过软件设置某个寄存器位如CCR中的SWREQ位或专门的软件触发命令来启动一次传输。阶段三数据传输执行DMA主导总线仲裁DMA控制器向系统总线仲裁器申请总线使用权。如果当前总线空闲或DMA优先级高于当前总线主设备如CPU则获得总线控制权。地址输出DMA控制器将源地址放到地址总线上。数据读取从源地址读取数据到DMA内部临时寄存器。地址输出将目标地址放到地址总线上。数据写入将内部临时寄存器的数据写入目标地址。更新指针与计数器根据配置自动递增或递减源地址和目标地址指针并将数据数量计数器CNDTR减1。单次传输结束一次数据传输单元如一个字节完成。如果采用突发传输Burst则一次可以连续传输多个数据单元再释放总线效率更高。循环判断检查CNDTR是否为0。如果不为0等待下一个外设请求外设触发模式或继续下一次传输软件触发/循环模式。如果为0表示本次配置的传输总量已完成。阶段四传输完成与善后CPU介入当CNDTR减到0时DMA硬件会自动如果使能了传输完成中断TCIE则产生DMA传输完成中断。根据循环模式配置决定下一步动作非循环模式单次模式自动关闭DMA通道EN位清零。通道需要CPU重新配置使能才能进行下一次传输。循环模式自动将CNDTR重载为初始值CPAR/CMAR也可能根据配置重载然后通道继续等待下一次请求实现环形缓冲区的自动填充/读取非常适合连续数据流如ADC连续采样、音频播放。CPU在中断服务程序ISR中需要清除DMA通道的中断标志位如TCIF。处理接收到的数据如果是接收DMA或准备下一批要发送的数据如果是发送DMA。如果是单次模式且需要再次传输可能需要重新配置CNDTR并重新使能通道。2.4 关键概念深度解析1. 传输方向与指针行为这是最容易混淆的地方之一。以STM32的DMA1通道4假设用于USART1_TX为例方向存储器 - 外设你要从内存发送数据到串口。CPARUSART1-DR(外设地址固定不变)CMARtx_buffer(内存地址通常需要递增)CCR中的DIR位设置为0从内存读。CCR中的PINC外设地址增量必须禁用0因为串口数据寄存器地址是固定的。CCR中的MINC内存地址增量通常使能1这样每发送一个字节后CMAR会自动指向缓冲区下一个位置。方向外设 - 存储器你要从串口接收数据到内存。CPARUSART1-DR(外设地址固定不变)CMARrx_buffer(内存地址通常需要递增)CCR中的DIR位设置为1从外设读。PINC必须禁用0。MINC通常使能1。2. 数据宽度与对齐数据宽度PSIZE,MSIZE可以设置为8位、16位、32位。这里有两个关键点源和目标宽度可以不同DMA控制器支持数据打包Packing和解包Unpacking。例如你可以设置源宽度为8位从字节数组读目标宽度为32位写入一个32位寄存器DMA会自动将4个8位数据组合成一个32位数据写入。但这需要仔细设计地址和计数器。地址对齐当数据宽度大于8位时源地址和目标地址最好对齐到相应的边界16位对齐到偶数地址32位对齐到4字节边界否则可能导致性能下降或硬件异常取决于具体芯片。例如一个32位数据的地址最好是0x4, 0x8, 0xC这样的。3. 循环模式 vs 单次模式单次模式传输完CNDTR指定的数量后通道自动禁用。适用于已知长度的单次数据传输。循环模式传输完CNDTR指定的数量后硬件自动将CNDTR重载为初始值通道保持使能状态继续等待下一次传输请求。这是实现“双缓冲”或“环形缓冲区”的硬件基础非常适合连续不断的数据流。例如ADC连续采样填充一个环形缓冲区CPU在另一个“半区”处理数据两者互不干扰。4. 优先级与仲裁当多个DMA通道同时请求时仲裁器根据优先级决定服务顺序。优先级通常有两种设置软件优先级在CCR寄存器中设置如Very High, High, Medium, Low。这是通道间的静态优先级。硬件优先级轮询如果软件优先级相同则通道编号低的通常有更高的硬件优先级。 高优先级的通道可以打断低优先级通道的传输前提是当前传输的突发周期结束这被称为“通道抢占”。需要根据系统实时性要求合理配置。3. DMA的实战应用场景与配置要点理解了原理我们来看看DMA在几个典型场景下的具体应用和配置陷阱。3.1 场景一USART串口高速不定长数据接收这是DMA最经典的应用之一。传统的串口接收使用中断每收到一个字节就进一次中断当波特率高达1Mbps甚至更高时中断频率会达到100kHz以上CPU将疲于奔命。使用DMA接收可以将成百上千个字节“静默”地搬运到内存缓冲区只在缓冲区半满或全满时通知CPU一次极大解放了CPU。配置步骤与代码片段以STM32 HAL库为例// 1. 定义接收缓冲区 #define RX_BUFFER_SIZE 256 uint8_t rx_buffer[RX_BUFFER_SIZE]; // 2. 初始化UART略假设huart1已初始化 // 3. 初始化DMA接收 __HAL_LINKDMA(huart1, hdmarx, hdma_usart1_rx); // 关联DMA句柄到UART句柄 hdma_usart1_rx.Instance DMA1_Channel5; // 根据数据手册选择通道 hdma_usart1_rx.Init.Direction DMA_PERIPH_TO_MEMORY; // 外设到内存 hdma_usart1_rx.Init.PeriphInc DMA_PINC_DISABLE; // 外设地址不增 hdma_usart1_rx.Init.MemInc DMA_MINC_ENABLE; // 内存地址递增 hdma_usart1_rx.Init.PeriphDataAlignment DMA_PDATAALIGN_BYTE; // 外设数据对齐字节 hdma_usart1_rx.Init.MemDataAlignment DMA_MDATAALIGN_BYTE; // 内存数据对齐字节 hdma_usart1_rx.Init.Mode DMA_CIRCULAR; // **循环模式关键** hdma_usart1_rx.Init.Priority DMA_PRIORITY_HIGH; if (HAL_DMA_Init(hdma_usart1_rx) ! HAL_OK) { Error_Handler(); } // 4. 启动DMA接收在UART初始化后或需要时调用 // 注意HAL_UART_Receive_DMA会启动DMA并开启UART的DMA接收请求 HAL_UART_Receive_DMA(huart1, rx_buffer, RX_BUFFER_SIZE);核心要点与避坑指南模式选择必须使用循环模式DMA_CIRCULAR。对于不定长数据流你不知道数据何时结束。循环模式让DMA在缓冲区末尾自动绕回到开头形成一个环形缓冲区永不停止确保不会丢失任何数据。如何判断收到数据DMA自己不会告诉你收到了多少新数据。常用方法有两种空闲中断Idle Interrupt使能串口的空闲线路检测中断IDLEIE。当串口总线在一帧数据结束后保持空闲状态超过一个字节时间时会产生空闲中断。在空闲中断服务程序里你可以计算RX_BUFFER_SIZE - __HAL_DMA_GET_COUNTER(hdma_usart1_rx)得到已接收的数据长度。这是最推荐的方式。定时器查询如果没有空闲中断可以开启一个定时器每隔一段时间如10ms检查一次DMA计数器__HAL_DMA_GET_COUNTER的变化来判断是否有新数据。效率较低。缓冲区溢出处理即使在循环模式下如果CPU处理数据的速度远慢于串口接收速度新数据会覆盖未处理的旧数据。解决方案是使用“双缓冲”或更大的环形缓冲区并提高CPU处理效率。DMA传输完成中断TC慎用在循环模式下使能TC中断意味着缓冲区每被完整填充一次就中断一次。这对于固定长度的数据块有用但对于连续流通常只用空闲中断或半传输中断HT即可。3.2 场景二ADC多通道扫描与DMA传输在需要同步采集多个传感器信号的场合如三相电流、多路温度ADC的多通道扫描模式配合DMA是标准做法。ADC按顺序转换多个通道每转换完一个DMA就自动把结果搬走所有通道转换完后DMA可能产生一次传输完成中断通知CPU。配置要点数据对齐ADC结果可能是12位、10位或16位。存储到内存时通常左对齐或右对齐为16位半字。因此DMA的内存数据宽度MemDataAlignment应设置为DMA_MDATAALIGN_HALFWORD16位。内存地址递增对于多通道每个通道的结果需要存到数组的不同位置所以MemInc必须使能。外设地址固定ADC的结果寄存器如ADC1-DR地址是固定的所以PeriphInc必须禁用。传输数量CNDTR应设置为通道数 × 每个通道的采样次数。例如扫描3个通道每个通道采样1次总数量为3。循环模式如果需要连续采集则使用循环模式。ADC配置为连续转换模式DMA配置为循环模式。这样ADC就会周而复始地扫描转换DMA周而复始地搬运形成一个实时的数据流。一个常见的坑数据错位。假设你配置ADC扫描通道0,1,2DMA目标数组是adc_values[3]。你期望adc_values[0]对应通道0[1]对应通道1。但如果ADC的DMA请求是在每次转换完成后立即发出而DMA的传输节奏没跟上就可能出现错位。确保ADC的DMA请求使能位如ADC_CR2中的DMA位在ADC校准和使能之前就配置好并且ADC的触发源如定时器和DMA的优先级配置正确。3.3 场景三内存到内存传输DMA不仅可以和外设打交道还可以在内存的两个区域之间快速搬运数据例如复制一个大数组、处理图像数据时搬运行/列。此时没有外设请求需要通过软件触发。配置关键方向MEMORY_TO_MEMORY。触发源通常设置为软件触发SWREQ。在STM32中使能通道后传输并不会立即开始需要额外设置一个寄存器位如CCR中的EN位或者对于某些DMA流需要先使能再触发来启动。在HAL库中调用HAL_DMA_Start并指定源、目标和长度后传输立即开始。优先级内存到内存传输通常占用总线带宽较大可能会影响其他总线主设备如CPU、其他DMA通道。建议将其优先级设置为较低并在系统空闲时进行。性能内存到内存传输是检验DMA性能和总线带宽的好方法。你可以测试不同数据宽度、是否使用突发传输Burst对搬运速度的影响。通常使用32位宽度和4节拍的增量突发传输能获得接近理论总线带宽的速度。3.4 场景四SPI/I2S与DMA驱动显示屏或音频编解码器对于SPI接口的显示屏如OLED, TFT或I2S接口的音频DAC需要持续不断地发送像素数据或音频采样数据。使用DMA可以构建一个显示/音频缓冲区实现流畅的刷新和播放。以SPI DMA发送显示数据为例准备一个帧缓冲区frame buffer大小等于屏幕分辨率×每个像素的字节数。配置SPI为发送模式数据宽度为8位或16位取决于屏驱动IC。配置DMA通道方向为内存到外设外设地址为SPI数据寄存器SPI-DR内存地址为帧缓冲区数据宽度匹配使用循环模式。启动DMA传输。DMA会持续不断地将帧缓冲区的数据发送到SPI。当CPU需要更新画面时它只需要修改帧缓冲区中对应位置的数据即可。由于DMA在循环读取修改后的数据会被自动发送出去实现“双缓冲”效果避免屏幕撕裂。避坑点SPI的TXE和RXNE。对于只发送不接收的场景如驱动显示屏只需使能SPI的DMA发送请求SPI_CR2中的TXDMAEN。对于全双工通信需要同时使能发送和接收DMA请求并配置两个DMA通道一个发送一个接收。要特别注意发送和接收的缓冲区管理确保数据同步。4. 高级话题与性能优化当你掌握了基本应用后下面这些进阶知识能帮助你更好地驾驭DMA优化系统性能。4.1 双缓冲Double Buffering技术这是DMA循环模式的一个高级应用用于解决生产者和消费者速度不匹配的问题实现无锁、高效的数据交换。原理将DMA的循环缓冲区在逻辑上分成两个相等的“半区”Half Buffer。DMA传输到一半时即半传输完成HT中断意味着一个半区已经被DMA填满或取空而另一个半区正在被DMA使用。此时CPU可以安全地处理那个已经被填满的半区消费者而不会干扰DMA正在操作的另一个半区生产者。当DMA传输完成TC中断时两个半区角色互换。配置方法定义一个两倍于“半区”大小的缓冲区uint8_t buffer[2 * HALF_BUFFER_SIZE]。配置DMA使用循环模式总长度为2 * HALF_BUFFER_SIZE。使能DMA的半传输完成中断HTIE和传输完成中断TCIE。在DMA中断服务程序中如果是HT中断说明DMA已经处理完buffer[0]到buffer[HALF_BUFFER_SIZE-1]正在处理第二个半区。此时CPU可以处理第一个半区的数据。如果是TC中断说明DMA已经处理完整个缓冲区回到了开头。此时CPU可以处理第二个半区的数据。应用场景ADC高速连续采样实时处理、音频播放/录制、摄像头数据流处理。它能保证CPU处理的数据块总是完整的、连续的避免了在缓冲区中间处理数据时被DMA修改的风险。4.2 突发传输Burst Transfer突发传输是指DMA在一次总线占用期间连续传输多个数据单元称为节拍Beat然后再释放总线。这减少了总线仲裁的开销提高了总线利用率和整体传输效率。相关寄存器在STM32的DMA流控制器如F4/F7/H7系列中有FCR寄存器可以配置突发传输大小MBurst和PBurst可选1、4、8、16个节拍。如何工作假设设置内存端突发大小为4数据宽度为32位4字节。那么DMA在从内存读取数据时会一次性申请总线连续读取4个32位数据共16字节到内部FIFO然后释放总线。接着再逐个将这些数据写入外设。这比每次只读/写一个32位数据要高效得多。使用条件与注意事项地址对齐突发传输对源地址和目标地址的对齐要求更严格。例如4节拍的32位突发传输要求起始地址是16字节对齐的。传输总数总传输数据量最好是突发大小的整数倍否则最后一次传输会是不完整的突发效率会打折扣。外设支持目标外设必须能接受突发传输。有些外设如某些慢速设备可能不支持或需要特殊配置。系统影响突发传输会长时间占用总线可能阻塞其他总线主设备如CPU、其他DMA。需要根据系统整体带宽和实时性要求权衡。4.3 链表传输Linked List与分散/聚集Scatter/Gather这是更高级的DMA特性在STM32的DMA2D图形加速器和某些高端型号的通用DMA中支持在专业的DSP或SoC中更常见。链表传输允许你预先在内存中定义一个“描述符”Descriptor链表。每个描述符包含了一次DMA传输的所有参数源地址、目标地址、数据量、控制信息以及下一个描述符的地址。DMA控制器完成当前描述符指定的传输后会自动加载下一个描述符并继续执行无需CPU干预。这可以实现复杂的、非连续的数据搬运任务。分散/聚集是链表传输的一种典型应用。例如你需要将多个分散在内存不同位置的数据块收集起来连续发送到外设聚集Gather或者将外设连续接收到的数据分散存放到内存的不同位置分散Scatter。通过一个描述符链表每个描述符对应一个数据块DMA可以自动完成整个流程。优势极大地减轻了CPU在管理多次、非连续DMA传输时的负担特别适合网络数据包处理、文件系统I/O等场景。4.4 DMA与Cache的一致性在带有Cache的微控制器如Cortex-M7, M33或应用处理器中DMA直接操作的是物理内存DRAM而CPU操作的是经过Cache缓存的内存视图。这就带来了一致性问题DMA写入CPU读取DMA将新数据写入内存但该数据可能还在CPU的Cache中有一份旧副本。CPU后续读取时会直接从Cache命中旧数据看不到DMA写入的新数据。CPU写入DMA读取CPU将数据写入Cache行但尚未写回内存。DMA随后从内存读取时得到的是旧数据。解决方案使用非缓存Non-Cacheable内存区域将DMA缓冲区定义在不需要Cache的内存区域如通过MPU配置或者使用特定的内存属性如__attribute__((section(“.noncache”)))。这是最简单直接的方法但损失了Cache带来的性能优势。手动维护Cache一致性在DMA读取内存之前CPU写之后确保CPU的写操作已经同步到内存。调用数据清洗Clean指令如SCB_CleanDCache_by_Addr对于Cortex-M7。在CPU读取DMA写入的内存之前使对应Cache行无效Invalidate强制CPU从内存重新加载数据如SCB_InvalidateDCache_by_Addr。使用硬件维护的一致性一些高级的SoC支持硬件维护的Cache一致性如ACP端口但大多数MCU级别的芯片需要软件管理。实操心得在STM32H7这类带Cache的芯片上使用DMA如果不处理一致性问题bug会非常诡异且难以复现。我的习惯是对于频繁通过DMA交换数据的大缓冲区直接将其分配到DTCM RAM如果可用它通常无Cache或者SRAM1/2中并通过MPU设置为Non-Cacheable。对于小数据量或偶尔的传输则使用手动Clean/Invalidate。务必在项目初期就规划好内存布局和Cache策略。5. 调试技巧与常见问题排查DMA的问题往往表现为数据错误、丢失、系统卡死调试起来不像普通代码那么直观。下面分享一些实用的调试方法和常见问题。5.1 DMA调试工具箱寄存器查看这是最基本也是最有效的方法。在调试器如ST-Link with STM32CubeIDE中实时查看DMA通道的寄存器CNDTR查看剩余传输数量。如果它不减少说明DMA根本没启动或没收到请求。CCR确认通道是否使能EN位方向、宽度、增量模式是否正确。CPAR/CMAR确认地址是否正确指向了外设寄存器和你的缓冲区。ISR查看中断标志位判断是否发生了传输完成、半传输或错误。外设寄存器联动检查DMA需要外设发出请求。检查外设相关的DMA使能位是否打开。例如对于USART检查USART_CR3中的DMAT发送DMA使能或DMAR接收DMA使能位。总线矩阵与时钟确保DMA控制器和外设的时钟都已使能在RCC寄存器中。在复杂的多总线架构芯片如STM32F4/F7上还要确认源地址和目标地址所在的内存/外设是否与DMA控制器在同一个时钟域和总线矩阵上否则访问会失败。逻辑分析仪/示波器对于时序要求严格的通信如SPI、I2S用逻辑分析仪抓取实际波形看数据是否按预期通过总线发出DMA的传输节奏是否正确。内存内容查看在调试器中查看DMA目标缓冲区的内存内容确认数据是否正确写入。可以在DMA传输完成中断处设置断点然后检查缓冲区。5.2 常见问题速查表问题现象可能原因排查步骤DMA根本不传输1. DMA或外设时钟未使能。2. DMA通道未使能CCR.EN0。3. 外设的DMA请求未使能。4. 外设未产生请求如USART未使能发送器。5. 总线访问错误地址非法。1. 检查RCC中DMAx和外设x的时钟。2. 单步调试查看CCR寄存器的EN位。3. 检查外设控制寄存器中的DMA使能位如USART_CR3.DMAT。4. 检查外设工作状态如USART是否正在发送。5. 检查CPAR/CMAR地址是否有效是否在可访问的地址空间。数据错位或乱码1. 数据宽度PSIZE/MSIZE配置错误。2. 地址增量PINC/MINC配置错误。3. 缓冲区溢出CPU处理太慢。4. 外设数据寄存器访问有特殊要求如需要先读状态再读数据。1. 核对源和目标的数据宽度是否与外设和缓冲区类型匹配。2. 确认外设地址是否应该递增通常否内存地址是否应该递增通常是。3. 增大缓冲区或使用双缓冲技术。4. 查阅芯片勘误表有些外设的DMA访问有特定顺序。只能传输一次不能循环1. DMA模式配置为单次模式NORMAL而非循环模式CIRCULAR。2. 传输完成后在中断里没有重新配置CNDTR和使能通道对于单次模式。3. 外设在传输完成后停止了请求如ADC单次转换模式。1. 检查CCR中的CIRC位。2. 对于单次模式需在TC中断中重新设置CNDTR并置位EN或调用HAL_DMA_Start_IT。3. 将外设配置为连续模式如ADC连续转换。系统随机卡死或进入HardFault1. 缓冲区地址未对齐特别是使能了突发传输时。2. 访问了非法内存地址指针错误。3. DMA传输过程中源/目标缓冲区被意外修改或释放如局部变量数组函数返回后栈空间被重用。4. 中断冲突或优先级配置不当导致DMA中断无法及时响应。1. 确保缓冲区地址按数据宽度对齐如32位数据4字节对齐。2. 检查CPAR/CMAR地址确保指向有效的、已分配的内存。3.确保DMA缓冲区是全局变量或静态变量或者是从堆中动态分配且生命周期覆盖DMA传输过程。绝对不要使用局部数组4. 检查NVIC优先级确保DMA中断优先级合理且未被更高优先级中断长时间阻塞。使用带Cache的芯片时数据不同步Cache一致性问题见4.4节。1. 将DMA缓冲区放在非缓存区域。2. 在CPU读取DMA数据前无效化Invalidate对应Cache行。3. 在DMA读取CPU数据前清洗Clean对应Cache行。多通道DMA时某个通道不工作1. 通道优先级较低被高优先级通道一直抢占。2. 通道资源冲突两个外设映射到了同一个DMA通道/流但芯片不支持时分复用。1. 调整通道优先级。2. 查阅芯片参考手册的DMA请求映射表确保每个外设使用的DMA通道是唯一的或者芯片支持该通道的复用且你已正确配置复用功能。5.3 一个真实的调试案例SPI DMA发送图片花屏现象用STM32F4的SPI DMA向显示屏发送一张位图图像出现随机条纹和错位。排查过程检查基础配置SPI时钟、数据宽度、相位极性均正确。DMA配置为内存到外设循环模式。查看寄存器SPI的TXE标志一直为1说明发送寄存器一直空DMA似乎在送数据。但CNDTR递减得很慢不符合SPI时钟速度。逻辑分析仪抓波形发现SPI的SCK时钟时有时无并不是连续的。进一步发现当SCK停止时正是DMA访问内存的时候。分析根源SPI和DMA都挂载在APB2总线上而我的帧缓冲区放在DTCM RAM通过AHBS总线矩阵访问。当DMA从DTCM RAM读取数据时需要经过总线矩阵仲裁和跨时钟域访问延迟较大。SPI的时钟很快DMA来不及供给数据导致SPI时钟断流。解决方案方案A软件降低SPI的波特率分频给DMA留出更多的数据准备时间。方案B硬件/优化将帧缓冲区从DTCM RAM移到与DMA和SPI同属一个总线矩阵从设备、访问速度更快的SRAM如STM32F4的CCM RAM或SRAM1。同时启用DMA的突发传输4节拍让DMA一次多读一些数据到内部FIFO。 我采用了方案B将缓冲区定义在__attribute__((section(“.ccmram”)))并配置了4节拍的突发传输花屏问题立刻消失刷新率也大幅提升。这个案例告诉我们DMA的性能不仅取决于配置还与芯片的存储架构、总线带宽紧密相关。在设计高速数据流系统时必须考虑数据缓冲区的存放位置。