本文的大部分内容来自B站up主 江协科技, 此文只供本人学习记录用途, 侵删
一、DMA
- DMA(Direct Memory Access)直接存储器存取
- DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
- DMA完成的功能就是把外设(可以是任何地址)里的东西搬到另一个你想要的地址
- 12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)每个通道都支持软件触发和特定的硬件触发
- STM32F103C8T6 DMA资源:DMA1(7个通道)
二、存储器映像
DMA的作用就是把指定地址里的数据搬运到另一处地址
可以把他当成赋值, 但这次赋值可以软件触发, 也可以硬件触发, 当某个标志位被置位时自动运行的赋值, 这就大大节省了软件资源(不用在事件结束后进中断)
三、内存搬运示例
配置框图
以下代码实现了存储器到存储器的搬运, 使用软件手动搬运
void MyDMA_Init(uint8_t* arr1,uint8_t* arr2,uint8_t Size){
RCC_AHBPeriphClockCmd(RCC_AHBENR_DMA1EN,ENABLE);//开启时钟
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)arr2; //目标地址
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)arr1; //外设地址
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte; //目标数据宽度
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte; //外设目标宽度
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//指定目标地址是否自增
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Enable;//指定外设地址是否自增
DMA_InitStructure.DMA_BufferSize=Size; //传输多少个数据
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//设置外设站点为源端(也可以是目的地)
DMA_InitStructure.DMA_M2M=DMA_M2M_Enable; //使用软件触发(存储器到存储器)
//指定传输计数器不自动重装(软件触发不能自动重装)
DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority=DMA_Priority_High;//指定转运优先级
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1,DISABLE);
}
//手动搬运
void MyDMA_Transfer(uint8_t Size){
DMA_Cmd(DMA1_Channel1,DISABLE);//使能DMA
DMA_SetCurrDataCounter(DMA1_Channel1, Size); //重新装填DMA
DMA_Cmd(DMA1_Channel1,ENABLE);//使能DMA
//等待转运完成
while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);
DMA_ClearFlag(DMA1_FLAG_TC1);
}
四、ADC+DMA多通道
//自己开一个数组放数据
void AD_Init(uint16_t *ADvelue){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBENR_DMA1EN,ENABLE);//开启时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); //设置ADC时钟分频
GPIO_InitTypeDef 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;
//PA0(ADC12_IN0) PA1(ADC12_IN1) PA2(ADC12_IN2) PA3(ADC12_IN3)模拟输入
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规则组通道配置
ADC1 没啥好说
ADC_Channel_0 选择通道0
1 代表这个通道在规则组的序列1
ADC_SampleTime_55Cycles5 采样时间为55.5个CLK周期
*/
ADC_InitTypeDef ADC_InitStructure;
ADC_StructInit(&ADC_InitStructure);//先初始化用不到的参数
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=4;
ADC_Init(ADC1, &ADC_InitStructure);
/*
ADC_Mode_Independent: ADC为独立模式, 其他参数都为双ADC
ADC_DataAlign_Right: 数据右对齐
ADC_ExternalTrigConv_None: 关闭外部触发, 这里用软件触发
ADC_ContinuousConvMode = ENABLE 开启连续模式
ADC_ScanConvMode = ENABLE 开启扫描模式
ADC_NbrOfChannel=4 规则组通道的数目
*/
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)ADvelue;//目标数组的地址
//外设地址(ADC的数据寄存器)
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;
//数据宽度:半字(16Bit)
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;
//目标地址自增, 外设地址固定
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_BufferSize=4; //搬运次数: 4(4个模拟输入)
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//外设地址为源地址
DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//关闭软件开启
DMA_InitStructure.DMA_Mode=DMA_Mode_Circular;//开启循环模式
DMA_InitStructure.DMA_Priority=DMA_Priority_High;//设置搬运优先级
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1,ENABLE);//使能DMA
ADC_DMACmd(ADC1,ENABLE);//使能DMA搬运ADC
ADC_Cmd(ADC1,ENABLE);//使能ADC
//启动校准
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1)==SET);
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1)==SET);
//手动开启ADC搬运
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}