DMA(Data Memory Access)直接存储器访问

 DMA(Data Memory Access)直接存储器访问

DMA是不经过CPU控制,直接把数据从一个设备传给另一个设备。

DMA有两个控制器,DMA1,和DMA2。

DMA1支持:外设到存储器,存储器到外设

DMA2:外设到存储器,存储器到外设,存储器到存储器。

 每个数据流都与一个 DMA 请求相关联,此 DMA 请求可以从 8 个可能的通道请求中选出。
此选择由 DMA_SxCR 寄存器中的 CHSEL[2:0] 位控制。

DMA1请求映射

 DMA2请求映射

  

 FIFO——源和目标之间的一个数据中转站。(first in, first out)

1—每个数据流有4字(16字节)FIFO,阈值级别有1/4、1/2、3/4、4/4(满),可以通过 DMA数据流

xFIFO 控制寄存器 DMA_SxFCR 的 FTH[1:0]位来控制 FIFO 的阈值,如果数据存储量达到阈值级别时,FIFO 内容将传输到目标中。

2—DMA传输具有 FIFO模式和直接模式。在开启FIFO的时候,直接模式要禁止。 

  直接模式在每个外设请求都立即启动对存储器传输。在直接模式下,如果 DMA配置为存储器到外设传输,那 DMA会见一个数据存放在 FIFO 内,如果外设启动 DMA传输请求就可以马上将数据传输过去。

  FIFO 用于在源数据传输到目标地址之前临时存放这些数据。如果数据存储量达到阈值级别时,FIFO 内容将传输到目标中。

  FIFO 对于要求源地址和目标地址数据宽度不同时非常有用,比如源数据是源源不断的字节数据,而目标地址要求输出字宽度的数据,即在实现数据传输时同时把原来 4 个 8 位字节的数据拼凑成一个 32 位字数据。此时使用 FIFO功能先把数据缓存起来,分别根据需要输出数据。

FIFO的阈值配置

1 - FIFO大小:4个字,16个字节,半字即2个字节,字即4个字节。

2 - 节拍:即MSIZE的单位。

 

 两个DMA控制器系统实现

 

编程时需要用到的固件库函数

DMA的实验设计

1- M to M:FLASH to SRAM,把内部FLASH的数据传输到内部的SRAM。

2 - M to P: SRAM to 串口,同时LED等闪烁,演示DMA传数据不需要占用CPU。

M to M 编程要点

1 - 在FLASH中定义好要传输的数据,在SRAM中定义好用来接收的FLASH数据的变量。

2 - 确定使用DMA2,哪个数据流,哪个通道?然后定义宏,方便修改。

3 - 初始化DMA,主要是配置DMA初始化结构体。

参考《STM32F4XX参考手册》9.3.17流的配置过程

4 - 编写数据比较函数。

5 - 编写main函数。

编程注意:

通过const定义一个数组,这样的数组存放在常量区中,(常量存放在内部flash中),再定义一个全局数组不加const的,存放在全局区,(变量存放在SRAM中)。

先开时钟(时钟就是心脏),DAM初始化需要先复位,而且要等待复位完成

/* DMA数据流通道选择 */
  DMA_InitStructure.DMA_Channel = DMA_CHANNEL;  
  /* 源数据地址 */
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer;
  /* 目标地址 */
  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)aDST_Buffer;
  /* 存储器到存储器模式 */
  DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToMemory;
  /* 数据数目 */
  DMA_InitStructure.DMA_BufferSize = (uint32_t)BUFFER_SIZE;
  /* 使能自动递增功能 */
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
  /* 使能自动递增功能 */
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  /* 源数据是字大小(32位) */
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
  /* 目标数据也是字大小(32位) */
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
  /* 一次传输模式,存储器到存储器模式不能使用循环传输 */
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
  /* DMA数据流优先级为高 */
  DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  /* 禁用FIFO模式 */
  DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;     
  DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
  /* 单次模式 */
  DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  /* 单次模式 */
  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  /* 完成DMA数据流参数配置 */
  DMA_Init(DMA_STREAM, &DMA_InitStructure);
 /* 清除DMA数据流传输完成标志位 */
  DMA_ClearFlag(DMA_STREAM,DMA_FLAG_TCIF);
/* 使能DMA数据流,开始DMA数据传输 */ 
DMA_Cmd(DMA_STREAM, ENABLE);

所以上面的最后两句需要:

  /* 清除DMA数据流传输完成标志位 */
  DMA_ClearFlag(DMA_STREAM,DMA_FLAG_TCIF);
  
  /* 使能DMA数据流,开始DMA数据传输 */
  DMA_Cmd(DMA_STREAM, ENABLE);
这个软件写1的寄存器是LIFCR,对LIFCR寄存器相应的位写1,就可以吧TCFIx寄存器相应的位清零。
M to P 编程要点

1 - 初始化串口(从现有的例程移植过来)

2 - 配置DMA初始化结构体

3 - 编写主函数(开启串口发送DMA请求)。

这个和M TO M有一点不同,需要我们注意,我们通过 M TO P,外设选择串口,内存选择数组需要注意以下几点:

1.和 M TO M不同,DMA初始化之后,需要外设发送DMA请求,才会发生DMA。

int main(void)
{
  uint16_t i;
  /* 初始化USART */
  Debug_USART_Config(); 

  /* 配置使用DMA模式 */
  USART_DMA_Config();
  
  /* 配置RGB彩色灯 */
  LED_GPIO_Config();

  printf("
 USART1 DMA TX 测试 
");
  
  /*填充将要发送的数据*/
  for(i=0;i<SENDBUFF_SIZE;i++)
  {
    SendBuff[i] = 'A';
    
  }

  /*为演示DMA持续运行而CPU还能处理其它事情,持续使用DMA发送数据,量非常大,
  *长时间运行可能会导致电脑端串口调试助手会卡死,鼠标乱飞的情况,
  *或把DMA配置中的循环模式改为单次模式*/        
  
  /* USART1 向 DMA发出TX请求 */
  USART_DMACmd(DEBUG_USART, USART_DMAReq_Tx, ENABLE);

  /* 此时CPU是空闲的,可以干其他的事情 */  
  //例如同时控制LED
  while(1)
  {
    LED1_TOGGLE
    Delay(0xFFFFF);
  }
}

注意主函数中的红色部分,初始化DMA完毕,需要外设发起DMA请求,我们想要通过串口发送,所以是发送请求,虽然数据是从内存传到串口数据寄存器再传到串口调试助手的,但我们的目的是串口DMA发送,不占用CPU资源,所以是发送请求。

2.为了演示DMA传输时不占用CPU,我们把DMA模式设置成为

  DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;循环模式,一直发送,但是可能串口调试助手会有卡屏现象,不过这样的演示效果最好。

/*usart1 tx对应dma2,通道4,数据流7*/    
  DMA_InitStructure.DMA_Channel = DEBUG_USART_DMA_CHANNEL;  
  /*设置DMA源:串口数据寄存器地址*/
  DMA_InitStructure.DMA_PeripheralBaseAddr = DEBUG_USART_DR_BASE;     
  /*内存地址(要传输的变量的指针)*/
  DMA_InitStructure.DMA_Memory0BaseAddr = (u32)SendBuff;
  /*方向:从内存到外设*/        
  DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;

内存是SendBuff数组(源地址),外设地址是串口的DR寄存器地址(目标地址),方向是内存到外设。

其余一些FIFO,或者突发模式,根据以后的业务需要做调整。

 

欢迎加入作者的小圈子

扫描下方左边二维码加入QQ交流群,扫描下方右边二维码关注个人微信公众号并,获取更多隐藏干货,QQ交流群:859800032 微信公众号:Crystal软件学堂

作者:Liu_Jing
bilibili视频教程地址:https://space.bilibili.com/5782182
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在转载文章页面给出原文连接。
如果你觉得文章对你有所帮助,烦请点个推荐,你的支持是我更文的动力。
文中若有错误,请您务必指出,感谢给予我建议并让我提高的你。
原文地址:https://www.cnblogs.com/Liu-Jing/p/7146831.html