当前位置: 首页 > backend >正文

STM32之DMA详解

一、DMA

1. DMA的引入

在嵌入式系统或计算机系统中,数据的传输和处理是非常重要的操作。以下通过一个简单的示例来展示传统数据操作方式与 DMA 引入的必要性:

int a = 10;
int b = 20;a = b;

        上述代码包含了变量定义、初始化以及变量数据赋值操作。在传统的数据处理流程中,这些看似简单的操作都需要 MCU(微控制单元)/CPU(中央处理器)参与整个过程。具体来说,数据内容需要通过 MCU/CPU 的寻址行为,找到对应数据存储空间,进行数据提取;变量定义时,内存空间申请需要 CPU 操作;数据内容赋值同样也需要 CPU 参与。

        这些操作会占用 MCU/CPU 资源。在一些对数据传输速度要求较高、数据量较大的场景中,比如从传感器快速读取大量数据并存储到内存,或者在内存不同区域之间快速搬运数据,如果一直让 CPU 参与数据传输的每一个步骤,CPU 就会被大量的数据搬运工作所占据,无法及时处理其他重要的任务,例如处理复杂算法、响应中断等。

        为了解决上述问题,引入了 DMA(Direct Memory Access,直接内存访问 / 高速数据总线)技术。DMA 可以实现 Memory to Memory 的数据传递,在数据传输过程中,不涉及 MCU 的逻辑判断或者说控制指向。预先设定好数据传输的源地址、目标地址、传输数据的长度等规则之后,DMA 控制器能够直接控制数据在内存与外设之间,或者内存不同区域之间进行快速传递,无需 CPU 频繁参与,大大提高了数据传输的效率,进而提升整个 MCU 的工作效率。

2. DMA 主要特征

  1. 独立于 CPU 操作:DMA 控制器拥有独立的总线接口,可以直接访问系统内存和外设。在数据传输过程中,CPU 可以继续执行其他任务,只有在 DMA 传输开始前和结束后,可能需要 CPU 进行一些简单的配置和处理,比如设置 DMA 传输参数、处理 DMA 传输完成的中断等。这使得 CPU 的利用率得到极大提高,能够专注于更复杂的计算和控制任务。
  2. 高速数据传输:由于 DMA 不需要 CPU 的干预,减少了 CPU 在数据传输过程中的开销,因此能够实现高速的数据传输。它可以在系统总线允许的情况下,以接近总线带宽的速度进行数据传输,这对于一些高速外设(如高速 ADC、网络接口等)的数据采集和处理非常重要。
  3. 多种传输模式
    • 内存到内存传输:可以将数据从内存的一个区域快速复制到另一个区域,例如在图像处理中,将图像数据从缓存区复制到显示内存。
    • 外设到内存传输:常用于从外设(如 ADC、UART 等)读取数据到内存。比如 ADC 持续采集模拟信号并转换为数字信号,通过 DMA 可以快速将这些数字信号传输到内存中存储,供后续处理。
    • 内存到外设传输:把内存中的数据发送到外设,像将待发送的数据从内存传输到串口,实现数据的串口发送。
  4. 可编程配置:可以通过配置 DMA 控制器的相关寄存器,灵活设置数据传输的源地址、目标地址、传输数据的长度、数据宽度(字节、半字、字等)、传输方向、优先级等参数。这些配置能够满足不同应用场景下的数据传输需求。
  5. 中断机制:当 DMA 传输完成、传输错误或者达到预设的传输条件时,DMA 控制器可以向 CPU 发送中断请求,通知 CPU 进行相应的处理。例如,在一次大数据量的传输任务完成后,CPU 可以对传输的数据进行校验、分析等后续操作。
  6. 突发传输:支持突发传输模式,即一次传输多个数据单元,减少了总线占用的次数,提高了总线的利用率。在突发传输过程中,DMA 控制器可以连续地从源地址读取数据,并写入目标地址,而不需要每次传输都重新进行总线仲裁等操作。

3. DMA 的应用场景

  1. 数据采集系统:在使用 ADC(模拟数字转换器)进行数据采集时,ADC 不断将模拟信号转换为数字信号。如果使用 CPU 来读取 ADC 转换后的数据,会占用大量 CPU 资源,导致 CPU 无法及时处理其他任务。通过 DMA,可以在 ADC 转换完成后,自动将数据快速传输到内存中,CPU 只需要在 DMA 传输完成后进行数据处理即可。
  2. 网络通信:在网络接口接收和发送数据时,DMA 可以将接收到的网络数据包从网卡缓冲区快速传输到内存,或者将内存中的数据快速发送到网卡进行传输。这提高了网络数据的传输效率,减少了数据传输的延迟。
  3. 音频和视频处理:在音频处理中,DMA 可以将音频数据从内存传输到音频解码器进行播放;在视频处理中,DMA 可以将图像数据从内存传输到显示设备进行显示,保证了音频和视频数据的流畅传输和播放。
  4. 存储设备数据读写:对于一些存储设备(如 SD 卡等),DMA 可以实现快速的数据读写操作,提高存储设备的访问效率。

4. DMA框图

5. DMA 映射关系

DMA1 映射关系:

DMA2 映射关系:

6. DMA开发

DMA 是一个通道!!不占用内存空间,在 MCU 内部设计预留的数据管道。

数据源 SRC
  • 【数据源地址】
  • 【数据宽度】全字,半字,字节
  • 【数据增量】从指定内存位置提取数据之后,下一次数据提取是从当前位置继续读取,还是选择读取下一个空间地址
    • 例如 ADC 转换通道
      • 规则通道:数据转换完成存储寄存器有且只有 2 个字节,每一次 ADC 转换之后的数据,都输存储到对应的指定寄存器位置,地址不变,【不设置数据增量】
      • 注入通道:数据转换完成寄存器有 4 * 16 位组成,每一组数据是占用 2 个字节,需要从注入通道中读取数据,需要设置【数据增量】,同时限制增量范围。
目标地址 DEST
  • 【目标接收数据地址】
  • 【数据宽度】全字,半字,字节,通常情况下,数据源和目标地址对应的【数宽】一致
  • 【数据增量】如果设置数据增量,下一次存储数据地址是当前地址 + 数宽。如果不设置,每一次存储数据的地址都是固定地址
    • 例如多数据存储到数组中,需要利用数据增量按照数组下标 0 - N 进行逐一的数据存储
    • 例如单数据存储,仅考虑当前固定地址进行数据存储操作。

一、DMA 通道的基本概念

核心定义:DMA 是 MCU 内部预先设计的 “数据管道”,不占用内存空间,专门用于快速搬运数据,让 CPU 从繁琐的数据传输中解放。

二、数据源(SRC)配置

1. 基础参数
  • 数据源地址:明确数据从哪里开始搬运(如内存地址、外设寄存器地址)。
  • 数据宽度:定义单次搬运的数据大小,可选 全字(4 字节)、半字(2 字节)、字节(1 字节),需与实际数据匹配。
  • 数据增量:控制 “下一次从哪读数据”:
    • 启用增量:读完当前地址,自动跳到下一个地址(如读完 0x2000,下一次读 0x2002,假设半字宽度);
    • 禁用增量:永远从固定地址重复读(适合 “同一地址持续输出数据” 的场景,如规则通道 ADC)。
2. 示例:ADC 转换通道的差异
通道类型硬件寄存器设计数据增量怎么用?
规则通道结果存在固定地址(仅 2 字节)禁用增量!因为每次结果都存在同一地址,重复覆盖。
注入通道结果存在连续地址(4 组 ×16 位)启用增量!每次读 “下一组地址”(如读完 0x4000,下一次读 0x4002 ),避免数据覆盖。

三、目标地址(DEST)配置

1. 基础参数
  • 目标接收数据地址:明确数据要搬到哪里(如内存数组、缓冲区地址)。
  • 数据宽度:需与 “数据源宽度”保持一致(否则会数据错位,如源是半字,目标设字节会丢数据)。
  • 数据增量:控制 “下一次往哪写数据”:
    • 启用增量:写完当前地址,自动跳到下一个地址(如写完 0x3000,下一次写 0x3002 ,按数组下标顺序存);
    • 禁用增量:永远往固定地址重复写(适合 “覆盖旧数据” 的场景,如单数据存储)。
2. 示例:数据存储的不同需求
场景怎么配置增量?效果
数组存多数据(如 buf[10]启用增量!地址每次 + 数据宽度(如半字 + 2)数据按 buf[0]→buf[1]→…→buf[9] 顺序存,不覆盖。
单数据存储(如 result 变量)禁用增量!地址固定新数据覆盖旧数据(适合只需要最

7. DMA 开发相关寄存器

7.1 DMA_CCRx 寄存器

位域名称功能描述
0EN通道使能位(Channel enable):0:关闭 DMA 通道 ;1:开启 DMA 通道。注意,在配置完所有其他参数后,最后再置位位来启动传输。
1TCIE传输完成中断使能(Transfer complete interrupt enable):0:禁止传输完成中断 ;1:使能传输完成中断。当 DMA_ISR 中的 TCIFx 置位时,会产生中断。
2HTIE半传输中断使能(Half transfer interrupt enable):0:禁止半传输中断 ;1:使能半传输中断。当传输达到一半时,DMA_ISR 中的 HTIFx 置位,产生中断。
3TEIE传输错误中断使能(Transfer error interrupt enable):0:禁止传输错误中断 ;1:使能传输错误中断。当发生传输错误时,DMA_ISR 中的 TEIFx 置位,产生中断。
4DIR数据传输方向(Data transfer direction):0:从外设到存储器(Peripheral-to-memory)模式,源地址 = 外设地址(如 USARTx_DR ),目标地址 = 内存地址(如 mybuffer );1:从存储器到外设(Memory-to-peripheral)模式,源地址 = 内存地址,目标地址 = 外设地址(如 USARTx_DR )。注意,存储器到存储器模式(MEM2MEM)的配置见下文。
5CIRC循环模式(Circular mode):0:非循环模式,传输完成后(达到设定的数据量 ),通道自动禁用(EN 位被硬件清除 );1:循环模式,传输完成后,地址寄存器(DMA_CPARx 和 DMA_CMARx )自动重置为初始值,传输重新开始。这对于需要连续传输的场景非常有用(如 ADC 连续采样、DAC 输出波形 )。
6PINC外设地址增量模式(Peripheral increment mode):0:不增量,每次传输后,外设地址寄存器保持不变,适用于传输到 / 来自单一外设寄存器(如 USART—DR );1:增量,每次传输后,外设地址寄存器根据 PSIZE 自动增加,适用于传输到 / 来自外设的一组连续寄存器(比较少见 )。
7MINC存储器地址增量模式(Memory increment mode):0:不增量,每次传输后,存储器地址寄存器保持不变,适用于传输到 / 来自一个固定的内存变量 ;1:增量,每次传输后,存储器地址寄存器根据 MSIZE 自动增加。这是最常见的情况,用于在内存数组和外设间传输数据。
8 - 9PSIZE[1:0]外设数据宽度(Peripheral size):00:8 位(Byte);01:16 位(HalfWord);10:32 位(Word)。必须与外设数据寄存器的宽度匹配,例如,USART 是 8 位,ADC 是 16 位 。
10 - 11MSIZE[1:0]存储器数据宽度(Memory size):00:8 位(Byte);01:16 位(HalfWord);10:32 位(Word)。必须与内存缓冲区数据类型的宽度匹配。
12 - 13PL[1:0]通道优先级(Channel priority level):当多个 DMA 通道同时请求时,仲裁器根据此优先级响应 ;00:低(Low);01:中(Medium);10:高(High);11:最高(Very high) 。
14MEM2MEM存储器到存储器模式(Memory to memory mode):0:禁用工作在普通模式,由外设请求触发 ;1:使能,通道工作在存储器到存储器模式,一旦软件启动(EN = 1 ),传输立即开始,不需要硬件触发信号,传输完成后通道自动停止。注意:此模式下,DIR 位不再指示方向(因为方向固定为内存→内存 ),但通常需要设置 MINC 和 PINC 都为增量。

7.2 DMA_CPARxDMA_CMARx

设置当前

  • 外设地址寄存器地址
  • 存储器地址寄存器地址

两个地址都存储目标内存的首地址,且根据当前选择的数宽,自动延展到目标内存

  • 例如选择半字,当前地址是对应半字空间的首地址
  • 例如选择全字,当前地址是对应全字空间的首地址

8. 开发流程

9. 代码实现

#include "stm32f10x.h"  
#include "delay.h"        // 需自行实现 Delay_ms 函数(如用 SysTick 延时)// 全局变量:存储 ADC DMA 传输结果
u16 adc_dma_value = 0;  /*** @brief  光敏传感器 ADC + DMA 初始化(ADC3 通道 6 + DMA2 通道 5)* @note   1. 配置 PF8 为模拟输入(ADC3_IN6)*         2. 初始化 ADC3 为连续转换、右对齐、最大采样周期*         3. 配置 DMA2 通道 5,实现 ADC3_DR -> adc_dma_value 自动搬运*/
void LSEN_Init(void)
{// ==================== 1. 使能时钟(RCC) ====================// 使能 GPIOF 时钟(PF8 作为 ADC 输入)RCC->APB2ENR |= (1 << 5);  // GPIOF 时钟使能(APB2 总线)// 使能 ADC3 时钟(ADC 属于 APB2 总线外设)RCC->APB2ENR |= (1 << 15); // 使能 DMA2 时钟(DMA2 属于 AHB 总线)RCC->AHBENR |= (1 << 1);   // ==================== 2. 配置 GPIO(PF8 模拟输入) ====================// PF8 端口配置:清除原有配置GPIOF->CRH &= ~(0x0F << ( (8 - 8) * 4 ));  // PF8 对应 CRH 第 0 组(Bits 0-3)// 模拟输入模式:0000GPIOF->CRH |= (0x00 << ( (8 - 8) * 4 ));  // ==================== 3. 配置 ADC3(时钟、通道、采样周期等) ====================// 3.1 配置 ADC 时钟分频:ADCCLK = PCLK2 / 6(PCLK2=72MHz,ADCCLK=12MHz ≤14MHz)RCC->CFGR &= ~(0x03 << 14);  // 清除原有配置RCC->CFGR |= (0x02 << 14);   // 分频因子 6: 01->2分频(旧版描述可能有误,实际 02 对应 6 分频?需核对手册!)// 正确写法:STM32F103 ADC 时钟分频需设置为 6(PCLK2/6),对应 RCC_CFGR_ADCPRE_1 // 若发现时钟错误,可改为:RCC->CFGR |= (1 << 15); // 实际工程建议查手册确认!// 3.2 配置 ADC 规则通道:仅开启 ADC3_IN6(通道 6)ADC3->SQR1 &= ~(0x0F << 20); // 规则通道序列长度:1 个通道(SQ1)ADC3->SQR3 &= ~(0x1F);       // 清除原有通道配置ADC3->SQR3 |= 0x06;          // 规则通道 1 配置为 ADC3_IN6(通道 6)// 3.3 配置采样周期:239.5 + 12.5 周期(最大采样周期,提高精度)ADC3->SMPR2 |= (0x07 << 18); // 通道 6 的采样时间:239.5 周期(SMPR2 对应通道 0~9)// 3.4 配置 ADC 控制寄存器 CR1 / CR2ADC3->CR1 &= ~(0x0F << 16);  // 独立模式(DUALMOD[19:16] = 0000)ADC3->CR1 &= ~(0x01 << 8);   // 关闭扫描模式(SCAN = 0)ADC3->CR2 &= ~(0xFFFFFFFF);  // 清除 CR2 原有配置ADC3->CR2 |= (0x01 << 22);   // 软件触发规则通道转换(SWSTART = 1)ADC3->CR2 |= (0x01 << 20);   // 使能外部触发(EXTTRIG = 1)ADC3->CR2 |= (0x07 << 17);   // 外部触发选择:SWSTART(EXTSEL[19:17] = 111)ADC3->CR2 &= ~(0x01 << 11);  // 数据右对齐(ALIGN = 0)ADC3->CR2 |= (0x01 << 1);    // 连续转换模式(CONT = 1)// ==================== 4. ADC 校准(必须步骤!) ====================ADC3->CR2 &= ~(0x01);        // 关闭 ADC(ADON = 0)Delay_ms(10);                // 延时稳定ADC3->CR2 |= 0x01;           // 打开 ADC(ADON = 1)Delay_ms(10);                // 延时稳定// 复位校准ADC3->CR2 |= (0x01 << 3);    // RSTCAL = 1while (ADC3->CR2 & (0x01 << 3)); // 等待复位校准完成(RSTCAL 自动清零)Delay_ms(10);// 开始校准ADC3->CR2 |= (0x01 << 2);    // CAL = 1while (ADC3->CR2 & (0x01 << 2)); // 等待校准完成(CAL 自动清零)Delay_ms(10);ADC3->CR2 |= 0x01;           // 确保 ADC 开启(ADON = 1)// ==================== 5. 配置 DMA2 通道 5(配合 ADC3) ====================// 使能 ADC 的 DMA 请求(ADC_CR2_DMA = 1)ADC3->CR2 |= (0x01 << 8);    // 允许 ADC 触发 DMA 请求// 5.1 配置 DMA 源地址、目标地址DMA2_Channel5->CPAR = (uint32_t)(&(ADC3->DR)); // 源地址:ADC3 数据寄存器DMA2_Channel5->CMAR = (uint32_t)(&adc_dma_value); // 目标地址:全局变量// 5.2 配置传输数据量:1 个半字(u16)DMA2_Channel5->CNDTR = 1;// 5.3 配置 DMA 模式(CCR 寄存器)DMA2_Channel5->CCR = 0x0000; // 先清零// 关键配置:// - MEM2MEM=0(外设→内存) | PL=11(最高优先级) | MSIZE=01(半字) | PSIZE=01(半字)// - MINC=0(内存不增量)   | PINC=0(外设不增量) | CIRC=1(循环模式) | DIR=0(外设→内存) | EN=1(使能)DMA2_Channel5->CCR = 0x3521; // 5.4 使能 DMA2 通道 5DMA2_Channel5->CCR |= (0x01 << 0); // 或直接写 0x3521 已包含 EN=1
}/*** @brief  获取光敏传感器 ADC 转换结果(DMA 搬运后的值)* @retval u16 类型,ADC 转换结果(0~4095)*/
u16 LSEN_GetValue(void)
{return adc_dma_value;
}

https://github.com/0voice

http://www.xdnf.cn/news/18706.html

相关文章:

  • 算法题记录01:
  • 8月25日
  • 专题:2025人工智能2.0智能体驱动ERP、生成式AI经济现状落地报告|附400+份报告PDF、原数据表汇总下载
  • [论文阅读]RQ-RAG: Learning to Refine Queries for Retrieval Augmented Generation
  • k8s的etcd备份脚本
  • AR技术赋能农业机械智能运维
  • 电机控制::基于编码器的速度计算与滤波::RLS
  • 【C++】第二十六节—C++11(中) | 右值引用和移动语义(续集)+lambda
  • Linux_用 `ps` 按进程名过滤线程,以及用 `pkill` 按进程名安全杀进程
  • 机器学习-大语言模型Finetuning vs. Prompting
  • 大型语言模型基准测试综述《A Survey on Large Language Model Benchmarks.pdf》核心内容总结
  • 京东前端社招面经
  • 多维度指标交叉计算查询方案
  • 【芯片后端设计的灵魂:Placement的作用与重要性】
  • 6、RocketMQ消息积压问题如何解决
  • Python爬虫实战:Selenium模拟操作爬取马蜂窝旅游攻略
  • 数据挖掘 6.1 其他降维方法(不是很重要)
  • redis----list详解
  • 深度学习入门第一课——神经网络实现手写数字识别
  • 读《精益数据分析》:A/B测试与多变量测试
  • 【栈 - LeetCode】739.每日温度
  • [Java恶补day51] 46. 全排列
  • 无人机芯片休眠模式解析
  • 关于传统的JavaWeb(Servlet+Mybatis)项目部署Tomcat后的跨域问题解决方案
  • 日语学习-日语知识点小记-构建基础-JLPT-N3阶段(19):文法复习+单词第7回1
  • 基于知识图谱的装备健康智能维护系统KGPHMAgent
  • C++ #pragma
  • 少儿舞蹈小程序需求规格说明书
  • 【Hot100】二分查找
  • Fluent Bit系列:字符集转码测试(上)