基于STM32 DMA—直接存储区访问(结合代码讲解)

目录

前言

一、DMA的基本介绍

二、 DMA功能框图

 三、DMA 数据配置

3.1 数据从哪里来到哪里去

 3.2 DMA传输单位

3.3  DMA传输完成判断

四、DMA配置详解(代码)

 五、编程思路

配置DMA结构体:

 中断函数与主函数(示例)

总结


前言

本章所运用的知识点都是博主从各个网站搜集来的(侵删),也是做个笔记方便以后看。本章所用到的开发板是野火的霸道F103系列开发板,需要完整可运行代码的同学也可以找@我拿。
       上一章我们学习了如何去串口,今天我们来学习整个单片机学习里面的DMA(欢迎大佬指出错误)。

一、DMA的基本介绍

什么是DMA (DMA的基本定义)
DMA,全称Direct Memory Access,即直接存储器访问。

DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。

我们知道CPU有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是CPU,

CPU无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的复制和存储数据,如果我们把这部分的CPU资源拿出来,让CPU去处理其他的复杂计算事务,是不是能够更好的利用CPU的资源呢?

因此:转移数据(尤其是转移大量数据)是可以不需要CPU参与。比如希望外设A的数据拷贝到外设B,只要给两种外设提供一条数据通路,直接让数据由A拷贝到B 不经过CPU的处理,

二、 DMA功能框图

DMA 控制器独立于内核,属于一个单独的外设,结构比较简单,从编程的角度来看,我们只需 掌握功能框图中的三部分内容即可,具体见图 DMA 框图 :DMA 控制器的框图。

 ① DMA请求:

        如果外设要想通过 DMA 来传输数据,必须先给 DMA 控制器发送 DMA 请求,DMA 收到请求信 号之后,控制器会给外设一个应答信号,当外设应答后且 DMA 控制器收到应答信号之后,就会 启动 DMA 的传输,直到传输完毕。

        DMA 有 DMA1 和 DMA2 两个控制器,DMA1 有 7 个通道,DMA2 有 5 个通道,不同的 DMA 控 制器的通道对应着不同的外设请求,这决定了我们在软件编程上该怎么设置,具体见 下图

② 通道:

        DMA 具有 12 个独立可编程的通道,其中 DMA1 有 7 个通道,DMA2 有 5 个通道,每个通道对 应不同的外设的 DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一 个,不能同时接收多个。

③ 仲裁器:

        当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器管理。

        仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在 DMA_CCRx 寄存器 中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以 上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高, 比如通道 0 高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的 优先级。

 三、DMA 数据配置

        使用 DMA,最核心就是配置要传输的数据,包括数据从哪里来,要到哪里去,传输的数据的单 位是什么,要传多少数据,是一次传输还是循环传输等等。

3.1 数据从哪里来到哪里去

        我们知道 DMA 传输数据的方向有三个:从外设到存储器,从存储器到外设,从存储器到存储器。 具体的方向 DMA_CCR 位 4 DIR 配置:0 表示从外设到存储器,1 表示从存储器到外设。这里面 涉及到的外设地址由 DMA_CPAR 配置,存储器地址由 DMA_CMAR 配置。

外设到存储器

        当我们使用从外设到存储器传输时,以 ADC 采集为例。DMA 外设寄存器的地址对应的就是 ADC 数据寄存器的地址,DMA 存储器的地址就是我们自定义的变量(用来接收存储 AD 采集的数据) 的地址。方向我们设置外设为源地址。

存储器到外设

        当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。DMA 外设寄存器的地址对 应的就是串口数据寄存器的地址,DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲 区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。

存储器到存储器

        当我们使用从存储器到存储器传输时,以内部 FLASH 向内部 SRAM 复制数据为例。DMA 外设寄 存器的地址对应的就是内部 FLASH(我们这里把内部 FALSH 当作一个外设来看)的地址,DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部 FLASH 的数据) 的地址。方向我们设置外设(即内部 FLASH)为源地址。跟上面两个不一样的是,这里需要把 DMA_CCR 位 14:MEM2MEM:存储器到存储器模式配置为 1,启动 M2M 模式。

 3.2 DMA传输单位

        以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由 DMA_CNDTR 配置,这是一个 32 位的寄存器,一次最多只能传输 65535 个数据。

        要想数据传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是 8 位的,所以 我们定义的要发送的数据也必须是 8 位。外设的数据宽度由 DMA_CCR 的 PSIZE[1:0] 配置,可 以是 8/16/32 位,存储器的数据宽度由 DMA_CCR 的 MSIZE[1:0] 配置,可以是 8/16/32 位。

        在 DMA 控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置 两边数据指针的增量模式。外设的地址指针由 DMA_CCRx 的 PINC 配置,存储器的地址指针由 MINC 配置。以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地 址指针就应该加 1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。具体的数据 指针的增量模式由实际情况决定。

3.3  DMA传输完成判断

        数据什么时候传输完成,我们可以通过查询标志位或者通过中断的方式来鉴别。每个 DMA 通道 在 DMA 传输过半、传输完成和传输错误时都会有相应的标志位,如果使能了该类型的中断后, 则会产生中断。有关各个标志位的详细描述请参考 DMA 中断状态寄存器 DMA_ISR 的详细描述。

四、DMA配置详解(代码)

         标准库函数对每个外设都建立了一个初始化结构体 xxx_InitTypeDef(xxx 为外设名称),结构体成 员用于设置外设工作参数,并由标准库函数 xxx_Init() 调用这些设定参数进入设置外设相应的寄 存器,达到配置外设工作环境的目的。

DMA_ InitTypeDef 初始化结构体

typedef struct

{

                uint32_t DMA_PeripheralBaseAddr; // 外设地址,一般设置为外设的数据 寄存器地址,如果   是存储器到存储器模式则设置为其中一个存储器地址。

                uint32_t DMA_MemoryBaseAddr;// 存储器地址,一般设置为我们自定义 存储区的首地址。

                uint32_t DMA_DIR; // 传输方向,传输方向选择,可选外设到存储器、存储器到外设。当使    用存储器到存储器时,只需要把 其中一个存储器当作外设使用即可。

                uint32_t DMA_BufferSize; // 设定待传输数据数目

                uint32_t DMA_PeripheralInc; // 外设地址增量模式,一般外设都是只有一个数据寄存器,所以一般不会使能 该位。

                uint32_t DMA_MemoryInc; // 存储器地址增量模式,我们自定义的存储区一般都是存放多个数据的,所以 要使能存储器地址自动递增功能。

                uint32_t DMA_PeripheralDataSize; // 外设数据宽度,可选字节 (8 位)、半字 (16 位) 和字 (32 位)

                uint32_t DMA_MemoryDataSize; // 存储器数据宽度,可选字节 (8 位)、半字 (16 位) 和字 (32 位)

                uint32_t DMA_Mode; // DMA 传输模式选择,可选一次传输或者循环传输

                uint32_t DMA_Priority; // 通道优先级,有 4 个可选优先级分别为非常高、高、中和低,如果是单个通道,优先级可以随便设置。

                uint32_t DMA_M2M; // 存储器到存储器模式

} DMA_InitTypeDef;

 五、编程思路

  1. 使能 DMA 时钟;
  2. 配置 DMA 数据参数;
  3. 使能 DMA,进行传输;
  4. 等待传输完成,并对源数据和目标地址数据进行比较。
配置DMA结构体:

        源地址和目标地址使用之前定义的数组首地址,传输的数据量为宏 BUFFER_SIZE 决定,源和目 标地址指针地址递增,使用一次传输模式不能循环传输,因为只有一个 DMA 通道,优先级随便 设置,最后调用 DMA_Init 函数完成 DMA 的初始化配置。

        DMA_ClearFlag 函数用于清除 DMA 标志位,代码用到传输完成标志位,使用之前先清除传输完 成标志位以免产生不必要干扰。DMA_ClearFlag 函数需要 1 个形参,即事件标志位,可选有传输 完成标志位、半传输标志位、FIFO 错误标志位、传输错误标志位等等,非常多,我们这里选择 传输完成标志位,由宏 DMA_FLAG_TC 定义。

        DMA_Cmd 函数用于启动或者停止 DMA 数据传输,它接收两个参数,第一个是 DMA 通道,另 外一个是开启 ENABLE 或者停止 DISABLE。

 中断函数与主函数(示例)
中断函数 void DEBUG_USART_IRQHandler(void)
{

	if(USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE) == SET)          //检查中断是否发生
	{	
		DMA_Cmd(USART_TX_DMA_CHANNEL,DISABLE);                         //关闭DMA传输

		t = DMA_GetCurrDataCounter(USART_TX_DMA_CHANNEL);              //获取剩余的数据数量
		flag=1;		
		
		DMA_SetCurrDataCounter(USART_TX_DMA_CHANNEL,RECEIVEBUFF_SIZE);    //重新设置传输的数据数量

		DMA_Cmd(USART_TX_DMA_CHANNEL,ENABLE);                          //开启DMA传输
		
		USART_ReceiveData(DEBUG_USARTx);                              //读取一次数据,不然会一直进中断
		USART_ClearFlag(DEBUG_USARTx,USART_FLAG_IDLE);                //清除串口空闲中断标志位
	}
	
}

主函数/**
  * @brief  主函数
  * @param  无
  * @retval 无
  */
int main(void)
{
	uint8_t buffer = '1';
  /* 初始化USART */
  USART_Config(); 

  /* 配置使用DMA模式 */
  USARTx_DMA_Config();
  
  /* 配置RGB彩色灯 */
  LED_GPIO_Config();
	
  printf("\r\nDMA外设到存储器模式,用电脑向开发板串口发送数据,数据会返回到电脑。\r\n");
	
  /* USART1 向 DMA发出RX请求 */
  USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Rx, ENABLE);
 //用电脑向开发板串口发送数据,数据会返回到电脑。
  while(1)
  {
		if(flag == 1)
		{
			if(ReceiveBuff[0]==0x01)
				{
				Usart_SendArray(DEBUG_USARTx,ReceiveBuff,RECEIVEBUFF_SIZE-t);       //向电脑返回数据(接收数据数量 = SENDBUFF_SIZE - 剩余未传输的数据数量)
				flag=0;
				}
				else 
				{
						Usart_SendString(DEBUG_USARTx,"EEROR");       //向电脑返回数据(接收数据数量 = SENDBUFF_SIZE - 剩余未传输的数据数量)

						flag=0;

				}

			
			}

		}

  }

如果串口收到数据,标志为flag,主循环判断收到的串口起始帧是否为想要的帧头,若为则发送收到的数据,复位标志位。若不为则返回报错EEROR

总结

DMA只有在数据多的情况下才使用,平常还是正常使用串口即可,希望对大家有所帮助,谢谢!

  • 15
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
下面是一个基于STM32DMA传输ADC数据的示例代码,供参考: ```c #include "stm32f10x.h" #define ADC1_DR_Address ((u32)0x4001244C) // ADC1 data register address #define ADC_CHANNEL_NUM 1 // ADC channel number #define ADC_SAMPLE_NUM 10 // ADC sample number #define ADC_DMA_BUF_SIZE (ADC_CHANNEL_NUM * ADC_SAMPLE_NUM) // ADC DMA buffer size uint16_t adc_data[ADC_DMA_BUF_SIZE]; // ADC DMA buffer void ADC1_GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; // Enable ADC1 and GPIOA clock RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); // Configure PA0 as analog input GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; GPIO_Init(GPIOA, &GPIO_InitStructure); } void ADC1_DMA_Configuration(void) { DMA_InitTypeDef DMA_InitStructure; // Enable DMA1 clock RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // Configure DMA1 Channel1 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)ADC1_DR_Address; DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&adc_data; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize = ADC_DMA_BUF_SIZE; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(DMA1_Channel1, &DMA_InitStructure); // Enable DMA1 Channel1 DMA_Cmd(DMA1_Channel1, ENABLE); } void ADC1_Configuration(void) { ADC_InitTypeDef ADC_InitStructure; // Enable ADC1 clock RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // ADC1 configuration ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode = ENABLE; ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel = ADC_CHANNEL_NUM; ADC_Init(ADC1, &ADC_InitStructure); // ADC1 regular channel configuration ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_28Cycles5); // Enable ADC1 DMA ADC_DMACmd(ADC1, ENABLE); // Enable ADC1 ADC_Cmd(ADC1, ENABLE); // Start ADC1 Software Conversion ADC_SoftwareStartConvCmd(ADC1, ENABLE); } int main(void) { // Configure ADC1 GPIO ADC1_GPIO_Configuration(); // Configure ADC1 DMA ADC1_DMA_Configuration(); // Configure ADC1 ADC1_Configuration(); while (1) { // Do something with the ADC data } } ``` 上述代码中,我们首先定义了一个ADC DMA缓冲`adc_data`,其大小为`ADC_DMA_BUF_SIZE`。然后在`ADC1_Configuration`函数中,我们使能了ADC1,并配置了ADC1的扫描模式、转换通道和采样时间。同时,我们还使能了ADC1的DMA功能,并开启了ADC1的软件转换。 在`main`函数中,我们可以通过访问`adc_data`数组来获取DMA传输的ADC数据。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值