22章DMA

DMA(Direct Memory Access)—直接存储器存取,是单片机的一个外设,它的主要功能是用来搬数据,但是不需要占用CPU,即在传输数据的时候,CPU 可以干其他的事情,好像是多线程一样。
数据传输支持从外设到存储器或者存储器到存储器,这里的存储器可以是SRAM 或者是FLASH。DMA 控制器包含了DMA1 和DMA2,其中DMA1 有7 个通道,DMA2 有5 个通道,这里的通道可以理解为传输数据的一种管道。要注意的是DMA2 只存在于大容量的单片机中。

DMA 功能框图

从编程的角度来看,我们只需掌握功能框图中的三部分内容即可,
DMA
DMA 请求
不同的DMA 控制器的通道对应着不同的外设请求,这决定了我们在软件编程上该怎么设置,
DMA1 有7 个通道,DMA2 有5 个通道具体见DMA 请求映像表。DMA1
DMA2
其中ADC3、SDIO 和TIM8 的DMA 请求只在大容量产品中存在,这个在具体项目时要注意。
虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。
仲裁器
发生多个DMA 通道请求时,仲裁器工作。
仲裁器管理DMA 通道请求分为两个阶段。

  1. 第一阶段属于软件阶段,可以在DMA_CCRx 寄存器中设置,有4 个等级:非常高、高、中和低四个优先级
  2. 第二阶段属于硬件阶段,如果两个或以上的DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道0 高于通道1。在大容量产品和互联型产品中,DMA1 控制器拥有高于DMA2 控制器的优先级。

DMA 数据配置
DMA 传输数据的方向有三个:从外设到存储器,从存储器到外设,从存储器到存储器。

  1. 外设到存储器
    当我们使用从外设到存储器传输时,以ADC 采集为例。DMA 外设寄存器的地址对应的就是ADC数据寄存器的地址,DMA 存储器的地址就是我们自定义的变量(用来接收存储AD 采集的数据)的地址。方向我们设置外设为源地址。
  2. 存储器到外设
    当我们使用从存储器到外设传输时,以串口向电脑端发送数据为例。DMA 外设寄存器的地址对应的就是串口数据寄存器的地址,DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。
  3. 存储器到存储器
    当我们使用从存储器到存储器传输时,以内部FLASH 向内部SRAM 复制数据为例。DMA 外设寄存器的地址对应的就是内部FLASH(我们这里把内部FALSH 当作一个外设来看)的地址,DMA存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部FLASH 的数据)的地址。方向我们设置外设(即内部FLASH)为源地址。跟上面两个不一样的是,这里需要把DMA_CCR 位14:MEM2MEM:存储器到存储器模式配置为1,启动M2M 模式。
  4. 要传多少,单位是什么
    以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由DMA_CNDTR配置,这是一个32 位的寄存器,一次最多只能传输65535 个数据。
    要想数据传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是8 位的,所以我们定义的要发送的数据也必须是8 位。外设的数据宽度由DMA_CCRx 的PSIZE[1:0] 配置,可以是8/16/32 位,存储器的数据宽度由DMA_CCRx 的MSIZE[1:0] 配置,可以是8/16/32 位。
    在DMA 控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置两边数据指针的增量模式。外设的地址指针由DMA_CCRx 的PINC 配置,存储器的地址指针由MINC 配置。以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地址指针就应该加1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定。
  5. 什么时候传输完成
    数据什么时候传输完成,我们可以通过查询标志位或者通过中断的方式来鉴别。每个DMA 通道在DMA 传输过半、传输完成和传输错误时都会有相应的标志位,如果使能了该类型的中断后,则会产生中断。有关各个标志位的详细描述请参考DMA 中断状态寄存器DMA_ISR 的详细描述。
    传输完成还分两种模式,是一次传输还是循环传输,一次传输很好理解,即是传输一次之后就停止,要想再传输的话,必须关断DMA 使能后再重新配置后才能继续传输。循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输,不断的重复。具体的由DMA_CCRx 寄存器的CIRC 循环模式位控制。

DMA 初始化结构体详解

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;
  1. DMA_PeripheralBaseAddr:外设地址,设定DMA_CPAR 寄存器的值;一般设置为外设的数据寄存器地址,如果是存储器到存储器模式则设置为其中一个存储器地址。
  2. DMA_Memory0BaseAddr:存储器地址,设定DMA_CMAR 寄存器值;一般设置为我们自定义存储区的首地址。
  3. DMA_DIR:传输方向选择,可选外设到存储器、存储器到外设。它设定DMA_CCR 寄存器的DIR[1:0] 位的值。这里并没有存储器到存储器的方向选择,当使用存储器到存储器时,只需要把其中一个存储器当作外设使用即可。
  4. DMA_BufferSize:设定待传输数据数目,初始化设定DMA_CNDTR 寄存器的值。
  5. DMA_PeripheralInc:如果配置为DMA_PeripheralInc_Enable,使能外设地址自动递增功能,它设定DMA_CCR 寄存器的PINC 位的值;一般外设都是只有一个数据寄存器,所以一般不会使能该位。
  6. DMA_MemoryInc:如果配置为DMA_MemoryInc_Enable,使能存储器地址自动递增功能,它设定DMA_CCR 寄存器的MINC 位的值;我们自定义的存储区一般都是存放多个数据的,所以要使能存储器地址自动递增功能。
  7. DMA_PeripheralDataSize:外设数据宽度,可选字节(8 位)、半字(16 位) 和字(32 位),它设定DMA_CCR 寄存器的PSIZE[1:0] 位的值。
  8. DMA_MemoryDataSize:存储器数据宽度,可选字节(8 位)、半字(16 位) 和字(32 位),它设定DMA_CCR 寄存器的MSIZE[1:0] 位的值。当外设和存储器之间传数据时,两边的数据宽度应该设置为一致大小。
  9. DMA_Mode:DMA 传输模式选择,可选一次传输或者循环传输,它设定DMA_CCR 寄存器的CIRC 位的值。例程我们的ADC 采集是持续循环进行的,所以使用循环传输模式。
  10. DMA_Priority:软件设置通道的优先级,有4 个可选优先级分别为非常高、高、中和低,它设定DMA_CCR 寄存器的PL[1:0] 位的值。DMA 通道优先级只有在多个DMA 通道同时使用时才有意义,如果是单个通道,优先级可以随便设置。
  11. DMA_M2M:存储器到存储器模式,使用存储器到存储器时用到,设定DMA_CCR 的位14MEN2MEN 即可启动存储器到存储器模式。

实例:DMA 存储器到存储器

要点步骤:

  1. 使能DMA 时钟;
  2. 配置DMA 数据参数;
  3. 使能DMA,进行传输;
  4. 等待传输完成,并对源数据和目标地址数据进行比较。

DMA 数据配置

 void DMA_Config(void)
 {
 DMA_InitTypeDef DMA_InitStructure;

 RCC_AHBPeriphClockCmd(DMA_CLOCK, ENABLE);// 开启DMA 时钟
 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)aSRC_Const_Buffer; // 设置源数据地址
 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)aDST_Buffer; // 设置目标地址
 // 方向:外设到存储器(这里的外设是内部的FLASH)
 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
 DMA_InitStructure.DMA_BufferSize = BUFFER_SIZE; // 传输大小
 // 外设(内部的FLASH)地址递增
 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable;
 // 内存地址递增
 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
 // 外设数据单位
 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word;
 // 内存数据单位
 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word;
 // DMA 模式,一次或者循环模式
 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
 //DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
 // 优先级:高
 DMA_InitStructure.DMA_Priority = DMA_Priority_High;
 // 使能内存到内存的传输
 DMA_InitStructure.DMA_M2M = DMA_M2M_Enable;
 // 配置DMA 通道
 DMA_Init(DMA_CHANNEL, &DMA_InitStructure);
 // 使能DMA
 DMA_Cmd(DMA_CHANNEL,ENABLE);
 }

调用RCC_AHBPeriphClockCmd 函数开启DMA 时钟,使用DMA 控制器之前必须开启对应的时钟。
源地址和目标地址使用之前定义的数组首地址,传输的数据量为宏BUFFER_SIZE 决定,源和目标地址指针地址递增,使用一次传输模式不能循环传输,因为只有一个DMA 通道,优先级随便设置,最后调用DMA_Init 函数完成DMA 的初始化配置。
DMA_ClearFlag 函数用于清除DMA 标志位,代码用到传输完成标志位,使用之前先清除传输完成标志位以免产生不必要干扰。DMA_ClearFlag 函数需要1 个形参,即事件标志位,可选有传输完成标志位、半传输标志位、FIFO 错误标志位、传输错误标志位等等,非常多,我们这里选择传输完成标志位,由宏DMA_FLAG_TC 定义。
DMA_Cmd 函数用于启动或者停止DMA 数据传输,它接收两个参数,第一个是DMA 通道,另外一个是开启ENABLE 或者停止DISABLE。

nt main(void)
{
  uint16_t i;
  /* 初始化USART */
  USART_Config(); 

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

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

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

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

USART_Config 函数定义在bsp_usart_dma.c 中,它完成USART 初始化配置,包括GPIO 初始化,USART 通信参数设置等等,具体可参考USART 章节讲解。
USARTx_DMA_Config 函数也是定义在bsp_usart_dma.c 中,之前我们已详细分析。
LED_GPIO_Config 函数定义在bsp_led.c 中,它完成RGB 彩色灯初始化配置,具体可参考GPIO章节讲解。
使用for 循环填充源数据,SendBuff[SENDBUFF_SIZE] 是定义在bsp_usart_dma.c 中的一个全局无符号8 位整数数组,是DMA 传输的源数据,在USART_DMA_Config 函数中已经被设置为存储器地址。
USART_DMACmd 函数:用于控制USART 的DMA 请求的启动和关闭。它接收三个参数,第一个参数用于设置串口外设,可以是USART1/2/3 和UART4/5 这5 个参数可选,第二个参数设置串口的具体DMA 请求,有串口发送请求USART_DMAReq_Tx 和接收请求USART_DMAReq_Rx 可选,第三个参数用于设置启动请求ENABLE 或者关闭请求DISABLE。运行该函数后USART 的DMA 发送传输就开始了,根据配置存储器的数据会发送到串口。
DMA 传输过程是不占用CPU 资源的,可以一边传输一次运行其他任务。

常用存储器介绍

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值