STM32——DMA

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


一,DMA简介

DMA(Direct Memory Access)直接存储器存取(在存储器之间进行数据转运)
DMA可以提供外设(即外设寄存器)和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预,节省了CPU的资源
12个独立可配置的通道: DMA1(7个通道), DMA2(5个通道)
每个通道都支持软件触发和特定的硬件触发(每个外设的硬件触发源并不相同,需要我们额外配置)

STM32F103C8T6 DMA资源:DMA1(7个通道)

存储器映像

在这里插入图片描述
ROM只读存储器,非易失性,掉电不丢失的存储器。
RAM随机存储器,易失性,掉电丢失的存储器。

DMA框图

在这里插入图片描述
STM32为了高效,有条理的访问存储器,设计了总线矩阵。如图,矩阵左边是主动单元(拥有访问权),右边是被动单元(只能被左边的主动单元读写)。
可见,DMA1有7个通道,DMA2有5个通道,各个通道分别可以设置它们的源地址和目的地址,这样通道就可以独立工作了。
仲裁器:虽说DMA中通道可以分别地设置源地址和目的地址,独立的工作,但DMA总线只有一条,故需要分时复用DMA总线,这时,仲裁器来判断优先级。
补充:总线矩阵处也存在仲裁器,来 防止DMA与CPU访问同一目标时,产生冲突,但CPU仍能得到一半的带宽,使其仍能正常工作。

AHB从设备:连接AHB总线,存在于DMA中,属于DMA自身的寄存器,用来配置DMA参数。故可见,DMA即是主动单元,也是AHB总线上的被动单元。
DMA请求:连接各个外设,为DMA的硬件触发源。
补充:Flash只读存储器,只可读取数据,不能写入,如果DMA的目的地址指向Flash,程序出错。(想要强行更改Flash时,需配置Flash接口控制器)。

DMA基本结构
在这里插入图片描述
转运方向:DMA的数据转运,可以是从外设寄存器到存储器,也可以是存储器到外设寄存器。(转运方向有参数配置)。
外设和存储器:各有三个参数,分别为,起始地址,数据宽度,地址是否自增
起始地址:即数据存储的首地址。(开始转运的第一个数据的地址)
数据宽度:指定转运一次要按多大的数据宽度进行。(可选择字节Byte,半字HalfWord,字Word。分别对应,8位,16位,32位
地址是否自增:一次转运完成后,决定下一次转运是否需要地址移动到下一给位置(两边存储器可独立配置,如配置外设为不自增,存储器为自增,现象为存储器不同位置不断接收外设起始地址的数据)

注:虽然上文一直介绍的是外设(即外设寄存器)和存储器,但同样适用于存储器与存储器的相关配置,不必拘泥于图中的外设寄存器(只是名字而已,用来配置另一个存储器也适用)。

传输计数器:自减计数器,用来决定DMA转运几次,等于零时,DMA会恢复到起始地址,进行新一轮的转运。
自动重装器:决定当传输计数器为0时,是否恢复到最初的值。不重装为单次模式。重装为循环模式,模式选择取决于工程需求。如转运一个数组到其他存储器,使用单次模式,尽快转运完成;使用ADC循环模式时,通常配置ADC不断刷新数据,使用循环模式。

M2M(memory to memory)存储器到存储器:决定DMA使用软件触发还是硬件触发。M2M为1时选择软件触发(不同于ADC的软件触发,不需每转运一次调用一次,调用软件触发函数后,程序会尽快完成全部转运),为0时选择硬件触发。

最后,使用DMA必须满足三个条件(程序配置时额外注意):
DMA使能(调用DMA_Cmd()函数);传输计数器大于0;触发源,必须有触发信号
注:更改传输计数器的值时,需先关闭DMA,再用相关函数配置传输计数器的值,然后再使能DMA。
在这里插入图片描述
可见,DMA使用不同外设信号作为触发源时,需选择指定的通道。(参数配置时注意对照上图选择所选通道)。另外,一个通道上有多个外设,具体选择你哪个外设作为触发信号,需在相关外设库函数中调用xxx_DMACmd()函数,开启触发信号输出。图中通道号越小,优先级越高,也可在程序中配置优先级,非常灵活。

数据宽度与对齐

在这里插入图片描述
简单来说,如果源端数据宽度与目标数据宽度相等,则数据正常转运;源端数据宽度小于目标数据宽度时,目标数据高位补零,低位为实际数据,数据成功转运;源端数据宽度大于目标数据宽度,源端数据高位丢失,低位为目标数据。

程序配置

ADC扫描模式+DMA

在这里插入图片描述

ADC为扫描模式,不断将通道里的数据存储到数据寄存器中,我们需要让DMA配合ADC,实现数据寄存器里的数据在被覆盖前搬运至其它存储器中。
主要配置流程:外设寄存器起始地址为ADC数据寄存器的地址,存储器首地址为自定义数组(AD_Value[])用来接收转运数据,数据宽度都为半字,外设地址不自增,存储器地址自增(以达到接收不同ADC中通道值的效果),计数器是否自动重装,取决于ADC的配置,单词扫描则单次转运,连续扫描则连续转运。触发源选择ADC硬件触发。

库函数介绍

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);//DMA使能
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设置当前数据寄存器(写入DMA传输寄存器中)
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);//清除中断挂起位

ADC配合DMA实现多通道转运代码

uint16_t AD_Value[4];
void MyADC_DMA(void);
void AD_Init(void)
{
	/*开启时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	/*配置ADCCLK预分频系数*/
	RCC_ADCCLKConfig(RCC_PCLK2_Div6);
	
	/*配置GPIO*/
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AIN;//配置位模拟输入模式
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3; 
	GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	
	/*选择通道*/
	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);
	
	/*配置ADC转化器*/
	 ADC_InitTypeDef ADC_InitStruct;
	ADC_InitStruct.ADC_Mode=ADC_Mode_Independent;//独立模式
	ADC_InitStruct.ADC_DataAlign=ADC_DataAlign_Right ;//右对齐模式
	ADC_InitStruct.ADC_ExternalTrigConv=ADC_ExternalTrigConv_None;//软件触发
	ADC_InitStruct.ADC_ContinuousConvMode=ENABLE;//连续模式
	ADC_InitStruct.ADC_ScanConvMode=ENABLE;//扫描模式
	ADC_InitStruct.ADC_NbrOfChannel=4;
	
	ADC_Init(ADC1,&ADC_InitStruct);
	

	/*开启ADC*/
	ADC_Cmd(ADC1, ENABLE);
	
	/*ADC校准*/
	ADC_ResetCalibration(ADC1);
	while(ADC_GetResetCalibrationStatus(ADC1)==SET);//等待是否校准完成
	ADC_StartCalibration(ADC1);
	while(ADC_GetCalibrationStatus(ADC1)==SET);	
	MyADC_DMA();
	//ADC_SoftwareStartConvCmd(ADC1,ENABLE);
}
void MyADC_DMA()
{

	/*开启时钟*/
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);//DMA连接AHB总线,故使用时开启AHP总线上的时钟
	
	/*DMA初始化*/
	DMA_InitTypeDef DMA_InitStruct;
	
	DMA_InitStruct.DMA_PeripheralBaseAddr=(uint32_t)&ADC1->DR;//外设寄存器地址
	DMA_InitStruct.DMA_PeripheralDataSize=DMA_PeripheralDataSize_HalfWord;//一个字节的数据宽度
	DMA_InitStruct.DMA_PeripheralInc=DMA_PeripheralInc_Disable;//完成一次,后地址不自增,一直转运ADC数据寄存器里的数据
	
	DMA_InitStruct.DMA_Priority=DMA_Priority_Medium;//中级优先级
	
	DMA_InitStruct.DMA_MemoryBaseAddr=(uint32_t)AD_Value;//数据寄存器地址
	DMA_InitStruct.DMA_MemoryDataSize=DMA_MemoryDataSize_HalfWord;//选择半字的数据宽度,对应16位的ADC数据寄存器。
	DMA_InitStruct.DMA_MemoryInc=DMA_MemoryInc_Enable;//完成一次,后地址自增,对应着将ADC不同通道不断刷新的数据分别搬运至指定的存储器中。
	DMA_InitStruct.DMA_Mode=DMA_Mode_Circular;	//循环模式,对应ADC的连续模式
	DMA_InitStruct.DMA_BufferSize=4;//传输寄存器值的大小
	DMA_InitStruct.DMA_DIR=DMA_DIR_PeripheralSRC;//配置数据传输方向以外设寄存器的数据位源数据
	DMA_InitStruct.DMA_M2M=DMA_M2M_Disable;//软件触发失能,使用ADC触发
	
	DMA_Init(DMA1_Channel1,&DMA_InitStruct);
	
	/*启动DMA通道*/
	ADC_DMACmd(ADC1,ENABLE);//使能ADC中DMA的硬件触发源
	DMA_Cmd(DMA1_Channel1,ENABLE);
	
	/*DMA传输数据的三个条件,
	传输数据器大于0,
	DMA启动,
	信号触发源*/

}

注:对于(uint32_t)&ADC1->DR,ADC1为结构体指针,DR为结构体的成员,ADC1->DR为指向结构体中的DR,即指向DR(ADC数据寄存器),取地址后,再强制类型转换为无符号32位后,传递地址。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

可见,ADC1的基地址(ADC的起始地址)被强制转换为结构体指针,ST公司定义了一个结构体变量一一对应ADC的各个寄存器(即地址经过映射后,变量的地址和寄存器的地址相同),来供我们调用ADC的各个寄存器。(其他硬件同理)

  • 23
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在使用STM32CubeMX进行串口DMA收发时,可以按照以下步骤进行设置: 1. 首先,打开STM32CubeMX,并创建一个新的工程。 2. 在"Pinout & Configuration"选项中,选择需要使用的串口引脚。 3. 在"Peripherals"选项中,找到并启用DMA控制器和相应的串口。 4. 在"Configuration"选项中,配置串口的波特率、数据位、停止位等参数。 5. 在"Configuration"选项中,选择需要使用的DMA通道,并将其分配给相应的串口发送和接收功能。 6. 在生成代码后,将生成的代码导入到你的工程中。 7. 在代码中,使用相关的HAL库函数来初始化和配置串口DMA发送和接收。 8. 在需要发送数据时,使用HAL_UART_Transmit_DMA函数将数据发送到串口。 9. 在需要接收数据时,使用HAL_UART_Receive_DMA函数来启动接收DMA,并在接收完成时触发相应的DMA中断。 10. 在DMA中断处理函数中,根据需要处理接收到的数据。 以上是使用STM32CubeMX进行串口DMA收发的基本步骤。你可以参考这些步骤来进行具体的设置和应用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [HAL库 STM32CubeMX——DMA /中断串口发送与接收](https://blog.csdn.net/qq_59128292/article/details/121180289)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [使用stm32cubeMX进行串口DMA收发](https://blog.csdn.net/boomboomy/article/details/121147873)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值