17:(标准库)DMA一:DMA数据搬运

1、DMA的简介

   DMA是直接存储器存取,它可以提供外设寄存器和存储器,存储器与存储器之间的高速数据的传输,无需CPU的干预,这样节省了CPU的资源。简单来说DMA就是数据的搬运工

STM32中的存储器:
在这里插入图片描述DMA的3种搬运方式:
1.存储器(ROM)------>存储器(RAM)(数据的拷贝)
在这里插入图片描述

2.存储器(RAM/ROM)------>外设(将某数据写入串口寄存器TDR)
在这里插入图片描述

3.外设--------->存储器(RAM)(将串口接收寄存器RDR的数据搬运到内存,避免数据的覆盖)
在这里插入图片描述

2、STM32中的DMA结构

   在单片机中有12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)。每个通道都支持软件触发特定的硬件触发。而在STM32F103C8T6中只有DMA1(7个通道),且挂载在AHB总线上面。
在这里插入图片描述
  ①如上图所示:DMA能搬运数据的方向:Flash---->SRAM,Flash---->外设,SRAM---->外设,外设---->SRAM,但是不能xxx------>Flash,因为Flash是只读存储器
  ②若有2个通道对DMA发出请求,DMA按照软件编程设置(共有四级:很高、高、中等和低)优先级来进行数据的搬运。若2个通道的优先级设置为相等,则按照通道号来进行数据搬运。一般情况下通道号越小,优先级越高

  • DMA通道请求
    ①DMA请求就是DMA触发,由如下图可知,每个通道的硬件请求都是特定的,比如:TIM2_CH3的请求不能通过DMA1的通道2对DMA进行触发。但是每个通道都有软件请求。所以每个通道都支持软件触发和特定的硬件触发。软件触发一般用在存储器------>存储器(数据的拷贝)。
    ②每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求在这里插入图片描述
    在这里插入图片描述

3、DMA数据搬运

在这里插入图片描述
由上图所示:外设寄存器和存储器里面有起始地址,数据宽度,地址是否自增。分别代表上面意思喃?
①起始地址:数据从哪里转运到哪里
②数据宽度:每次搬运的数据有多大(Byte(uint8_t)/HalfWord(uint16_t)/Word(uint32_t))
③地址是否自增:第一次搬运完成后,进行下一次搬运时是否发生地址的偏移。
④传输计数器:总共需要搬运几次,是一个16位自减的计数器,所以最大值为65535。为0就不在搬运了。
【注】传输计数器变为0后,自增的地址也会恢复到起始地址
⑤自动重装器:传输计数器减为0后,计数器是否恢复初值,重新又开始搬运。
⑥M2M:触发控制,为1时就是软件触发,为0就是硬件触发。
【注】软件触发一般用于存储器到存储器的转运,触发一次,以最快的要求计数器清0。所以,软件触发不能和自动重装器同时用
⑦DMA开始搬运的3大调节
1.DMA开关必须关闭,2.计数器必修大于0,3.必须要有触发源

DMA为非自动重装时:如果转运完成,计数器清0 。想要进行第二次转运,则先要关闭DMA,再写入计数器次数,然后给触发源,开启DMA。

4、实验案列

4.1、将数组DataA中的数据搬运到DataB中

在这里插入图片描述
与之相关的标准库编程接口:
在这里插入图片描述

我们先查看变量存储在哪个存储器当中?

#include "stm32f10x.h"                 
#include "OLED.h"

uint8_t a = 10;
int main(void)
{
	OLED_Init();
	OLED_Clear();
	
	OLED_ShowNum(1,1,a,2);
	OLED_ShowHexNum(2,1,(uint32_t)&a,8);
	while(1)
	{
		
	}
}

OLED屏幕上面显示的是:
10
20000000

变量的地址为0x20000000,代表变量存储在SRAM存储器中

我们查看常量存储在哪个存储器当中?

#include "stm32f10x.h"                 
#include "OLED.h"

const uint8_t a = 10;
int main(void)
{
	OLED_Init();
	OLED_Clear();
	
	OLED_ShowNum(1,1,a,2);
	OLED_ShowHexNum(2,1,(uint32_t)&a,8);
	while(1)
	{
		
	}
}

OLED屏幕上面显示的是:
10
08000E40

变量的地址为0x08000E40,代表常量存储在Flash存储器中

实验1的代码如下:
①MyDMA.c文件的代码如下:

#include "MyDMA.h"

/**
 * DMA1的初始化
 * ADDrA:外设站点的起始地址
 * ADDrB:目的站点的起始地址
 */
uint16_t My_Size = 0;
void DMA1_Init(uint32_t ADDrA,uint32_t ADDrB)
{
    My_Size = Size;
    /* 1、使能DMA1的时钟 */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    /* 2、配置DMA1的通道1 */
    DMA_InitTypeDef DMA_InitStruct;
	DMA_InitStruct.DMA_PeripheralBaseAddr = ADDrA;                      //“外设站点”的起始地址,注外设站点地址也可以是内存的地址
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//数据宽度,8位
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;        //外设站点地址是否自增,这里选择自增
	
	DMA_InitStruct.DMA_MemoryBaseAddr = ADDrB;                          //内存站点起始地址
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;        //数据宽度,8位
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;                //目的站点地址是否自增,这里选择自增
	
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;                     //搬运方向的选择:外设站点---->内存站点:DMA_DIR_PeripheralSRC
	DMA_InitStruct.DMA_BufferSize = 0;                                  //传输计数器的大小,代表搬运数据的个数
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;                          //是否自动重装,这里选择不自动重装
	DMA_InitStruct.DMA_M2M = DMA_M2M_Enable;                            //是否软件触发,这里选择是
	DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;                  //优先级,这里选择中等
	DMA_Init(DMA1_Channel1,&DMA_InitStruct);                            //配置DMA1的通道1
	
//	DMA_Cmd(DMA1_Channel1,ENABLE);                                      //使能DMA1的通道1
	DMA_Cmd(DMA1_Channel1,DISABLE);                                     //先失能DMA1的通道1
}
   
/**
 * DMA1通道1开启搬运函数
 */
void DMA1_Transport(uint16_t DataNumber)
{
    /* 1、失能DMA1 */
    DMA_Cmd(DMA1_Channel1,DISABLE); 
    
    /* 2、先设置传输计数器的计数值 */
    DMA_SetCurrDataCounter(DMA1_Channel1, DataNumber);
    
    /* 3、使能DMA1 */
    DMA_Cmd(DMA1_Channel1,ENABLE);   
   
    /* 4、等待搬运完成 */
    while(!DMA_GetFlagStatus(DMA1_FLAG_TC1));   //等待DMA1的通道1搬运完成
	DMA_ClearFlag(DMA1_FLAG_TC1);               //清除标志位 
}

②MyDMA.h文件的代码如下:

#ifndef __MyDMA_H
#define __MyDMA_H
#include "stm32f10x.h"

void DMA1_Init(uint32_t ADDrA,uint32_t ADDrB);
void DMA1_Transport(uint16_t DataNumber);

#endif

③主函数main.c文件的代码如下:

/*
	存储器------>存储器(数据的拷贝)DMA的使用
*/
#include "stm32f10x.h"                 
#include "Delay.h"
#include "UART.h"
#include "MyDMA.h"

int main(void)
{
    uint8_t DataA[7] = {1,2,3,4,5,6,7};
    uint8_t DataB[7];
    UART1_Init();
    printf("DMA搬运:\r\n");
    DMA1_Init((uint32_t)DataA, (uint32_t)DataB);
    DMA1_Transport(7);                             //开始搬运
    for(uint8_t i =0; i < 7; i++)
    {
        printf("DataB[%d] = %d ",i,DataB[i]);     //将数组DataB打印出来
    }
    printf("\r\n");
    
	while(1)
	{ 
        Delay_ms(500);
        for(uint8_t i =0; i < 7; i++)             //让DataA数组值+1
        {
            DataA[i]++;
        }
        DMA1_Transport(7);                         //开始搬运
        
        for(uint8_t i =0; i < 7; i++)
        {
            printf("DataB[%d] = %d ",i,DataB[i]); //将数组DataB打印出来
        }
        Delay_ms(1000);
	}
}

在这里插入图片描述

4.2、DMA中断方式

①MyDMA.c文件的代码如下:

#include "MyDMA.h"
#include "UART.h"

uint16_t My_Size = 0;
uint8_t Count = 1;
/**
 * DMA1的初始化
 * ADDrA:外设站点的起始地址
 * ADDrB:目的站点的起始地址
 */
void DMA1_Init(uint32_t ADDrA,uint32_t ADDrB)
{
    My_Size = Size;
    /* 1、使能DMA1的时钟 */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    /* 2、配置DMA1的通道1 */
    DMA_InitTypeDef DMA_InitStruct;
	DMA_InitStruct.DMA_PeripheralBaseAddr = ADDrA;                      //外设站点的起始地址
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//数据宽度,8位
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;        //外设站点地址是否自增,这里选择自增
	
	DMA_InitStruct.DMA_MemoryBaseAddr = ADDrB;                          //内存站点起始地址
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;        //数据宽度,8位
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;                //目的站点地址是否自增,这里选择自增
	
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;                     //搬运方向的选择:外设站点---->内存站点:DMA_DIR_PeripheralSRC
	DMA_InitStruct.DMA_BufferSize = 0;                                  //传输计数器的大小,代表搬运数据的个数
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;                          //是否自动重装,这里选择不自动重装
	DMA_InitStruct.DMA_M2M = DMA_M2M_Enable;                            //是否软件触发,这里选择是
	DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;                  //优先级,这里选择中等
	DMA_Init(DMA1_Channel1,&DMA_InitStruct);                            //配置DMA1的通道1
	
    /* 3、配置DMA中断和NVIC */
    DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);                     //使能DMA1搬运完成中断
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
    NVIC_InitTypeDef NVIC_InitStruct;
    NVIC_InitStruct.NVIC_IRQChannel = DMA1_Channel1_IRQn;
    NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStruct);
    
    DMA_Cmd(DMA1_Channel1,DISABLE);                                     //先失能DMA1的通道1
}

/**
 * DMA1通道1开启搬运函数
 */
void DMA1_Transport(uint16_t DataNumber)
{
    /* 1、失能DMA1 */
    DMA_Cmd(DMA1_Channel1,DISABLE); 
    
    /* 2、先设置传输计数器的计数值 */
    DMA_SetCurrDataCounter(DMA1_Channel1, DataNumber);
    
    /* 3、使能DMA1 */
    DMA_Cmd(DMA1_Channel1,ENABLE);   
}
   
/**
 * DMA1通道1的中断服务函数
 */
void DMA1_Channel1_IRQHandler(void)
{
    if(DMA_GetFlagStatus(DMA1_FLAG_TC1))                //获取通道1搬运完成标志位
    {
        DMA_ClearFlag(DMA1_FLAG_TC1);                   //清除标志位
        Count = DMA_GetCurrDataCounter(DMA1_Channel1);  //获取传输计数器的值,若Count置0代表搬运完成
    }
}

②MyDMA.h文件的代码如下:

#ifndef __MyDMA_H
#define __MyDMA_H
#include "stm32f10x.h"

extern uint8_t Count;
void DMA1_Init(uint32_t ADDrA,uint32_t ADDrB);
void DMA1_Transport(uint16_t DataNumber);

#endif

③主函数main.c文件的代码如下:

/*
	存储器------>存储器(数据的拷贝)DMA的使用
*/
#include "stm32f10x.h"                 
#include "UART.h"
#include "MyDMA.h"
#include "Delay.h"

int main(void)
{
    uint8_t DataA[7] = {1,2,3,4,5,6,7};
    uint8_t DataB[7];
    UART1_Init();
    printf("DMA搬运:\r\n");
    DMA1_Init((uint32_t)DataA, (uint32_t)DataB;
    DMA1_Transport(7); //开始搬运
    if(!Count)
    {
        for(uint8_t i =0; i < 7; i++)
        {
            printf("DataB[%d] = %d ",i,DataB[i]);       //将数组DataB打印出来
        } 
    }
    printf("\r\n");
    
	while(1)
	{ 
        for(uint8_t i =0; i < 7; i++)
        {
            DataA[i]++;
        } 
        DMA1_Transport(7);                               //再次启动搬运
        
        if(!Count)
        {
            for(uint8_t i =0; i < 7; i++)
            {
                printf("DataB[%d] = %d ",i,DataB[i]);   //将数组DataB打印出来
            } 
        }
        printf("\r\n");
        Delay_ms(1000);
	}
}

在这里插入图片描述

4.3、ADC扫描模式(多通道)+DMA

在这里插入图片描述在这里插入图片描述如上图所示:ADC1的请求通道是DMA1的通道1。

4.3.1、ADC单次扫描模式+DMA不自动重装模式

①ADC.c文件的代码如下:

#include "ADC.h"

/**
 * ADC1初始化函数
 */
void ADC1_Init(void)
{
    /* 1、使能GPIO和ADC时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);  //使能ADC1时钟
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);//对ADC时钟源分频

    /* 2、对PA0(ADC1的通道0引脚)进行引脚配置 */
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;    //PA0,PA1,PA2,PA3作为模拟通道输入引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* 3、配置ADC1工作模式 */
    ADC_InitTypeDef ADC_InitStructure;
    ADC_DeInit(ADC1);                                                       //将外设ADC1的全部寄存器重设为缺省值
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;                      //ADC1和ADC2工作在独立模式
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;                            //扫描(多通道)模式
    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;                     //非连续模式
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;     //转换由软件而不是外部触发启动
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;                  //ADC1数据右对齐
    ADC_InitStructure.ADC_NbrOfChannel = 4;                                 //转换的ADC通道的数目,有PA0,PA1,PA2,PA3
    ADC_Init(ADC1, &ADC_InitStructure);                                     //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
    
    /* 4、配置规则组*/
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //ADC1,ADC通道,盒子序列1,采样时间为55.5周期
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); //ADC1,ADC通道,盒子序列2,采样时间为55.5周期
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5); //ADC1,ADC通道,盒子序列3,采样时间为55.5周期
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5); //ADC1,ADC通道,盒子序列4,采样时间为55.5周期
    
    ADC_DMACmd(ADC1 ,ENABLE);   //开启ADC1硬件触发源,当一个通道转运完成后,就会自动请求DMA
    ADC_Cmd(ADC1, ENABLE);      //使能ADC1


    /* 5、ADC校准 */
    ADC_ResetCalibration(ADC1);                     //使能复位校准
    while (ADC_GetResetCalibrationStatus(ADC1));    //等待复位校准结束
    ADC_StartCalibration(ADC1);                     //开启AD校准
    while (ADC_GetCalibrationStatus(ADC1));         //等待校准结束

    /* 6、软件触发 */
//    ADC_SoftwareStartConvCmd(ADC1, ENABLE);       //启动ADC转换

}

/**
 * 获取ADC中的结果寄存器的值
 */
//uint16_t Get_ADCResult(void)
//{
//    while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);  //等待转换完成标志位置1
//    return ADC_GetConversionValue(ADC1);                    //获取ADCx->DR里面的数据,ECO自动清除
//                                                            //【注意】返回的数据是12位的二进制数
//}

②MyDMA.c文件的代码如下:

#include "MyDMA.h"

uint16_t DataA[4];//定义数组用来保存DMA搬运ADC转换的数据
/**
 * DMA1通道1搬运ADC的初始化
 */
void DMA1_Init(void)
{
    /* 1、使能DMA1的时钟 */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    /* 2、配置DMA1的通道1 */
    DMA_InitTypeDef DMA_InitStruct;
	DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR);          //外设站点的起始地址:ADC的数据寄存器地址
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//数据宽度,16位
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;           //外设站点地址是否自增,这里选择不自增
                                                                            //因为是将DR数据寄存器里面的数据挪出来,始终是DR寄存器
	
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)DataA;                    //内存站点起始地址
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;        //数据宽度,16位
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;                    //目的站点地址是否自增,这里选择自增
	
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;                     //搬运方向的选择:外设站点---->内存站点:DMA_DIR_PeripheralSRC
	DMA_InitStruct.DMA_BufferSize = 0;                                  //传输计数器的大小,代表搬运数据的个数,4个通道的数据
	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;                          //是否自动重装,这里选择不自动重装,搬运完成后手动给他重装
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;                           //是否软件触发,这里选择又硬件触发
	DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;                  //优先级,这里选择中等
	DMA_Init(DMA1_Channel1,&DMA_InitStruct);                            //配置DMA1的通道1
	
//	DMA_Cmd(DMA1_Channel1,ENABLE);                                      //使能DMA1的通道1
	DMA_Cmd(DMA1_Channel1,DISABLE);                                     //先失能DMA1的通道1
}
   
/**
 * DMA1通道1开启搬运函数
 */
void ADC_DMA1_Transport(void)
{
    /* 1、失能DMA1 */
    DMA_Cmd(DMA1_Channel1,DISABLE); 
    
    /* 2、先设置传输计数器的计数值 */
    DMA_SetCurrDataCounter(DMA1_Channel1, 4);
    
    /* 3、使能DMA1 */
    DMA_Cmd(DMA1_Channel1,ENABLE);

    ADC_SoftwareStartConvCmd(ADC1, ENABLE);     //启动ADC转换
   
    /* 4、等待搬运完成 */
    while(!DMA_GetFlagStatus(DMA1_FLAG_TC1));   //等待DMA1通道1全部搬运完成
	DMA_ClearFlag(DMA1_FLAG_TC1);               //清除标志位 
}

③MyDMA.h文件的代码如下:

#ifndef __MyDMA_H
#define __MyDMA_H
#include "stm32f10x.h"

extern uint16_t DataA[];
void DMA1_Init(void);
void ADC_DMA1_Transport(void);

#endif

④主函数main.c文件的代码如下:

#include "stm32f10x.h"                 
#include "Delay.h"
#include "UART.h"
#include "MyDMA.h"
#include "ADC.h"

/* ADC多通道扫描非连续模式 + DMA非自动重装 */
int main(void)
{
    float Value0,Value1,Value2,Value3;
    UART1_Init();
    printf("ADC转换 DMA搬运:\r\n");
    ADC1_Init();
    DMA1_Init();

	while(1)
	{ 
        ADC_DMA1_Transport();   //启动ADC转换,并开始DMA开始搬运
        
        Value0 = 3.3 / 4095 * DataA[0];//对通道0的数据进行计算
        printf("通道0的电压为:%0.2f\r\n",Value0);
        
        Value1 = 3.3 / 4095 * DataA[1];//对通道1的数据进行计算
        printf("通道1的电压为:%0.2f\r\n",Value1);
        
        Value2 = 3.3 / 4095 * DataA[2];//对通道2的数据进行计算
        printf("通道2的电压为:%0.2f\r\n",Value2);
        
        Value3 = 3.3 / 4095 * DataA[3];//对通道3的数据进行计算
        printf("通道3的电压为:%0.2f\r\n",Value3);
        Delay_ms(1000);
	}
}

4.3.2、ADC连续扫描+DMA自动重装模式

①ADC.c文件的代码如下:

#include "ADC.h"

/**
 * ADC1初始化函数
 */
void ADC1_Init(void)
{
    /* 1、使能GPIO和ADC时钟 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIOA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);  //使能ADC1时钟
    RCC_ADCCLKConfig(RCC_PCLK2_Div6);//对ADC时钟源分频

    /* 2、对PA0(ADC1的通道0引脚)进行引脚配置 */
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;    //PA0,PA1,PA2,PA3作为模拟通道输入引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入引脚
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    /* 3、配置ADC1工作模式 */
    ADC_InitTypeDef ADC_InitStructure;
    ADC_DeInit(ADC1);                                                       //将外设ADC1的全部寄存器重设为缺省值
    ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;                      //ADC1和ADC2工作在独立模式
    ADC_InitStructure.ADC_ScanConvMode = ENABLE;                            //扫描(多通道)模式
//    ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;                   //非连续模式
    ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;                      //连续模式
    ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;     //转换由软件而不是外部触发启动
    ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;                  //ADC1数据右对齐
    ADC_InitStructure.ADC_NbrOfChannel = 4;                                 //转换的ADC通道的数目,有PA0,PA1,PA2,PA3
    ADC_Init(ADC1, &ADC_InitStructure);                                     //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器
    
    /* 4、配置规则组*/
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); //ADC1,ADC通道,盒子序列1,采样时间为55.5周期
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); //ADC1,ADC通道,盒子序列2,采样时间为55.5周期
    ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5); //ADC1,ADC通道,盒子序列3,采样时间为55.5周期
    ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5); //ADC1,ADC通道,盒子序列4,采样时间为55.5周期
    
    ADC_DMACmd(ADC1 ,ENABLE);   //开启ADC1硬件触发源,当一个通道转运完成后,就会自动请求DMA
    ADC_Cmd(ADC1, ENABLE);      //使能ADC1


    /* 5、ADC校准 */
    ADC_ResetCalibration(ADC1);                     //使能复位校准
    while (ADC_GetResetCalibrationStatus(ADC1));    //等待复位校准结束
    ADC_StartCalibration(ADC1);                     //开启AD校准
    while (ADC_GetCalibrationStatus(ADC1));         //等待校准结束

    /* 6、软件触发 */
//    ADC_SoftwareStartConvCmd(ADC1, ENABLE);       //启动ADC转换

}

/**
 * 获取ADC中的结果寄存器的值
 */
//uint16_t Get_ADCResult(void)
//{
//    while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);  //等待转换完成标志位置1
//    return ADC_GetConversionValue(ADC1);                    //获取ADCx->DR里面的数据,ECO自动清除
//                                                            //【注意】返回的数据是12位的二进制数
//}

②MyDMA.c文件的代码如下:

#include "MyDMA.h"

uint16_t DataA[4];//定义数组用来保存DMA搬运ADC转换的数据
/**
 * DMA1通道1搬运ADC的初始化
 */
void DMA1_Init(void)
{
    /* 1、使能DMA1的时钟 */
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

    /* 2、配置DMA1的通道1 */
    DMA_InitTypeDef DMA_InitStruct;
	DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&(ADC1->DR);          //外设站点的起始地址:ADC的数据寄存器地址
	DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//数据宽度,16位
	DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;           //外设站点地址是否自增,这里选择不自增
                                                                            //因为是将DR数据寄存器里面的数据挪出来,始终是DR寄存器
	
	DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)DataA;                    //内存站点起始地址
	DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;        //数据宽度,16位
	DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;                    //目的站点地址是否自增,这里选择自增
	
	DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;                     //搬运方向的选择:外设站点---->内存站点:DMA_DIR_PeripheralSRC
	DMA_InitStruct.DMA_BufferSize = 4;                                  //传输计数器的大小,代表搬运数据的个数,4个通道的数据
//	DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;                          //是否自动重装,这里选择不自动重装,搬运完成后手动给他重装
    DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;                        //是否自动重装,这里选择自动重装,因为ADC为连续模式
	DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;                           //是否软件触发,这里选择又硬件触发
	DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;                  //优先级,这里选择中等
	DMA_Init(DMA1_Channel1,&DMA_InitStruct);                            //配置DMA1的通道1
	
	DMA_Cmd(DMA1_Channel1,ENABLE);                                      //使能DMA1的通道1
    
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);                             //启动ADC转换
}
   
/**
 * DMA1通道1开启搬运函数
 */
//void ADC_DMA1_Transport(void)
//{
//    /* 1、失能DMA1 */
//    DMA_Cmd(DMA1_Channel1,DISABLE); 
//    
//    /* 2、先设置传输计数器的计数值 */
//    DMA_SetCurrDataCounter(DMA1_Channel1, 4);
//    
//    /* 3、使能DMA1 */
//    DMA_Cmd(DMA1_Channel1,ENABLE);

//    ADC_SoftwareStartConvCmd(ADC1, ENABLE);     //启动ADC转换
//   
//    /* 4、等待搬运完成 */
//    while(!DMA_GetFlagStatus(DMA1_FLAG_TC1));   //等待搬运完成
//	DMA_ClearFlag(DMA1_FLAG_TC1);               //清除标志位 
//}

③主函数main.c文件的代码:

#include "stm32f10x.h"                 
#include "Delay.h"
#include "UART.h"
#include "MyDMA.h"
#include "ADC.h"

/* ADC多通道扫描连续模式 + DMA自动重装 */
int main(void)
{
    float Value0,Value1,Value2,Value3;
    UART1_Init();
    printf("ADC转换 DMA搬运:\r\n");
    ADC1_Init();
    DMA1_Init();

	while(1)
	{ 
        Value0 = 3.3 / 4095 * DataA[0];//对通道0的数据进行计算
        printf("通道0的电压为:%0.2f\r\n",Value0);
        
        Value1 = 3.3 / 4095 * DataA[1];//对通道1的数据进行计算
        printf("通道1的电压为:%0.2f\r\n",Value1);
        
        Value2 = 3.3 / 4095 * DataA[2];//对通道2的数据进行计算
        printf("通道2的电压为:%0.2f\r\n",Value2);
        
        Value3 = 3.3 / 4095 * DataA[3];//对通道3的数据进行计算
        printf("通道3的电压为:%0.2f\r\n",Value3);
        Delay_ms(1000);
	}
}

综上:①DMA的非自动重装模式常常和手动设置传输计数器函数搭配使用,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值