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的非自动重装模式常常和手动设置传输计数器函数搭配使用,