STM32 DMA全面解析:基于CubeMX的HAL库实现详解

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • 一、stm32 dma是什么?
  • 二、使用步骤
  • 总结

  • 前言

    本文章主要介绍基于stm32 dma 的作用 和使用方法


    一、DMA是什么?

    STM32的DMA(Direct Memory Access)中文 :直接 储存器 访问,是一种用于在微控制器内部实现高速数据传输的功能。它允许外设或内存之间的数据传输绕过CPU,从而提高效率和性能。DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输

    DMA的好处
            DMA用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU的干预,通过DMA数据可以快速地移动。这就节省了CPU的资源来做其他操作

    DMA的传输方式:

            1,内存到内存

            2,内存到外设

            3,外设到外设

            4,外设到内存

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

    STM32少个DMA资源?

    对于大容量的STM32芯片有2个DMA控制器 两个DMA控制器,DMA1有7个通道,DMA2有5个通道。
    每个通道都可以配置一些外设的地址。以f103c8t6为例只有一个DMA控制器。

    DMA通道:

    DMA工作系统框图:

    上方的框图,我们可以看到STM32内核,存储器,外设及DMA的连接,这些硬件最终通过各种各样的线连接到总线矩阵中,硬件结构之间的数据转移都经过总线矩阵的协调,使各个外设和谐的使用总线来传输数据。

    传输过程

    在发生一个事件后,外设向DMA控制器发送一个请求信号。DMA控制器根据通道的优先权处理请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即发送给它一个应答信号。当从DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA控制器同时撤销应答信号。DMA传输结束,如果有更多的请求时,外设可以启动下一个周期。

    总之,每次DMA传送由3个操作组成:

  • 从外设数据寄存器或当前外设/存储器地址寄存器读取数据

  • 源地址(DMA_CPARx寄存器):这是DMA传输的源地址,如果DMA从外设读取数据,DMA_CPARx寄存器会指定外设的数据寄存器地址。
  • 数据读取:DMA控制器会从这个地址读取数据,然后将数据存储到内存中(由DMA_CMARx寄存器指定的内存地址)。
  • 第一次传输的开始地址:在第一次传输时,数据的源地址是DMA_CPARx寄存器中指定的地址。
  • 将数据存储到外设数据寄存器或当前外设/存储器地址寄存器

  • 目标地址(DMA_CMARx寄存器):这是DMA传输的目标地址,如果DMA将数据写入外设,DMA_CMARx寄存器会指定目标外设的数据寄存器地址。
  • 数据写入:DMA控制器会将数据从内存(由DMA_CMARx寄存器指定的地址)写入外设。
  • 第一次传输的开始地址:在第一次传输时,数据的目标地址是DMA_CMARx寄存器中指定的地址。
  • 执行DMA_CNDTRx寄存器的递减操作

  • DMA_CNDTRx寄存器:这个寄存器包含了当前未完成的数据传输的数量。每次数据传输完成后,DMA_CNDTRx中的值会递减。
  • 递减操作:在每次传输完成后,DMA_CNDTRx寄存器的值会减少,直到传输计数值减到零,表示所有的数据传输已经完成。
  • DMA传输方式

    方法1:DMA_Mode_Normal,正常模式,

    当一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次
      
    方法2:DMA_Mode_Circular ,循环传输模式

    当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式

    仲裁器


    仲裁器的作用是确定各个DMA传输的优先级

    仲裁器根据通道请求的优先级来启动外设/存储器的访问。

    优先权管理分2个阶段:

    软件:每个通道的优先权可以在DMA_CCRx寄存器中设置,有4个等级

  • 最高优先级
  • 高优先级
  • 中等优先级
  • 低优先级;
  • 硬件:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优先权。比如:如果软件优先级相同,通道2优先于通道4。

    注意: 在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级。

  • DMA 传输通道
    每个通道都可以在有固定地址的外设寄存器和存储器地址之间执行DMA传输。DMA传输的数据 量是可编程的,大达到65535。包含要传输的数据项数量的寄存器,在每次传输后递减。

    可编程的数据量:
    外设和存储器的传输数据量可以通过DMA_CCRx寄存器中的PSIZE和MSIZE位编程。

    指针递增模式
    根据 DMA_SxCR 寄存器中 PINC 和 MINC 位的状态,外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值

    通过单个寄存器访问外设源或目标数据时,禁止递增模式十分有用。

    如果使能了递增模式,则根据在 DMA_SxCR 寄存器 PSIZE 或 MSIZE 位中编程的数据宽度,下一次传输的地址将是前一次传输的地址递增 1个数据宽度、2个数据宽度或 4个数据宽度。

    存储器到存储器模式
    DMA通道的操作可以在没有外设请求的情况下进行,这种操作就是存储器到存储器模式。

    当设置了DMA_CCRx寄存器中的MEM2MEM位之后,在软件设置了DMA_CCRx寄存器中的EN位启动DMA通道时,DMA传输将马上开始。当DMA_CNDTRx寄存器变为0时,DMA传输结束。存储器到存储器模式不能与循环模式同时使用

    DMA中断
    每个DMA通道都可以在DMA传输过半、传输完成和传输错误时产生中断。为应用的灵活性考虑,通过设置寄存器的不同位来打开这些中断。


    使没开启,我们也可以通过查询这些位来获得当前 DMA 传输的状态。这里我们常用的是 TCIFx位,即数据流 x 的 DMA 传输完成与否标志。

    可编程的数据传输宽度、对齐方式和数据大小端
    当PSIZE和MSIZE不相同时,DMA模块按照下图进行数据对齐。

    DMA的内存占用
    在STM32控制器中,芯片采用Cortex-MX架构,总线结构有了很大的优化,DMA占用另外的地址总线,并不会与CPU的系统总线发生冲突。也就是说,DMA的使用不会影响CPU的运行速度

    但是要注意:
    DMA 控制器和Cortex-M3核共享系统数据总线执行直接存储器数据传输。当CPU和DMA同时访问相同的目标(RAM或外设)时,DMA请求可能会停止 CPU访问系统总线达若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽。

    二、使用步骤

    1.初始化

  • 源地址(Source Address):数据传输的起始地址。
  • 目标地址(Destination Address):数据传输的目的地地址。
  • 数据传输方向(Direction):定义数据是从内存到外设,还是从外设到内存,或者内存到内存。
  • 传输大小(Data Size):每次传输的数据块的大小,比如字节、半字(2字节)或字(4字节)。
  • 传输模式(Transfer Mode):例如正常模式(一次传输)或循环模式(连续传输)。
  • 如图为cubemax的DMA的配置:

    包含了 源地址,目标地址,传输大小,传输模式,优先级。以此实现串口与内存之间通过DMA传输数据。

    生成的代码如下:

    1.DMA的初始化

    void MX_DMA_Init(void)
    {
    
      /* DMA controller clock enable */
      __HAL_RCC_DMA1_CLK_ENABLE();//dma使能
    
      /* DMA interrupt init */
      /* DMA1_Channel4_IRQn interrupt configuration */
      HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 0, 0);//设置通道4优先级
      HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);//使能通道4中断
      /* DMA1_Channel5_IRQn interrupt configuration */
      HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 0, 0);//设置通道4优先级
      HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);//使能通道5中断
    
    }
    
    

    2.UART的初始化 (对串口所需的gpio dma 中断进行配置)

    void MX_USART1_UART_Init(void)
    {
    
      /* USER CODE BEGIN USART1_Init 0 */
    
      /* USER CODE END USART1_Init 0 */
    
      /* USER CODE BEGIN USART1_Init 1 */
    
      /* USER CODE END USART1_Init 1 */
      huart1.Instance = USART1;
      huart1.Init.BaudRate = 9600;
      huart1.Init.WordLength = UART_WORDLENGTH_8B;
      huart1.Init.StopBits = UART_STOPBITS_1;
      huart1.Init.Parity = UART_PARITY_NONE;
      huart1.Init.Mode = UART_MODE_TX_RX;
      huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
      huart1.Init.OverSampling = UART_OVERSAMPLING_16;
      if (HAL_UART_Init(&huart1) != HAL_OK)
      {
        Error_Handler();
      }
      /* USER CODE BEGIN USART1_Init 2 */
    
      /* USER CODE END USART1_Init 2 */
    
    }
    
    void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
    {
    
      GPIO_InitTypeDef GPIO_InitStruct = {0};
      if(uartHandle->Instance==USART1)
      {
      /* USER CODE BEGIN USART1_MspInit 0 */
    
      /* USER CODE END USART1_MspInit 0 */
        /* USART1 clock enable */
        __HAL_RCC_USART1_CLK_ENABLE();
    
        __HAL_RCC_GPIOA_CLK_ENABLE();
        /**USART1 GPIO Configuration
        PA9     ------> USART1_TX
        PA10     ------> USART1_RX
        */
        GPIO_InitStruct.Pin = GPIO_PIN_9;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
        GPIO_InitStruct.Pin = GPIO_PIN_10;
        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
        GPIO_InitStruct.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    
        /* USART1 DMA Init */
        /* USART1_RX Init */
        hdma_usart1_rx.Instance = DMA1_Channel5;
        hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
        hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
        hdma_usart1_rx.Init.MemInc = DMA_MINC_DISABLE;
        hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
        hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
        hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;
        if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
        {
          Error_Handler();
        }
    
        __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);
    
        /* USART1_TX Init */
        hdma_usart1_tx.Instance = DMA1_Channel4;
        hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
        hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
        hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
        hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
        hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
        hdma_usart1_tx.Init.Mode = DMA_NORMAL;
        hdma_usart1_tx.Init.Priority = DMA_PRIORITY_LOW;
        if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)
        {
          Error_Handler();
        }
    
        __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx);
    
        /* USART1 interrupt Init */
        HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
        HAL_NVIC_EnableIRQ(USART1_IRQn);
      /* USER CODE BEGIN USART1_MspInit 1 */
    
      /* USER CODE END USART1_MspInit 1 */
      }
    }
    
    void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
    {
    
      if(uartHandle->Instance==USART1)
      {
      /* USER CODE BEGIN USART1_MspDeInit 0 */
    
      /* USER CODE END USART1_MspDeInit 0 */
        /* Peripheral clock disable */
        __HAL_RCC_USART1_CLK_DISABLE();
    
        /**USART1 GPIO Configuration
        PA9     ------> USART1_TX
        PA10     ------> USART1_RX
        */
        HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);
    
        /* USART1 DMA DeInit */
        HAL_DMA_DeInit(uartHandle->hdmarx);
        HAL_DMA_DeInit(uartHandle->hdmatx);
    
        /* USART1 interrupt Deinit */
        HAL_NVIC_DisableIRQ(USART1_IRQn);
      /* USER CODE BEGIN USART1_MspDeInit 1 */
    
      /* USER CODE END USART1_MspDeInit 1 */
      }
    }

    3.UART通过DMA触发中断的回调函数。

    void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
    {
    	uint8_t tx_str[] = "Data Transfer completed\r\n";
    	HAL_UART_Transmit(&huart1,tx_str,sizeof(tx_str),200);
    }

    4.main函数进行测试 

     while (1)
      {
    	   //send_Data(max30102_has_interrupt(&max30102));
        /* USER CODE END WHILE */
    			HAL_UART_Transmit_DMA(&huart1,Sent_Data,4);
    			HAL_UART_DMAResume(&huart1);
    		  HAL_Delay(1000);
        /* USER CODE BEGIN 3 */
      }

    5.结果如下


    总结

    DMA(Direct Memory Access)是一种在内存和外设之间直接传输数据的机制,它能够在不占用CPU资源的情况下完成数据传输。DMA用于提高数据传输的效率,减轻CPU负担,广泛应用于实时系统、数据采集等领域。

    作者:立志成为工程师的小菜狗

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 DMA全面解析:基于CubeMX的HAL库实现详解

    发表回复