当前位置: 首页 > news >正文

STM32学习笔记10—DMA

DMA

DMA简介

  • DMA(Direct Memory Access)直接存储器存取——存储数据小助手,主要是协助CPU,完成数据转运的工作
  • DMA可以提供外设(DR:比如ADC的数据寄存器、串口的数据寄存器)和存储器(运行内存SRAM和程序寄存器Flash)或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
  • 12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)
  • 每个通道都支持软件触发(存储器到存储器的转运)和特定的硬件触发(外设到存储器的转运:因为需要考虑时机)
  • STM32F103C8T6 DMA资源:DMA1(7个通道)

存储器映像

ROM区:系统存储器:Bootloader一般是厂家更改,不易配置;

               选项字节:一般是配置看门狗,flash的读写保护

RAM区:内核外设储存器:一般配置NVIC和SysTick

DMA框图

Flash是默认只读的闪存,可配置

为了使访问变得有序,中间设置了一个总线矩阵,左端的是主动单元,拥有主动存储器的访问权;右端是被动单元,对应的存储器只能被左边的主动单元读写

主动单元:Dcode总线是专门访问Flash的;

                系统总线是访问其他东西的;

       另外:DMA总线1,2,3:第三根是以太网私有的DMA,一般不用;

                     在DMA1和DMA2中,DMA有7个通道,而DMA2有5个通道,各个通道可以分别设置它们转运数据的源地址和目的地址,其中有个仲裁器 :虽然通道可以独立的运行,但是最终的DMA总线只有一条,所以由仲裁器根据通道的优先级,决定顺序,同时在总线矩阵中也存在仲裁器,作用于当DMA和CPU访问为同一目标时,DMA会停止访问。

                        在仲裁器下面有个AHB从设备 :DMA的自身寄存器,它连接在了总线矩阵右边,证明了DMA既是总线矩阵的主动单元,可以读写各种存储器,也是AHB总线上的被动单元,CPU可以通过此线路对DMA进行配置。

DMA请求:就是触发的意思,是通过外设输入的信号源引起的触发,一般是硬件触发源。

DMA基本结构


数据转运:

方向由一个参数控制:由于Flash默认为只读,所以不存在SRAM读写Flash和Flash读写Flash的情况

两端的对应参数,这两块配置可以随意配置,与它的命名无关,故可认为是站点A的配置参数和站点B的配置参数:

进行数据转运的3个参数配置:

数据宽度:决定一次转运的数据大小

地址是否自增:指定一次转运后,确定下一个转运的寄存器位置:对于外设寄存器来说要多次转运,所以不必增值;对于存储器来说,一个萝卜一个坑,需自增;


传输计数器:自减计数器,每转运一次,计数器就减1;当计数器减为0后,停止转运同时之前自增的地址,恢复到起始地址的位置;

旁边的自动重装器:设周期,当计数器减到0后,是否要自动恢复到最初的值;循环模式可对ADC多次+扫描模式


触发控制:决定DMA需要在什么时机进行转运的

触发源有:硬件触发和软件触发——由M2M参数决定

              软件触发:不是以前的用程序触发的意思,而以最快的速度,连续不断地触发DMA,争取把传输计数器清0,所以当为软件触发时,不能开启自动重装器;一般使用与存储器到存储器的转运:因为是软件启动、不需要时机,越快越好。

              硬件触发:反之。与外设有关。

DMA转运的条件:

  1. 开关控制:DMA_Cmd()必须使能;
  2. 传输计数器必须大于0
  3. 触发源必须有触发信号

配置传输计数器,必须关闭DMA


DMA请求——DMA触发部分

这个图不好理解,要对应基本结构;

这么多触发源如何选择:对应的外设是否开启了DMA是输出来决定的,比如要使用ADC1,那会有库函数叫ADC_DMACmd,通过此函数开启ADC1的一路输出。

优先级:默认通道越小越高,也可以配置

数据宽度与对齐

当对应的数据宽度不一样时:


例子:

1.数据转运+DMA

站点配置:

1.起始地址:DataA        DataB

2.数据宽度:uint_8             uint_8

3.是否自增:自增               自增

4.方向参数:DataA——》Data2

5.传输计数器:设置为7,自动重装不需要

6.选择触发源:软件触发——这个存储器对存储器的数据转运

7.使能

此功能相当于复制功能

2.ADC扫描模式+DMA

站点配置:

1.起始地址:ADC_DR        ADValue

2.数据宽度:uint_16              uint_16

3.是否自增:不自增                  自增

4.方向参数:DataA——》Data2

5.传输计数器:设置为7,自动重装不需要(需要额外判断,ADC是否为连续模式)

6.选择触发源:硬件触发——这里是需要ADC单个通道转换的时机,来完成DMA的

       在单个通道转运完成后,要触发DMA请求

7.使能

接线图

8-1 DMA数据转运

首先需要确定定义的数据,是不是真的存储在相应的地址区间——存储器映像

uint8_t aa=0x66;
const uint8_t aa1=0x66;
OLED_ShowHexNum(1,1,aa,2);
OLED_ShowHexNum(2,1,(uint32_t)&aa,8); //对应地址为20000000 对应地址在SRAM区
OLED_ShowHexNum(3,1,(uint32_t)&aa1,8);//对应地址为08000E30 对应地址在Flash地址
OLED_ShowHexNum(4,1,(uint32_t)&ADC1->DR,8);//对应地址为4001244C 对应地址在外设地址区中,是ADC1的数据寄存器地址,此地址是固定的

外设基地址:0x40000000

APB2外设基地址:0x40000000+10000

ADC1的基地址:0x40000000+10000+0x2400

在ADC1的基地址上+偏移就能找到DR的地址

——偏移用的是结构体指针


  1. 初始化RCC开启DMA时钟
  2. 直接调用DMA_Init,初始化对应参数
  3. 开关控制DMA_Cmd
  4. 设置触发源,如果想更改传输计数器,需先失能

DMA库函数

void DMA_DeInit(DMA_Channel_TypeDef* DMAy_Channelx); 

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);

void DMA_StructInit(DMA_InitTypeDef* DMA_InitStruct);

void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);

void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);  //中断输出使能

void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);       //DMA设置当前数据寄存器,给传输计数器写数据的

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);        //DMA获取当前数据寄存器,返回传输计数器的值

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);           //清除中断状态

注:需要使用AHB的时钟开启——多用手册或网络去查看

main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyDMA.h"
//#include "OLED_Font.h"
//DMA数据转运
//定义DMA的配置——属于系统里头
//配置DMA转运的源端数组和目的数组
uint8_t DataA[]={0x01,0x02,0x03,0x04};
uint8_t DataB[]={0,0,0,0};
int main(void){OLED_Init();
//	//首先需要确定定义的数据,是不是真的存储在相应的地址区间——存储器映像//uint8_t aa=0x66;
//const uint8_t aa1=0x66;
//	OLED_ShowHexNum(1,1,aa,2);
//	OLED_ShowHexNum(2,1,(uint32_t)&aa,8); //对应地址为20000000 对应地址在SRAM区
//	OLED_ShowHexNum(3,1,(uint32_t)&aa1,8);//对应地址为08000E30 对应地址在Flash地址
//	OLED_ShowHexNum(4,1,(uint32_t)&ADC1->DR,8);//对应地址为4001244C 对应地址在外设地址区中,是ADC1的数据寄存器地址,此地址是固定的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);OLED_ShowHexNum(3,8,(uint32_t)DataB,8);	OLED_ShowHexNum(2,1,DataA[0],2);OLED_ShowHexNum(2,4,DataA[1],2);OLED_ShowHexNum(2,7,DataA[2],2);OLED_ShowHexNum(2,10,DataA[3],2);OLED_ShowHexNum(4,1,DataB[0],2);OLED_ShowHexNum(4,4,DataB[1],2);OLED_ShowHexNum(4,7,DataB[2],2);OLED_ShowHexNum(4,10,DataB[3],2);while(1){DataA[0]++;DataA[1]++;DataA[2]++;DataA[3]++;OLED_ShowHexNum(2,1,DataA[0],2);OLED_ShowHexNum(2,4,DataA[1],2);OLED_ShowHexNum(2,7,DataA[2],2);OLED_ShowHexNum(2,10,DataA[3],2);OLED_ShowHexNum(4,1,DataB[0],2);OLED_ShowHexNum(4,4,DataB[1],2);OLED_ShowHexNum(4,7,DataB[2],2);OLED_ShowHexNum(4,10,DataB[3],2);	Delay_ms(1000);MyDMA_Tranfer();OLED_ShowHexNum(2,1,DataA[0],2);OLED_ShowHexNum(2,4,DataA[1],2);OLED_ShowHexNum(2,7,DataA[2],2);OLED_ShowHexNum(2,10,DataA[3],2);OLED_ShowHexNum(4,1,DataB[0],2);OLED_ShowHexNum(4,4,DataB[1],2);OLED_ShowHexNum(4,7,DataB[2],2);OLED_ShowHexNum(4,10,DataB[3],2);	Delay_ms(1000);}
}
MyDMA.c
#include "stm32f10x.h"                  // Device header
uint16_t My_Size;
void MyDMA_Init(uint32_t ADDrA,uint32_t ADDrB,uint16_t Size){My_Size=Size;RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);DMA_InitTypeDef DMA_InitStructure;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_DIR_PeripheralDST:存储器站点作为目的地   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);//第一个参数DMAy_Channelx——即选择了是哪个DMA,也选择了是哪个DMA的通道DMA_Cmd(DMA1_Channel1,DISABLE);
}//此时我们想让DataA的数据变化,使再转运一次
void MyDMA_Tranfer(void){DMA_Cmd(DMA1_Channel1,DISABLE);DMA_SetCurrDataCounter(DMA1_Channel1,My_Size); DMA_Cmd(DMA1_Channel1,ENABLE);//等待转运完成while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);//手动清除标志位DMA_ClearFlag(DMA1_FLAG_TC1);
}

8-2 DMA+AD多通道

main.c
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "AD.h"int main(void)
{/*模块初始化*/OLED_Init();				//OLED初始化AD_Init();					//AD初始化/*显示静态字符串*/OLED_ShowString(1, 1, "AD0:");OLED_ShowString(2, 1, "AD1:");OLED_ShowString(3, 1, "AD2:");OLED_ShowString(4, 1, "AD3:");while (1){OLED_ShowNum(1, 5, AD_Value[0], 4);		//显示转换结果第0个数据OLED_ShowNum(2, 5, AD_Value[1], 4);		//显示转换结果第1个数据OLED_ShowNum(3, 5, AD_Value[2], 4);		//显示转换结果第2个数据OLED_ShowNum(4, 5, AD_Value[3], 4);		//显示转换结果第3个数据Delay_ms(100);							//延时100ms,手动增加一些转换的间隔时间}
}
AD.c
#include "stm32f10x.h"                  // Device headeruint16_t AD_Value[4];					//定义用于存放AD转换结果的全局数组/*** 函    数:AD初始化* 参    数:无* 返 回 值:无*/
void AD_Init(void)
{/*开启时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);	//开启ADC1的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);		//开启DMA1的时钟/*设置ADC时钟*/RCC_ADCCLKConfig(RCC_PCLK2_Div6);						//选择时钟6分频,ADCCLK = 72MHz / 6 = 12MHz/*GPIO初始化*/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;GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA0、PA1、PA2和PA3引脚初始化为模拟输入/*规则组通道配置*/ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);	//规则组序列1的位置,配置为通道0ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);	//规则组序列2的位置,配置为通道1ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);	//规则组序列3的位置,配置为通道2ADC_RegularChannelConfig(ADC1, ADC_Channel_3, 4, ADC_SampleTime_55Cycles5);	//规则组序列4的位置,配置为通道3/*ADC初始化*/ADC_InitTypeDef ADC_InitStructure;											//定义结构体变量ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;							//模式,选择独立模式,即单独使用ADC1ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;						//数据对齐,选择右对齐ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;			//外部触发,使用软件触发,不需要外部触发ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;							//连续转换,使能,每转换一次规则组序列后立刻开始下一次转换ADC_InitStructure.ADC_ScanConvMode = ENABLE;								//扫描模式,使能,扫描规则组的序列,扫描数量由ADC_NbrOfChannel确定ADC_InitStructure.ADC_NbrOfChannel = 4;										//通道数,为4,扫描规则组的前4个通道ADC_Init(ADC1, &ADC_InitStructure);											//将结构体变量交给ADC_Init,配置ADC1/*DMA初始化*/DMA_InitTypeDef DMA_InitStructure;											//定义结构体变量DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;				//外设基地址,给定形参AddrADMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;	//外设数据宽度,选择半字,对应16为的ADC数据寄存器DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;			//外设地址自增,选择失能,始终以ADC数据寄存器为源DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)AD_Value;					//存储器基地址,给定存放AD转换结果的全局数组AD_ValueDMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;			//存储器数据宽度,选择半字,与源数据宽度对应DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;						//存储器地址自增,选择使能,每次转运后,数组移到下一个位置DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;							//数据传输方向,选择由外设到存储器,ADC数据寄存器转到数组DMA_InitStructure.DMA_BufferSize = 4;										//转运的数据大小(转运次数),与ADC通道数一致DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;								//模式,选择循环模式,与ADC的连续转换一致DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;								//存储器到存储器,选择失能,数据由ADC外设触发转运到存储器DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;						//优先级,选择中等DMA_Init(DMA1_Channel1, &DMA_InitStructure);								//将结构体变量交给DMA_Init,配置DMA1的通道1/*DMA和ADC使能*/DMA_Cmd(DMA1_Channel1, ENABLE);							//DMA1的通道1使能ADC_DMACmd(ADC1, ENABLE);								//ADC1触发DMA1的信号使能ADC_Cmd(ADC1, ENABLE);									//ADC1使能/*ADC校准*/ADC_ResetCalibration(ADC1);								//固定流程,内部有电路会自动执行校准while (ADC_GetResetCalibrationStatus(ADC1) == SET);ADC_StartCalibration(ADC1);while (ADC_GetCalibrationStatus(ADC1) == SET);/*ADC触发*/ADC_SoftwareStartConvCmd(ADC1, ENABLE);	//软件触发ADC开始工作,由于ADC处于连续转换模式,故触发一次后ADC就可以一直连续不断地工作
}////启动ADC,获取结果
//void ADC_Get(void){
//	DMA_Cmd(DMA1_Channel1,DISABLE);
//	
//	DMA_SetCurrDataCounter(DMA1_Channel1,4); 
//	
//	DMA_Cmd(DMA1_Channel1,ENABLE);
//	
//	ADC_SoftwareStartConvCmd(ADC1, ENABLE);//用软件转换
//	while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);
//	DMA_ClearFlag(DMA1_FLAG_TC1);
//}

http://www.dtcms.com/a/330077.html

相关文章:

  • JSON索引香港VPS:高效数据处理的完美解决方案
  • JDK17下载与安装图文教程(保姆级教程)
  • 《汇编语言:基于X86处理器》第13章 复习题和编程练习
  • VerIF
  • 【R语言】RStudio 中的 Source on Save、Run、Source 辨析
  • [系统架构设计师]系统架构基础知识(一)
  • MySQL表约束
  • 关于大学计算机专业的课程的一些看法
  • windows通过共享网络上网
  • JavaWeb之响应
  • 使用BeautifulReport让自动化测试报告一键生成
  • 开源组件的“暗礁”:第三方库中的输入与边界风险治理
  • 「数据获取」《广西调查年鉴》(2007-2024)(2009缺失)(获取方式看绑定的资源)
  • GISBox工具处理:将高斯泼溅模型导出为3DTiles
  • 【15】Transformers快速入门:添加自定义 Token
  • 服务器安全防护
  • ARM芯片架构之CoreSight Channel Interface 介绍
  • 基于边缘深度学习的棒球击球训练评估研究
  • 模型训练不再“卡脖子”:国产AI训练平台对比与落地实践指南
  • 马力是多少W,常见车辆的马力范围
  • RK3568项目(十四)--linux驱动开发之常用外设
  • 中科米堆CASAIM蓝光三维扫描仪用于焊接件3D尺寸检测
  • 2025 开源语音合成模型全景解析:从工业级性能到创新架构的技术图谱
  • Python实现点云概率ICP(GICP)配准——精配准
  • static 和 extern 关键字
  • 公用表表达式和表变量的用法区别?
  • 【SpringBoot】12 核心功能-配置文件详解:Properties与YAML配置文件
  • WinForm中C#扫描枪功能实现(含USB串口)
  • 终端安全检测与防御
  • 20250813比赛总结