1、原理

软件触发适用于从存储器到存储器的数据转运(通道可以任意选择)。如把Flash里的数据,转运到SRAM里
硬件触发适用于从外设到存储器的数据转运,外设的数据需要处理完再转运。如转运ADC的数据,等到ADC每个通道AD转换完成后硬件触发一次DMA,之后DMA再转运

特定的硬件触发是指每个DMA通道的硬件触发源是不同的,想要使用特定的硬件触发源就要使用其对应的通道
内部结构分析

总线矩阵的左端为主动单元 ,拥有存储器的访问权,如DCode(专门访问Flash)、系统总线、 DMA总线;右端为被动单元,它们的存储器只能被左边的主动单元读写
DMA模块内部存在多个通道,可以分别设置它们转运数据的源地址和目的地址 仲裁器用于多个通道产生冲突时,根据通道的优先级决定顺序。多个通道虽然可以独立转运数据,但是所有通道只能分时复用一条DMA总线
在总线矩阵内部也存在一个仲裁器,如果DMA和CPU同时访问同一个目标,CPU的访问会被暂停(CPU仍能得到一半的总线带宽)
AHB从设备,也是DMA自身的寄存器,用于DMA配置,DMA既是总线矩阵上的主动单元,也是AHB总线上的被动单元,CPU通过AHB总线就可以对DMA进行配置了
DMA请求,DMA硬件触发源,如ADC转换完成、串口接收到数据,就会通过这条线路来向DMA发送硬件触发信号
基本结构

要进行存储器到存储器的数据转运,将其中一个存储器的地址放在外设站点。
传输计数器用于指定总共需要转运几次(DMA关闭时),自减计数器,每次转运1次计数器的数就会减1。
自动重装器,设置循环模式,传输计数器减到0之后可以选择恢复到最初的值,进行下一轮工作
M2M ,存储器到存储器,M2M位为1时,DMA选择软件触发。与ADC软件触发不同,以最快的速度,连续不断地触发DMA,将传输计数器清零,完成这一轮转换。软件触发 适合从存储器到存储器的转运,软件启动,不需要特定的时机。M2M位为0时,DMA选择硬件触发,一般与外设有关,如ADC转换完成、串口接收数据、定时器时间到,同时还要在对应外设调用XXX_DMA_Cmd。
DMA进行转运条件:
1、开关控制,DMA_Cmd使能
2、传输计数器大于0
3、有触发信号
M2M软件触发和自动重装器循环模式不能一起使用,不然传输计数器会不断地清零再恢复,DMA停不下来。
2、代码
不重装,软件触发
cpp
uint16_t MyDmasize=0;
/*
@para Addr1:外设地址
@para Addr2:存储器地址
@para size:转运数据的个数
*/
void MyDMA_Init(uint32_t Addr1, uint32_t Addr2, uint16_t size)
{
MyDmasize=size;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_InitTypeDef DMA_InitStruct;
//外设站点配置
//起始地址
DMA_InitStruct.DMA_PeripheralBaseAddr=Addr1;
//数据宽度
DMA_InitStruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;
//是否自增
DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Enable;
//存储器站点配置
DMA_InitStruct.DMA_MemoryBaseAddr=Addr2;
DMA_InitStruct.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;
DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable;
//传输方向
DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralSRC;
//传输计数器
DMA_InitStruct.DMA_BufferSize=size;
//配置传输计数器是否要重装
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);
DMA_Cmd(DMA1_Channel1, ENABLE);
}
//每次转换时调用一次
void DMA_Transfer()
{
//给传输计数器赋值时,DMA需要处于失能状态
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1, MyDmasize);
DMA_Cmd(DMA1_Channel1, ENABLE);
//等待数据转运完成
while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);
DMA_ClearFlag(DMA1_FLAG_TC1);
}
DMA+AD多通道,自动重装
cpp
#include "stm32f10x.h" // Device header
uint16_t AD_Value[4];
void Ad_Init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
//配置ADCCLK时钟分频,ADC的输入时钟不得超过14MHz
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
GPIO_InitTypeDef GPIO_InitStructure;
//初始化为模拟输入引脚
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_3|GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_1Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_1Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 3, ADC_SampleTime_1Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_6, 4, ADC_SampleTime_1Cycles5);
ADC_InitTypeDef ADC_InitStruct;
//独立模式 ADC1 和 ADC2 单独转换
ADC_InitStruct.ADC_Mode=ADC_Mode_Independent;
//选择数据对齐 左对齐、右对齐
ADC_InitStruct.ADC_DataAlign=ADC_DataAlign_Right;
//外部触发源选择, 这里使用软件触发,所以选择None
ADC_InitStruct.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;
//连续转换模式, 选择单次转换还是连续转换
ADC_InitStruct.ADC_ContinuousConvMode=ENABLE;
//扫描转换模式, 选择扫描模式还是非扫描模式
ADC_InitStruct.ADC_ScanConvMode=ENABLE;
//通道数目, 只有在扫描模式下才能使用多个通道
ADC_InitStruct.ADC_NbrOfChannel=4;
ADC_Init(ADC1, &ADC_InitStruct);
DMA_InitTypeDef DMA_InitStruct;
//外设站点配置
//起始地址
//设置地址为ADC1数据寄存器,别忘了取地址符&
DMA_InitStruct.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;
//数据宽度
//DR寄存器里低16位是ADC1数据存储的地方,所以DMA转运的数据宽度为16位
DMA_InitStruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;
//是否自增
//始终转运ADC1->DR上的数据
DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
//存储器站点配置
DMA_InitStruct.DMA_MemoryBaseAddr=(uint32_t)AD_Value;
DMA_InitStruct.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;
DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable;
//传输方向
DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralSRC;
//传输计数器
DMA_InitStruct.DMA_BufferSize=4;
//配置传输计数器是否要重装
DMA_InitStruct.DMA_Mode=DMA_Mode_Circular;
//软件触发还是硬件触发
DMA_InitStruct.DMA_M2M=DMA_M2M_Disable;
DMA_InitStruct.DMA_Priority=DMA_Priority_Medium;
DMA_Init(DMA1_Channel1, &DMA_InitStruct);
DMA_Cmd(DMA1_Channel1, ENABLE);
//开启ADC的DMA输出
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
//手册中建议在每次上电后执行一次校准
//复位校准
ADC_ResetCalibration(ADC1);
//等待复位校准完成, 复位校准完成后为 RESET
while(ADC_GetResetCalibrationStatus(ADC1)==SET);
//开始校准
ADC_StartCalibration(ADC1);
//等待校准完成
while(ADC_GetCalibrationStatus(ADC1)==SET);
//连续转换仅需要在最开始触发一次就可以,
//软件触发转换函数可以挪到初始化最后,不需要每次取值都使用
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}