DMA直接存储器访问
1.DMA概述
高速数据总线, 可以不依赖MCU执行指令,之间将数据按照配置,从A端到B端。例如
int a = 10;int b = 20;a = b;
以上代码内容,都需要MCU执行整个过程,包括变量定义,数据赋值等操作,对应MCU而言在硬件端可以采用DMA模式进行优化。降低MCU的参与。
希望MCU只是用于处理业务逻辑,数据处理,数据阈值判断的,而数据搬运过程,不通过MCU执行,降低MCU压力,利用DMA高速数据中线进行点到点的数据搬运操作。
DMA解决的问题
-
降低 MCU 参与数据搬运的赋值管理过程,降低 MCU 的执行压力
-
利用 DMA 高速数据总线特征,提供数据的传递速度。
1.2DMA主要特征
1.3DMA框图
1.4DMA映射关系【重点】
1.4.1DMA1映射关系框图和表格
1.4.2DMA2映射关系框图和表格
1.5DMA开发流程概述
DMA是一个数据搬运通道,配置完成之后,不占用MCU资源,同时对于内存的消耗较小。DMA不占用内存。需要知道【数据源地址】和【数据目标地址】
- 数据源需要配置和提供的内容
- 数据源地址,存储器地址或者外设设备数据地址
- 数据宽度/数宽,全字(4字节)半字(2字节)字节(1字节)
- 数据增量,从指定内存地址提取数据之后,根据当前数宽跳转到下一个数据位置,需要告知增量范围。类似于数组作为函数的参数,需要提供地址和数据有效元素个数(size)/数组容量(capacity)
- 例如ADC的注入转换通道。转换结果数据一共是4*16bit,每一个数据对2字节,总计四组数据,8个字节
- 设置数据源地址ADC注入转化通道数据结果地址,数据宽度/数宽 设置为半字,数据增量设置为4,DMA可以将ADC注入通道的所有数据搬运到目标地址
- 数据目标DEST
- 数据源地址,存储器地址或者外设设备数据地址
- 数据宽度/数宽,全字(4字节)半字(2字节)字节(1字节)
- 数据增量,从指定内存地址提取数据之后,根据当前数宽跳转到下一个数据位置,需要告知增量范围。类似于数组作为函数的参数,需要提供地址和数据有效元素个数(size)/数组容量(capacity)
1.6DMA相关寄存器
1.6.1DMA中断相关寄存器
1.6.2DMA数据地址相关寄存器
1.6.3DMA_CCRx 寄存器【重点】
1.6.4DMA_CNDTRx 通道传输数据个数寄存器
1.7光敏传感器ADC数据利用DMA进行数据搬运
1.7.1原理图和寄存器分析
-
ADC 采样光敏传感器数据相关内容已完成开发。
-
根据原理分析,和 DMA 映射关系分析,需要开启 DMA2 通道 5,根据当前数据情况完成开发
-
DMA2 时钟使能 ==> AHBENR
-
DMA2 通道 5 对应源地址和目标地址处理
-
源地址 src ==> ADC3_DR ADC3 的数据寄存器。
-
目标地址 dst ==> 选择 u16 类型的变量作为当前数据的目标存储空间,需要提供当前变量的首地址作为 DMA 数据存储端的【目标地址】
-
-
DMA2_Channel5 配置
-
MEM2MEM [位14] ==> 当前数据是从 ADC 到内存,属于外设到内存,选择 0 外设到内存模式
-
PL [位 13:12] ==> 当前通道优先级,选择 11 最高,10 高, 01 中, 00 最低
-
MSZIE[位11:10] ==> 存储器数宽,对应当前用于作为存储目标的 u16 类型变量,对应数据类型占用内存空间为 2 字节,选择半字 ==> 01
-
PSZIE[位9:8] ==> 外设数据宽度,对应 ADC->DR ,考虑到 ADC 规则数据存储器为 2 个字节,当前设置为 半字 ==> 01
-
MINC[位7] ==> 存储器地址增量,当前数据不需要增量处理,==> 0
-
PINC[位6] ==> 外设地址增量,当前数据不需要增量处理,==> 0
-
CIRC[位5] ==> 循环模式打开,需要进行 ADC 数据连续采样 ==> 1
-
DIR[位4] ==> 当前操作是外设到内部存储器 ==> 0
-
EN[位0] ==> DMA 打开 ,需要 ==> 1
-
-
0011 0101 0010 0001 ==> 0x3521
1.7.2 代码实现
其他代码实现参考上一篇ADCADC 模拟量转数字量-CSDN博客
#include "adc.h"
u16 adc_dma_value = 0;
void LSEN_Init(void)
{
/*
1.RCC 时钟使能,需要使能有 GPIOF 和 ADC3
因为当前光敏电阻对应 GPIO PF8 ,ADC 通道是 ADC3_IN6
*/
RCC->APB2ENR |= GPIOF_RCC_APB2_CLOCK_ENABLE | ADC3_RCC_APB2_CLOCK_ENABLE;
/*
2. GPIOF 对应 PF8 配置为【模拟输入模式】
对应 GPIOF->CRH [CNF8:MODE8]
*/
GPIOF->CRH &= ~(0x0F);
/*
3. ADC 配置
*/
/*
3.1 ADCCLK 对应 APB2 预分频倍数配置,对应寄存器是
RCC->CFGR, 设置预分配倍数位 6. ADCCLK 对应 12MHz
*/
RCC->CFGR &= ~(0x03 << 14);
RCC->CFGR |= (0x02 << 14);
/*
3.2 ADC 采用周期时间控制
Tconv = 采样时间 + 12.5个周期 官方 ADC 规定周期。
一般情况下采样周期越长,对应的 ADC 数据精度越高。
239.5 + 12.5 周期。
*/
ADC3->SMPR2 |= (0x07 << 18);
/*
3.3 ADC 工作通道配置
选择规则通道进行 ADC 转换,同时开放一个转换通道。
对应 ADC3_IN6
*/
ADC3->SQR1 &= ~(0x0F << 20); // ADC 规则通道开始一个转换通道
ADC3->SQR3 &= ~(0x1F); // 擦除 ADC3 规则转换通道第一个转换通道原本数据
ADC3->SQR3 |= (0x06); // 赋值 ADC3 规则转换通道第一个转换通道对应 ADC3_IN6
/*
3.4 ADC CR 配置
CR1
DUALMOD[3:0] [位19:16] 配置 ADC 独立模式 ==> 0000
SCAN [位8] 关闭 ADC 扫描模式 ==> 0
CR2
SWSTART [位22] 开启规则转换通道 ==> 1
EXTTRIG [位20] 规则转换通道外部触发方式开始 ==> 1
EXTSEL[2:0] [位19:17] 选择启动规则通道组转换的外部事件 ==> 111
明确为软件触发当前 ADC 转换。
ALIGN [位11] 转换结果数据对齐方式 ==> 0 右对齐
CONT [位1] 连续转换 ==> 1 连续转换模式
*/
ADC3->CR1 &= ~(0x0F << 16); // DUALMOD[3:0] [位19:16]
ADC3->CR1 &= ~(0x01 << 8); // SCAN [位8]
ADC3->CR2 &= ~(0xFFFFFFFF);
ADC3->CR2 |= (0x01 << 22); // SWSTART [位22]
ADC3->CR2 |= (0x01 << 20); // EXTTRIG [位20]
ADC3->CR2 |= (0x07 << 17); // EXTSEL[2:0] [位19:17]
ADC3->CR2 |= (0x01 << 1);
// 【DMA 模块开发补充内容】 ADC3 开启 DMA 支持
ADC3->CR2 |= (0x01 << 8);
/*
因为整个 ADC3->CR2 已经进行整体擦除操作,对应 ALIGN 无需再次赋值。
*/
/*
3.5 ADC 校准和开启
需要在 ADC 时钟,通道和其他相关配置完成之后,才可以开启校准。
ADC 自校准 + 重启过程
需要利用 ADC CR2 配置寄存器完成
RSTCAL [位3] 复位校准
CAL [位2] AD 校准
ADON [位0] AD 转换模块开启
*/
// 3.5.1 ADC 关闭掉电,重启
ADC3->CR2 &= ~(0x01); // ADC 关闭断电
Delay_ms(10); // 延时 10 ms
ADC3->CR2 |= 0x01; // ADC 打开
/*
3.5.2 ADC 开启复位校准
需要对 CR2 寄存器 RSTCAL 位赋值为 1,如果 复位校准完毕
硬件会将对应 RSTCAL 位置为 0.
1 ==> 开启复位校准
0 ==> 硬件复位校准结束
*/
ADC3->CR2 |= (0x01 << 3); // 开启复位校准
while (ADC3->CR2 & (0x01 << 3)); // 等待硬件将对应寄存器位置为 0,校准结束
Delay_ms(10); // 延时 10 ms
/*
3.5.3 ADC A/D 校准
需要对 CR2 寄存器 CAL 位赋值为 1,如果 复位校准完毕
硬件会将对应 CAL 位置为 0.
1 ==> 开启 A/D 校准开启
0 ==> 硬件 A/D 校准结束
*/
ADC3->CR2 |= (0x01 << 2); // 开启 A/D 校准
while (ADC3->CR2 & (0x01 << 2)); // 等待硬件将对应寄存器位置为 0,校准结束
ADC3->CR2 |= 0x01; // ADC 打开
/*
DMA 模块开发
1. 因为当前 ADC 模块为 ADC3。根据 MCU 框图分析,对应的 DMA 模块
为 DMA2,通道 Channel5
【时钟使能 DMA2】
*/
RCC->AHBENR |= DMA2_RCC_AHB_CLOCK_ENABLE; // (0x01 << 1);
// 2. 【DMA 模块开发补充内容】 Line:69
/*
3. 设置源地址和目标地址
DMA 外设地址 ==> ADC3->DR 转换完成数据寄存器
DMA 存储地址 ==> 当前 adc.c 中 adc_dma_value 变量地址
*/
DMA2_Channel5->CPAR = (u32)(&(ADC3->DR)); // DMA 外设地址 配置
DMA2_Channel5->CMAR = (u32)(&adc_dma_value); // DMA 存储器地址 配置
/*
4. DMA 每次搬运的数据数量,有且只有一路 ADC,对应数据为 2 字节,一个数据
*/
DMA2_Channel5->CNDTR = 1;
/*
5. DMA2_Channel5 核心配置 【重点】
- MEM2MEM [位14] ==> 当前数据是从 ADC 到内存,属于外设到内存,选择 0 外设到内存模
式
- PL [位 13:12] ==> 当前通道优先级,选择 11 最高,10 高, 01 中, 00 最低
- MSZIE[位11:10] ==> 存储器数宽,对应当前用于作为存储目标的 u16 类型变量,对应数
据类型
占用内存空间为 2 字节,选择半字 ==> 01
- PSZIE[位9:8] ==> 外设数据宽度,对应 ADC->DR ,考虑到 ADC 规则数据存储器为 2
个字节,
当前设置为 半字 ==> 01
- MINC[位7] ==> 存储器地址增量,当前数据不需要增量处理,==> 0
- PINC[位6] ==> 外设地址增量,当前数据不需要增量处理,==> 0
- CIRC[位5] ==> 循环模式打开,需要进行 ADC 数据连续采样 ==> 1
- DIR[位4] ==> 当前操作是外设到内部存储器 ==> 0
- EN[位0] ==> DMA 打开 ,需要 ==> 1
*/
DMA2_Channel5->CCR = 0;
/*
DMA2_Channel5->CCR &= ~(0x01 << 14); // MEM2MEM
DMA2_Channel5->CCR |= (0x03 << 12); // PL
DMA2_Channel5->CCR |= (0x01 << 10); // MSZIE
DMA2_Channel5->CCR |= (0x01 << 8); // PSZIE
DMA2_Channel5->CCR &= ~(0x01 << 7); // MINC
DMA2_Channel5->CCR &= ~(0x01 << 6); // PINC
DMA2_Channel5->CCR |= (0x01 << 5); // CIRC
DMA2_Channel5->CCR &= ~(0x01 << 4); // DIR
DMA2_Channel5->CCR |= 0x01; // EN
*/
DMA2_Channel5->CCR = 0x3521;
}
u16 LSEN_GetValue(void)
{
return adc_dma_value;
}