stm32F407下DMA

STM32F4 最多有 2 个 DMA 控制器(DMA1 和 DMA2),共 16 个数据流(每个控制器 8 个),每一个 DMA 控制器都用于管理一个或多个外设的存储器访问请求。

每个数据流总共可以有多达 8个通道(或称请求)。每个数据流通道都有一个仲裁器,用于处理 DMA 请求间的优先级。

一、任务

  1. 使用DMA实现从内存至内存传输数据
  2. 使用DMA实现从内存至外设传输数据

二、几个概念

  1. 控制器:就是DMA1和DMA2,他们就是控制器
  2. 请求:每个控制器可以发送的
  3. 数据流:每个控制器 8 个
  4. 模式:Normal表单次传输,传输一次后终止传输,Circular表示循环传输,传输完成后又重新开始继续传输,不断循环永不停止
  5. 方向:3种,内存到内存,内存到外设,外设到内存
  6. 地址自增:串口发送数据是将数据不断存进串口的发送数据寄存器(USARTx_TDR)。所以外接的地址是不递增。而内存储器存储的是要发送的数据,所以地址指针要递增才能将所以的数据发送出去。
  7. 传输字宽:每次传输的数据长度,可以一个字节,两个字节(半字),四个字节(字),串口数据发送寄存器只能存储8bit,每次发送一个字节,所以数据长度选择Byte

三、配置

(一)usart串口配置

启用usart1串口,显示传输数据的内容

以下三项为基本配置,两种传输模式均使用此共同配置

  1. PA10<-->USART1.RX
  2. PA9<-->USART1.TX
  3. RCC和sys配置与之前一致,无需特别调整

四、实现

(一)内存至内存传输数据

存储器到存储器需要外设接口可以访问存储器,而仅 DMA2 的外设接口可以访问存储器,所以仅 DMA2 控制器支持存储器到存储器的传输,DMA1 不支持

1.DMA配置

  1. System Core->DMA->DMA2,新增(add)一个DMA
  2. DMA Request(请求):MEMTOMEM(内存到内存)
  3. Stream(通道):DMA2 Steam0-7均可,8个通道可用
  4. Direction(方向):Memory to Memory
  5. Mode(模式):normal(正常模式),即单次传输,发送完即停止
  6. Increment Address(指针自增模式):src memory和dst memory,都选择自增(内存至内存传输数据,都选择自增)
  7. Data width:byte(以字节长度传输数据),串口数据发送寄存器只能存储8bit,每次发送一个字节,所以数据长度选择Byte

2.实现

  1. 定义两个int数组变量,用于存储源和目标数组,分别命名为:src_buf和des_buf

    /*
    * 定义两个数组,实现DMA从内存到内存进行传输数据
    * src_buf:源数组,保存在内存中;
    * des_buf:目标数组,DMA将源数组拷贝至本数组,实现内�?-->内存复制功能
    *
    */
    const uint8_t src_buf[16]={
         1,2,3,4,
         5,6,7,8,
         9,10,11,12,
         13,14,15,16
    };
    uint8_t des_buf[16];
    
  2. 传输数据

    使用HAL_DMA_Start函数,将源数据内容传输至目标数组,并使用printf通过串口将目标数组发送至串口调试工具

  /*
   * 1. 内存到内存传输数据
   * HAL_DMA_Start函数实现内存至内存数据传�?
   */
  HAL_DMA_Start(&hdma_memtomem_dma2_stream0, (uint32_t)src_buf, (uint32_t)des_buf, 16);
  /*
   * 将源数组和目标数据,通过串口输出至串口调试工�?
   */
  for(int i=0;i<16;i++) {      printf("src_buf[%d]=%d,des_buf[%d]+1=%d\r\n",i,src_buf[i],i,des_buf[i]+1);
  }
  1. 显示结果如下
    src_buf[0]=1,des_buf[0]+1=1
    src_buf[1]=2,des_buf[1]+1=3
    src_buf[2]=3,des_buf[2]+1=4
    src_buf[3]=4,des_buf[3]+1=5
    src_buf[4]=5,des_buf[4]+1=6
    src_buf[5]=6,des_buf[5]+1=7
    src_buf[6]=7,des_buf[6]+1=8
    src_buf[7]=8,des_buf[7]+1=9
    src_buf[8]=9,des_buf[8]+1=10
    src_buf[9]=10,des_buf[9]+1=11
    src_buf[10]=11,des_buf[10]+1=12
    src_buf[11]=12,des_buf[11]+1=13
    src_buf[12]=13,des_buf[12]+1=14
    src_buf[13]=14,des_buf[13]+1=15
    src_buf[14]=15,des_buf[14]+1=16
    src_buf[15]=16,des_buf[15]+1=17
    

    (二)内存至外设传输数据

    通过DMA将内存中的内容,发送至usart串口外设,这里的发送,不再使用printf函数,而是使用函数通过串口直接发送

1.DMA配置

  1. Connectivity->usart1->DMA Settings,新增(add)一个DMA
  2. DMA Request(请求):USART1_TX,使用usart1的发送端口
  3. Stream(通道):DMA2_stream7,这里只能选择7通道,可以查询DMA2控制器的8个通道功能表看不同接口可以使用的通道
  4. Direction(方向):memory to pheriperal(内存至外设)
  5. Mode:nomarl
  6. Increment Address(指针自增模式):src选择自增,dst不选,从内存到外设,读取内存,地址在变化,而外设地址是不变的。
  7. Data width:byte

2.实现

DMA从内存向外设(usart1)传输数据

  1. 定义一个字符串,用于存储向外发送的内容

    const uint8_t txt_buf[]="welcome to bytetoy.cn!\r\n";
    
  2. 传输数据

    使用HAL_UART_Transmit_DMA函数发送数据

    /*
    * 2. DMA从内存向外设(usart1)传输数据
    * 这里需要注意,HAL_UART_Transmit_DMA传输数据后,需要关闭端口,否则串口会一直busy,后面的无法使用串口传输数据
    */
    HAL_UART_Transmit_DMA(&huart1,(uint8_t *)txt_buf, sizeof(txt_buf));
    
  3. 显示结果如下

    welcome to bytetoy.cn!
    

五、注意事项

(一)HAL_DMA_Start函数

此函数的第二、三参数是源地址和目标内存地址,地址是32位长度,使用时需要强制转换

而HAL_UART_Transmit_DMA函数的源内存地址是8位长度

HAL_DMA_Start(&hdma_memtomem_dma2_stream0, (uint32_t)src_buf, (uint32_t)des_buf, 16);

(二)HAL_UART_Transmit_DMA函数

此函数如果不开启串口中断,则程序只能发送一次数据,程序不能判断DMA传输是否完成,USART一直处于busy状态,此配置在usart1的NVIC Settings配置种,选择使能interupt

需要手动关闭DMA通道(HAL_UART_DMAStop),串口方可进行后面的传输。

其次是,串口传输数据比较慢,使用此函数传输数据较长时,传输后,需要delay一段时间,否则数据无法传输完成。

HAL_UART_Transmit_DMA(&huart1,(uint8_t *)txt_buf, sizeof(txt_buf));
  HAL_Delay(5000);
  HAL_UART_DMAStop(&huart1);

  for(int i=0;i<16;i++) {
      printf("src_buf[%d]=%d,des_buf[%d]+1=%d\r\n",i,src_buf[i],i,des_buf[i]+1);
  }

results matching ""

    No results matching ""

    results matching ""

      No results matching ""