简介
直接存储器存取(DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传 输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。 两个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自 于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。
结构框图:
从总线、通道、仲裁器这三个方面去分析
1、从第一部分可以看出,DMA是挂载在AHB总线上的一个外设
2、一般设备有DMA1,在大容量、互联型中多出一个DMA2,在DMA1中拥有7个通道,DMA2中有5个通道,每个通道对应着各自的外设,意思是当外设想要使用DMA时,需要走专属的通道。同一时间只有一个通道能用。
3、仲裁器,类似于中断优先级的概念,用来处理多个外设同时请求时,先后顺序的问题。首先,DMA1通道所属的外设比DMA2上的优先级高,其次根据软件优先级(非常高、高、中、低)、再之后根据通道编号,数字越小,优先级越高。
初始化结构体
接下来由在固件库中定义的DMA结构体来介绍DMA的工作模式
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; // 外设数据宽度
uint32_t DMA_MemoryDataSize; // 存储器数据宽度
uint32_t DMA_Mode; // 模式选择
uint32_t DMA_Priority; // 通道优先级
uint32_t DMA_M2M; // 存储器到存储器模式
}DMA_InitTypeDef;
一般传输时,会涉及到发送端和接收端的概念;
在DMA中传输方向有 从外设到存储器,从存储器到外设,从存储 器到存储器。具体的方向 DMA_CCR 位 4 DIR 配置:0 表示从外设到存储器,1 表示从存 储器到外设。这里面涉及到的外设地址由 DMA_CPAR 配置,存储器地址由DMA_CMAR 配置。
uint32_t DMA_PeripheralBaseAddr; 外设地址
uint32_t DMA_MemoryBaseAddr; 存储器地址
这两个结构体成员完成了传输时两个端是谁的说明
uint32_t DMA_DIR;
这句话完成了方向的说明
uint32_t DMA_BufferSize; // 传输数目
数据的数量
uint32_t DMA_PeripheralInc; // 外设地址增量模式
uint32_t DMA_MemoryInc; // 存储器地址增量模式增量模式意思是外设、存储器地址在传输的时候,是否需要改动地址
举个例子,外设 -> 存储器
外设是个数组,存储器是个变量,在传输时,外设需要传完第一个元素后,传下一个,这时就需要数组名+1,来指向下一个元素
uint32_t DMA_PeripheralDataSize; // 外设数据宽度
uint32_t DMA_MemoryDataSize; // 存储器数据宽度字面意思,传输时数据单位的大小,这时要求没有特殊情况,要求数据大小一致,不然会产生数据丢失的情况
uint32_t DMA_Mode; // 模式选择
传完数据之后,是停止,还是要一直循环传输下去
uint32_t DMA_Priority; // 通道优先级对应仲裁器中介绍的内容,设置软件优先级
uint32_t DMA_M2M; // 存储器到存储器模式如果是存储器 -> 存储器,需要这里单独开启
实践部分
编程目的:
1、实现DMA的初始化配置
2、使用比较函数来判断外设地址和存储器地址是否相同,通过LED灯来在单片机上反映
初始化代码:
void Dma_Config_MTM(void)
{
DMA_InitTypeDef DMA_InitStructure;
// 开启外设DMA时钟
RCC_AHBPeriphClockCmd(MTM_DMA_CLK, ENABLE);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE;
DMA_InitStructure.DMA_MemoryInc = DMA_PeripheralInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
//调用初始化函数
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
}
比较函数:
uint8_t Buffercmp(const uint32_t* pBuffer, uint32_t* pBuffer1, uint16_t BufferLength)
{
/* 数据长度递减 */
while(BufferLength--)
{
/* 判断两个数据源是否对应相等 */
if(*pBuffer != *pBuffer1)
{
/* 对应数据源不相等马上退出函数,并返回0 */
return 0;
}
/* 递增两个数据源的地址指针 */
pBuffer++;
pBuffer1++;
}
/* 完成判断并且对应数据相对 */
return 1;
}
使用的传输源、目标(看方向的,比如外设到存储器,那么外设就是源,存储器就是目标)
const uint32_t aSRC_Const_Buffer[BUFFER_SIZE]= {
0x01020304,0x05060708,0x090A0B0C,0x0D0E0F10,
0x11121314,0x15161718,0x191A1B1C,0x1D1E1F20,
0x21222324,0x25262728,0x292A2B2C,0x2D2E2F30,
0x31323334,0x35363738,0x393A3B3C,0x3D3E3F40,
0x41424344,0x45464748,0x494A4B4C,0x4D4E4F50,
0x51525354,0x55565758,0x595A5B5C,0x5D5E5F60,
0x61626364,0x65666768,0x696A6B6C,0x6D6E6F70,
0x71727374,0x75767778,0x797A7B7C,0x7D7E7F80};
uint32_t aDST_Buffer[BUFFER_SIZE];
主函数:
#include "stm32f10x.h"
#include "bsp_led.h"
#include "bsp_dma.h"
#define SOFT_DELAY Delay(0x0FFFFF);
void Delay(__IO u32 nCount);
extern const uint32_t aSRC_Const_Buffer[BUFFER_SIZE];
extern uint32_t aDST_Buffer[BUFFER_SIZE];
int main(void)
{
int state;
/* LED 端口初始化 */
LED_GPIO_Config();
Dma_Config_MTM();
LED_YELLOW;
SOFT_DELAY;
state = Buffercmp(aSRC_Const_Buffer,aDST_Buffer, BUFFER_SIZE);
if( state == 0 ) {
LED_RED;
} else {
LED_GREEN;
}
}
void Delay(__IO uint32_t nCount) //简单的延时函数
{
for(; nCount != 0; nCount--);
}