今天总结一下DMA(Direct Memory Access,直接存储器存储),在以往我们从串口读取数据到内存的流程是,cpu通过串口读取导数据,然后CPU再将数据写入固定的内存。这种读取方式占用了大量的cpu资源,如果数据量非常大,CPU将耗费大量的时间来进行数据的读写操作。因此DMA应运而生。DMA的作用是啥呢?从名字能看出来一二,它可以直接从串口读取数据存储在内存中,几乎不占用CPU的资源。打个比方来说,一个砌墙的工人,他的主要工作是砌墙,但他首先需要耗费大量时间去搬砖到自己身边,然后才开始砌墙,这样他砌墙的进度肯定很慢。现在砌墙的工人不自己去搬砖了,他雇佣了另外一个人来帮助他去把砖搬过来,在搬之前砌墙的工人告诉搬砖的工人,他要去哪里搬砖、搬什么样的砖、搬砖的速度、搬过来后放在那里等问题。这样虽然在开始耗费了点时间去告诉搬砖工人一些要求,看起来比较繁琐,其实这些所占用的时间比起自己去搬砖耗费的时间少多了。在这个例子中,砌墙的工人就是我们所说的CPU,砖就是需要读写数据,搬砖工人就是DMA,DMA的引入为CPU读写数据节省了大量的时间,CPU只需要在开始耗费极少的时间对DMA进行相关的配置,就可以去做其他的事情。
我在做实验的过程中,遇到了很多狗血的bug,不过最终都得以解决。今天做的实验是利用DMA通过USART1向发送数据,同时LED持续点亮,当发送数据完成时,进入中断将LED熄灭,从而来验证DMA工作时CPU可以去做别的事情,而没有影响。下面来总结一下DMA的开发流程:
- NVIC中断优先级的配置
- DMA_InitTypeDef结构体的配置(记得调用NVIC配置的函数,我多次忘记调用)
- 初始化DMA,DMA_Init(DMA1_Channel4,&结构体)(DMA1_Channel4为串口1的发送,具体的可以看手册)
- 使能DMA,DMA_Cmd(DMA1_Channel4,ENABLE)
- 配置并使能中断DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE)
- 开始利用DMA进行串口发送数据USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
- 在中断中DMA_GetFlagStatus(DMA1_FLAG_TC4) == SET来确定发送数据完成
根据以上步骤进行先关的配置,其他的比如串口和LED的配置在前面已经配置过了直接把USART和LED的配置文件添加在该项目中即可。下面来看代码,代码中只有DMA的配置和主函数。
下面这是dma.c文件中的内容:
#include"dma.h"
uint16_t SendBuff[SENDSBUFF_SIZE];
static void NVIC_Config(void)
{
NVIC_InitTypeDef nvic_struct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
nvic_struct.NVIC_IRQChannel = DMA1_Channel4_IRQn;
nvic_struct.NVIC_IRQChannelPreemptionPriority = 1;
nvic_struct.NVIC_IRQChannelSubPriority = 1;
nvic_struct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic_struct);
}
void DMA1_Config(void)
{
DMA_InitTypeDef dma_struct;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
NVIC_Config();
dma_struct.DMA_PeripheralBaseAddr = USART1_BASE + 0x04;//数据要到达的地址
dma_struct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//每次发送数据的大小
dma_struct.DMA_MemoryBaseAddr = (u32)SendBuff;//数据所在的地址
dma_struct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//每次读取数据的大小
dma_struct.DMA_BufferSize = SENDSBUFF_SIZE;//总共传输的数据的大小
dma_struct.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储数据的地址是否自动增加
dma_struct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//数据到达的地址是否自动增加
dma_struct.DMA_DIR = DMA_DIR_PeripheralDST;//传输的方向内存到外设
dma_struct.DMA_M2M = DMA_M2M_Disable;//内存到内存是否允许
dma_struct.DMA_Mode = DMA_Mode_Normal;//传输方式,发送一次还是循环发送
dma_struct.DMA_Priority = DMA_Priority_Medium;//优先级
DMA_Init(DMA1_Channel4,&dma_struct);//初始化
DMA_Cmd(DMA1_Channel4,ENABLE);//使能
DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE);//中断配置并使能
}
SendBuff数组存储的是要发送的数据,两个函数为中断配置函数,在前面的外部中断中已经配置过了。第二个函数为DMA的配置函数,与其他外设的配置类似,先定义一个结构体,然后开启时钟。这个DMA1的时钟和之前的都不一样,在AHB上,配置时需要注意。
在main.c文件中:
#include"led.h"
#include"usart.h"
#include"dma.h"
extern uint16_t SendBuff[SENDSBUFF_SIZE];//引用外部定义的变量
int i;
int main(void)//主函数
{
GPIO_LED_Config();
USART1_Config();
DMA1_Config();
for(i = 0;i < SENDSBUFF_SIZE;i++)//为发送数据赋值
{
SendBuff[i] = 0xff;
}
printf("Send start!");
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
LED(ON);
printf("wait interrupt!");
while(1);
}
void DMA1_Channel4_IRQHandler(void)//中断函数
{
printf("into interrupt!");
if(DMA_GetFlagStatus(DMA1_FLAG_TC4) == SET)//判断是否发送完成
{
printf("send success");
LED(OFF);
DMA_ClearFlag(DMA1_FLAG_TC4);//清除标志位
}
}
在实验的过程中,编译出现symbol multiply defined错误的原因,因为在xxx.h中定义了许多变量,xxx.c中调用xxx.h中的变量,在主文件中也调用了xxx.h中的变量,导致变量被重复定义。
解决办法:不应该在xxx.h中定义xxx.c中使用的变量,应该在xxx.c中定义所需要的变量,然后再在主程序中将调用xxx.c中定义的变量使用extern 例:extern u32 test.