文章目录
DMA简介
Data Memory Access,直接储存器访问。可以把数据从一个地方搬移到另一个地方,同时不占用CPU。
其方向有
- Memory -> Memory 存储器到存储器
- Memory -> Periph 存储器到外设
- Periph -> Memory 外设到存储器
DMA功能框图
DMA请求
在参考手册中可以找到DMA请求映像,里面规定了不同设备间传输数据应用哪条通道。
仲裁器
当多个DMA请求同时发出时,仲裁器会仲裁顺序。这里就需要配置优先级了。可以通过软件编辑优先级,当软件优先级相同时,判断硬件优先级。这里数字越小优先级越高,如通道1优先级高于通道2。同时,DMA1的优先级高于DMA2。
寄存器讲解
寄存器这里在参考手册,通道配置过程中写的很详细,其中解释了不同寄存器对应设置的功能。这里把整个过程分析一下。
首先,在需要调用数据时,外设发送请求,仲裁后使能DMA,开始调取数据。需要指定数据所在位置和传输地。
传输数据时,需要指定每次传输多少数据,一共传输多少次。另外传输时指针是否按照地址递增也是可以配置的,这就是增量模式是否开启。
最后,传输完成后,可以配置是否循环发送。
固件库编程
结构体
在 “stm32f10x_dma.h” 这个文件中有初始化函数,其中初始化结构体表明了一系列的配置。
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;
开始编程(一)
这里编程完成两个任务。第一个,把FLASH中的值用DMA调到SRAM中。
1.在FLASH中写入数据
使用 const 关键字可以定义常量,这种量只可读不可修改,会被存储到FLASH中。这里写入的数据是无意义的,只是为了实验。
#define BUFFER_SIZE 32
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};
2.建立一个数组用来接收数据
uint32_t aDST_Buffer[BUFFER_SIZE];
3.初始化DMA
void DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStruct;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer;
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStruct.DMA_BufferSize = BUFFER_SIZE;
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
DMA_InitStruct.DMA_Priority = DMA_Priority_High;
DMA_InitStruct.DMA_M2M = DMA_M2M_Enable;
DMA_Init(DMA1_Channel6,&DMA_InitStruct);
DMA_Cmd(DMA1_Channel6,ENABLE);
}
开启时钟,DMA是挂载在 AHB 时钟下的,使用 DMA1 。
这里把FLASH视为外设,把SRAM视为存储器。要把 aSRC_Const_Buffer数据移动到 aDST_Buffer,这里两个参数需要传入地址的值,因此用 (uint32_t) 把指针强制转化为值。
我们想要从外设读取数据,因此配置为 DMA_DIR_PeripheralSRC;若从存储器读取则用 DMA_DIR_PeripheraDIR。
数组中一共有BUFFER_SIZE个数据,所以传输数目为BUFFER_SIZE ;每个数据有4个字节;因此使用 DMA_PeripheralDataSize_Word 和 DMA_MemoryDataSize_Word 。
数据需要自动移位,开启 Inc。
模式为正常 DMA_Mode_Normal ,发送完后停止;优先级随意设置;因这里是M2M模式,所以开启M2M。
使用初始化函数初始化。使用Cmd使能。因为是 M2M模式,选择的通道没有特殊要求。
4.检验是否传输成功
编写一个函数,比较两个地方的数据是否相同。
uint8_t Buffercmp(const uint32_t* pBuffer,
uint32_t* pBuffer1,uint16_t BufferLength)
{
while(BufferLength--)
{
if(*pBuffer != *pBuffer1)
{
return 0;
}
pBuffer++;
pBuffer1++;
}
return 1;
}
5.主函数中操作
int main(void)
{
LED_GPIO_Config();
DMA_Config();
LED_G(Buffercmp(aSRC_Const_Buffer,aDST_Buffer,BUFFER_SIZE));
}
主函数中,配置好LED,配置DMA。如果传输成功,两端数据相同,Buffercmp 返回1,绿色LED被点亮。
6.增强严谨性
传输过程中会耗费时间,虽然在我们调用下一个函数时,数据已经传输完毕,但为了严谨,我们还是要在传输后检查是否传输完成。DMA传输完成后相应寄存器会置位,使用固件库函数可以检测。修改 main 函数如下。
int main(void)
{
LED_GPIO_Config();
DMA_Config();
while(DMA_GetFlagStatus(DMA1_FLAG_TC6) == RESET);
LED_G(Buffercmp(aSRC_Const_Buffer,aDST_Buffer,BUFFER_SIZE));
}
在初始化时,要保证这个标志位复位。由此,在初始化函数中加入下面的语句。
DMA_ClearFlag(DMA1_FLAG_TC6);
至此编程工作已经完成。但要注意,因为 main 中使用了别的 .c 文件的全局变量,这需要在 main 中使用 extern 关键字再次声明。
开始编程(二)
这里完成外设和DMA之间的传输。这里把SRAM的数据通过DMA交由串口发出去。因为是外设请求DMA,所以要看好外设通道请求表,可以看见,USART1在DMA1的通道4。下面开始初始化DMA。
1.设置SRAM数据
设置一个 uint8_t 类型的数组,32个成员。32个成员是随意的。但为什么是uint8_t类型?下面会解释。
#define BUFFSIZE 32
uint8_t SentBuff[BUFFSIZE];
2.初始化DMA
已知,由串口发送的数据都是暂存在 DR 寄存器中,因此第一个参数外设基地址就是 USART1 的DR寄存器的地址;在手册中可以查到,DR 的地址偏移为0x04。而 USART1 的基地址可以在 stm32f10x.h 头文件中查到
由此得到 USART1 的 DR 寄存器的地址。
#define USART1_DR_ADDESS (USART1_BASE+0X04)
同时,可以看出 DR 寄存器只有8位的储存,在设置传输数据大小 DataSize 时要注意。
void DMA_M2P_Config(void)
{
DMA_InitTypeDef DMA_InitStruct;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
DMA_InitStruct.DMA_PeripheralBaseAddr = USART1_DR_ADDESS;
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t) SentBuff;
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStruct.DMA_BufferSize = BUFFSIZE;
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStruct.DMA_PeripheralDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStruct.DMA_Priority = DMA_Priority_High;
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable;
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
DMA_Init(DMA1_Channel4,&DMA_InitStruct);
DMA_ClearFlag(DMA1_FLAG_TC4);
DMA_Cmd(DMA1_Channel4,ENABLE);
}
要多次读取储存器中的数据,而外设的地址是固定的。因此Memory的 Inc 开启,Peripheral关闭。
初始化时注意通道,USART1在DMA1的通道4。
3.从外设发送请求
外设发出请求,DMA才会工作,在USART库中有函数 USART_DMACmd 用于配置请求。
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
4.整合代码
extern uint8_t SentBuff[BUFFSIZE];
int main(void)
{
uint16_t i=0;
//向数组中填入数据
for(i=0;i<BUFFSIZE;i++)
{
SentBuff[i] = 'P';
}
USART_Config();//开启串口
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);//开启串口请求
DMA_M2P_Config(); //配置串口
}