DMA数据搬运
- 1、DMA的简介
- 2、STM32中的DMA结构
- 3、案列
- 3.1、将数组DataA中的数据搬运到DataB中
- 3.2、ADC扫描模式+DMA
1、DMA的简介
DMA是直接存储器存取,它可以提供外设寄存器和存储器,存储器与存储器之间的高速数据的传输,无需CPU的干预,这样节省了CPU的资源。简单来说DMA就是数据的搬运工。
STM32中的存储器:
DMA的3种搬运方式:
1.存储器------>存储器(数据的拷贝)
2.存储器------>外设(将某数据写入串口寄存器TDR)
3.外设--------->存储器(将串口接收寄存器RDR的数据搬运到内存,避免数据的覆盖)
2、STM32中的DMA结构
在单片机中12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)。每个通道都支持软件触发和特定的硬件触发。而在STM32F103C8T6中只有DMA1(7个通道),且挂载在AHB总线上面。
由于Flash存储是只读存储器,所以DMA转运的数据不能存储在Flash存储器里面,这是不允许的,所以只能存储在SRAM存储器里面。
-
DMA请求
DMA请求就是DMA触发,由如下图可知,每个通道的硬件请求都是特定的,比如:TIM2_CH1的请求不能通过DMA1的通道2对DMA进行触发。但是每个通道都有软件请求。所以每个通道都支持软件触发和特定的硬件触发。软件触发一般用在存储器------>存储器(数据的拷贝)。
-
DMA基本结构细节
由下图所示:外设寄存器和存储器里面有起始地址,数据宽度。是否自增。
1.起始地址:数据从哪里转运到哪里
2.数据宽度:每次转运的数据有多大(Byte(uint8_t)/HalfWord(uint16_t)/Word(uint32_t))
3.地址是否自增:第一次转运完成后,进行下一次转运时是否发生地址的偏移。
4.传输计数器:总共需要几次转运,是一个自减的计数器。为0就不转运了。
【注】传输计数器变为0后,自增的地址也会恢复到起始地址。
5.自动重装器:传输计数器减为0后,是否恢复初值,又开始转运。如果没有开启自动重装器。
6.M2M:触发控制,为1时就是软件触发,为0就是硬件触发。
【注】软件触发一般用于存储器到存储器的转运,触发一次,以最快的要求计数器清0。所以,软件触发不能和自动重装器同时用
7.DMA开始转运的3大调节:
①DMA开关必须关闭
②计数器必修大于0
③必须要有触发源
如果转运完成,计数器清0 。想要进行第二次转运,则想要关闭DMA,写入计数器次数,然后给触发源,开启DMA。
3、案列
3.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存储器中
数据拷贝的代码如下:
MyDMA.c文件的代码如下:
#include "stm32f10x.h" // Device header
uint8_t My_Size;
void MyDMA_Init(uint32_t ADDrA,uint32_t ADDrB,uint8_t Size)
{
My_Size = Size;
//1.开启时钟
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;//数据宽度
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);//DMA1的通道1
DMA_Cmd(DMA1_Channel1,DISABLE);
}
void My_DMA_TransFer(void)//DMA开启搬运的函数
{
DMA_Cmd(DMA1_Channel1,DISABLE);//关闭DMA
DMA_SetCurrDataCounter(DMA1_Channel1,My_Size);//写入传输计数器的值
DMA_Cmd(DMA1_Channel1,ENABLE);//开启DMA
while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//等待搬运完成
DMA_ClearFlag(DMA1_FLAG_TC1);//手动清除标志位
}
主程序文件的代码如下:
/*
存储器------>存储器(数据的拷贝)DMA的使用
*/
#include "stm32f10x.h"
#include "OLED.h"
#include "Delay.h"
#include "MyDMA.h"
uint8_t DataA[] = {01,02,03,04};
uint8_t DataB[] = {0,0,0,0};
int main(void)
{
OLED_Init();
OLED_Clear();
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);//显示DataA的地址
OLED_ShowHexNum(3,8,(uint32_t)DataB,8);//显示DataB的地址
// OLED_ShowNum(2,1,DataA[0],2);
// OLED_ShowNum(2,4,DataA[1],2);
// OLED_ShowNum(2,7,DataA[2],2);
// OLED_ShowNum(2,10,DataA[3],2);
// OLED_ShowNum(4,1,DataB[0],2);
// OLED_ShowNum(4,4,DataB[1],2);
// OLED_ShowNum(4,7,DataB[2],2);
// OLED_ShowNum(4,10,DataB[3],2);
while(1)
{
OLED_ShowNum(2,1,DataA[0],2);
OLED_ShowNum(2,4,DataA[1],2);
OLED_ShowNum(2,7,DataA[2],2);
OLED_ShowNum(2,10,DataA[3],2);
Delay_ms(1000);
My_DMA_TransFer();//开始搬运
OLED_ShowNum(4,1,DataB[0],2);
OLED_ShowNum(4,4,DataB[1],2);
OLED_ShowNum(4,7,DataB[2],2);
OLED_ShowNum(4,10,DataB[3],2);
DataA[0]++;
DataA[1]++;
DataA[2]++;
DataA[3]++;
Delay_ms(1000);
}
}
3.2、ADC扫描模式+DMA
ADC单次扫描+DMA不自动重装模式
ADC.c文件的代码如下:
代码如下:
#include "stm32f10x.h" // Device header
uint16_t AD_Value[4];
void AD_Init(void)
{
//1.开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
//2.对ADC时钟进行分频
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72MHZ/6 = 12MHz
//3.对通道1(PA0)进行配置
GPIO_InitTypeDef GPIOInitStruct;
GPIOInitStruct.GPIO_Mode = GPIO_Mode_AIN;//模拟输入模式,ADC的专属模式
GPIOInitStruct.GPIO_Pin = GPIO_Pin_0 |GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
GPIO_Init(GPIOA,&GPIOInitStruct);
//4.对ADC规则组进行配置
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//ADC几,通道,序列,采样时间
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);
//5.初始化ADC 单次扫描模式
ADC_InitTypeDef ADC_InitStruct;
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;//工作模式:独立模式/双ADC模式,这里的独立模式
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//ADC触发选择,选择软件触发
ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;//连续/单次模式,这里选择单次
ADC_InitStruct.ADC_ScanConvMode = ENABLE;//扫描/非扫描,这里选择扫描
ADC_InitStruct.ADC_NbrOfChannel = 4;//在扫描模式下的盒子数目
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐,右对齐
ADC_Init(ADC1,&ADC_InitStruct);
//6.DMA1的通道1的初始化,硬件触发非自动重装
DMA_InitTypeDef DMA_InitStruct;
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//外设站点的起始地址,这里选择DR寄存器
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//数据宽度,16位
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//是否自增,这里选择不自增,
//因为是将DR数据寄存器里面的数据挪出来,始终是DR寄存器
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_Normal;//是否自动重装,这里选择不自动重装
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;//是否软件触发,这里选择硬件触发
DMA_InitStruct.DMA_Priority = DMA_Priority_Medium;//优先级
DMA_Init(DMA1_Channel1,&DMA_InitStruct);//DMA1的通道1
//6.开启ADC电源
ADC_Cmd(ADC1,ENABLE);
//7.校准
ADC_ResetCalibration(ADC1);//将校准复位,给CR2_RSTCAL置1,进行复位
while(ADC_GetResetCalibrationStatus(ADC1) == SET);//复位完成,硬件置0
ADC_StartCalibration(ADC1);//开始校准,给CR2_CAL置1
while(ADC_GetCalibrationStatus(ADC1) == SET);//校准完成,硬件置0
//8.开启搬运
ADC_DMACmd(ADC1 ,ENABLE);//开启ADC1硬件触发源,当一个通道转运完成后,就会自动请求DMA
DMA_Cmd(DMA1_Channel1,ENABLE);
}
void AD_GetValue(void)//ADC触发函数并数据搬运
{
DMA_Cmd(DMA1_Channel1,DISABLE);//关闭DMA
DMA_SetCurrDataCounter(DMA1_Channel1,4);//写入传输计数器的值
DMA_Cmd(DMA1_Channel1,ENABLE);//开启DMA
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发
while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//等待搬运完成
DMA_ClearFlag(DMA1_FLAG_TC1);//手动清除标志位
}
主程序文件的代码:
/*
ADC+DMA的使用
*/
#include "stm32f10x.h"
#include "OLED.h"
#include "Delay.h"
#include "ADC.h"
int main(void)
{
OLED_Init();
OLED_Clear();
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);//显示通道1采样到的数据
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);
}
}
ADC连续扫描+DMA自动重装模式
ADC.c文件的代码如下:
#include "stm32f10x.h" // Device header
uint16_t AD_Value[4];
void AD_Init(void)
{
//1.开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
//2.对ADC时钟进行分频
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//72MHZ/6 = 12MHz
//3.对通道1(PA0)进行配置
GPIO_InitTypeDef GPIOInitStruct;
GPIOInitStruct.GPIO_Mode = GPIO_Mode_AIN;//模拟输入模式,ADC的专属模式
GPIOInitStruct.GPIO_Pin = GPIO_Pin_0 |GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
GPIO_Init(GPIOA,&GPIOInitStruct);
//4.对ADC规则组进行配置
ADC_RegularChannelConfig(ADC1,ADC_Channel_0,1,ADC_SampleTime_55Cycles5);//ADC几,通道,序列,采样时间
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);
//5.初始化ADC,连续扫描模式
ADC_InitTypeDef ADC_InitStruct;
ADC_InitStruct.ADC_Mode = ADC_Mode_Independent;//工作模式:独立模式/双ADC模式,这里的独立模式
ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;//ADC触发选择,选择软件触发
ADC_InitStruct.ADC_ContinuousConvMode = ENABLE;//连续/单次模式,这里选择连续
ADC_InitStruct.ADC_ScanConvMode = ENABLE;//扫描/非扫描,这里选择非扫描
ADC_InitStruct.ADC_NbrOfChannel = 4;//在扫描模式下的盒子数目
ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;//数据对齐,右对齐
ADC_Init(ADC1,&ADC_InitStruct);
//6.DMA1的通道1的初始化,自动重装模式
DMA_InitTypeDef DMA_InitStruct;
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;//外设站点的起始地址,这里选择DR寄存器
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;//数据宽度,16位
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//是否自增,这里选择不自增,
//因为是将DR数据寄存器里面的数据挪出来,始终是DR寄存器
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);//DMA1的通道1
//6.开启ADC电源
ADC_Cmd(ADC1,ENABLE);
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//软件触发
//7.校准
ADC_ResetCalibration(ADC1);//将校准复位,给CR2_RSTCAL置1,进行复位
while(ADC_GetResetCalibrationStatus(ADC1) == SET);//复位完成,硬件置0
ADC_StartCalibration(ADC1);//开始校准,给CR2_CAL置1
while(ADC_GetCalibrationStatus(ADC1) == SET);//校准完成,硬件置0
//8.开启搬运
ADC_DMACmd(ADC1 ,ENABLE);//开启ADC1硬件触发源
DMA_Cmd(DMA1_Channel1,ENABLE);
}
void AD_GetValue(void)//ADC触发函数并数据搬运
{
// DMA_Cmd(DMA1_Channel1,DISABLE);//关闭DMA
// DMA_SetCurrDataCounter(DMA1_Channel1,4);//写入传输计数器的值
// DMA_Cmd(DMA1_Channel1,ENABLE);//开启DMA
while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//等待搬运完成
DMA_ClearFlag(DMA1_FLAG_TC1);//手动清除标志位
}
主程序文件的代码:
/*
ADC+DMA的使用
*/
#include "stm32f10x.h"
#include "OLED.h"
#include "Delay.h"
#include "ADC.h"
int main(void)
{
OLED_Init();
OLED_Clear();
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);
}
}