该系列为笔者在学习STM32过程(主线是江科大的视频)中的记录与发散思考。
初学难免有所纰漏、错误,还望大家不吝指正,感谢~
1. DMA简介
DMA(Direct Memory Access,直接存储器存取)是一种数据传输方式,它允许外设与存储器之间直接交换数据,而不需要CPU的干预。使用DMA可以大大提高数据传输效率,减少CPU负担,尤其适用于大数据量的连续数据传输,如ADC采样数据、串口通信、内存块复制等。
DMA的优点:
- 减少CPU负担:在DMA操作时,CPU可以执行其他任务,而不需要参与数据传输。
- 提高数据传输速度:DMA能够高速传输数据,尤其是对于大块数据传输来说,比传统的中断方式效率高得多。
(其他信息如图)

2. 存储器映像
存储器映像指的是DMA传输过程中,源和目标地址的数据存放方式。STM32的DMA控制器支持从不同类型的存储器(如内存、外设)进行数据传输,并且可以灵活地选择源和目标地址。
- 内存到内存:从一个内存地址读取数据,然后将数据写入另一个内存地址。
- 外设到内存:从外设(如ADC、串口、定时器等)读取数据并存储到内存中。
- 内存到外设:从内存中读取数据并发送到外设。
- ROM:是非易失性存储器,通常用于存储程序代码和常量数据。ROM是只读的,无法修改其内容。(掉电不丢失)
- RAM:是易失性存储器,广泛用于存储临时数据。在DMA操作中,ADC转换的结果通常会通过DMA传输到RAM中。由于RAM具有较快的访问速度,它常常作为DMA操作的目标存储器。(掉电丢失)
常见映像如图:

3. DMA框图
STM32的DMA模块具有多个通道和流,每个通道可以连接一个外设或内存进行数据传输。DMA的工作框图如下:
- 外设/内存:数据的源(如ADC、内存等)和目标(如内存、外设等)。
- DMA通道:数据从源到目标的传输路径。
- DMA寄存器:DMA控制器的配置寄存器,用于控制数据传输的相关参数。
CPU/DMA直接访问FLASH的话只可读不可写入。
寄存器是软件和硬件之间的桥梁。

4. DMA基本结构
STM32的DMA控制器通常包括以下基本结构
- 源和目标:源是数据传输的起点。目标是数据传输的终点,它决定了数据传输后存放的位置。(需设置地址、数据宽度、是否自增等参数)
- 硬件触发:由外设或系统事件自动触发DMA传输。硬件触发通常由外设(如ADC、定时器等)生成DMA请求信号,使DMA控制器进行数据传输。
- 软件触发:由CPU通过软件命令手动启动DMA传输的方式。通过编程,CPU可以直接控制DMA的启动,而无需依赖外部信号。以最快的方式转运数据清零传输计数器,与自动重装器不可同时使用(不然就是吊在驴前的萝卜,永远也吃不到>﹏<)。
- 传输计数器:在DMA配置时,传输计数器会设置为需要传输的数据项的数量。当DMA完成指定数量的数据传输后,传输计数器会归零,并触发DMA传输完成的标志。(只能在DMA关闭时设置值)

5. DMA请求
DMA请求是由外设产生的信号,用来通知DMA控制器开始数据传输。常见的DMA请求源包括:
- 外设请求:如ADC、USART、I2C、定时器等外设产生的DMA请求信号。(取决你用函数将哪个外设的DMA输出开启)
- 定时器请求:定时器的溢出或匹配事件可以触发DMA。
- 软件请求:CPU通过软件启动DMA传输。
- 优先级:默认通道数字越小优先级越高,也可自行设置,影响不大。
每个DMA都可通道以配置为响应不同的外设或定时器请求信号。DMA请求通常以事件触发的方式进行,外设在需要传输数据时会发出请求,DMA控制器接收到请求后进行数据转运。

6. 数据宽度与对齐
-
数据宽度:表示每次传输的数据的位数。STM32的DMA支持不同的数据宽度:
- 8位:每次传输1字节数据。
- 16位:每次传输2字节数据。
- 32位:每次传输4字节数据。
-
数据对齐:目标宽度大于源端宽度,多出的位补0,源端宽度大于目标宽度,舍去高位。

7. 数据转运+DMA
数据转运(Data Transfer)是DMA的核心功能,它使得数据在不同存储区域之间进行自动传输。以下是几种常见的数据转运模式:
- 内存到内存:DMA直接从一个内存位置复制数据到另一个内存位置。这种模式不涉及外设,主要用于内存数据的快速复制。
- 外设到内存:如从ADC采集数据到内存。ADC产生数据,通过DMA直接传输到内存(这俩天生一对φ(* ̄0 ̄))。
- 内存到外设:如从内存传输数据到USART。这种模式常用于将数据从内存写入外设进行通信。

8. ADC扫描模式+DMA
在ADC扫描模式+DMA的组合下,DMA可以自动将ADC采集的数据传输到指定内存,避免数据被覆盖,(上节介绍过,规则组有十六个通道,但相应寄存器同时只能存储一个通道的值)适用于多通道ADC数据采集的场景。(看来ADC要完整实现功能离不开DMA啊( ̄︶ ̄)↗ )
工作原理:
- 配置ADC为扫描模式:在扫描模式下,ADC会依次采样多个通道的信号。
- 启用DMA:DMA会监听ADC的数据转换完成信号,一旦ADC完成一个通道的数据转换,DMA就会自动将数据传输到指定的内存位置。
- 数据存储:DMA自动将所有通道的数据按顺序存储到指定内存。

9.代码部分
DMA+AD多通道初始化(那啥搭配,干活不累(¬‿¬)):
#include "stm32f10x.h" // Device header
uint16_t AD_Value[4]; //定义用于存放AD转换结果的全局数组
/**
* 函 数:AD初始化
* 参 数:无
* 返 回 值:无
*/
void AD_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //开启ADC1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA1的时钟
/*设置ADC时钟*/
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0、PA1、PA2和PA3引脚初始化为模拟输入
/*规则组通道配置*/
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //规则组序列1的位置,配置为通道0
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); //规则组序列2的位置,配置为通道1
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5); //规则组序列3的位置,配置为通道2
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5); //规则组序列4的位置,配置为通道3
/*ADC初始化*/
ADC_InitTypeDef ADC_InitStructure; //定义结构体变量
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //模式,选择独立模式,即单独使用ADC1
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐,选择右对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发,使用软件触发,不需要外部触发
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续转换,使能,每转换一次规则组序列后立刻开始下一次转换
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //扫描模式,使能,扫描规则组的序列,扫描数量由ADC_NbrOfChannel确定
ADC_InitStructure.ADC_NbrOfChannel = 4; //通道数,为4,扫描规则组的前4个通道
ADC_Init(ADC1, &ADC_InitStructure); //将结构体变量交给ADC_Init,配置ADC1
/*DMA初始化*/
DMA_InitTypeDef DMA_InitStructure; //定义结构体变量
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; //外设基地址,给定形参AddrA
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据宽度,选择半字,对应16为的ADC数据寄存器
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址自增,选择失能,始终以ADC数据寄存器为源
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value; //存储器基地址,给定存放AD转换结果的全局数组AD_Value
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //存储器数据宽度,选择半字,与源数据宽度对应
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //存储器地址自增,选择使能,每次转运后,数组移到下一个位置
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,选择由外设到存储器,ADC数据寄存器转到数组
DMA_InitStructure.DMA_BufferSize = 4; //转运的数据大小(转运次数),与ADC通道数一致
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //模式,选择循环模式,与ADC的连续转换一致
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //存储器到存储器,选择失能,数据由ADC外设触发转运到存储器
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //优先级,选择中等
DMA_Init(DMA1_Channel1, &DMA_InitStructure); //将结构体变量交给DMA_Init,配置DMA1的通道1
/*DMA和ADC使能*/
DMA_Cmd(DMA1_Channel1, ENABLE); //DMA1的通道1使能
ADC_DMACmd(ADC1, ENABLE); //ADC1触发DMA1的信号使能
ADC_Cmd(ADC1, ENABLE); //ADC1使能
/*ADC校准*/
ADC_ResetCalibration(ADC1); //固定流程,内部有电路会自动执行校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET);
ADC_StartCalibration(ADC1);
while (ADC_GetCalibrationStatus(ADC1) == SET);
/*ADC触发*/
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //软件触发ADC开始工作,由于ADC处于连续转换模式,故触发一次后ADC就可以一直连续不断地工作
}
感谢阅读,跟着光啊♪(´▽`)
DMA直接存储器存取&spm=1001.2101.3001.5002&articleId=144551000&d=1&t=3&u=07cf74896a8f4d60a965f89c8db5265b)
1608

被折叠的 条评论
为什么被折叠?



