DMA数据转运&DMA+AD多通道

DMA数据转运&DMA+AD多通道

DMA

  1. 什么是DMA?
    DMA(Direct Memory Access)直接存储器存取,DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源.DMA有12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道),每个通道都支持软件触发和特定的硬件触发,STM32F103C8T6 DMA资源:DMA1(7个通道).
  2. 存储器映象
    Flash中的代码是只能读不能写的,数组以及代码一般写在SRAM中.
    在这里插入图片描述
  3. DMA框图
  1. DMA基本结构
    在这里插入图片描述

  2. DMA与外设请求映象
    在配置ADC和定时器的时候要查看此图来根据ADC的型号或定时器的型号来配置对应的DMA通道.

DMA数据转运

  • 接线图如下:
    在这里插入图片描述
  • 由于DMA配置不涉及外部电路,所以在Systerm文件夹下(而不是以前的Harwaer文件夹下)创建MyDMA.c文件和MyDMA.h文件
    MyDMA.c代码:
#include "stm32f10x.h"                  // Device header

uint16_t MyDMA_Size;
//地址在寄存器以32bit存储,所以用uint32_t来修饰地址变量
//Size是DMA的传输次数,范围是0~65535,所以要用uint16_t(2的16次方是65536)
void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size)
{
	MyDMA_Size = Size;
	
	//开启DMA时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	
	DMA_InitTypeDef DMA_InitStructure;
	//指定DMA传输次数,取值范围是0~65535,所以Size的是16位数据
	DMA_InitStructure.DMA_BufferSize = Size;
	//指定外设站点是目的地还是源头,DMA_DIR_PeripheralSRC指定外设站点是源头,传输方向是外设站点到寄存器
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
	//指定DMA是否是软件到软件传输,也就是否开启DMA软件触发
	DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
	//指定存储器站点的起始地址
	DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;
	//指定存储器站点的单次传输的数据大小,即数据宽度
	//在main.c中的数组DataB的单个数据大小为1B,所以在这里也要指定为DMA_MemoryDataSize_Byte,以字节传输
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	//指定存储器站点地址是否自增
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	//指定DMA是否自动重装,注意:自动重装和软件出发不能同时设置,否则DMA就会无休止的转运,永不会停下来
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
	//指定外设寄存器的起始地址
	DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;
	//指定外设寄存器站点的单次传输的数据大小,即数据宽度
	//在main.c中的数组DataA的单个数据大小为1B,所以在这里也要指定为DMA_MemoryDataSize_Byte,以字节传输
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
	//指定外设寄存器站点地址是否自增
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
	//指定通道的软件优先级,这里只有一个通道,所以就随便设置了
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
	//参数DMA1_Channel1,的DMA1指定了使用DMA1;Channel1指定了使用DMA1的通道1
	//DMA1_Channel1既指定了使用哪个DMA,又指定了使用该DMA的那个通道
	DMA_Init(DMA1_Channel1,&DMA_InitStructure);
	
	//DMA上电
	DMA_Cmd(DMA1_Channel1,DISABLE);
}

void MyDMA_Transfer(void)
{
	//重新给传输计数器赋值,赋值时必须要DMA是关闭的
	DMA_Cmd(DMA1_Channel1,DISABLE);
	//给DMA重新赋值,赋的值是本次DMA传输需要完成的数据转运次数。
	DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);
	DMA_Cmd(DMA1_Channel1,ENABLE);
	
	//等待转运完成
	while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
	DMA_ClearFlag(DMA1_FLAG_TC1);
}

值得注意的是:
DMA连接在AHB总线上,所以开启时钟要开启AHB总线上的时钟.
DMA的传输次数表示需要搬运的“数据单元”数量,每个数据单元的大小由数据宽度(如字节、半字、字)决定。例如:
若数据宽度设置为字节(Byte,8位),传输次数设为N,则DMA将搬运N个字节(共N×8位);
若数据宽度设置为半字(Half-Word,16位),传输次数设为N,则DMA将搬运N个半字(共N×16位);
若数据宽度设置为字(Word,32位),传输次数设为N,则DMA将搬运N个字(共N×32位)。
传输次数的最大值受限于DMA寄存器的位宽(如STM32的DMA_CNDTRx为16位寄存器),因此最大传输次数为65535(即2^16 - 1)。DMA的传输次数由结构体参数DMA_BufferSize指定,这里由外部传参.
③参数DMA_DIR是指定外设站点是目的地还是源头,DMA_DIR_PeripheralSRC指定外设站点是源头,传输方向是外设站点到寄存器
④参数DMA_M2M 指定DMA是否是软件到软件传输,也就是否开启DMA软件触发,要注意:自动重装和软件触发不能同时设置,否则DMA就会无休止的转运,永不会停下来.
⑤参数DMA_Mode 是是否开启DMA循环模式,注意:自动重装和软件出发不能同时设置,否则DMA就会无休止的转运,永不会停下来,DMA循环模式和单次模式对比如下:
在这里插入图片描述
⑥DMA初始化函数DMA_Init(DMA1_Channel1,&DMA_InitStructure);中第一个参数DMA1_Channel1的DMA1指定了使用DMA1;Channel1指定了使用DMA1的通道1,所以该参数既指定了使用哪个DMA,又指定了使用该DMA的那个通道.
⑦在函数void MyDMA_Transfer(void);中有一个给DMA重新赋值的函数DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size);,该函数的第二个参数MyDMA_Size是由外部声明的全局变量,**该变量在MyDMA_Init();中由局部参数Size赋值,**是一个很不错的编程技巧.
⑧函数void MyDMA_Transfer(void);的目的是根据转运次数数值来转运数据,转运后立即停止.注意:重新给传输计数器赋值,赋值时必须要DMA是关闭的.

  • 根据MyDMA.c代码,单次使用DMA的配置流程如下图:

main.c代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"

uint8_t DataA[] = {0x01,0x02,0x03,0x04};
uint8_t DataB[] = {0,0,0,0};
int main(void)
{
	OLED_Init();
	MyDMA_Init((uint32_t)DataA,(uint32_t)DataB,4);
	
	
	OLED_ShowString(1,1,"DataA");
	OLED_ShowString(3,1,"DataB");
	
	//显示一下这两个数组的地址
	OLED_ShowHexNum(1,8,(uint32_t)DataA,8);
	OLED_ShowHexNum(3,8,(uint32_t)DataB,8);
	
	
	
		
	while (1)
	{
		DataA[0]++;
		DataA[1]++;
		DataA[2]++;
		DataA[3]++;
		
		OLED_ShowHexNum(2,1,DataA[0],2);
		OLED_ShowHexNum(2,4,DataA[1],2);
		OLED_ShowHexNum(2,7,DataA[2],2);
		OLED_ShowHexNum(2,10,DataA[3],2);
		OLED_ShowHexNum(4,1,DataB[0],2);
		OLED_ShowHexNum(4,4,DataB[1],2);
		OLED_ShowHexNum(4,7,DataB[2],2);
		OLED_ShowHexNum(4,10,DataB[3],2);
		
		Delay_ms(1000);
		
		MyDMA_Transfer();	
		
		Delay_ms(1000);		
	}
}

DMA+AD多通道

  1. 接线图如下:
    在这里插入图片描述
  2. 复制DMA数据转运项目并修改MyDMA.c文件
    MyDMA.c代码:
#include "stm32f10x.h"                  // Device header

uint16_t AD_Value[4];

void AD_Init(void)
{
	//ADC都是总线APB2上的设备
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	//开启DMA时钟
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
	
	//ADCCLK配置,ADC最大时钟频率是14MHZ,RCC_PCLK2_Div6表示6分频,使用的内部时钟是72MHZ,6分频后是12MHZ
	//配置ADCCLK是为了驱动ADC内部有规律的逐次比较
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	/*GPIO初始化*/
	GPIO_InitTypeDef GPIO_InitStructure;
	//使用参数GPIO_Mode_AIN是因为模数转换需要未经任何“加工”的原始模拟电压
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
	//GPIO_Speed(速度)参数在配置为模拟输入模式时是​​不起作用的,但是程序上仍需要填写一个合法值
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	//配置ADC规则组的1~4序列对应的ADC转运通道
	ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_1,2,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_2,3,ADC_SampleTime_55Cycles5);
	ADC_RegularChannelConfig(ADC1,ADC_Channel_3,4,ADC_SampleTime_55Cycles5);	
	
	//结构体初始化ADC
	ADC_InitTypeDef ADC_InitStructure;
	//转换模式,连续转换或者单次转换,ENABLE是连续转换
	ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
	//ADC是12bit的,而数据寄存器是16bit的,ADC_DataAlign指定ADC数据在数据寄存器是左对齐还是右对齐
	ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
	//外部触发转换选择,指定外部触发器的触发源,这里使用内部软件触发,所有填参数ADC_ExternalTrigConv_None
	ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
	//选择ADC是独立转换还是双ADC模式,ADC_Mode_Independent是独立转换模式
	ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
	//指定ADC在扫描模式下一次扫描几个通道,仅在扫描模式下生效,4代表序列的前4个位置
	ADC_InitStructure.ADC_NbrOfChannel = 4;
	//扫描模式,连续扫描模式或者单次扫描模式,ENABLE表示连续扫描模式
	ADC_InitStructure.ADC_ScanConvMode = ENABLE;
	ADC_Init(ADC1,&ADC_InitStructure);
		
	DMA_InitTypeDef DMA_InitStructure;
	//指定DMA传输次数,取值范围是0~65535,所以Size的是16位数据
	DMA_InitStructure.DMA_BufferSize = 4;
	//指定外设站点是目的地还是源头,DMA_DIR_PeripheralSRC指定外设站点是源头,传输方向是外设站点到存储器
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
	//指定DMA是否是软件到软件传输,也就是否开启DMA软件触发,触发源为外设ADC,所以关闭
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
	//指定存储器站点的起始地址,类型为uint32_t
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;
	//指定存储器站点的单次传输的数据大小,即数据宽度
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	//指定存储器站点地址是否自增
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	//指定DMA是否自动重装,注意:自动重装和软件出发不能同时设置,否则DMA就会无休止的转运,永不会停下来
	//DMA_Mode_Circular为DMA循环模式
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
	//指定外设寄存器的起始地址,类型为uint32_t,在ADC中,每次ADC处理的数据会放到ADC1->DR,一次只能处理一组数据
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
	//指定外设寄存器站点的单次传输的数据大小,即数据宽度
	//ADC寄存器是12bit的,超过1B但是小于1word(32bit),所以使用半字HalfWord(16bit)
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	//指定外设寄存器站点地址是否自增,ADC的规则组1次只能处理1组数据到ADC->DR,所以不自增
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	//指定通道的软件优先级,这里只有一个通道,所以就随便设置了
	DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
	//参数DMA1_Channel1,的DMA1指定了使用DMA1;Channel1指定了使用DMA1的通道1
	//DMA1_Channel1既指定了使用哪个DMA,又指定了使用该DMA的那个通道
	//根据DMA与ADC的通道对应关系,ADC1接在DMA1的通道1上
	DMA_Init(DMA1_Channel1,&DMA_InitStructure);
	
	//DMA上电
	DMA_Cmd(DMA1_Channel1,ENABLE);
	
	//开启ADC1到DMA的输出
	ADC_DMACmd(ADC1,ENABLE);
	
	//ADC上电
	ADC_Cmd(ADC1,ENABLE);
	
	//上电后校准,大幅减小因 ADC 内部电容器组变化造成的精度误差,提高转换准确性
	//复位校准
	ADC_ResetCalibration(ADC1);
	//获取复位校准状态,由ADC_ResetCalibration(ADC1);设置为校准状态为1,硬件校准完毕后将校准状态置0
	while(ADC_GetResetCalibrationStatus(ADC1) == SET);
	//开始校准
	ADC_StartCalibration(ADC1);
	//等待校准完成
	while(ADC_GetCalibrationStatus(ADC1) == SET);
	
	//软件触发转换
	ADC_SoftwareStartConvCmd(ADC1,ENABLE);
}

//void AD_GetValue(void)
//{
//	//重新给传输计数器赋值,赋值时必须要DMA是关闭的
//	DMA_Cmd(DMA1_Channel1,DISABLE);
//	//给DMA重新赋值,赋的值是本次DMA传输需要完成的数据转运次数。
//	DMA_SetCurrDataCounter(DMA1_Channel1,4);
//	DMA_Cmd(DMA1_Channel1,ENABLE);

//	
//	//等待转运完成
//	while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);
//	DMA_ClearFlag(DMA1_FLAG_TC1);



//}

值得注意的是:
此工程是在AD单通道扫描模式下,DMA开启循环模式对ADC规则组的寄存器进行数据的自动转运.
②首先要配置ADC规则组的序列号和ADC转运通道的对应,用函数ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);,该函数的中间两个参数,ADC_Channel_0规定了ADC的转运通道为0号通道,数字1规定了转运序列号1,这两个参数的含义是将ADC的转运通道为0号的通道放在1号转运序列,DMA的转运顺序是从上到下(从序列号小的开始).直观图如下:

③在ADC初始化中,该代码ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;指定了ADC进行连续转换.该模式下会对序列中的按序列号的从小到大依次处理数据.
④在ADC初始化中,这句代码ADC_InitStructure.ADC_ScanConvMode = ENABLE;开启ADC的扫描模式.
⑤注意在初始化DMA的结构体参数中DMA_BufferSize指定DMA传输次数,范围是0~65535,因为DMA寄存器是16bit的.
⑥因为触发源为外设ADC,所以使用该代码DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;关闭软件触发.
该代码DMA_InitStructure.DMA_MemoryBaseAddr的类型是uint32_t,指定存储器站点的起始地址,所以要将地址参数强转为uint32_t.
⑧该代码DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;指定存储器站点的单次传输的数据大小,即数据宽度.该代码DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;指定外设站点的单次传输数据大小.该代码DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;指定外设站点是目的地还是源头,DMA_DIR_PeripheralSRC指定外设站点是源头,传输方向是外设站点到存储器,需要注意的是,DMA是16bit的如果指定存储站点的数据宽度为1B,而外设站点的数据宽度为Half_Word(2B),ADC的数据寄存器的位数是12bit,那么,DMA会根据存储站点的数据宽度1B来从外设站点取数据1B来转运到存储器站点,而存储器站点的单组数据为2B,现在值转运了1B,所以高8位是空的,DMA再次转运时会将1B数据再次存储在这空的高8位中去,而不是自增后下一组数据中去.
⑨ADC_SoftwareStartConvCmd(ADC1,ENABLE);在用户代码中的作用是:启动ADC的连续转换过程,使ADC按照配置的扫描模式(依次转换PA0~PA3通道)、连续转换模式(自动循环)和DMA循环模式(自动搬运数据),实现“模拟信号→数字信号→内存存储”的全流程自动化。该函数只需调用一次,即可让ADC持续工作,无需后续手动触发,是ADC数据采集的“启动开关”。代码ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;规定了使用内部软件触发.除了使用ADC_SoftwareStartConvCmd(ADC1,ENABLE)这句软件代码开启ADC转换,STM32 ADC还支持通过硬件层面的触发源(如定时器、外部中断等)自动启动转换,但要实现修改ADC_InitStructure.ADC_ExternalTrigConv的触发模式.
在DMA上电到ADC上电中间加上一句ADC_DMACmd(ADC1,ENABLE);来大同DMA转运ADC数据通道.

  • 根据MyDMA.c文件的DMA配合ADC自动完成数据转运,无需人工干预,流程图如下:

main.c代码:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"


int main(void)
{
	/*模块初始化*/
	OLED_Init();		//OLED初始化
	AD_Init();

	OLED_ShowString(1,1,"AD0:");
	OLED_ShowString(2,1,"AD1:");
	OLED_ShowString(3,1,"AD2:");
	OLED_ShowString(4,1,"AD3:");
	while (1)
	{
//		AD_GetValue();
		
		OLED_ShowNum(1,5,AD_Value[0],4);
		OLED_ShowNum(2,5,AD_Value[1],4);
		OLED_ShowNum(3,5,AD_Value[2],4);
		OLED_ShowNum(4,5,AD_Value[3],4);
		
		Delay_ms(100);
	}
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

必胜的思想钢印

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值