想象你在经营一家繁忙的快递仓库。CPU 是那位既要签合同、又要算账、还要接电话的老板。仓库里的货物——也就是数据——需要从外设货台搬到内存货架。如果没有帮手,老板只能一趟趟亲自搬:先放下手里的活,走到货台搬一箱,放回货架,再回来继续算账。货一多,老板被拖得寸步难行。这时你雇来一位专职工人 DMA(Direct Memory Access,直接内存访问)。他接过搬运的活,老板只需下个指令,DMA 就按部就班地把一箱箱数据从外设搬到内存,搬完打个电话(中断)通知老板。老板终于能专心干自己的正事。这个故事贯穿全文:DMA 不是魔法,而是一位训练有素的“硬件搬运工”,它存在的一切意义,就是让 CPU 从繁重的数据搬运中解放出来。 本文以 STM32 为主线,从 DMA 控制器的架构、通道映射、传输模式、优先级仲裁,讲到 ADC 采集、UART 收发等实战配置,最后讨论数据竞争与优化。你将像配一位新员工一样,从头到尾学会给 CPU 排班、下指令、防差错。

岗位关联

对 AI/嵌入式从业者而言,DMA 是让系统吞吐量翻倍的“免费午餐”。掌握它,你才能设计出高实时性、低 CPU 占用的采集与通信系统,这是 IoT 设备固件、机器人控制和边缘计算底层的硬功夫。

目录
  1. 可视化演示
  2. DMA基础原理
  3. 控制器架构
  4. 通道与外设映射
  5. 传输模式
  6. 优先级仲裁
  7. 典型应用场景
  8. 配置开发流程
  9. 数据安全与优化
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

DMA基础原理

回到我们的快递仓库。老板 CPU 过去最头疼的,就是每天要亲手把一箱箱数据从外设货台搬到内存货架。搬的过程中,他不能算账、不能签合同、不能接电话——整个芯片的工作都停摆。直到他雇了一位叫 DMA 的搬运工:CPU 只需要在开工前说一句“从 ADC 货台搬 100 箱到内存地址 0x20000000”,然后就可以转身去忙别的事。DMA 自己会一箱一箱搬完,最后用“电话”(中断)汇报一声。这个故事里的每一个环节,对应着 DMA 实际的工作机制。

DMA 的全称是 Direct Memory Access,直接内存访问。它是 STM32 芯片内部的一个硬件模块,可以看作一位不依赖 CPU 指令逐字节执行、而是由硬件状态机自动运转的搬运专员。它与外设和内存都连在总线上,能直接读写数据。当外设准备好数据,DMA 就启动搬运;搬运期间,CPU 完全不需要参与,可以并行执行主程序——这就是系统的“多线程”错觉来源。

一次典型的 DMA 搬运可以拆成四个环节:外设发出请求,DMA 控制器向总线仲裁器申请使用权,仲裁通过后 DMA 完成搬运并将传输计数减一,最后计数归零时触发中断通知 CPU。整个过程的耗时,与搬运的数据量、数据宽度和总线速率直接相关:

其中 N 是搬运的数据项数,L 是每项数据的宽度(字节数),ν 是总线的实际搬运速率。

回到仓库的故事:老板 CPU 的解放程度,正是由公式里的三个变量决定的。搬的箱数越多、每箱越沉、传送带越快,DMA 的价值就越明显。这一节是所有 DMA 开发的基石——理解了“CPU 只下指令、DMA 负责执行”,后面的通道配置和模式选择才有意义。

t搬=N×Lνt_{\text{搬}} = \frac{N \times L}{\nu}
  1. N搬运的数据项数:就是 DMA 控制器里 NDTR 寄存器的初值,它像一张“搬运清单”,上面写着总共要搬多少箱货。每次搬完一箱,清单上的数字就减一。
    NN
  2. L每项数据的宽度:指一次搬运一个数据单元占多少字节,比如外设数据寄存器是 8 位还是 16 位,对应 1 字节或 2 字节。
    LL
  3. ν总线搬运速率:STM32 的 DMA 挂在 AHB 总线上,实际吞吐速度受总线时钟和仲裁影响。传送带越快,同样数量的货物搬得越快。
    ν\nu

控制器架构

这家快递仓库不止一个搬运大队。为了不让所有货物都挤在一条传送带上,STM32 里安排了两位大队长:DMA1 和 DMA2。他们各带一支队伍,分管不同的装卸口。在 STM32F1 系列中,DMA1 有 7 个通道,DMA2 有 5 个通道。DMA1 主要服务挂在 APB1 总线上的外设,比如 USART2、I2C1、SPI2 这些“慢一些”的货台;DMA2 则主要服务 APB2 总线上的外设,例如 ADC1、USART1、SPI1 和高级定时器 TIM1。两位大队长都站在 AHB 这条主传送带上,随时准备接活。

每个 DMA 控制器内部并不是一个黑箱子,而是由一组寄存器组成的“调度台”。DMA_CCR 是通道控制寄存器,决定搬运方向、数据宽度、模式等参数;CNDTR 装的是“剩余箱数”;CPAR 和 CMAR 分别记录外设货台和内存货架的地址。CPU 老板要做的,就是在开工前把这些寄存器写好,相当于把搬运路线和清单写在调度台上。

DMA1 和 DMA2 的划分并不是随意的。外设的 DMA 请求信号(比如 ADC 的“采样完成,快来取走数据!”)会通过硬件连线送到固定的控制器和通道。如果配置时把 USART2 的请求接到了 DMA2 的通道上,硬件根本不会响应——映射关系是芯片出厂时就焊死的。这就像大队长只能接自家装卸口的单,不能越界去隔壁园区收货。

把两位大队长的分工记牢,是配置 DMA 的第一步。选错大队长或弄混 APB1/APB2 的管理范围,是新手最常见的“货送错仓库”事故。后面每一节,不管是通道映射还是中断配置,都要先回到这张架构图上找位置。

通道与外设映射

每位搬运工的工位(通道)都有固定的班表。DMA1 的通道 1 到通道 7、DMA2 的通道 1 到通道 5,每个通道并不是“什么都能搬”,而是接入了固定的外设请求线路。比如 DMA1 的通道 4 可以服务 SPI1 的接收请求,也能服务 TIM1 的更新请求;DMA1 的通道 5 可以服务 USART2 的接收请求,也能服务 I2C1 的发送请求。这些对应关系写在芯片参考手册的“DMA 请求映射表”里,是配置 DMA 时必须查阅的“排班表”。

映射表的核心规则是:一个通道在同一时刻只能服务一路外设请求。虽然通道 5 既支持 USART2_RX 又支持 I2C1_TX,但你不能在同一个项目里同时让它们俩占用 DMA1_Channel5。为什么?因为硬件连线是一根信号线,同一时间只能传递一位“客户”的请求脉冲。这就像一根电话线只能接通一家客户,接 A 就不能接 B。如果两个外设的 DMA 请求同时被使能,DMA 控制器会按照优先级仲裁,但结果往往是不可预期的——设计上应主动避免这种冲突。

在实际工程中,查映射表是每个 DMA 项目的第一步:确定你要用哪个外设、它支持哪些通道、那个通道是否已经被别的外设占用。配置时,外设 DMA 请求使能位(如 USART_CR3 的 DMAR 位)和 DMA 通道的使能位必须一起打开,搬运工才会真正开始干活。

把通道想象成固定的工位编号,外设想象成货物类型,你就明白为什么“随手选一个通道”行不通。芯片出厂时已经画好了连线图,你能做的是查阅并遵守。这个约束看似麻烦,但它换来了硬件实现的确定性——不需要软件去动态路由,中断延迟和响应时间都可预测,这正是嵌入式实时系统最看重的品质。

传输模式

同一位搬运工,可以接不同性质的活。DMA 控制器提供了四种典型的“值班模式”。第一种是正常模式(Normal):搬完清单上所有的货物后,搬运工就下班回家,NDTR 清零,通道自动关闭。如果老板还想再搬一批,必须重新配置并再次使能通道。第二种是循环模式(Circular):搬运工搬完最后一箱后,清单自动恢复成初值,然后继续从头搬——像一班永远不停的地铁,非常适合 ADC 连续采样这类“永远有货”的场景。

第三种是内存到内存模式(Memory-to-Memory):普通外设搬运必须由外设硬件发出请求信号来触发,而内存到内存模式不需要任何外设,CPU 软件一使能通道,DMA 就立刻开始从一块内存搬向另一块内存。这相当于两位仓库管理员私下调拨货物,不需要第三方客户下单。第四种是突发传输模式(Burst):DMA 不再一箱一箱搬,而是调用一台大型叉车,一次抓起一大块连续数据(如 4 个 32 位数据),成批运走。对于搬运大量连续数据(比如 DMA2 搬运图像帧缓冲),突发模式能显著减少总线仲裁次数,提高吞吐量。

选择哪一种模式,取决于业务的性质。如果只需要搬一次数据,比如用户按下按键后读取一次传感器,用正常模式;如果外设持续不断地产出数据,比如 ADC 一直采样,用循环模式;如果是在程序里把某个大数组从临时缓冲搬到处理缓冲,用内存到内存模式;如果数据量大且连续,再叠加突发传输。

把这些模式对应到快递仓库的生活场景:正常模式是“专程送一次货”,循环模式是“每天循环的班车”,内存到内存是“仓库内部盘点调拨”,突发模式是“开叉车批量上架”。选对模式,搬运工的效率才能真正发挥出来。很多新手不管三七二十一全用循环模式,结果在只需要单次传输的场景里,数据被反复覆盖,反而制造出一堆 bug。

优先级仲裁

当多个通道同时有货要搬时,传送带只有一条,听谁的?DMA 控制器有一套仲裁规则。这套规则分为两级:第一级是软件优先级,你可以在每个通道的配置寄存器里设置四个档位——低、中、高、极高;第二级是硬件优先级,由通道编号决定,编号越小的通道硬件优先权越高。仲裁时,先比较所有请求者的软件优先级,软件优先级相同的,再比较硬件编号。

我们可以用一个简单的公式描述“谁先走”的判定依据:

数值越大,通道越先获得服务。注意这里的核心是“先比软件,再比硬件”——软件等级相差一档,硬件编号根本无法翻盘。

理解这套仲裁机制,对实时系统设计至关重要。如果你的 ADC 采样和数据通信同时启动 DMA,而通信数据恰好占据了高优先级通道,那么 ADC 的搬运就可能被延迟,导致采样数据丢失。合理做法是把实时性要求最高的外设分配到软件优先级最高的通道,并尽量利用硬件编号的天然顺序。

这就像快递仓库同时来了十几位客户催单,老板不会看谁嗓门大,而是先看包裹上的加急标签(软件优先级),标签一样的再看谁的工位号靠前(硬件优先级)。规则透明且固定,系统行为就可预期——这正是嵌入式工程师最需要的品质。

优先级有效=软件等级×4+硬件编号\text{优先级}_{\text{有效}} = \text{软件等级} \times 4 + \text{硬件编号}
  1. 软件等级寄存器 PL 位配置的 0~3 四档优先级,数值越大越紧急,相当于快递单上的“加急标签”。
    软件等级∈{0,1,2,3}\text{软件等级} \in \{0,1,2,3\}
  2. 乘以 4 放大把四档软件优先级映射成一个较大的权重,确保软件优先级不同时,硬件编号的差异不会干扰比较结果。
    ×4\times 4
  3. 硬件编号通道号本身(1~7),编号小的优先。它只在软件等级相同时起作用,相当于“先到先得”的排队顺序。
    硬件编号∈{1,2,…,7}\text{硬件编号} \in \{1,2,\dots,7\}

典型应用场景

理论说完了,让搬运工上岗干几件实事。最经典的应用是 ADC 连续采集。在电机控制或电池监测项目中,ADC 需要以固定的频率不断采样。配置 ADC 为连续转换模式 + DMA 循环模式,CPU 只在启动时配置一次,之后 ADC 每转换完一个通道,DMA 就自动把结果搬到内存缓冲区。1000 次采样中 CPU 一次都不需要介入,采样率可轻松做到几十甚至上百 kHz,而 CPU 占用几乎为零。

第二个场景是 UART 收发。传统做法是每收到一个字节就触发一次中断,CPU 进中断把字节读走——波特率 115200 时,大约每 86 微秒就要打断一次 CPU。用 DMA + 串口空闲中断(IDLE)则不同:DMA 把一帧数据整体搬到缓冲区,当串口检测到总线空闲时产生一次中断,CPU 在中断里一次性处理整帧数据。这样中断次数从“每字节一次”降为“每帧一次”,CPU 开销骤减。接收不定长数据的标准套路,就是“循环模式 DMA + 空闲中断 + 重设缓冲”。

第三个场景是内存到内存搬运。比如一个网络协议栈需要把应用层数据移动到发送缓冲区,或者将摄像头的一帧图像从 raw buffer 搬到处理 buffer。用 CPU memcpy 也能完成,但 DMA 内存到内存模式不占用 CPU,搬运期间内核可以继续跑 RTOS 任务。

这三个场景代表了 DMA 在嵌入式世界的全部价值形态:外设到内存(采集)、外设到外设(转发)、内存到内存(搬移)。在仓库比喻里,它们分别对应“从客户货台收件入库”“把 A 客户的货直接转给 B 客户”“仓库内部货位调整”。学会这三招,你已经能覆盖绝大多数实战需求。

配置开发流程

给搬运工下开工令,在 STM32 里通常遵循一套固定的流程。以 ADC + DMA 为例:第一步,开启 DMA 和外设的时钟。DMA1 挂在 AHB 总线上,要先用 RCC_AHBPeriphClockCmd 打开它的时钟;ADC 挂在 APB2 上,要打开 ADC1 和 GPIO 的时钟。第二步,填充 DMA_InitTypeDef 结构体:选择通道、外设基地址(如 &ADC1->DR)、内存基地址(数组首地址)、数据传输方向(外设到内存)、外设与内存的数据宽度、传输数量(NDTR)、循环模式与否。

第三步,把外设和 DMA 联动起来。对 ADC,要设置 ADC_Init 并开启 ADC_DMACmd;对串口,要设置 USART_DMACmd 使能 DMA 请求。第四步,使能 DMA 通道,通常还要配置 DMA 中断(传输完成、传输过半、传输错误),并实现中断服务函数。最后,启动外设转换。

调试 DMA 时有一个非常实用的技巧:随时读 DMA_GetCurrDataCounter 或直接读 CNDTR 寄存器,查看还剩多少数据没搬完。如果你发现它停在某个值不动,往往意味着外设没有产生 DMA 请求,或者请求没有正确映射到当前通道。另外,务必在修改 DMA 参数前先关闭对应通道,否则硬件状态机可能还在运行,写入的新配置会被立刻覆盖或产生不可预料的行为。

这套流程相当于“排班五步曲”:开电源(时钟)、写排班表(InitStruct)、通知客户(外设联动)、让工人上岗(使能通道)、事后检查(读计数器)。看似繁琐,但只要按部就班,DMA 极少出问题。真正让工程师头疼的从来不是配置本身,而是配置能写对、数据却在系统层面“打架”——这正是下一节要讨论的问题。

数据安全与优化

搬运工干活再利索,也可能在复杂仓库里出岔子。第一个隐患是数据竞争:CPU 和 DMA 同时读写同一块内存缓冲区。比如 DMA 正在往 buf[128] 里填充串口数据,CPU 主循环却已经读到一半的数据,结果拼出一帧残缺的报文。解决竞争的经典手段是双缓冲:一块缓冲区被 DMA 填满后,DMA 自动切换到第二块继续填,同时 CPU 安全地处理第一块。两块缓冲轮值,谁也不会踩到谁的脚。此外,在传输完成中断里设置“数据已就绪”标志,CPU 主循环检查标志再读数据,能彻底避免竞争。

第二个隐患是缓冲区溢出:DMA 搬运的数据量超过了你分配的缓冲区大小。NDTR 配置的是“数据项数”,而不是“字节数”,如果数据宽度是 16 位而缓冲区按 8 位分配,搬运一半就会越过边界写坏相邻变量。更隐蔽的是循环模式下 DMA 覆盖旧数据——如果 CPU 处理速度跟不上采样速度,新数据会直接盖掉还没被读走的旧数据。对策是设置传输完成中断并统计丢数据次数,或者用队列缓冲逐级搬运。

第三个问题是缓存一致性。在高性能 STM32(比如 H7 系列)中,CPU 带 L1 缓存,DMA 读内存时绕过缓存,会读到“过期数据”。这时需要清理或使缓存失效。对齐问题同样不可忽视:DMA 对四字节对齐的内存访问效率最高,配置缓冲区时用 __ALIGNED(4) 确保对齐。至于性能调优,核心原则是减少总线竞争:合理设置优先级、避免多个通道同时高频工作、尽量使用突发模式,并把不用的 DMA 通道关闭以降低功耗。

把双缓冲、完成标志、对齐和缓存处理放进仓库故事里,就是“给两位搬运工划清各自的仓库区域,交接时签字确认(中断标志),码放的箱子对齐托盘(内存对齐),并在引入高科技传送带后检查新旧两套系统的记录是否一致(缓存一致性)”。DMA 把 CPU 从搬运中解放出来之后,这些精细化的小事,才是决定一个嵌入式系统是否可靠、是否专业的分水岭。

AI 应用场景

工业物联网数据采集网关

阶段:数据采集与预处理

解决的问题:传统采集方案依赖 CPU 中断逐字搬运,高频传感器(如振动、电流)数据量大时,CPU 占用率飙升,导致网关无法同时运行通信协议栈和 AI 推理的轻量预处理。采用 ADC + 循环 DMA 采集 + UART + DMA 上报后,CPU 只在整帧数据到达时做一次特征提取,实时性显著提升。

具体案例:某工厂设备健康监测网关需要同时采集 3 路 ADC 振动信号和 1 路温湿度串口数据。工程师配置 DMA1 的通道 1 做 ADC 循环搬运,DMA1 的通道 5 配合 UART 空闲中断接收外部传感器帧,CPU 只负责每 100ms 做一次 FFT 特征计算并上报云端,整机 CPU 占用率从 78% 降到 15%。

电机 FOC 矢量控制驱动器

阶段:实时控制回路数据搬运

解决的问题:FOC 控制需要在 PWM 周期内同时采样两相电流和母线电压,任何一帧数据丢失都会导致电流环抖动。用注入式 ADC + DMA 双缓冲,可以在电流环中断里“零等待”取到最新采样值,避免逐次读取 ADC 寄存器造成的时序抖动。

具体案例:某无人机电调在 48kHz PWM 频率下使用 ADC 注入组转换三相电流,DMA 以循环模式将结果交替写入双缓冲。DMA 传输完成中断只翻转一个标志位并重设缓冲指针,CPU 在主控制循环中读取已完成的那块数据,电流环稳定运行,电机噪声明显下降。

智能语音交互面板

阶段:音频数据采集与送入推理引擎

解决的问题:语音唤醒需要不间断采集 16kHz/16bit 音频流,如果每帧数据都靠 CPU 搬动,会导致音频断流。I2S 外设 + DMA 循环搬运可以把音频数据源源不断写入环形缓冲区,离线 AI 推理引擎直接从缓冲区取数据做唤醒检测,CPU 还有余力运行屏幕渲染和网络协议栈。

具体案例:某智能家居语音面板使用 STM32F4 配合 I2S2 采集麦克风阵列数据,DMA2 通道 3 做循环搬运,每当缓冲区写入半帧就触发中断,程序将半帧数据送入 DSP 降噪和关键词识别模型,实时率稳定,唤醒延迟低于 100ms。

Python 演示

python43 行
# 模拟 CPU 搬运与 DMA 搬运的 CPU 占用差异class DataPacket:    def __init__(self, name, size):        self.name = name        self.size = size  def cpu_copy(packets):    # 每个字节都要 CPU 亲自处理,耗时等于数据量    busy = 0.0    for p in packets:        busy += p.size * 1.0        print("CPU copies:", p.name, p.size, "bytes")    return busy  def dma_copy(packets):    # CPU 只负责发起和收尾,搬运交给 DMA    setup = 2.0 * len(packets)    dma_work = sum(p.size * 0.5 for p in packets)    for p in packets:        print("DMA moves:", p.name, p.size, "bytes")    return setup, dma_work  def main():    packets = [        DataPacket("ADC samples", 256),        DataPacket("UART frame", 1024),        DataPacket("mem-to-mem", 4096),    ]    cpu_used = cpu_copy(packets)    dma_cpu, dma_work = dma_copy(packets)    print("=== result ===")    print("CPU busy (no DMA):", cpu_used, "ticks")    print("CPU busy (DMA):", dma_cpu, "ticks")    print("DMA controller busy:", dma_work, "ticks")    saved = (1 - dma_cpu / cpu_used) * 100    print("CPU time saved: %.1f%%" % saved)  if __name__ == "__main__":    main()
目的:用 Python 模拟“CPU 逐字节搬运”与“DMA 批量搬运”的 CPU 占用差异,直观展示 DMA 带来的性能释放。
  1. class DataPacket定义数据包,记录货物名称和大小,对应 DMA 搬运的缓冲区。
  2. busy += p.size * 1.0CPU 逐字节搬运模型:每个字节都要占用 CPU 一个 tick。
  3. setup = 2.0 * len(packets)DMA 方案中 CPU 只在每个任务发起和收尾时介入,共 2 个 tick。
  4. dma_work = sum(p.size * 0.5 for p in packets)DMA 控制器实际搬运时间,与 CPU 并行,不占用 CPU。
  5. saved = (1 - dma_cpu / cpu_used) * 100计算 CPU 释放比例,即 DMA 带来的性能收益。

常见误区

误区DMA 既然叫“直接内存访问”,那搬运的整个过程 CPU 肯定一点忙都不帮,就像传送带全自动运转。

正解DMA 搬运前,CPU 必须完成一系列配置:选择通道、设置源地址和目的地址、写入 NDTR、使能通道。搬运完成后,CPU 还要响应 DMA 中断做后续处理。真正“免打扰”的只是搬运数据本体这一段时间。类比快递仓库,老板虽然不搬货了,但雇人、排班、签收回执都还得他自己来。

为何常见:初学者被“无需 CPU 干预”的宣传语误导,把 DMA 理解成了完全自治的设备,忽略了它归根结底是一个需要 CPU 初始化的外设。

误区既然芯片里有这么多 DMA 通道,那我随便选一个通道,把外设地址填进去就行了吧?

正解每个 DMA 通道和外设请求之间有固定的硬件映射。比如 USART1 的接收请求只能接入 DMA1 的通道 5(F1 系列),你把外设基地址硬填到通道 3,硬件根本不会产生搬运。配置之前必须查阅参考手册的 DMA 请求映射表,像查列车时刻表一样认真。

为何常见:很多教程的 DMA 初始化代码看起来只是填结构体,让人觉得通道号只是个普通参数,掩盖了硬件连线的固定性。

误区正常模式传完 NDTR 变 0、通道关闭,循环模式也是这样,只是速度快所以看起来像没停。

正解循环模式的关键区别在于:当 NDTR 减到 0 时,硬件会自动把初值重新加载回来,通道继续保持使能,搬运从源地址开始继续。这正是它能不间断服务 ADC 的原因。你可以同时用“传输过半中断 + 传输完成中断”来配合双缓冲。

为何常见:初学阶段只写正常模式的代码,看到 NDTR 归零就停,容易把“归零”这个共同现象放大,忽略循环模式独有的自动重载机制。

误区串口收完一帧数据,DMA 中断触发了,那我就在中断里慢慢解析协议、校验 CRC、甚至把数据转发到另一个串口吧。

正解DMA 中断也是中断,执行时间同样会影响系统的实时性。如果中断里做大量耗时操作,会阻塞其他高优先级中断,甚至让 RTOS 调度抖动。正确的做法是中断里只做最快的事:置标志位、切换缓冲区指针;协议解析放到主循环或任务中。一句话:搬运工按铃是通知你收货,不是让你在门口当场拆包。

为何常见:总认为 DMA 已经帮 CPU 省了搬运时间,省出来的时间就可以在中断里挥霍,却忘了中断环境的实时性约束并未改变。

误区内存到内存搬运由软件启动,那只要使能通道,它就会自动搬运,跟外设请求完全没关系。

正解概念上没错,但要注意:F1 系列只有 DMA2 支持内存到内存模式(存疑时需查手册具体型号),且这种模式下 DMA 会持续占用总线连续搬运,直到 NDTR 归零,期间 CPU 和总线上其他外设的访问都会被延迟。所以传输量不能设置太大,否则会饿死其他总线请求者。

为何常见:只关注“软件触发”这一个特征,忽视了内存到内存模式对总线占有率的独占性,以及不同型号支持度的差异。

面试题

Q1. DMA 的中文全称是?

单选题 · easy

  • A.直接内存访问← 正确答案
  • B.动态内存分配
  • C.数据管理单元
  • D.双通道内存架构

答案:A。DMA 是 Direct Memory Access,直接内存访问,核心含义是外设与内存之间可以绕过 CPU 直接搬运数据。动态内存分配是 Dynamic Memory Allocation,数据管理单元是 MMU,双通道内存是 DRAM 技术,都不相关。

Q2. DMA 搬运数据期间,CPU 在做什么?

单选题 · easy

  • A.一直等待搬运完成
  • B.继续执行其他程序← 正确答案
  • C.复制一遍数据
  • D.关闭所有外设

答案:B。DMA 的意义就是让 CPU 从搬运中解放出来。搬运期间 CPU 可以继续执行主程序,直到 DMA 传输完成产生中断,CPU 才停下一瞬来处理完成事件。像老板把货交给搬运工后继续签合同,而不是站在传送带旁边干等。

Q3. 以下哪个不是 DMA 的典型应用场景?

单选题 · easy

  • A.ADC 连续采样结果搬运
  • B.UART 收发数据缓冲
  • C.CPU 复杂浮点计算← 正确答案
  • D.内存到内存批量搬移

答案:C。DMA 擅长的是数据搬运,而不是计算。复杂浮点运算属于 CPU 或 FPU 的工作。ADC 采样、UART 通信、内存批量移动都是典型的数据搬运场景,DMA 都能大显身手。

Q4. STM32F1 中 DMA1 主要服务哪类外设?

单选题 · mid

  • A.AHB 高速外设
  • B.APB1 总线外设← 正确答案
  • C.APB2 总线外设
  • D.所有外设均可用

答案:B。STM32F1 中 DMA1 主要挂接在 APB1 上的外设,如 USART2/3、I2C1、SPI2 等;DMA2 主要服务 APB2 外设,如 ADC1、USART1、SPI1、TIM1。这也是硬件连线的固定划分,选错之后外设请求不会触发任何搬运。

Q5. DMA 正常模式传输完成后,NDTR 的值是多少?

单选题 · mid

  • A.保持初值不变
  • B.自动重载为初值
  • C.变为 0← 正确答案
  • D.变为最大值

答案:C。正常模式下 NDTR 每搬运一个单元就减一,减到 0 时传输结束、通道自动关闭。循环模式才会在归零时自动重载初值继续搬运。记住“正常模式一锤子买卖,循环模式无限循环”。

Q6. DMA 配置中 NDTR 的单位是什么?

单选题 · mid

  • A.字节
  • B.位
  • C.数据项← 正确答案
  • D.内存块

答案:C。NDTR 表示要搬运的数据项数量,每一项的实际字节数由数据宽度寄存器(PSIZE/MSIZE)决定。比如 NDTR=100、数据宽度为 16 位时,实际搬运 200 字节。容易把“项数”和“字节数”混淆,这是缓冲区溢出最常见的原因。

Q7. 关于 DMA 循环模式,下列说法正确的有?

多选题 · mid

  • A.NDTR 归零后自动重载初值← 正确答案
  • B.适合 ADC 连续采样场景← 正确答案
  • C.传输完成后通道保持使能← 正确答案
  • D.必须配合外部中断使用

答案:ABC。循环模式的核心是 NDTR 归零自动重载、通道始终使能,因而适合 ADC 连续采样这类持续高产的外设。它并不强制要求外部中断,传输完成中断可按需配置。D 项说法无依据。

Q8. 关于 DMA 优先级仲裁,正确的是?

单选题 · hard

  • A.只比较软件优先级,硬件编号无关
  • B.软件优先级相同时,硬件编号小的优先← 正确答案
  • C.硬件编号不论软件等级总是优先
  • D.所有通道优先级必须独立不同

答案:B。仲裁规则是先比较软件优先级(低/中/高/极高),软件优先级相同的通道再比较硬件编号,编号小的先服务。软件优先级允许相同,所以 D 错。硬件编号只是第二级比较因子,A 和 C 都片面。

Q9. 要避免 DMA 与 CPU 之间的数据竞争,可以在哪些?

多选题 · hard

  • A.使用双缓冲轮流填充← 正确答案
  • B.依靠完成中断置标志位再读数← 正确答案
  • C.在中断里做完整的数据解析
  • D.CPU 随意读写同一缓冲区

答案:AB。双缓冲让 DMA 填一块、CPU 读另一块,配合完成标志位,是防竞争的标准方案。中断里应做短小处理,不能做完整解析,所以 C 不建议。D 选项直接让 CPU 和 DMA 同时访问同一片内存,必然产生竞争。

Q10. 关于 STM32 DMA 数据安全与性能优化,正确的有?

多选题 · extreme

  • A.修改 DMA 参数前应先关闭通道← 正确答案
  • B.缓冲区四字节对齐可提升效率← 正确答案
  • C.高优先级通道越多越好
  • D.带缓存型号需注意 Cache 一致性← 正确答案

答案:ABD。运行中修改 DMA 配置必须先关通道,否则行为不可预料,A 正确。四字节对齐对 AHB 总线访问更高效,B 正确。带 L1 缓存的型号 CPU 与 DMA 看到的数据可能不一致,需要清理/使能 Cache,D 正确。优先级通道应该按需分配,不是越多越好,过多高优先级通道会让低优先级外设挨饿,C 错误。