DMA简介
1.DMA(Direct Memory Access)直接存储器存取
2.DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
3.12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)
4.每个通道都支持软件触发和特定的硬件触发
STM32F103C8T6 DMA资源:DMA1(7个通道)
存储器映像
DMA框图
可以看到DMA挂载在AHB总线上
DMA1有7个通道
DMA2有5个通道
仲裁器:毕竟是多通道共用一条总线,所以的通道分时复用这条总线,为了防止冲突,仲裁器根据优先级来决定谁先用,谁后用;
在总线矩阵那里也有一个仲裁器,如果CPU和DMA都要访问同一个目标时;DMA就会暂停CPU的访问,以防止冲突,不过总线仲裁器,任然会保证CPU得到一半的总线带宽,使得CPU正常工作
DMA基本结构
DMA请求
注意硬件请求的DMA通道并不是任意的,已经分配好通道了
例如:ADC1请求 通道1
数据宽度与对齐
简单的理解就是:
源地址>目标地址:高位去掉保留低位
源地址<目标地址:高位补零
注意大小端
数据转运+DMA
ADC扫描模式+DMA
通过以上了解,接下来结合代码进一步掌握
接下来看DMA.H的相关库函数
void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx);
//恢复缺省值
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
//DMA初始化
void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);
//结构体初始化
void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
//使能,开启DMA
void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
//DMA中断输出使能
void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
//设置DMA当前数据寄存器(给传输计数器写数据)
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
//获取当前数据寄存器(返回传输数据寄存器的值)
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
//获取标志位状态
void DMA_ClearFlag(uint32_t DMAy_FLAG);
//清除标志位状态
ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
//获取中断标志位状态
void DMA_ClearITPendingBit(uint32_t DMAy_IT);
//清除中断标志位状态
DMA转运配置驱动代码
储存器和储存器之间转换
#include "MyDMA.h"
uint16_t MyDMA_Size;
/**
* @brief DMA1_Channel1初始化,存储器到存储器转运
* @param
* @retval 3个条件才能让DMA工作
* @attention 1.传输计数器大于0 2.触发源有触发信号 3.DMA使能
*/
void MyDMA_Init(uint32_t AddrA, uint32_t AddrB, uint16_t Size)
{
MyDMA_Size = Size;
/***************************开启时钟********************************/
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/***************************定义结构体变量********************************/
DMA_InitTypeDef DMA_InitStructure;
/***************************DMA参数配置********************************/
DMA_InitStructure.DMA_PeripheralBaseAddr = AddrA;//外设站点的起始地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//数据宽度,字节
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;//是否自增
DMA_InitStructure.DMA_MemoryBaseAddr = AddrB;//存储器站点的起始地址
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//数据宽度,字节
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//是否自增
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//传输方向:外设站点到存储器
DMA_InitStructure.DMA_BufferSize = Size;//缓存区大小;其实就是传输计数器
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//传输模式;其实就是是否使用自动重装
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;//是否是存储器到存储器;其实就是是否是硬件触发,还是软件触发
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//优先级
DMA_Init(DMA1_Channel1, &DMA_InitStructure);//初始化
DMA_Cmd(DMA1_Channel1,ENABLE);//使能
}
/**
* @brief DMA传输函数
* @param 调用一次传输一次
* @retval
* @attention 给DMA赋值要先让其**失能**
*/
void MyDMA_Transfer(void)
{
DMA_Cmd(DMA1_Channel1, DISABLE);//失能
DMA_SetCurrDataCounter(DMA1_Channel1, MyDMA_Size);//给传输计数器赋值
DMA_Cmd(DMA1_Channel1, ENABLE);//使能
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//等待转运完成标志位置1
DMA_ClearFlag(DMA1_FLAG_TC1);//清除转运完成标志位
}
外设和储存器之间转换
ADC1单次扫描模式,DMA单次转运模式
/**
* @brief ADC1单次扫描模式,DMA单次转运模式
* @param
* @retval
* @attention
*/
#if 0
uint16_t AD_Value[5];//存储的位置
void AD_DMA_Init(void)
{
/*****************************开启时钟******************************/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/*****************************结构体变量******************************/
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
/****************************ADCCLK分频器*******************************/
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //6分频;72MHZ/6=12MHZ
/*****************************引脚模式配置******************************/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*****************************规则组输入通道配置放到读取函数里******************************/
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_RegularChannelConfig(ADC1, ADC_Channel_8, 5, ADC_SampleTime_55Cycles5); //规则组输入通道
/****************************ADC结构体初始化*******************************/
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //工作模式:独立模式
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐:右对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发转换选择:内部软件触发
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; //单次转换 //需要不断触发
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //扫描转换模式
ADC_InitStructure.ADC_NbrOfChannel = 5; //通道数目为1~16之间
ADC_Init(ADC1, &ADC_InitStructure);
ADC_DMACmd(ADC1,ENABLE); //ADC——DMA使能
ADC_Cmd(ADC1, ENABLE); //开启ADC电源
/***************************DMA参数配置********************************/
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//外设站点的起始地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//数据宽度,半字,取DR寄存器的低16位
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//是否自增,不自增
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;//存储器站点的起始地址
DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord;//数据宽度,字节
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//是否自增,自增
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//传输方向:外设站点到存储器
DMA_InitStructure.DMA_BufferSize = 5;//缓存区大小;其实就是传输计数器
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//传输模式;其实就是是否使用自动重装
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//是否是存储器到存储器;其实就是是否是硬件触发,还是软件触发
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//优先级
DMA_Init(DMA1_Channel1, &DMA_InitStructure);//初始化
DMA_Cmd(DMA1_Channel1,ENABLE);//使能
/*****************************ADC进行校准******************************/
ADC_ResetCalibration(ADC1); //复位校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET); //判断校准是否完成(准备下次校准)
ADC_StartCalibration(ADC1); //启动校准
while (ADC_GetCalibrationStatus(ADC1) == SET); //获取校准状态
}
void AD_DMA_GetValue(void)
{
DMA_Cmd(DMA1_Channel1, DISABLE);//失能
DMA_SetCurrDataCounter(DMA1_Channel1, 5);//给传输计数器赋值
DMA_Cmd(DMA1_Channel1, ENABLE);//使能
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //开启软件触发转换
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//等待转运完成标志位置1
DMA_ClearFlag(DMA1_FLAG_TC1);//清除转运完成标志位
}
#endif
ADC1连续扫描模式,DMA连续循环转运模式
/**
* @brief ADC1连续扫描模式,DMA连续循环转运模式
* @param
* @retval
* @attention
*/
#if 1
uint16_t AD_Value[5];//存储的位置
void AD_DMA_Init(void)
{
/*****************************开启时钟******************************/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
/*****************************结构体变量******************************/
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
/****************************ADCCLK分频器*******************************/
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //6分频;72MHZ/6=12MHZ
/*****************************引脚模式配置******************************/
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; //模拟输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*****************************规则组输入通道配置放到读取函数里******************************/
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_RegularChannelConfig(ADC1, ADC_Channel_8, 5, ADC_SampleTime_55Cycles5); //规则组输入通道
/****************************ADC结构体初始化*******************************/
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; //工作模式:独立模式
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; //数据对齐:右对齐
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; //外部触发转换选择:内部软件触发
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; //连续转换 //不需要不断触发
ADC_InitStructure.ADC_ScanConvMode = ENABLE; //扫描转换模式
ADC_InitStructure.ADC_NbrOfChannel = 5; //通道数目为1~16之间
ADC_Init(ADC1, &ADC_InitStructure);
ADC_DMACmd(ADC1,ENABLE); //ADC——DMA使能
ADC_Cmd(ADC1, ENABLE); //开启ADC电源
/***************************DMA参数配置********************************/
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//外设站点的起始地址
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//数据宽度,半字,取DR寄存器的低16位
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//是否自增,不自增
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;//存储器站点的起始地址
DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_HalfWord;//数据宽度,字节
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//是否自增,自增
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;//传输方向:外设站点到存储器
DMA_InitStructure.DMA_BufferSize = 5;//缓存区大小;其实就是传输计数器
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;//传输模式;循环模式;使用自动重装
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//是否是存储器到存储器;其实就是是否是硬件触发,还是软件触发
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//优先级
DMA_Init(DMA1_Channel1, &DMA_InitStructure);//初始化
DMA_Cmd(DMA1_Channel1,ENABLE);//使能
/*****************************ADC进行校准******************************/
ADC_ResetCalibration(ADC1); //复位校准
while (ADC_GetResetCalibrationStatus(ADC1) == SET); //判断校准是否完成(准备下次校准)
ADC_StartCalibration(ADC1); //启动校准
while (ADC_GetCalibrationStatus(ADC1) == SET); //获取校准状态
/***************************直接放在初始化最后一行********************************/
ADC_SoftwareStartConvCmd(ADC1, ENABLE); //开启软件触发转换
}
/*******直接不需要了
void AD_DMA_GetValue(void)
{
DMA_Cmd(DMA1_Channel1, DISABLE);//失能
DMA_SetCurrDataCounter(DMA1_Channel1, 5);//给传输计数器赋值
DMA_Cmd(DMA1_Channel1, ENABLE);//使能
while (DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//等待转运完成标志位置1
DMA_ClearFlag(DMA1_FLAG_TC1);//清除转运完成标志位
}*/
#endif