STM32H7——HAL库的DMA双缓冲配置使用

基于STM32H7 USART_DMA双缓冲配置

  • 一. 前言
  • 二.软硬件版本信息
  • 三.使用Cube MX配置工程
    • 1.配置时钟
    • 2.配置串口1
    • 3.生成工程
  • 四.配置生成的工程
    • 1.配置串口空闲中断
    • 2.配置DMA双缓存
      • 1).设置一个二维缓存数组
      • 2).配置DMA为回环模式
      • 3).启动双缓存
    • 3.设计中断
      • 1).设计串口空闲中断函数
      • 3).中断回调函数设计思路
      • 2).设计中断回调函数
  • 五.总结

一. 前言

现在ST出的芯片性能越来越强大,对应的程序也是越来越复杂,为了让芯片功能的开发更加的容易,他们推出了Cube MX这款用于配置相关芯片HAL库程序基础工程的一个不错的软件,虽然存在bug不过一直在完善,近些年来这个软件也是越发的人性化了,当然毕竟没有标准库发展的时间长,网络上对于HAL库的讲解和资料也不是很全面,新手入门用HAL显然不是很明智,而对于习惯了标准库却又想用性能更强的芯片的朋友这个HAL库太乱,我也是研究了好久才弄明白这个HAL库使用和配置的方法,我将以笔记的形式来记录每一种有意思的配置操作。这篇是关于DMA双缓冲接收的配置方案(发送大家可以试着配置一下),大家可以获取我上传的配置文件来更容易理解讲的内容.配置文件的下载链接

二.软硬件版本信息

STM32Cube Mx 5.30
Keil 5.29.0.0
STM32H750XB

三.使用Cube MX配置工程

1.配置时钟

STM32H7——HAL库的DMA双缓冲配置使用_第1张图片
在这里插入图片描述

2.配置串口1

STM32H7——HAL库的DMA双缓冲配置使用_第2张图片
加入DMA
STM32H7——HAL库的DMA双缓冲配置使用_第3张图片
使能串口中断
STM32H7——HAL库的DMA双缓冲配置使用_第4张图片

3.生成工程

STM32H7——HAL库的DMA双缓冲配置使用_第5张图片

STM32H7——HAL库的DMA双缓冲配置使用_第6张图片
div align=center

四.配置生成的工程

1.配置串口空闲中断

STM32H7——HAL库的DMA双缓冲配置使用_第7张图片

	__HAL_UART_ENABLE_IT(&huart1,UART_IT_IDLE);//使能串口空闲中断

只需这个一句就可以了,把这条语句放到串口使能函数后执行就可以。

2.配置DMA双缓存

1).设置一个二维缓存数组

在这里插入图片描述

2).配置DMA为回环模式

hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;

也可以在使用Cube MX配置DMA时直接配置成回环模式:
STM32H7——HAL库的DMA双缓冲配置使用_第8张图片

3).启动双缓存

	HAL_DMAEx_MultiBufferStart(&hdma_usart1_rx,USART1->RDR,(uint32_t)&Rx_Buffer[0][0],(uint32_t)&Rx_Buffer[1][0],Rx_Len);

这句话可以放到HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx)之后;

    hdma_usart1_rx.Instance = DMA1_Stream0;
    hdma_usart1_rx.Init.Request = DMA_REQUEST_USART1_RX;
    hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
    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;
    hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);
	HAL_DMAEx_MultiBufferStart(&hdma_usart1_rx,USART1->RDR,(uint32_t)&Rx_Buffer[0][0],(uint32_t)&Rx_Buffer[1][0],Rx_Len);

这里开始先解释一下配置的内容以及原因:
函数解释:HAL_DMAEx_MultiBufferStart();

/**
  * @brief  Starts the multi_buffer DMA Transfer.
  * @param  hdma      : pointer to a DMA_HandleTypeDef structure that contains
  *                     the configuration information for the specified DMA Stream.
  * @param  SrcAddress: The source memory Buffer address
  * @param  DstAddress: The destination memory Buffer address
  * @param  SecondMemAddress: The second memory Buffer address in case of multi buffer Transfer
  * @param  DataLength: The length of data to be transferred from source to destination
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_DMAEx_MultiBufferStart(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t SecondMemAddress, uint32_t DataLength)

SrcAddress:源内存缓冲区地址;
DstAddress:目标内存缓冲区地址;
SecondMemAddress:第二个内存缓冲区地址;
DataLength:从源传输到目标的数据长度;

我们在配置的时候将SrcAddress配置为USART1->RDR
stm32h750xx.h中对串口寄存器地址定义在了一个结构体中,
STM32H7——HAL库的DMA双缓冲配置使用_第9张图片
UART_HandleTypeDef结构体中引用了上述结构体
在这里插入图片描述
在使能串口时就已经把外设寄存器地址分配好了,串口传回的数据先在这个外设寄存器存储起来等待调用,所以源寄存器地址自然是配置时给外设分配的那个寄存器地址。

DstAddress和SecondMemAddress分别时两个缓存地址
DstAddress:这个对应的是Memory0缓存区的内容
SecondMemAddress:对应的是Memory1缓存的内容
这个Memory0和Memory1的切换时库里面已经配置好的了,对于中断来说始终串口中断只有1个,我们要每次判断现在用作传输缓存的是哪一个Memory,从而处理另一个Memory的数据。下面介绍如何设计中断函数。

DataLength设置一个接收长度
这个可以根据需要来定义。

3.设计中断

1).设计串口空闲中断函数

前提是先设计两个中断回调函数:

void (*dma_M0_rx_callback)(void);
void (*dma_M1_rx_callback)(void);
void dma_M0_callback(void);
void dma_M1_callback(void);

在使能空闲中断时将加入下列代码建立回调函数

	dma_M0_rx_callback=dma_M0_callback;
	dma_M1_rx_callback=dma_M1_callback;

如图:
STM32H7——HAL库的DMA双缓冲配置使用_第10张图片

然后将原来stm32h7xx_it.h中的HAL_UART_IRQHandler(&huart1);函数注释掉,然后将USART1_IRQHandler(void)函数改为下面的内容:

void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */

  /* USER CODE END USART1_IRQn 0 */
//  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
if(__HAL_UART_GET_IT_SOURCE(&huart1,UART_IT_IDLE)!=RESET) //检测是否发生空闲中断
	{
		__HAL_UART_CLEAR_IDLEFLAG(&huart1);//清除中断标志位
		
	/* Current memory buffer used is Memory 0 */
      if((((DMA_Stream_TypeDef   *)hdma_usart1_rx.Instance)->CR & DMA_SxCR_CT) == 0U)
	  {
		  __HAL_DMA_DISABLE(&hdma_usart1_rx);
	       /* Transfer complete Callback for memory1 */
		  if(dma_M1_rx_callback!=NULL)dma_M1_rx_callback();
		__HAL_DMA_ENABLE(&hdma_usart1_rx);
	  }
      /* Current memory buffer used is Memory 1 */
      else
      {
		  __HAL_DMA_DISABLE(&hdma_usart1_rx);
            if(dma_M0_rx_callback!=NULL)
			dma_M0_rx_callback();
			
       /* Transfer complete Callback for memory0 */
		  __HAL_DMA_ENABLE(&hdma_usart1_rx);
      }
	}
  /* USER CODE END USART1_IRQn 1 */
}
		

3).中断回调函数设计思路

这部分代码可以借鉴HAL内部的代码,在stm32h7xx_hal_dma.h中的void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)函数,其中有一段的配置如下:

    /* Transfer Complete Interrupt management ***********************************/
    if ((tmpisr_dma & (DMA_FLAG_TCIF0_4 << (hdma->StreamIndex & 0x1FU))) != 0U)
    {
      if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_TC) != 0U)
      {
        /* Clear the transfer complete flag */
        regs_dma->IFCR = DMA_FLAG_TCIF0_4 << (hdma->StreamIndex & 0x1FU);

        if(HAL_DMA_STATE_ABORT == hdma->State)
        {
          /* Disable all the transfer interrupts */
          ((DMA_Stream_TypeDef   *)hdma->Instance)->CR  &= ~(DMA_IT_TC | DMA_IT_TE | DMA_IT_DME);
          ((DMA_Stream_TypeDef   *)hdma->Instance)->FCR &= ~(DMA_IT_FE);

          if((hdma->XferHalfCpltCallback != NULL) || (hdma->XferM1HalfCpltCallback != NULL))
          {
            ((DMA_Stream_TypeDef   *)hdma->Instance)->CR  &= ~(DMA_IT_HT);
          }

          /* Clear all interrupt flags at correct offset within the register */
          regs_dma->IFCR = 0x3FUL << (hdma->StreamIndex & 0x1FU);

          /* Process Unlocked */
          __HAL_UNLOCK(hdma);

          /* Change the DMA state */
          hdma->State = HAL_DMA_STATE_READY;

          if(hdma->XferAbortCallback != NULL)
          {
            hdma->XferAbortCallback(hdma);
          }
          return;
        }

        if(((((DMA_Stream_TypeDef   *)hdma->Instance)->CR) & (uint32_t)(DMA_SxCR_DBM)) != 0U)
        {
          /* Current memory buffer used is Memory 0 */
          if((((DMA_Stream_TypeDef   *)hdma->Instance)->CR & DMA_SxCR_CT) == 0U)
          {
            if(hdma->XferM1CpltCallback != NULL)
            {
              /* Transfer complete Callback for memory1 */
              hdma->XferM1CpltCallback(hdma);
            }
          }
          /* Current memory buffer used is Memory 1 */
          else
          {
            if(hdma->XferCpltCallback != NULL)
            {
              /* Transfer complete Callback for memory0 */
              hdma->XferCpltCallback(hdma);
            }
          }
        }
        /* Disable the transfer complete interrupt if the DMA mode is not CIRCULAR */
        else
        {
          if((((DMA_Stream_TypeDef   *)hdma->Instance)->CR & DMA_SxCR_CIRC) == 0U)
          {
            /* Disable the transfer complete interrupt */
            ((DMA_Stream_TypeDef   *)hdma->Instance)->CR  &= ~(DMA_IT_TC);

            /* Process Unlocked */
            __HAL_UNLOCK(hdma);

            /* Change the DMA state */
            hdma->State = HAL_DMA_STATE_READY;
          }

          if(hdma->XferCpltCallback != NULL)
          {
            /* Transfer complete callback */
            hdma->XferCpltCallback(hdma);
          }
        }
      }
    }

我们只用其中的一段:

          /* Current memory buffer used is Memory 0 */
          if((((DMA_Stream_TypeDef   *)hdma->Instance)->CR & DMA_SxCR_CT) == 0U)
          {
            if(hdma->XferM1CpltCallback != NULL)
            {
              /* Transfer complete Callback for memory1 */
              hdma->XferM1CpltCallback(hdma);
            }
          }
          /* Current memory buffer used is Memory 1 */
          else
          {
            if(hdma->XferCpltCallback != NULL)
            {
              /* Transfer complete Callback for memory0 */
              hdma->XferCpltCallback(hdma);
            }
          }
        

将其中的回调函数换成我们自己定义的中断回调函数就可以了。(这个是一个很好的方法学习HAL库,本身他内部就已经给出了很多功能的程序设计,我们多查看库里面的内容对我们设计程序实际上帮助非常的大)
这个接收双缓存就已经配置好了。
设计思想:先判断现在的缓存区是Memory0还是Memory1,如果是0则处理1中的数据,如果是1则处理0中的信息,处理时不会影响另一个进行数据的接收

2).设计中断回调函数

STM32H7——HAL库的DMA双缓冲配置使用_第11张图片
这两个回调函数如何设计读者可以自行设计运行一下。

五.总结

我们在学习HAL库的时候要学会把他所封装的内容剖解开来进行对比学习,实际上和标准库的使用差不多,只不过这个搜索路径比较长,内容乱,整理一下就可以找出源头了!

大家在使用STM32H7的时候,串口+DMA接收数据的时候总是收到的和发送的内容不符合,主要就是Cache搞的鬼,之后的二到三篇将着重讲解这个问题该如何解决!

你可能感兴趣的:(HAL库学习笔记)