写在前面:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
目录
一、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的效率大为提高。
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);