news 2026/5/16 17:25:12

DMA直接存储器存取

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
DMA直接存储器存取

1.DMA简介

注意:存储器到存储器的数据转运一般使用软件触发;外设到存储器的数据转运一般使用硬件触发

2.STM32的存储器映像

内核外设是NVIC和SysTick

3.DWA大致的内部结构

整体结构就是CPU+存储器组成的,寄存器是连接软件和硬件的桥梁

细节:

1.DCode专门访问Flash,系统总线访问其他东西

2.仲裁器:虽然多个通道可以独立转运数据,但是DMA总线只有一条,所有通道只能分时复用这一条总线,此时仲裁器就会根据优先级来分出使用顺序。

3.AHB从设备:DMA自身的寄存器(CPU通过其可以配置DMA)

4.DMA请求(用于硬件触发DMA数据转运):就是DMA的硬件触发源(触发DMA转运数据)

5.这里的Flash是只读的,不能通过总线访问;SRAM是运行内存,可以正常读写。

4.DMA基本结构(具体)

1.传输计数器(自减计数器):指定转运次数(注意:写传输计数器时必须先关闭DMA

2.自动重装器:决定是模式单次模式(不重装,即不会回到原来的次数)还是循环模式(重装,即会回到原来的次数)

3.触发控制:由M2M参数决定,为1时,DMA选择软件触发(连续触发DMA尽早将计数器清零,不能与循环模式同时使用,一般用于存储器到存储器的转运);为0时,DMA选择硬件触发(触发源可以为ADC、串口、定时器等,一般都与外设有关)

5.细节

1.DMA请求

M2M:数据选择器的控制位

EN:开关控制(为0时不工作,为1时工作)

注意:使用某个硬件触发源的话,就必须使用它所在的通道,而软件触发可以任意选择

选择哪个硬件触发源取决于哪个外设的DMA输出开启了

2.数据宽度与对齐

存在原因:数据转运的两个站点的数据宽度可能会不一样

1.如果目标的数据宽度比源端的数据宽度大,那就在目标数据前面补0

2.如果目标的数据宽度比源端的数据宽度小,就把多出来的高位舍弃掉

3.例子

1.数据转运+DMA(数组间的数据转运,相当于复制)

2.ADC扫描模式+DMA

6.实战代码

1.部分函数功能

//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); //获取当前数据寄存器的值(剩余的转运次数) 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);//清除中断挂起位

2.配置思路

1.RCC开启时钟(把DMA的时钟打开)

2.DMA初始化(外设和存储器站点的起始地址、数据宽度、地址是否自增、传输计数器等)

3.打开DMA(用DMA_Cmd使能)

注意:如果使用的是硬件触发,要开启对应的外设的触发信号的输出;如果需要DMA中断就调用DMA_ITConfig来开启中断输出

3.基本配置格式

//DMA数据转运 void MyDMA_Init(uint32_t AddrA,uint32_t AddrB,uint16_t Size) { MyDMA_Size=Size; //开启DMA的时钟(DMA是AHB总线上的设备) RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //初始化DMA 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_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); } //DMA传输函数(调用一次就在进行一次DMA转运) //手动实现多次转运(也可以通过重装实现,但是软件触发和重装不能同时使用) void MyDMA_Transfer(void) { //重新给计数器赋值 DMA_Cmd(DMA1_Channel1,DISABLE);//DMA失能 DMA_SetCurrDataCounter(DMA1_Channel1,MyDMA_Size); DMA_Cmd(DMA1_Channel1,ENABLE);//重新使能 while(DMA_GetFlagStatus(DMA1_FLAG_TC1)==RESET);//等待标志位 DMA_ClearFlag(DMA1_FLAG_TC1);//手动清除标志位 } //DMA+AD多通道(AD扫描模式下需要DMA转运数据) //此处配置为ADC单次扫描模式+DMA单次转运 //注意:当模式配置成ADC连续扫描+DMA循环转运模式时,可以去掉AD_GetValue函数,然后在ADC校准完成后软件触发一次ADC即可实现连续转换,循环转运 uint16_t AD_Value[4]; void AD_Init(void) { //ADC都是APB2上的设备 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);//开启ADC1的时钟 //需要用到PA0口将可调的电压输出 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启DMA的时钟(DMA是AHB总线上的设备) RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //配置ADCCLK RCC_ADCCLKConfig(RCC_PCLK2_Div6); //配置GPIO时钟 GPIO_InitTypeDef GPIO_InitStructure;//结构体定义 GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; //模拟输入(GPIO无效,即为ADC专属模式) GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;//IO口 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 ADC_InitTypeDef ADC_InitStructure; ADC_InitStructure.ADC_Mode=ADC_Mode_Independent;//ADC模式(独立还是双模式) 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=1; //扫描模式下总工会用到的通道数 ADC_Init(ADC1,&ADC_InitStructure); //初始化DMA(ADC扫描模式和DNA组合下的配置) DMA_InitTypeDef DMA_InitStructure; DMA_InitStructure.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;//外设站点的起始地址 DMA_InitStructure.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//数据宽度 DMA_InitStructure.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//是否自增(此处不自增) DMA_InitStructure.DMA_MemoryBaseAddr=(uint32_t)AD_Value;//存储器站点起始地址 DMA_InitStructure.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//数据宽度 DMA_InitStructure.DMA_MemoryInc=DMA_MemoryInc_Enable;//是否自增(此处自增) DMA_InitStructure.DMA_DIR=DMA_DIR_PeripheralSRC;//传输方向(外设站点作为数据源) DMA_InitStructure.DMA_BufferSize=4;//传输计数器 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到DMA的输出 ADC_DMACmd(ADC1,ENABLE); ADC_Cmd(ADC1,ENABLE);//开启ADC //校准 ADC_ResetCalibration(ADC1);//复位校准 while(ADC_GetResetCalibrationStatus(ADC1)==SET);//获取复位校准状态 ADC_StartCalibration(ADC1);//开始校准 while(ADC_GetCalibrationStatus(ADC1)==SET);//获取开始校准状态 } //转换过程 void AD_GetValue(void) { DMA_Cmd(DMA1_Channel1,DISABLE);//DMA失能 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);//手动清除标志位 }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/10 22:01:32

33、利用TiMidity搭建卡拉OK系统全攻略

利用TiMidity搭建卡拉OK系统全攻略 1. TiMidity基础介绍 TiMidity本质上是一款MIDI播放器,并非专门的卡拉OK播放器,不过它具备一定的可扩展性,经过配置后也能用于卡拉OK场景。默认情况下,它仅播放MIDI音乐并打印歌词。例如,运行以下命令: $timidity ../54154.mid执行…

作者头像 李华
网站建设 2026/5/15 14:26:55

骨髓来源抑制细胞(MDSC)

骨髓来源抑制细胞(Myeloid-derived suppressor cells, MDSC)分为粒形/多核形MDSCs(G-MDSC或PMN-MDSC)与MNP样MDSCs(M-MDSC)。单核吞噬细胞(Mononuclear phagocytes (MNPs))包括单核细胞、巨噬细胞和树突状细…

作者头像 李华
网站建设 2026/5/8 15:37:44

14、邮件系统的插件、安全及配置全解析

邮件系统的插件、安全及配置全解析 在当今数字化的时代,邮件系统是我们日常工作和生活中不可或缺的一部分。无论是个人用户收发邮件,还是企业进行业务沟通,一个稳定、安全且功能丰富的邮件系统至关重要。下面将详细介绍邮件系统相关的插件、安全防护以及配置方法。 一、Sq…

作者头像 李华
网站建设 2026/5/13 17:19:18

22、Procmail 正则表达式及高级应用全解析

Procmail 正则表达式及高级应用全解析 正则表达式简介 正则表达式是处理数据的强大工具。在 Procmail 中,正则表达式的实现与其他 UNIX 实用程序略有不同。Procmail 的匹配默认情况下不区分大小写,除非使用 D 标志,并且默认使用多行匹配。 简单来说,正则表达式可以理解为…

作者头像 李华
网站建设 2026/5/14 9:56:30

3大维度解锁reMarkable客户端:从基础操作到专业工作流

3大维度解锁reMarkable客户端:从基础操作到专业工作流 【免费下载链接】awesome-reMarkable A curated list of projects related to the reMarkable tablet 项目地址: https://gitcode.com/gh_mirrors/aw/awesome-reMarkable reMarkable客户端生态提供了多样…

作者头像 李华
网站建设 2026/5/16 0:41:03

胡桃工具箱:免费开源的终极原神智能助手

胡桃工具箱:免费开源的终极原神智能助手 【免费下载链接】Snap.Hutao 实用的开源多功能原神工具箱 🧰 / Multifunctional Open-Source Genshin Impact Toolkit 🧰 项目地址: https://gitcode.com/GitHub_Trending/sn/Snap.Hutao 还在为…

作者头像 李华