STM32 学习笔记(七)DMA直接存储器存取

该系列为笔者在学习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的核心功能,它使得数据在不同存储区域之间进行自动传输。以下是几种常见的数据转运模式:

  1. 内存到内存:DMA直接从一个内存位置复制数据到另一个内存位置。这种模式不涉及外设,主要用于内存数据的快速复制。
  2. 外设到内存:如从ADC采集数据到内存。ADC产生数据,通过DMA直接传输到内存(这俩天生一对φ(* ̄0 ̄))。
  3. 内存到外设:如从内存传输数据到USART。这种模式常用于将数据从内存写入外设进行通信。

8. ADC扫描模式+DMA

ADC扫描模式+DMA的组合下,DMA可以自动将ADC采集的数据传输到指定内存,避免数据被覆盖,(上节介绍过,规则组有十六个通道,但相应寄存器同时只能存储一个通道的值)适用于多通道ADC数据采集的场景。(看来ADC要完整实现功能离不开DMA啊( ̄︶ ̄)↗ ) 

工作原理:

  1. 配置ADC为扫描模式:在扫描模式下,ADC会依次采样多个通道的信号。
  2. 启用DMA:DMA会监听ADC的数据转换完成信号,一旦ADC完成一个通道的数据转换,DMA就会自动将数据传输到指定的内存位置。
  3. 数据存储: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就可以一直连续不断地工作
}


感谢阅读,跟着光啊♪(´▽`)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值