想象你在经营一家繁忙的快递仓库。CPU 是那位既要签合同、又要算账、还要接电话的老板。仓库里的货物——也就是数据——需要从外设货台搬到内存货架。如果没有帮手,老板只能一趟趟亲自搬:先放下手里的活,走到货台搬一箱,放回货架,再回来继续算账。货一多,老板被拖得寸步难行。这时你雇来一位专职工人 DMA(Direct Memory Access,直接内存访问)。他接过搬运的活,老板只需下个指令,DMA 就按部就班地把一箱箱数据从外设搬到内存,搬完打个电话(中断)通知老板。老板终于能专心干自己的正事。这个故事贯穿全文:DMA 不是魔法,而是一位训练有素的“硬件搬运工”,它存在的一切意义,就是让 CPU 从繁重的数据搬运中解放出来。 本文以 STM32 为主线,从 DMA 控制器的架构、通道映射、传输模式、优先级仲裁,讲到 ADC 采集、UART 收发等实战配置,最后讨论数据竞争与优化。你将像配一位新员工一样,从头到尾学会给 CPU 排班、下指令、防差错。
对 AI/嵌入式从业者而言,DMA 是让系统吞吐量翻倍的“免费午餐”。掌握它,你才能设计出高实时性、低 CPU 占用的采集与通信系统,这是 IoT 设备固件、机器人控制和边缘计算底层的硬功夫。
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 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 负责执行”,后面的通道配置和模式选择才有意义。
- N搬运的数据项数:就是 DMA 控制器里 NDTR 寄存器的初值,它像一张“搬运清单”,上面写着总共要搬多少箱货。每次搬完一箱,清单上的数字就减一。
- L每项数据的宽度:指一次搬运一个数据单元占多少字节,比如外设数据寄存器是 8 位还是 16 位,对应 1 字节或 2 字节。
- ν总线搬运速率:STM32 的 DMA 挂在 AHB 总线上,实际吞吐速度受总线时钟和仲裁影响。传送带越快,同样数量的货物搬得越快。
控制器架构
这家快递仓库不止一个搬运大队。为了不让所有货物都挤在一条传送带上,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 的搬运就可能被延迟,导致采样数据丢失。合理做法是把实时性要求最高的外设分配到软件优先级最高的通道,并尽量利用硬件编号的天然顺序。
这就像快递仓库同时来了十几位客户催单,老板不会看谁嗓门大,而是先看包裹上的加急标签(软件优先级),标签一样的再看谁的工位号靠前(硬件优先级)。规则透明且固定,系统行为就可预期——这正是嵌入式工程师最需要的品质。
- 软件等级寄存器 PL 位配置的 0~3 四档优先级,数值越大越紧急,相当于快递单上的“加急标签”。
- 乘以 4 放大把四档软件优先级映射成一个较大的权重,确保软件优先级不同时,硬件编号的差异不会干扰比较结果。
- 硬件编号通道号本身(1~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 演示
# 模拟 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()
class DataPacket定义数据包,记录货物名称和大小,对应 DMA 搬运的缓冲区。busy += p.size * 1.0CPU 逐字节搬运模型:每个字节都要占用 CPU 一个 tick。setup = 2.0 * len(packets)DMA 方案中 CPU 只在每个任务发起和收尾时介入,共 2 个 tick。dma_work = sum(p.size * 0.5 for p in packets)DMA 控制器实际搬运时间,与 CPU 并行,不占用 CPU。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 的中文全称是?
- A.直接内存访问← 正确答案
- B.动态内存分配
- C.数据管理单元
- D.双通道内存架构
答案:A。DMA 是 Direct Memory Access,直接内存访问,核心含义是外设与内存之间可以绕过 CPU 直接搬运数据。动态内存分配是 Dynamic Memory Allocation,数据管理单元是 MMU,双通道内存是 DRAM 技术,都不相关。
Q2. DMA 搬运数据期间,CPU 在做什么?
- A.一直等待搬运完成
- B.继续执行其他程序← 正确答案
- C.复制一遍数据
- D.关闭所有外设
答案:B。DMA 的意义就是让 CPU 从搬运中解放出来。搬运期间 CPU 可以继续执行主程序,直到 DMA 传输完成产生中断,CPU 才停下一瞬来处理完成事件。像老板把货交给搬运工后继续签合同,而不是站在传送带旁边干等。
Q3. 以下哪个不是 DMA 的典型应用场景?
- A.ADC 连续采样结果搬运
- B.UART 收发数据缓冲
- C.CPU 复杂浮点计算← 正确答案
- D.内存到内存批量搬移
答案:C。DMA 擅长的是数据搬运,而不是计算。复杂浮点运算属于 CPU 或 FPU 的工作。ADC 采样、UART 通信、内存批量移动都是典型的数据搬运场景,DMA 都能大显身手。
Q4. STM32F1 中 DMA1 主要服务哪类外设?
- A.AHB 高速外设
- B.APB1 总线外设← 正确答案
- C.APB2 总线外设
- D.所有外设均可用
答案:B。STM32F1 中 DMA1 主要挂接在 APB1 上的外设,如 USART2/3、I2C1、SPI2 等;DMA2 主要服务 APB2 外设,如 ADC1、USART1、SPI1、TIM1。这也是硬件连线的固定划分,选错之后外设请求不会触发任何搬运。
Q5. DMA 正常模式传输完成后,NDTR 的值是多少?
- A.保持初值不变
- B.自动重载为初值
- C.变为 0← 正确答案
- D.变为最大值
答案:C。正常模式下 NDTR 每搬运一个单元就减一,减到 0 时传输结束、通道自动关闭。循环模式才会在归零时自动重载初值继续搬运。记住“正常模式一锤子买卖,循环模式无限循环”。
Q6. DMA 配置中 NDTR 的单位是什么?
- A.字节
- B.位
- C.数据项← 正确答案
- D.内存块
答案:C。NDTR 表示要搬运的数据项数量,每一项的实际字节数由数据宽度寄存器(PSIZE/MSIZE)决定。比如 NDTR=100、数据宽度为 16 位时,实际搬运 200 字节。容易把“项数”和“字节数”混淆,这是缓冲区溢出最常见的原因。
Q7. 关于 DMA 循环模式,下列说法正确的有?
- A.NDTR 归零后自动重载初值← 正确答案
- B.适合 ADC 连续采样场景← 正确答案
- C.传输完成后通道保持使能← 正确答案
- D.必须配合外部中断使用
答案:ABC。循环模式的核心是 NDTR 归零自动重载、通道始终使能,因而适合 ADC 连续采样这类持续高产的外设。它并不强制要求外部中断,传输完成中断可按需配置。D 项说法无依据。
Q8. 关于 DMA 优先级仲裁,正确的是?
- A.只比较软件优先级,硬件编号无关
- B.软件优先级相同时,硬件编号小的优先← 正确答案
- C.硬件编号不论软件等级总是优先
- D.所有通道优先级必须独立不同
答案:B。仲裁规则是先比较软件优先级(低/中/高/极高),软件优先级相同的通道再比较硬件编号,编号小的先服务。软件优先级允许相同,所以 D 错。硬件编号只是第二级比较因子,A 和 C 都片面。
Q9. 要避免 DMA 与 CPU 之间的数据竞争,可以在哪些?
- A.使用双缓冲轮流填充← 正确答案
- B.依靠完成中断置标志位再读数← 正确答案
- C.在中断里做完整的数据解析
- D.CPU 随意读写同一缓冲区
答案:AB。双缓冲让 DMA 填一块、CPU 读另一块,配合完成标志位,是防竞争的标准方案。中断里应做短小处理,不能做完整解析,所以 C 不建议。D 选项直接让 CPU 和 DMA 同时访问同一片内存,必然产生竞争。
Q10. 关于 STM32 DMA 数据安全与性能优化,正确的有?
- A.修改 DMA 参数前应先关闭通道← 正确答案
- B.缓冲区四字节对齐可提升效率← 正确答案
- C.高优先级通道越多越好
- D.带缓存型号需注意 Cache 一致性← 正确答案
答案:ABD。运行中修改 DMA 配置必须先关通道,否则行为不可预料,A 正确。四字节对齐对 AHB 总线访问更高效,B 正确。带 L1 缓存的型号 CPU 与 DMA 看到的数据可能不一致,需要清理/使能 Cache,D 正确。优先级通道应该按需分配,不是越多越好,过多高优先级通道会让低优先级外设挨饿,C 错误。