STM32(11)——DMA

简介:

  DMA:Direct Memory Access,直接存储器访问。DMA传输数据从一个地址空间复制到另外一个地址空间。当CPU初始化这个传输动作,传输动作本身就是DMA控制器来实现和完成。典型的例子就是移动一个外部内存的区块到芯片内部更快的内存区。这样的操作并没有让处理器的工作拖延,反而可以重新排程去处理其他的工作。

  DMA传输对高效的嵌入式系统算法网络是很重要的,DMA的传输无需CPU直接控制传输数据的通路能使CPU的效率大大提高,DMA是一个非常好的功能,它不仅减轻了CPU的负担还提高了数据传输速率。

  STM32最多有2个DMA控制器(DMA仅存在于大容量产品中),DMA挂载的时钟为AHB总线,其时钟为72Mhz,所以可以实现高速数据搬运。,DMA1有7个通道DMA2有5个通道。每个通道管理一个或多个来自外设存储器访问的请求,还有一个仲裁起来协调各个DMA请求的优先权。

  

  STM32的DMA有以下一些特征

  1. 每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。
  2. 在七个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),假如在相等优先权时由硬件决定(请求 0 优先于请求 1,依此类推) 
  3. 独立的源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐
  4. 支持循环的缓冲器管理。
  5. 每个通道都有 3 个事件标志(DMA  半传输,DMA 传输完成和 DMA 传输出错),这 3 个事件标志逻辑或成为一个单独的中断请求。
  6. 存储器和存储器间的传输
  7. 外设和存储器,存储器和外设的传输
  8. 闪存、SRAM、外设的 SRAM、APB1 APB2 和 AHB 外设均可作为访问的源和目标。
  9. 可编程的数据传输数目:最大为 65536

库函数下DMA1通道4的配置步骤:

1.使能 DMA 时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);  //使能 DMA 时钟
2.初始化 DMA 通道 4 参数  

  DMA 通道配置参数种类比较繁多,包括内存地址,外设地址,传输数据长度,数据宽度,通道优先级等等。这些参数的配置在库函数中都是在函数 DMA_Init 中完成,下面我们看看函数定义:

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)

  函数的第一个参数是指定初始化的 DMA 通道号第二个参数,跟其他外设一样,同样是通过初始化结构体成员变量值来达到初始化的目的,下面我们来看看 DMA_InitTypeDef 结构体的定义:

   typedef struct
{
uint32_t DMA_PeripheralBaseAddr;   
//用来设置DMA的传输外设基地址,比如要进行串口DMA传输,那么外设基地址为串口接受发送数据存储器USART1->DR
uint32_t DMA_MemoryBaseAddr;    
//为内存基地址,也就是我们存放DMA传输数据的内存地址
uint32_t DMA_DIR;              
/*设置数据传输方向,决定是从外设读取数据到内存还送从内存读取数据发送到外设,也就是外设是源地还是目的地,
这里我们设置为从内存读取数据发送到串口,所以外设自然就是目的地了,所以选择值为 DMA_DIR_PeripheralDST。*/
uint32_t DMA_BufferSize;       //设置一次传输数据量的大小
uint32_t DMA_PeripheralInc;    
/*设置传输数据的时候外设地址是不变还是递增。如果设置为递增,那么下一次传输的时候地址加 1,
这里因为我们是一直往固定外设地址&USART1->DR发送数据,所以地址不递增,值为 DMA_PeripheralInc_Disable;*/  
uint32_t DMA_MemoryInc;        
/*设置传输数据时候内存地址是否递增。 这个参数 和DMA_PeripheralInc 意思接近,
只不过针对的是内存。这里我们的场景是将内存中连续存储单元的数据发送到串口,
毫无疑问内存地址是需要递增的,所以值为 DMA_MemoryInc_Enable。*/
uint32_t DMA_PeripheralDataSize; 
/*用来设置外设的数据长度是为字节传输(8bits),半字传输(16bits)还是字传输(32bits),
这里我们是8位字节传输,所以值设置为DMA_PeripheralDataSize_Byte。*/
uint32_t DMA_MemoryDataSize;     
/*是用来设置内存的数据长度,和第七个参数意思接近,这里我们同样设置为字节传输 DMA_MemoryDataSize_Byte。*/
uint32_t DMA_Mode;               
/*用来设置 DMA 模式是否循环采集,也就是说,比如我们要从内存中采集64个字节发送到串口,如果设置为重复采集,
那么它会在 64 个字节采集完成之后继续从内存的第一个地址采集,如此循环。这里我们设置为一次连续采集完成之后不循环。
所以设置值为 DMA_Mode_Normal。在我们下面的实验中,如果设置此参数为循环采集,那么你会看到串口不停的打印数据,
不会中断,大家在实验中可以修改这个参数测试一下。*/
uint32_t DMA_Priority; 
/*设置 DMA 通道的优先级,有低,中,高,超高三种模式,这里我们设置优先级别为中级,
所以值为 DMA_Priority_Medium。如果要开启多个通道,那么这个值就非常有意义。*/
uint32_t DMA_M2M; 
/*设置是否是存储器到存储器模式传输,这里我们选择DMA_M2M_Disable。*/
}DMA_InitTypeDef;

  实例代码:要配置的有DMA传输通道选择,传输的成员和方向、普通模式还是循环模式等等

void DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;       
DMA_DeInit(DMA1_Channel4);//串口1的DMA传输通道是通道4
DMA_InitStructure.DMA_PeripheralBaseAddr = &USART1->DR;   //DMA 外设 ADC 基地址
DMA_InitStructure.DMA_MemoryBaseAddr = cmar;   //DMA 内存基地址  ,DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;   //从内存读取发送到外设
DMA_InitStructure.DMA_BufferSize = 64;   //DMA 通道的 DMA 缓存的大小  DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;//传输大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;   //内存地址递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //8 位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 8 位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;   //工作在正常缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA 通道  x 拥有中优先级 
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;   //非内存到内存传输
DMA_Init(DMA_CHx, &DMA_InitStructure);   //根据指定的参数初始化
} 

3.使能DMA发送 

  进行 DMA 配置之后,我们就要开启串口的 DMA 发送功能,使用的函数是: 

USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);

  如果是要使能串口 DMA 接受,那么第二个参数修改为 USART_DMAReq_Rx 即可。

4.使能 DMA1 通道 4,启动传输

  使能串口 DMA 发送之后,我们接着就要使能 DMA 传输通道

DMA_Cmd(DMA_CHx, ENABLE);

  通过以上 3 步设置,我们就可以启动一次 USART1 的 DMA 传输了

5.查询 DMA 传输状态

  在 DMA 传输过程中,我们要查询 DMA 传输通道的状态,使用的函数是:

FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)

  比如我们要查询 DMA 通道 4 传输是否完成,方法是:

DMA_GetFlagStatus(DMA2_FLAG_TC4);

  这里还有一个比较重要的函数就是获取当前剩余数据量大小的函数:

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx)

  比如我们要获取 DMA 通道 4 还有多少个数据没有传输,方法是:

DMA_GetCurrDataCounter(DMA1_Channel4); 

总例程:

DMA_InitTypeDef DMA_InitStructure;

u16 DMA1_MEM_LEN;//保存DMA每次数据传送的长度      
//DMA1的各通道配置
//这里的传输形式是固定的,这点要根据不同的情况来修改
//从存储器->外设模式/8位数据宽度/存储器增量模式
//DMA_CHx:DMA通道CHx
//cpar:外设地址
//cmar:存储器地址
//cndtr:数据传输量 

void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);    //使能DMA传输
    DMA_DeInit(DMA_CHx); //将DMA的通道1寄存器重设为缺省值
    DMA1_MEM_LEN=cndtr;
    
    DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA外设ADC基地址
    DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA内存基地址
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //数据传输方向,从内存读取发送到外设
    DMA_InitStructure.DMA_BufferSize = cndtr; //DMA通道的DMA缓存的大小
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //数据宽度为8位
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级 
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
    DMA_Init(DMA_CHx, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
}

//开启一次DMA传输
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{ 
    DMA_Cmd(DMA_CHx, DISABLE ); //关闭USART1 TX DMA1 所指示的通道 
     DMA_SetCurrDataCounter(DMA1_Channel4,DMA1_MEM_LEN);//DMA通道的DMA缓存的大小
     DMA_Cmd(DMA_CHx, ENABLE); //使能USART1 TX DMA1 所指示的通道 
}



u8 SendBuff[5200];
MYDMA_Config(DMA1_Channel4,(u32)&USART1->DR,(u32)SendBuff,5168);//DMA1通道4,外设为串口1,存储器为SendBuff,长度5168.

USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA发送 
    
    MYDMA_Enable(DMA1_Channel4);//开始一次DMA传输!    
    if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=RESET)    //判断通道4传输完成
        {
            DMA_ClearFlag(DMA1_FLAG_TC4);//清除通道4传输完成标志
            break; 
        }
        pro=DMA_GetCurrDataCounter(DMA1_Channel4);

  补充:

1、DMA的配置
要配置的有DMA传输通道选择,传输的成员和方向、普通模式还是循环模式等等。
void DMA_Configuration(void)
{
    DMA_InitTypeDef DMA_InitStructure;
    //DMA设置:
    //设置DMA源:内存地址&串口数据寄存器地址
    //方向:内存-->外设
    //每次传输位:8bit
    //传输大小DMA_BufferSize=SENDBUFF_SIZE
    //地址自增模式:外设地址不增,内存地址自增1
    //DMA模式:一次传输,非循环
    //优先级:中
    DMA_DeInit(DMA1_Channel4);//串口1的DMA传输通道是通道4
    DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;
    DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//外设作为DMA的目的端
    DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;//传输大小
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不增加
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存地址自增1
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
    //DMA_Mode_Normal(只传送一次), DMA_Mode_Circular (不停地传送)
    DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//(DMA传送优先级为中等)
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
    DMA_Init(DMA1_Channel4, &DMA_InitStructure);
}
注:
1、传输通道:通过查表,串口1的发送对应的是DMA的通道4,所以此处选择通道4.
2、DMA传输方式:
(1) DMA_Mode_Normal,正常模式,当一次DMA数据传输完后,停止DMA传送,对于上例而言,就是DMA_PeripheralDataSize_Byte个字节的传送完成后,就停止传送。
(2) DMA_Mode_Circular
循环模式,当传输完一次后,重新接着传送,永不停息。
2、外设的DMA方式设置
将串口1设置成DMA模式:
每一个外设都有一个类似以下的一个DMA调用函数:xxx_DMACmd();
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);//发送就为USART_DMAReq_Tx;读取就为USART_DMAReq_Rx
3、待传输数据的定义和初始化
#define SENDBUFF_SIZE   10240
vu8 SendBuff[SENDBUFF_SIZE];
    for(i=0;i<sendbuff_size;i++)
    {
        SendBuff[i] = i%10+'0';
    }
4、开始DMA传输(使能对应的DMA通道)
DMA_Cmd(DMA1_Channel4, ENABLE);
5、DMA传输的完成
 while(DMA_GetFlagStatus(DMA1_FLAG_TC4) == RESET)
 {
       LED_1_REV;      //LED翻转
       Delay();        //浪费时间
 }
当传输完成后,就会跳出上面的死循环。
当然,使用串口作为外设的时候,还需要对串口进行初始化。
posted @ 2018-08-13 21:49  小猪利琦  阅读(668)  评论(0编辑  收藏  举报