DMA—直接存储区访问

本章参考资料:《 STM32F4xx 中文参考手册》 DMA 控制器章节。
学习本章时,配合《 STM32F4xx 中文参考手册》 DMA 控制器章节一起阅读,效果会
更佳,特别是涉及到寄存器说明的部分。本章内容专业名称较多,内容丰富也较难理解,
但非常有必要细读研究。

DMA 简介
DMA(Direct Memory Access,直接存储区访问)为实现数据高速在外设寄存器与存储器之
间或者存储器与存储器之间传输提供了高效的方法。之所以称之为高效,是因为 DMA
输实现高速数据移动过程无需任何 CPU 操作控制。从硬件层次上来说, DMA 控制器是独
立于 Cortex-M4 内核的,有点类似 GPIOUSART 外设一般,只是 DMA 的功能是可以快
速移动内存数据。
STM32F4xx 系列的 DMA 功能齐全,工作模式众多,适合不同编程环境要求。
STM32F4xx 系列的 DMA 支持外设到存储器传输、存储器到外设传输和存储器到存储器传
输三种传输模式。这里的外设一般指外设的数据寄存器,比如 ADCSPII2CDCMI
等外设的数据寄存器,存储器一般是指片内 SRAM、外部存储器、片内 Flash 等等。
外设到存储器传输就是把外设数据寄存器内容转移到指定的内存空间。比如进行 ADC
采集时我们可以利用 DMA 传输把 AD 转换数据转移到我们定义的存储区中,这样对于多
通道采集、采样频率高、连续输出数据的 AD 采集是非常高效的处理方法。
存储区到外设传输就是把特定存储区内容转移至外设的数据寄存器中,这种多用于外
设的发送通信。
存储器到存储器传输就是把一个指定的存储区内容拷贝到另一个存储区空间。功能类
似于 C 语言内存拷贝函数 memcpy,利用 DMA 传输可以达到更高的传输效率,特别是
DMA 传输是不占用 CPU 的,可以节省很多 CPU 资源。

 

P:外设      M:内存

DMA 功能框图
STM32F4xx 系列的 DMA 可以实现外设寄存器与存储器之间或者存储器与存储器之间
传输三种模式,这要得益于 DMA 控制器是采样 AHB 主总线的,可以控制 AHB 总线矩阵
来启动 AHB 事务。

是数据传输的一条链路,每个DMA控制器有8条独立的数据流,每次传输的数据量最大为65535,如果数据的单位为字的话,那一次可以传输256KB。
通道每个数据流有8个通道选择,每个通道对应不同的DMA请求。

②仲裁器
一个 DMA 控制器对应 8 个数据流,数据流包含要传输数据的源地址、目标地址、数
据等等信息。如果我们需要同时使用同一个 DMA 控制器(DMA1 DMA2)多个外设请求
时,那必然需要同时使用多个数据流,那究竟哪一个数据流具有优先传输的权利呢?这就
需要仲裁器来管理判断了。
仲裁器管理数据流方法分为两个阶段。第一阶段属于软件阶段,我们在配置数据流时
可以通过寄存器设定它的优先级别,具体配置 DMA_SxCR 寄存器 PL[1:0]位,可以设置为
非常高、高、中和低四个级别。第二阶段属于硬件阶段,如果两个或以上数据流软件设置
优先级一样,则他们优先级取决于数据流编号,编号越低越具有优先权,比如数据流 2
先级高于数据流 3

FIFO
每个数据流都独立拥有四级 32 FIFO(先进先出存储器缓冲区)DMA 传输具有 FIFO
模式和直接模式。
直接模式在每个外设请求都立即启动对存储器传输。在直接模式下,如果 DMA 配置
为存储器到外设传输那 DMA 会见一个数据存放在 FIFO 内,如果外设启动 DMA 传输请求
就可以马上将数据传输过去。
FIFO 用于在源数据传输到目标地址之前临时存放这些数据。可以通过 DMA 数据流
xFIFO 控制寄存器 DMA_SxFCR FTH[1:0]位来控制 FIFO 的阈值,分别为 1/41/23/4
和满。如果数据存储量达到阈值级别时, FIFO 内容将传输到目标中。
FIFO 对于要求源地址和目标地址数据宽度不同时非常有用,比如源数据是源源不断的
字节数据,而目标地址要求输出字宽度的数据,即在实现数据传输时同时把原来 4 8
字节的数据拼凑成一个 32 位字数据。此时使用 FIFO 功能先把数据缓存起来,分别根据需
要输出数据。
FIFO 另外一个作用使用于突发(burst)传输。

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

2-节拍:即MSIZE的单位

④存储器端口、⑤外设端口
DMA 控制器实现双 AHB 主接口,更好利用总线矩阵和并行传输。 DMA 控制器通过
存储器端口和外设端口与存储器和外设进行数据传输,关系见图 21-2DMA 控制器的功
能是快速转移内存数据,需要一个连接至源数据地址的端口和一个连接至目标地址的端口。
DMA2(DMA 控制器 2)的存储器端口和外设端口都是连接到 AHB 总线矩阵,可以使用
AHB 总线矩阵功能。 DMA2 存储器和外设端口可以访问相关的内存地址,包括有内部
Flash、内部 SRAMAHB1 外设、 AHB2 外设、 APB2 外设和外部存储器空间。

DMA1 的存储区端口相比 DMA2 的要减少 AHB2 外设的访问权,同时 DMA1 外设端
口是没有连接至总线矩阵的,只有连接到 APB1 外设,所以 DMA1 不能实现存储器到存储
器传输。

 

⑥编程端口
AHB 从器件编程端口是连接至 AHB2 外设的。 AHB2 外设在使用 DMA 传输时需要相
关控制信号。

 

编程实战:

 

 flash  -> sram

通过const定义一个数组,这样的数组存放在常量区中,再定义一个全局数组不加const的,存放在全局区。

DAM初始化注意点:

先开启时钟不再赘述,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);

虽然上面红色部分命名是外设基地址,但是它代表的是源数据地址,后面的DIR指明了M TO M模式。

 

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

  /* 清除DMA数据流传输完成标志位 */
  DMA_ClearFlag(DMA_STREAM,DMA_FLAG_TCIF);
  
  /* 使能DMA数据流,开始DMA数据传输 */
  DMA_Cmd(DMA_STREAM, ENABLE);

我们看看清除数据流传输标志函数操作的哪两个寄存器:
void DMA_ClearFlag(DMA_Stream_TypeDef* DMAy_Streamx, uint32_t DMA_FLAG)
{
  DMA_TypeDef* DMAy;

  /* Check the parameters */
  assert_param(IS_DMA_ALL_PERIPH(DMAy_Streamx));
  assert_param(IS_DMA_CLEAR_FLAG(DMA_FLAG));

  /* Determine the DMA to which belongs the stream */
  if (DMAy_Streamx < DMA2_Stream0)
  {
    /* DMAy_Streamx belongs to DMA1 */
    DMAy = DMA1; 
  } 
  else 
  {
    /* DMAy_Streamx belongs to DMA2 */
    DMAy = DMA2; 
  }

  /* Check if LIFCR or HIFCR register is targeted */
  if ((DMA_FLAG & HIGH_ISR_MASK) != (uint32_t)RESET)
  {
    /* Set DMAy HIFCR register clear flag bits */
    DMAy->HIFCR = (uint32_t)(DMA_FLAG & RESERVED_MASK);
  }
  else 
  {
    /* Set DMAy LIFCR register clear flag bits */
    DMAy->LIFCR = (uint32_t)(DMA_FLAG & RESERVED_MASK);
  }    
}

 这个清除标志当时我看寄存器的还觉得迷糊:

还是不够细腻,这个软件写1的寄存器是LIFCR,对LIFCR寄存器相应的位写1,就可以吧TCFIx寄存器相应的位清零。

这个和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("\r\n USART1 DMA TX 测试 \r\n");
  
  /*填充将要发送的数据*/
  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.本次实验采用DMA2,串口1,那么是不是任意源和任意通道都可以呢?

通过官方资料我们可以知道,必须选择数据流7和通道4,程序自然也该写成如下形式:

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

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

4.

  /*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,或者突发模式,根据以后的业务需要做调整。

posted @ 2017-06-28 15:00  Crystal_Guang  阅读(1018)  评论(0编辑  收藏  举报