STM32 进阶封神之路(二十一):DMA 深度解析 —— 从直接内存访问到无 CPU 干预数据传输(底层原理 + 寄存器配置)

0 阅读15分钟

STM32 进阶封神之路(二十一):DMA 深度解析 —— 从直接内存访问到无 CPU 干预数据传输(底层原理 + 寄存器配置)

上一篇我们掌握了 ADC 模拟信号采集的全场景应用,这一篇聚焦 STM32 的 “数据搬运神器”——DMA 直接内存访问。DMA(Direct Memory Access)是 STM32 内置的高速数据传输控制器,能够在不占用 CPU 资源的情况下,实现外设与内存、内存与内存之间的高速数据搬运,广泛应用于 ADC 采集、串口通信、SPI 闪存读写、DAC 输出等高频数据传输场景。

本文基于实战资料,从 DMA 核心价值、硬件架构、传输原理,到通道配置、寄存器解析、初始化流程,手把手带你吃透 DMA 的底层逻辑,为下一篇实战(ADC+DMA 采集、串口 DMA 收发)打下坚实基础,同时覆盖高频面试考点!

一、DMA 核心认知:为什么它是 “CPU 减负神器”?

1. DMA 的核心作用与价值

在 DMA 出现之前,外设与内存的数据传输必须依赖 CPU 干预:CPU 需先从外设读取数据到寄存器,再写入内存(或反之),这个过程中 CPU 无法执行其他任务,导致资源浪费。DMA 的核心价值就是 “解放 CPU”:

(1)核心作用
  • 无 CPU 干预数据传输:DMA 独立于 CPU 工作,自动完成外设与内存、内存与内存之间的数据搬运;
  • 高速传输:传输速度仅受总线带宽限制(STM32F103 DMA 最大传输速率可达 72MB/s);
  • 多通道支持:支持多个外设同时传输,通过通道优先级仲裁实现有序传输;
  • 降低功耗:CPU 可在 DMA 传输期间进入睡眠模式,减少系统功耗。
(2)核心应用场景
  • ADC 高速采集:ADC 连续采集数据,DMA 自动将采样值写入内存,CPU 无需等待;
  • 串口大数据收发:串口接收 / 发送大量数据(如文件传输),DMA 替代 CPU 中断处理,避免频繁中断占用资源;
  • SPI 闪存读写:W25Q64 等 SPI 闪存的批量数据读写,DMA 提升传输效率;
  • DAC 波形生成:DMA 循环传输波形数据到 DAC,实现高频信号输出;
  • 内存数据搬运:SRAM 内部、Flash 与 SRAM 之间的大批量数据复制。

2. DMA 与 CPU 中断传输的核心区别(面试高频)

很多新手混淆 DMA 与中断传输,两者虽都能 “并行处理任务”,但本质逻辑完全不同:

表格

对比维度DMA 传输CPU 中断传输
核心逻辑硬件直接搬运数据,无 CPU 参与CPU 响应中断后,手动读写数据
CPU 占用几乎为 0(仅需初始化配置)中断响应和数据处理占用 CPU 资源
传输效率高(硬件级传输,无软件开销)中(受中断响应时间和软件处理速度限制)
适用场景大批量、高频数据传输(如 ADC 连续采集、文件传输)小批量、低频率数据传输(如按键中断、串口单字节接收)
配置复杂度较高(需配置通道、传输模式、数据长度等)较低(仅需配置中断和数据处理函数)
功耗低(CPU 可休眠)较高(CPU 需频繁响应中断)

示例对比:ADC 采集 1000 个数据

  • 中断传输:ADC 每采集 1 个数据触发 1 次中断,CPU 需响应 1000 次中断,每次中断读取数据并写入内存;
  • DMA 传输:ADC 连续采集 1000 个数据,DMA 自动将所有数据写入内存,CPU 仅需在传输完成后处理一次数据。

3. STM32 DMA 硬件架构深度解析

STM32F103 系列内置 2 个 DMA 控制器(DMA1、DMA2),其中 DMA1 支持 7 个通道,DMA2 支持 5 个通道,每个通道对应特定的外设请求,硬件架构决定了其传输能力:

(1)核心架构框图

plaintext

外设(ADC1/USART1/SPI1等)→ DMA请求 → 通道选择 → 优先级仲裁器 → 数据搬运控制器 → 内存(SRAM/Flash)
                                  ↓
                              控制寄存器(配置传输模式、数据长度等)
(2)关键模块说明
  • DMA 控制器:STM32F103 有 2 个控制器(DMA1、DMA2),DMA2 仅在大容量和中容量芯片中存在(如 STM32F103ZET6),小容量芯片(如 STM32F103C8T6)仅支持 DMA1;
  • 通道(Channel) :每个 DMA 控制器包含多个通道,每个通道对应特定的外设请求(如 DMA1_Channel1 对应 ADC1),通道是 DMA 传输的 “专属通道”;
  • 优先级仲裁器:当多个通道同时请求传输时,按优先级高低排序(高优先级通道优先传输),支持 4 级优先级(最高、高、中、低);
  • 数据搬运控制器:核心执行单元,负责按配置的传输模式、数据长度、地址增量方式搬运数据;
  • 请求映射:外设需通过特定的 DMA 通道发起传输请求,不可随意选择(如 ADC1 只能通过 DMA1_Channel1 请求)。

4. DMA 核心传输术语(必掌握)

  • 传输方向

    • 外设→内存(Peripheral to Memory):如 ADC 采集数据写入 SRAM;
    • 内存→外设(Memory to Peripheral):如 SRAM 数据通过串口发送;
    • 内存→内存(Memory to Memory):如 SRAM 内部数据复制;
  • 传输模式

    • 正常模式(Normal mode):传输完成后停止 DMA,需手动重启;
    • 循环模式(Circular mode):传输完成后自动重启,持续循环传输(如 ADC 连续采集);
  • 地址增量模式

    • 外设地址增量(Peripheral increment mode):传输时外设地址是否递增(通常关闭,外设寄存器地址固定);
    • 内存地址增量(Memory increment mode):传输时内存地址是否递增(通常开启,连续写入内存);
  • 数据宽度

    • 外设数据宽度(Peripheral data size):8 位、16 位、32 位(需与外设数据格式匹配);
    • 内存数据宽度(Memory data size):8 位、16 位、32 位(需与内存数据类型匹配);
  • 传输计数器:记录待传输的数据个数,传输完成后计数器清零(正常模式)或自动重载(循环模式)。

二、STM32 DMA 通道与外设映射(实战选型关键)

DMA 通道与外设的映射关系是配置的核心,必须严格遵循,否则无法触发 DMA 传输。以下是 STM32F103 DMA1 的常用通道映射表(小容量芯片适用):

表格

DMA 控制器通道号对应外设请求典型应用
DMA1Channel1ADC1、TIM2_CH3ADC1 数据采集
DMA1Channel2USART2_TX、TIM1_CH4USART2 发送数据
DMA1Channel3USART2_RX、TIM3_CH1USART2 接收数据
DMA1Channel4USART1_TX、TIM1_CH1USART1 发送数据
DMA1Channel5USART1_RX、TIM1_CH2USART1 接收数据
DMA1Channel6SPI1_TX、TIM3_CH1SPI1 发送数据
DMA1Channel7SPI1_RX、TIM3_CH2SPI1 接收数据

关键注意

  • 每个通道可对应多个外设请求,但同一时间仅能激活一个请求;
  • 实战选型时,需根据外设选择对应的 DMA 通道(如 ADC1→DMA1_Channel1,USART1_RX→DMA1_Channel5)。

三、DMA 核心寄存器解析(底层配置关键)

DMA 的配置本质是操作控制寄存器,以下是初始化和传输过程中涉及的核心寄存器,结合配置流程解析:

1. DMA 通道配置寄存器(DMA_CCRx)

  • 地址:DMA1_Channel1 的 CCR 寄存器地址为 0x40020000,通道 2~7 依次递增 0x14;

  • 核心作用:配置 DMA 通道的传输模式、地址增量、数据宽度、优先级等;

  • 关键位说明:

    • EN(bit0):通道使能位(1 = 使能,0 = 禁用);
    • CIRC(bit5):循环模式使能位(1 = 循环,0 = 正常);
    • PINC(bit6):外设地址增量使能(1 = 使能,0 = 禁用,通常为 0);
    • MINC(bit7):内存地址增量使能(1 = 使能,0 = 禁用,通常为 1);
    • PSIZE(bit8~9):外设数据宽度(00=8 位,01=16 位,10=32 位);
    • MSIZE(bit10~11):内存数据宽度(00=8 位,01=16 位,10=32 位);
    • PL(bit12~13):通道优先级(00 = 低,01 = 中,10 = 高,11 = 最高);
    • DIR(bit14):传输方向(0 = 外设→内存,1 = 内存→外设);
    • MEM2MEM(bit14):内存到内存模式(仅当 DIR=1 时,该位 = 1 表示内存→内存)。

2. DMA 通道传输计数器寄存器(DMA_CNDTRx)

  • 地址:DMA1_Channel1 的 CNDTR 寄存器地址为 0x40020004;

  • 核心作用:存储待传输的数据个数(0~65535);

  • 关键说明:

    • 传输过程中,计数器自动递减,每次传输 1 个数据,计数器减 1;
    • 传输完成后,计数器清零(正常模式)或自动重载初始值(循环模式);
    • 可通过读取该寄存器获取剩余传输数据个数。

3. DMA 通道外设地址寄存器(DMA_CPARx)

  • 地址:DMA1_Channel1 的 CPAR 寄存器地址为 0x40020008;
  • 核心作用:存储外设数据寄存器的地址(如 ADC1_DR 的地址 = 0x40012440);
  • 关键说明:外设地址通常固定,传输过程中不会变化(PINC=0)。

4. DMA 通道内存地址寄存器(DMA_CMARx)

  • 地址:DMA1_Channel1 的 CMAR 寄存器地址为 0x4002000C;
  • 核心作用:存储内存缓冲区的起始地址(如 SRAM 中数组的首地址);
  • 关键说明:若开启内存地址增量(MINC=1),传输过程中内存地址自动递增(递增步长 = 数据宽度,如 16 位数据递增 2 字节)。

5. DMA 中断状态寄存器(DMA_ISR)

  • 地址:0x40020000 + 0x00000078(DMA1_ISR);

  • 核心作用:存储所有通道的中断状态标志;

  • 关键位说明:

    • TCIFx(bit1、bit5、bit9 等):通道 x 传输完成中断标志(1 = 传输完成);
    • HTIFx(bit2、bit6、bit10 等):通道 x 半传输中断标志(1 = 传输完成一半);
    • TEIFx(bit3、bit7、bit11 等):通道 x 传输错误中断标志(1 = 传输错误)。

四、DMA 初始化核心流程(必掌握!)

DMA 的初始化流程严格遵循 “使能时钟→配置通道参数→设置传输计数器→使能通道” 的顺序,以下以 “ADC1→DMA1_Channel1→内存” 传输为例,解析完整流程:

1. 步骤 1:使能 DMA 控制器时钟

DMA 控制器属于 AHB 总线外设,需通过 RCC 寄存器使能时钟:

c

运行

// 使能DMA1时钟(DMA1挂载AHB总线)
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

底层寄存器操作RCC->AHBENR |= (1<<0)(DMA1 时钟使能位为 bit0)。

2. 步骤 2:配置 DMA 通道参数(DMA_CCRx)

通过DMA_InitTypeDef结构体配置通道核心参数,对应寄存器位:

c

运行

DMA_InitTypeDef DMA_InitStruct;

// 1. 选择DMA通道(ADC1→DMA1_Channel1)
DMA_InitStruct.DMA_Channel = DMA_Channel_1;

// 2. 配置传输方向(外设→内存)
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC; // SRC=源,外设为数据来源

// 3. 配置内存地址(SRAM中存储ADC数据的数组首地址)
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)adc_data_buf; // adc_data_buf为uint16_t数组

// 4. 配置外设地址(ADC1数据寄存器地址)
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;

// 5. 内存地址增量使能(开启,连续写入内存)
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;

// 6. 外设地址增量使能(关闭,ADC_DR地址固定)
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

// 7. 内存数据宽度(16位,与ADC采样值宽度匹配)
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

// 8. 外设数据宽度(16位,ADC_DR为16位寄存器)
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;

// 9. 传输模式(循环模式,持续采集)
DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;

// 10. 通道优先级(中优先级)
DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;

// 11. 内存到内存模式(关闭,仅外设→内存)
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;

// 12. 写入配置参数到DMA通道
DMA_Init(DMA1_Channel1, &DMA_InitStruct);

3. 步骤 3:设置传输计数器(DMA_CNDTRx)

配置待传输的数据个数,若为循环模式,计数器会自动重载:

c

运行

// 配置传输数据个数(1000个数据,adc_data_buf数组长度≥1000)
DMA_SetCurrDataCounter(DMA1_Channel1, 1000);

底层寄存器操作DMA1_Channel1->CNDTR = 1000

4. 步骤 4:使能 DMA 通道

c

运行

// 使能DMA1_Channel1
DMA_Cmd(DMA1_Channel1, ENABLE);

底层寄存器操作DMA1_Channel1->CCR |= (1<<0)(EN 位 = 1)。

5. 步骤 5:使能外设 DMA 请求(如 ADC1 DMA 请求)

DMA 传输需由外设发起请求(如 ADC 转换完成后请求 DMA 搬运数据),需在了你那个外设中使能 DMA 请求:

c

运行

// 使能ADC1的DMA请求(ADC_CR2的DMA位=1)
ADC_DMACmd(ADC1, ENABLE);

底层寄存器操作ADC1->CR2 |= (1<<8)(ADC_CR2 的 DMA 位为 bit8)。

6. 步骤 6:(可选)配置 DMA 中断

若需在传输完成、半传输或传输错误时触发中断,需配置 NVIC 和 DMA 中断使能:

c

运行

// 使能DMA1_Channel1传输完成中断
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);

// 配置NVIC中断优先级
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = DMA1_Channel1_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);

五、DMA 相关面试高频题(附标准答案)

1. 问题 1:STM32 DMA 的核心作用是什么?与 CPU 中断传输相比有哪些优势?

标准答案:
  • 核心作用:在不占用 CPU 资源的情况下,实现外设与内存、内存与内存之间的高速数据传输;

  • 优势:

    1. 解放 CPU:传输过程无需 CPU 干预,CPU 可执行其他任务或进入睡眠模式;
    2. 传输效率高:硬件级传输,无软件开销,速度仅受总线带宽限制;
    3. 降低功耗:CPU 无需频繁响应中断,减少功耗;
    4. 适配大批量数据:适合 ADC 连续采集、串口文件传输等高频、大批量数据场景。

2. 问题 2:STM32 DMA 支持哪些传输方向?各自的典型应用是什么?

标准答案:
  • 支持三种传输方向:

    1. 外设→内存(Peripheral to Memory):典型应用是 ADC 采集数据写入 SRAM、串口接收大数据存储到内存;
    2. 内存→外设(Memory to Peripheral):典型应用是 SRAM 数据通过串口发送、DMA 驱动 DAC 输出波形;
    3. 内存→内存(Memory to Memory):典型应用是 SRAM 内部大批量数据复制、Flash 数据搬运到 SRAM。

3. 问题 3:STM32 DMA 的通道与外设的映射关系是什么?如何选择 DMA 通道?

标准答案:
  • 映射关系:每个 DMA 通道对应特定的外设请求(如 DMA1_Channel1 对应 ADC1,DMA1_Channel5 对应 USART1_RX),由 STM32 硬件设计固定,不可随意更改;

  • 选择方法:

    1. 确定待传输的外设(如 ADC1、USART1);
    2. 查阅 STM32 参考手册的 “DMA 通道与外设请求映射表”;
    3. 选择该外设对应的 DMA 通道(如 ADC1→DMA1_Channel1);
    4. 确保同一时间仅一个外设使用该通道。

4. 问题 4:DMA 的循环模式和正常模式有什么区别?各自适用场景是什么?

标准答案:
  • 循环模式(Circular mode):传输完成后,DMA 传输计数器自动重载初始值,继续循环传输,无需手动重启;适用于连续、周期性数据传输(如 ADC 连续采集、DAC 循环输出波形);
  • 正常模式(Normal mode):传输完成后,DMA 通道自动关闭,传输计数器清零,需手动调用DMA_SetCurrDataCounterDMA_Cmd重启传输;适用于一次性数据传输(如单次文件发送、内存数据复制)。

六、总结:DMA 底层原理核心要点与实战铺垫

1. 核心要点回顾

  • DMA 本质:无 CPU 干预的硬件数据搬运控制器,核心价值是 “解放 CPU + 高速传输”;
  • 核心参数:传输方向、通道选择、地址增量、数据宽度、传输模式、优先级,决定传输逻辑;
  • 初始化流程:使能 DMA 时钟→配置通道参数→设置传输计数器→使能通道→使能外设 DMA 请求;
  • 关键映射:外设与 DMA 通道的映射关系固定,需按手册选型;
  • 应用场景:大批量、高频数据传输,如 ADC 采集、串口大数据收发、SPI 闪存读写。

2. 下一篇实战铺垫

掌握底层原理后,下一篇我们将聚焦 DMA 实战开发,覆盖:

  • ADC+DMA 采集:ADC1 连续采集光敏电阻数据,DMA 自动写入内存,实现无 CPU 干预采集;
  • 串口 DMA 收发:USART1 通过 DMA 接收大数据(如字符串),DMA 自动存储到缓冲区;
  • 内存到内存传输:实现 SRAM 数组数据复制,验证 DMA 传输效率;
  • 中断处理:配置 DMA 传输完成中断,处理采集或传输后的数据。