目录
一.简介

DMA外设是可以直接访问32内部的存储器的,包括运行内存SRAM,程序存储器Flash和寄存器等
外设的数据寄存器DR,Data Register
存储器到存储器之间的转运需要软件触发
外设到存储器之间的转运需要硬件触发,对上外设数据的时机
二.存储器映像

主闪存的地址以0x800开头 ,串口下载的程序由STM32接收数据,然后刷新到0x0800位置
外设寄存器都是0x40开头
选项字节存储Flash的读保护,写保护还有看门狗等等的配置
SRAM也就是程序中定义变量,数组,结构体的地方
内核外设就是NVIC和SysTick,内核外设和其他外设不是一个公司设计的
寻址范围为32位,0xFFFF FFFF
Bootloader程序(类似于手机的刷机模式或电脑的PE系统,是辅助主程序进行自我更新的)也叫自举程序,Bootloader接收到USART1数据,刷新到程序存储器,这时主程序处于瘫痪状态,更新好之后再启动主程序,执行新程序

全片擦除后,所有的数据都是FF
选项字节

三.DMA结构框图

通过总线的形式,可以很好地将各种外设分离开来,可以独立各种外设来控制其使能与否,可以实现功耗的控制,以达到节能的目的
控制外设的使能与否就是控制其外设的时钟





四.测试
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
uint8_t a = 0x66;
int main()
{
OLED_Init();
OLED_ShowHexNum(1,1,a,5);
OLED_ShowHexNum(2,1,(uint32_t)&a,8);//只能显示数字,所以要进行强制类型转换
while(1)
{
}
}

#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
const uint8_t a = 0x66;
int main()
{
OLED_Init();
OLED_ShowHexNum(1,1,a,5);
OLED_ShowHexNum(2,1,(uint32_t)&a,8);
while(1)
{
}
}

当我们程序中出现大量不需要修改的数据时 ,就可以定义为常量,节省SRAM的空间,比如查找表,字库数据等
对于变量或常量来说,其地址是由编译器决定的,不同的程序,地址可能不一样
对于外设寄存器来说,其地址是固定的
起始地址+偏移=寄存器的实际地址


五.相关函数

互联型是STM32F105/107的型号

五.代码实现
(1)存储器到存储器数据转运
DMA.c
#include "stm32f10x.h" // Device header
uint8_t MyDMA_Size;
void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size)
{
DMA_InitTypeDef DMA_InitStructure;
MyDMA_Size = Size;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_InitStructure.DMA_MemoryBaseAddr=AddrA;//存储器站点地址
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//地址自增
DMA_InitStructure.DMA_PeripheralBaseAddr=AddrB;//外设站点地址
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Enable;
DMA_InitStructure.DMA_BufferSize=Size;//传输计数器
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralDST;//传输方向
DMA_InitStructure.DMA_M2M=DMA_M2M_Enable;//使用软件触发
DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//是否使用自动重装
DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级
DMA_Init(DMA1_Channel1,&DMA_InitStructure);
DMA_Cmd(DMA1_Channel1,ENABLE);
}
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);//等待转运完成
DMA_ClearFlag(DMA1_FLAG_TC1);//清除标志位
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"
const uint8_t arr1[] = {1,2,3,4};//想要由Flash转运到存储器就加const
uint8_t arr2[] = {0,0,0,0};
int main()
{
OLED_Init();
OLED_ShowString(1,1,"arr1");
OLED_ShowHexNum(1,6,(uint32_t)arr1,8);
OLED_ShowString(3,1,"arr2");
OLED_ShowHexNum(3,6,(uint32_t)arr2,8);
OLED_ShowNum(2,1,arr1[0],2);
OLED_ShowNum(2,4,arr1[1],2);
OLED_ShowNum(2,7,arr1[2],2);
OLED_ShowNum(2,10,arr1[3],2);
OLED_ShowNum(4,1,arr2[0],2);
OLED_ShowNum(4,4,arr2[1],2);
OLED_ShowNum(4,7,arr2[2],2);
OLED_ShowNum(4,10,arr2[3],2);
MyDMA_Init((uint32_t)arr1,(uint32_t)arr2,4);
while(1)
{
// arr1[0]++;
// arr1[1]++;
// arr1[2]++;
// arr1[3]++;
Delay_ms(1000);
MyDMA_Transfer();
OLED_ShowNum(2,1,arr1[0],2);
OLED_ShowNum(2,4,arr1[1],2);
OLED_ShowNum(2,7,arr1[2],2);
OLED_ShowNum(2,10,arr1[3],2);
OLED_ShowNum(4,1,arr2[0],2);
OLED_ShowNum(4,4,arr2[1],2);
OLED_ShowNum(4,7,arr2[2],2);
OLED_ShowNum(4,10,arr2[3],2);
Delay_ms(1000);
}
}

(2)ADC单次扫描
AD.C
#include "stm32f10x.h" // Device header
uint16_t SRAM_arr[4]={0,0,0,0};
void AD_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;//必须要写在程序的开头位置
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//ADC都是APB2上的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
//在AIN模式下GPIO是无效的,专门为ADC服务
//断开GPIO,防止GPIO的输入和输出对模拟电压造成干扰
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);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//分频设置ADC时钟,ADCCLK=12MHz
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);
//若想要在多个序列填充多个通道,可以复制19行进行填充,配置不同的采样时间
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//数据右对齐
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//配置ADC独立模式
ADC_InitStructure.ADC_ScanConvMode=ENABLE;//是否为扫描模式
ADC_InitStructure.ADC_NbrOfChannel=4;//扫描模式下会用到的通道数目
ADC_InitStructure.ADC_ContinuousConvMode=DISABLE;//配置成单次转换
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//不使用外部触发源
ADC_Init(ADC1,&ADC_InitStructure);
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;//此处ADC要加上&,外设站点地址
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)SRAM_arr;//存储器站点地址
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//地址自增
DMA_InitStructure.DMA_BufferSize=4;//传输计数器
DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向
DMA_InitStructure.DMA_M2M=DMA_M2M_Disable;//使用软件触发
DMA_InitStructure.DMA_Mode=DMA_Mode_Normal;//是否使用自动重装
DMA_InitStructure.DMA_Priority=DMA_Priority_Medium;//优先级
DMA_Init(DMA1_Channel1,&DMA_InitStructure);
DMA_Cmd(DMA1_Channel1,ENABLE);
ADC_DMACmd(ADC1,ENABLE);
ADC_Cmd(ADC1,ENABLE);//开启ADC电源
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1) == SET);//复位校准完成后硬件自动置0
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1)== SET);//校准完成后硬件自动置0
}
void ADC_GetValue(void)
{
DMA_Cmd(DMA1_Channel1,DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel1,4);//写入缓存
DMA_Cmd(DMA1_Channel1,ENABLE);
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//ADC为单次模式要用软件先进行触发一次
while(DMA_GetFlagStatus(DMA1_FLAG_TC1) == RESET);//等待转运完成
DMA_ClearFlag(DMA1_FLAG_TC1);//清除标志位
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
int main()
{
OLED_Init();
AD_Init();
OLED_ShowString(1,1,"AD1:");
OLED_ShowString(2,1,"AD2:");
OLED_ShowString(3,1,"AD3:");
OLED_ShowString(4,1,"AD4:");
while(1)
{
ADC_GetValue();
OLED_ShowNum(1,5,SRAM_arr[0],5);
OLED_ShowNum(2,5,SRAM_arr[1],5);
OLED_ShowNum(3,5,SRAM_arr[2],5);
OLED_ShowNum(4,5,SRAM_arr[3],5);
Delay_ms(100);
}
}
(3)ADC连续扫描
AD.C
#include "stm32f10x.h" // Device header
uint16_t SRAM_arr[4]={0,0,0,0};
void AD_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;//必须要写在程序的开头位置
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//ADC都是APB2上的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN;
//在AIN模式下GPIO是无效的,专门为ADC服务
//断开GPIO,防止GPIO的输入和输出对模拟电压造成干扰
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);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);//分频设置ADC时钟,ADCCLK=12MHz
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);
//若想要在多个序列填充多个通道,可以复制19行进行填充,配置不同的采样时间
ADC_InitStructure.ADC_DataAlign=ADC_DataAlign_Right;//数据右对齐
ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//配置ADC独立模式
ADC_InitStructure.ADC_ScanConvMode=ENABLE;//是否为扫描模式
ADC_InitStructure.ADC_NbrOfChannel=4;//扫描模式下会用到的通道数目
ADC_InitStructure.ADC_ContinuousConvMode=ENABLE;//配置成连续转换
ADC_InitStructure.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//不使用外部触发源
ADC_Init(ADC1,&ADC_InitStructure);
DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;//此处ADC要加上&,外设站点地址
DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)SRAM_arr;//存储器站点地址
DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//地址自增
DMA_InitStructure.DMA_BufferSize=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_Medium;//优先级
DMA_Init(DMA1_Channel1,&DMA_InitStructure);
DMA_Cmd(DMA1_Channel1,ENABLE);
ADC_DMACmd(ADC1,ENABLE);
ADC_Cmd(ADC1,ENABLE);//开启ADC电源
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1) == SET);//复位校准完成后硬件自动置0
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1)== SET);//校准完成后硬件自动置0
ADC_SoftwareStartConvCmd(ADC1,ENABLE);//ADC连续扫描,初始化结尾直接触发
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"
int main()
{
OLED_Init();
AD_Init();
OLED_ShowString(1,1,"AD1:");
OLED_ShowString(2,1,"AD2:");
OLED_ShowString(3,1,"AD3:");
OLED_ShowString(4,1,"AD4:");
while(1)
{
OLED_ShowNum(1,5,SRAM_arr[0],5);
OLED_ShowNum(2,5,SRAM_arr[1],5);
OLED_ShowNum(3,5,SRAM_arr[2],5);
OLED_ShowNum(4,5,SRAM_arr[3],5);
Delay_ms(100);
}
}
极大地减轻了软件的负担,提高外设性能
还可以使用定时器定时触发ADC,ADC触发DMA
STM32DMA与ADC高级操作:存储器映像、数据传输与连续采样应用
本文详细介绍了STM32中的DMA外设如何直接访问内存,存储器映像的配置,以及DMA用于存储器到存储器的数据搬运和ADC的单次和连续扫描功能。展示了DMA在简化软件编程和提升外设性能中的作用,以及如何通过定时器触发ADC操作。
923

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



