STM32笔记之 DMA(直接存储器访问)

写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。

目录

一、DMA介绍

二、工作过程

三、功能特性

四、DMA请求映像

五、代码实现过程

六、代码注释总分析


一、DMA介绍

直接内存访问Direct Memory Access,DMA)是计算机科学中的一种内存访问技术。它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载;否则,CPU 需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回到新的地方;在这个时间中,CPU 对于其他的工作来说就无法使用。

二、工作过程

  • 请求

CPU对 DMA控制器初始化,并向 I/O接口发出操作命令,I/O接口提出 DMA请求。

  • 响应

DMA控制器对 DMA请求判别优先级及屏蔽,向总线裁决逻辑提出总线请求。当 CPU执行完当前总线周期即可释放总线控制权。此时,总线裁决逻辑输出总线应答,表示 DMA已经响应,通过 DMA控制器通知 I/O接口开始 DMA传输。

  • 传输

DMA控制器获得总线控制权后,CPU即刻挂起或只执行内部操作,由 DMA控制器输出读写命令,直接控制 RAM与 I/O接口进行 DMA传输。

在 DMA控制器的控制下,在存储器和外部设备之间直接进行数据传送,在传送过程中不需要中央处理器的参与。开始时需提供要传送的数据的起始位置和数据长度。

  • 结束

当完成规定的成批数据传送后,DMA控制器即释放总线控制权,并向 I/O接口发出结束信号。当 I/O接口收到结束信号后,一方面停止 I/O设备的工作,另一方面向 CPU提出中断请求,使 CPU从不介入的状态解脱,并执行一段检查本次 DMA传输操作正确性的代码。最后,带着本次操作结果及状态继续执行原来的程序。


由此可见,DMA传输方式无需 CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为 RAM与 I/O设备开辟一条直接传送数据的通路,使 CPU的效率大为提高。

三、功能特性

四、DMA请求映像

1、DMA1通道

2、DMA2通道

五、代码实现过程

按照上一篇 STM32笔记之 ADC(模数转换)的原代码继续分析,定位到下图代码的位置

上一篇也说了,在 ADC例程中用了 DMA的操作,所以现在来逐一分析

在配置 DMA(/* DMA init structure parameters values */ 区域下),我们都是在定义好的结构体成员中赋相应的参数值,所以我们先来了解好它们的各个成员先

typedef struct
{
  uint32_t DMA_PeripheralBaseAddr; /*!< Specifies the peripheral base address for DMAy Channelx. */

  uint32_t DMA_MemoryBaseAddr;     /*!< Specifies the memory base address for DMAy Channelx. */

  uint32_t DMA_DIR;                /*!< Specifies if the peripheral is the source or destination.
                                        This parameter can be a value of @ref DMA_data_transfer_direction */

  uint32_t DMA_BufferSize;         /*!< Specifies the buffer size, in data unit, of the specified Channel. 
                                        The data unit is equal to the configuration set in DMA_PeripheralDataSize
                                        or DMA_MemoryDataSize members depending in the transfer direction. */

  uint32_t DMA_PeripheralInc;      /*!< Specifies whether the Peripheral address register is incremented or not.
                                        This parameter can be a value of @ref DMA_peripheral_incremented_mode */

  uint32_t DMA_MemoryInc;          /*!< Specifies whether the memory address register is incremented or not.
                                        This parameter can be a value of @ref DMA_memory_incremented_mode */

  uint32_t DMA_PeripheralDataSize; /*!< Specifies the Peripheral data width.
                                        This parameter can be a value of @ref DMA_peripheral_data_size */

  uint32_t DMA_MemoryDataSize;     /*!< Specifies the Memory data width.
                                        This parameter can be a value of @ref DMA_memory_data_size */

  uint32_t DMA_Mode;               /*!< Specifies the operation mode of the DMAy Channelx.
                                        This parameter can be a value of @ref DMA_circular_normal_mode.
                                        @note: The circular buffer mode cannot be used if the memory-to-memory
                                              data transfer is configured on the selected Channel */

  uint32_t DMA_Priority;           /*!< Specifies the software priority for the DMAy Channelx.
                                        This parameter can be a value of @ref DMA_priority_level */

  uint32_t DMA_M2M;                /*!< Specifies if the DMAy Channelx will be used in memory-to-memory transfer.
                                        This parameter can be a value of @ref DMA_memory_to_memory */
}DMA_InitTypeDef;

1、DMA_PeripheralBaseAddr成员

把 DMA映射到所需要的数据存放的输出寄存器地址,在例子中我们是要 ADC读取的数据,所以把 ADC数据输出寄存器的地址赋值给它,也就是下图的这个寄存器

然后我们去查看存储器的映像表找到 ADC1的首地址(0x40012400)

最后加上目标寄存器的偏移地址(0x4C),得到 ADC_DR这个寄存器的最终地址(0x4001244C)并赋值给成员 DMA_PeripheralBaseAddr;可能你会说,在上面的例程中可不是给出 0x4001244C这个值哦,是的,你不觉得这样查表很麻烦嘛,而且还容易出错,所以我们可以直接一点,用 ‘ & ’ 取地址符直接拿地址值,就像例程一样 &(ADCx->DR)

2、DMA_MemoryBaseAddr成员

这个是 DMA的内存(存储器)地址。是在映射到外设的寄存器,并且指定好 DMA通道后的内存基址,可以理解为 DMA的数据输出寄存器(但是因为 DMA并没有固定的数据存储地址,所以需要我们去指定一个);因此,定义一个 __IO uint16_t ADC_ConvertedValue 的变量,并且是 volatile变量;最后再 &ADC_ConvertedValue取地址,赋值给 DMA_MemoryBaseAddr

3、DMA_DIR成员

指定外设是源还是目标

对应的宏选择:

#define DMA_DIR_PeripheralDST              ((uint32_t)0x00000010)
#define DMA_DIR_PeripheralSRC              ((uint32_t)0x00000000)

4、DMA_BufferSize成员

指定通道的缓冲区大小(以数据单位表示),缓冲区的大小应该等于存储器的大小

5、DMA_PeripheralInc成员

指定外围地址寄存器是否递增

宏选择:

#define DMA_PeripheralInc_Enable           ((uint32_t)0x00000040)
#define DMA_PeripheralInc_Disable          ((uint32_t)0x00000000)

6、DMA_MemoryInc成员

指定内存地址寄存器是否递增

宏选择:

#define DMA_MemoryInc_Enable               ((uint32_t)0x00000080)
#define DMA_MemoryInc_Disable              ((uint32_t)0x00000000)

7、DMA_PeripheralDataSize成员

指定外围数据宽度,数据单元等于DMA_PeripheralDataSize中的配置集

宏选择:

#define DMA_PeripheralDataSize_Byte        ((uint32_t)0x00000000)
#define DMA_PeripheralDataSize_HalfWord    ((uint32_t)0x00000100)
#define DMA_PeripheralDataSize_Word        ((uint32_t)0x00000200)

因为在 ADC_DR寄存器中存储的数据大小是半字,即两个字节(16 bit),所以得选 DMA_PeripheralDataSize_HalfWord

8、DMA_MemoryDataSize成员

指定内存数据宽度,DMA_MemoryDataSize成员取决于传输方向

宏选择:

#define DMA_MemoryDataSize_Byte            ((uint32_t)0x00000000)
#define DMA_MemoryDataSize_HalfWord        ((uint32_t)0x00000400)
#define DMA_MemoryDataSize_Word            ((uint32_t)0x00000800)

同样的,因为我们定义的是 __IO uint16_t ADC_ConvertedValue,也是是半字(16 bit),所以选 DMA_MemoryDataSize_HalfWord

9、DMA_Mode成员

指定 DMA通道的操作模式

宏选择:

#define DMA_Mode_Circular                  ((uint32_t)0x00000020)
#define DMA_Mode_Normal                    ((uint32_t)0x00000000)

10、DMA_Priority成员

指定 DMA通道的软件优先级

宏选择:

#define DMA_Priority_VeryHigh              ((uint32_t)0x00003000)
#define DMA_Priority_High                  ((uint32_t)0x00002000)
#define DMA_Priority_Medium                ((uint32_t)0x00001000)
#define DMA_Priority_Low                   ((uint32_t)0x00000000)

11、DMA_M2M成员

指定 DMA通道是否将在内存到内存传输中使用

宏选择:

#define DMA_M2M_Enable                     ((uint32_t)0x00004000)
#define DMA_M2M_Disable                    ((uint32_t)0x00000000)

六、代码注释总分析

然后跟 DMA相关的配置有:

	// ADC1转换的电压值通过MDA方式传到SRAM
	__IO uint16_t ADC_ConvertedValue;



	/* Enable DMA1 clock */
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

	/* DMA init structure parameters values */
	/* 映射到 ADC数据寄存器地址 0x4001244C */
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(ADCx->DR));	
	/* 存储器地址,实际上就是一个内部 SRAM的变量 */
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&ADC_ConvertedValue;	
	/* 数据源来自外设 */
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;	
	/* 缓冲区大小为 1,缓冲区的大小应该等于存储器的大小 */
	DMA_InitStructure.DMA_BufferSize = 1;	
	/* 外设寄存器只有一个,地址不用递增 */
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	/* 存储器地址固定 */
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable; 	
	/* 外设数据大小为半字,即两个字节 */
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;	
	/* 存储器数据大小也为半字,跟外设数据大小相同 */
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;	
	/* 循环传输模式 */
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;	
	/* DMA 传输通道优先级为高,当使用一个 DMA通道时,优先级设置不影响 */
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;	
	/* 禁止存储器到存储器模式,因为是从外设到存储器 */
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;	
	/* 初始化 DMA */
	DMA_Init(ADC_DMA_CHANNEL, &DMA_InitStructure);
	
	/* Enable DMA ADC channel */
	DMA_Cmd(ADC_DMA_CHANNEL , ENABLE);

	/* Enable ADCx DMA */
	ADC_DMACmd(ADCx, ENABLE);
发布了48 篇原创文章 · 获赞 17 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_42992084/article/details/104109207