DMA数据转运&DMA+AD多通道
DMA
- 什么是DMA?
DMA(Direct Memory Access)直接存储器存取,DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源.DMA有12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道),每个通道都支持软件触发和特定的硬件触发,STM32F103C8T6 DMA资源:DMA1(7个通道). - 存储器映象
Flash中的代码是只能读不能写的,数组以及代码一般写在SRAM中.

- DMA框图
-
DMA基本结构

-
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多通道
- 接线图如下:

- 复制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);
}
}

6959

被折叠的 条评论
为什么被折叠?



