HAL库——UART的DMA接收中的一些问题

上篇简单的说明了如何通过DMA的方式接收UART数据,看着这个UART的DMA接收很简单,为了弄明白DMA中断和UART中断之间的关系,还是要看一下程序是如何完成这些看似简单的操作。首先先说一下整个接收的过程:

  • 启动UART的DMA接收(这里面还定义了DMA回调函数):HAL_UART_Receive_DMA
  • 接收完成后,请求DMA中断(判断中断的类型):HAL_DMA_IRQHandler
  • DMA接收完成回调函数(同时关闭了DMA接收):UART_DMAReceiveCplt
  • UART接收回调函数(处理数据,启动DMA接收):HAL_UART_RxCpltCallback

看着还是挺简单的4个过程,现在写出来的这4个过程是我看了整整一天才弄明白(不太聪明的样子)。当时最大的困惑就是怎么一会UART中断,一会DMA中断;一会UART回调,一会DMA回调。当时的想法是就是既然用了DMA接收,为什么不直接在DMA的中断和回调里面完成。下面详细的说明一下这个过程:


 

一:HAL_UART_Receive_DMA

这是第一个执行的函数,所以先搞它,需要重点关注回调函数的定义:

HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)                                                     
{
  uint32_t *tmp;

  /* Check that a Rx process is not already ongoing */
  if (huart->RxState == HAL_UART_STATE_READY)
  {
    if ((pData == NULL) || (Size == 0U))
    {
      return HAL_ERROR;
    }

    /* Process Locked */
    __HAL_LOCK(huart);

    huart->pRxBuffPtr = pData;                                       //看这里1
    huart->RxXferSize = Size;

    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->RxState = HAL_UART_STATE_BUSY_RX;

    /* Set the UART DMA transfer complete callback */
    huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt;           //看这里2

    /* Set the UART DMA Half transfer complete callback */
    huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt;        

    /* Set the DMA error callback */
    huart->hdmarx->XferErrorCallback = UART_DMAError;

    /* Set the DMA abort callback */
    huart->hdmarx->XferAbortCallback = NULL;

    /* Enable the DMA stream */                                     //看这里3
    tmp = (uint32_t *)&pData;
    HAL_DMA_Start_IT(huart->hdmarx, (uint32_t)&huart->Instance->DR, *(uint32_t *)tmp, Size);

    /* Clear the Overrun flag just before enabling the DMA Rx request: can be mandatory for the second transfer */
    __HAL_UART_CLEAR_OREFLAG(huart);

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

    /* Enable the UART Parity Error Interrupt */
    SET_BIT(huart->Instance->CR1, USART_CR1_PEIE);

    /* Enable the UART Error Interrupt: (Frame error, noise error, overrun error) */
    SET_BIT(huart->Instance->CR3, USART_CR3_EIE);

    /* Enable the DMA transfer for the receiver request by setting the DMAR bit
    in the UART CR3 register */
    SET_BIT(huart->Instance->CR3, USART_CR3_DMAR);

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

看着很长,但是这部分对理解整个过程很关键(重点关注”看这里“)。解释一下这段程序:

  • 函数的参数有三个,简单的说就是:UART的结构体 、接收数据的数组指针、接收数据的多少
  • 看这里1:将自定义的数组和size赋值给huart结构体,这样数据就会存储到我们定义的数组中了
  • 看这里2:自定义DMA接收完成的回调函数(通过函数指针的方式)
  • 看这里3:使能UART DMA数据流,就可以接收UART发送过来的数据了

 

二: HAL_DMA_IRQHandler

 DMA中断请求函数,每一种外设都有很多类型的中断,但是只有一个中断请求的入口,这样就显得很简洁,好,看一下这个函数说了什么:

void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
{
  uint32_t tmpisr;
  __IO uint32_t count = 0U;
  uint32_t timeout = SystemCoreClock / 9600U;

  /* calculate DMA base and stream number */
  DMA_Base_Registers *regs = (DMA_Base_Registers *)hdma->StreamBaseAddress;

  tmpisr = regs->ISR;

  /* Transfer Error Interrupt management ***************************************/
  if ((tmpisr & (DMA_FLAG_TEIF0_4 << hdma->StreamIndex)) != RESET)
  {
    if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_TE) != RESET)
    {
      /* Disable the transfer error interrupt */
      hdma->Instance->CR  &= ~(DMA_IT_TE);
      
      /* Clear the transfer error flag */
      regs->IFCR = DMA_FLAG_TEIF0_4 << hdma->StreamIndex;
      
      /* Update error code */
      hdma->ErrorCode |= HAL_DMA_ERROR_TE;
    }
  }
  /* FIFO Error Interrupt management ******************************************/
  if ((tmpisr & (DMA_FLAG_FEIF0_4 << hdma->StreamIndex)) != RESET)
  {
    if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_FE) != RESET)
    {
      /* Clear the FIFO error flag */
      regs->IFCR = DMA_FLAG_FEIF0_4 << hdma->StreamIndex;

      /* Update error code */
      hdma->ErrorCode |= HAL_DMA_ERROR_FE;
    }
  }
  /* Direct Mode Error Interrupt management ***********************************/
  if ((tmpisr & (DMA_FLAG_DMEIF0_4 << hdma->StreamIndex)) != RESET)
  {
    if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_DME) != RESET)
    {
      /* Clear the direct mode error flag */
      regs->IFCR = DMA_FLAG_DMEIF0_4 << hdma->StreamIndex;

      /* Update error code */
      hdma->ErrorCode |= HAL_DMA_ERROR_DME;
    }
  }
  /* Half Transfer Complete Interrupt management ******************************/
  if ((tmpisr & (DMA_FLAG_HTIF0_4 << hdma->StreamIndex)) != RESET)
  {
    if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_HT) != RESET)
    {
      /* Clear the half transfer complete flag */
      regs->IFCR = DMA_FLAG_HTIF0_4 << hdma->StreamIndex;
      
      /* Multi_Buffering mode enabled */
      if(((hdma->Instance->CR) & (uint32_t)(DMA_SxCR_DBM)) != RESET)
      {
        /* Current memory buffer used is Memory 0 */
        if((hdma->Instance->CR & DMA_SxCR_CT) == RESET)
        {
          if(hdma->XferHalfCpltCallback != NULL)
          {
            /* Half transfer callback */
            hdma->XferHalfCpltCallback(hdma);
          }
        }
        /* Current memory buffer used is Memory 1 */
        else
        {
          if(hdma->XferM1HalfCpltCallback != NULL)
          {
            /* Half transfer callback */
            hdma->XferM1HalfCpltCallback(hdma);
          }
        }
      }
      else
      {
        /* Disable the half transfer interrupt if the DMA mode is not CIRCULAR */
        if((hdma->Instance->CR & DMA_SxCR_CIRC) == RESET)
        {
          /* Disable the half transfer interrupt */
          hdma->Instance->CR  &= ~(DMA_IT_HT);
        }
        
        if(hdma->XferHalfCpltCallback != NULL)
        {
          /* Half transfer callback */
          hdma->XferHalfCpltCallback(hdma);
        }
      }
    }
  }
  /* Transfer Complete Interrupt management ***********************************/ // 看这里1
  if ((tmpisr & (DMA_FLAG_TCIF0_4 << hdma->StreamIndex)) != RESET)
  {
    if(__HAL_DMA_GET_IT_SOURCE(hdma, DMA_IT_TC) != RESET)
    {
      /* Clear the transfer complete flag */
      regs->IFCR = DMA_FLAG_TCIF0_4 << hdma->StreamIndex;
      
      if(HAL_DMA_STATE_ABORT == hdma->State)
      {
        /* Disable all the transfer interrupts */
        hdma->Instance->CR  &= ~(DMA_IT_TC | DMA_IT_TE | DMA_IT_DME);
        hdma->Instance->FCR &= ~(DMA_IT_FE);
        
        if((hdma->XferHalfCpltCallback != NULL) || (hdma->XferM1HalfCpltCallback != NULL))
        {
          hdma->Instance->CR  &= ~(DMA_IT_HT);
        }

        /* Clear all interrupt flags at correct offset within the register */
        regs->IFCR = 0x3FU << hdma->StreamIndex;

        /* Process Unlocked */
        __HAL_UNLOCK(hdma);

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

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

      if(((hdma->Instance->CR) & (uint32_t)(DMA_SxCR_DBM)) != RESET)
      {
        /* Current memory buffer used is Memory 0 */
        if((hdma->Instance->CR & DMA_SxCR_CT) == RESET)
        {
          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);                            //看这里2
          }
        }
      }
      /* Disable the transfer complete interrupt if the DMA mode is not CIRCULAR */
      else
      {
        if((hdma->Instance->CR & DMA_SxCR_CIRC) == RESET)
        {
          /* Disable the transfer complete interrupt */
          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);
        }
      }
    }
  }
  
  /* manage error case */
  if(hdma->ErrorCode != HAL_DMA_ERROR_NONE)
  {
    if((hdma->ErrorCode & HAL_DMA_ERROR_TE) != RESET)
    {
      hdma->State = HAL_DMA_STATE_ABORT;

      /* Disable the stream */
      __HAL_DMA_DISABLE(hdma);

      do
      {
        if (++count > timeout)
        {
          break;
        }
      }
      while((hdma->Instance->CR & DMA_SxCR_EN) != RESET);

      /* Process Unlocked */
      __HAL_UNLOCK(hdma);

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

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

这个更长了,因为这里面处理了DMA所有的中断请求,肯定要查询一遍寄存器,进来的是哪个中断,然后对症下药,我们用到的是DMA接收UART数据完成的中断,所以关注这一部分就可以了。解释一下这段程序:

  • 看这里1:那个位置就是这段程序通过查询各个状态寄存器,终于找到了原来是你小子(DMA接收完成中断)在敲门
  • 看这里2:到这里是这段程序搞明白了开哪个门你能进来,这个门就是对应的回调函数
  • 当然,仔细看了之后会发现,有两个门很相似 hdma->XferCpltCallback(hdma)和hdma->XferM1CpltCallback(hdma),这是因为DMA可以配置成双缓冲模式,两个缓冲器交替工作,由于本例没有使用这种模式,所以程序会直接开第一个门
  • 在上面说到了,通过函数指针的方式,定义了DMA的接收完成回调函数,所以程序执行hdma->XferCpltCallback(hdma)时就会调用定义的UART_DMAReceiveCplt

 

 三:UART_DMAReceiveCplt

 DMA接收完成回调函数,主要实现两个任务:关闭DMA的接收,调用UART接收回调函数(在DMA发送时,这部分会不一样,下篇再见)。好,看一下程序:

static void UART_DMAReceiveCplt(DMA_HandleTypeDef *hdma)
{
  UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;
  /* DMA Normal mode*/
  if ((hdma->Instance->CR & DMA_SxCR_CIRC) == 0U)
  {
    huart->RxXferCount = 0U;

    /* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */
    CLEAR_BIT(huart->Instance->CR1, USART_CR1_PEIE);
    CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

    /* Disable the DMA transfer for the receiver request by setting the DMAR bit
       in the UART CR3 register */
    CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);       //看这里1

    /* At end of Rx process, restore huart->RxState to Ready */
    huart->RxState = HAL_UART_STATE_READY;
  }
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)                 //看这里2
  /*Call registered Rx complete callback*/
  huart->RxCpltCallback(huart);
#else
  /*Call legacy weak Rx complete callback*/
  HAL_UART_RxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
}

这段程序比较短,基本都是干货,解释几个点:

  • 看这里1:关闭了UART的DMA接收模式
  • 看这里2:这是一段条件编译,(USE_HAL_UART_REGISTER_CALLBACKS == 1)是说允许用户动态编写回调函数,在UART部分没有自定义回调函数,所以执行else,系统默认会有一个回调弱函数,不过我们会重新写这个函数,所以会转到执行我们写的HAL_UART_RxCpltCallback。

 

四: HAL_UART_RxCpltCallback

UART接收回调函数,完成数据处理和开启下次DMA接收的任务,示例如下:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	RemoteDateProcess(rData);//数据处理
 
	HAL_UART_Receive_DMA(&huart1, rData, 18);//开启串口DMA的接收
}

总结:一图以蔽之

 

以上两篇就是整个UART的DMA接收过程啦~~下篇就简单写一下发送中的一些区别~~

 

  • 56
    点赞
  • 112
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
下面是基于 STM32 HAL UART2 DMA 初始化代码示例: ```c /* 定义 UART2 接收缓冲区大小 */ #define UART2_RX_BUF_SIZE 64 /* 定义 DMA 缓冲区大小 */ #define DMA_BUF_SIZE 128 /* 定义 UART2 句柄和 DMA 句柄 */ UART_HandleTypeDef huart2; DMA_HandleTypeDef hdma_usart2_rx; /* 定义 UART2 接收缓冲区和 DMA 缓冲区 */ uint8_t uart2_rx_buf[UART2_RX_BUF_SIZE]; uint8_t dma_rx_buf[DMA_BUF_SIZE]; void UART2_DMA_Init(void) { /* 使能 DMA 时钟 */ __HAL_RCC_DMA1_CLK_ENABLE(); /* 配置 DMA 句柄 */ hdma_usart2_rx.Instance = DMA1_Channel6; hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE; hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_usart2_rx.Init.Mode = DMA_CIRCULAR; hdma_usart2_rx.Init.Priority = DMA_PRIORITY_HIGH; hdma_usart2_rx.Init.Request = DMA_REQUEST_2; if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK) { /* 初始化 DMA 失败 */ Error_Handler(); } /* 关联 DMA 句柄到 UART2 */ __HAL_LINKDMA(&huart2, hdmarx, hdma_usart2_rx); /* 使能 UART2 时钟 */ __HAL_RCC_USART2_CLK_ENABLE(); /* 配置 UART2 */ huart2.Instance = USART2; huart2.Init.BaudRate = 115200; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_RX; huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart2.Init.OverSampling = UART_OVERSAMPLING_16; if (HAL_UART_Init(&huart2) != HAL_OK) { /* 初始化 UART2 失败 */ Error_Handler(); } /* 开启 UART2 接收 DMA */ HAL_UART_Receive_DMA(&huart2, dma_rx_buf, DMA_BUF_SIZE); } /* UART2 接收 DMA 断回调函数 */ void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { /* 处理接收到的数据 */ // ... } /* DMA 传输错误断回调函数 */ void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { /* 处理 DMA 错误 */ // ... } ``` 在上述代码,我们首先定义了 UART2 接收缓冲区大小和 DMA 缓冲区大小,然后定义了 UART2 句柄和 DMA 句柄以及这两个缓冲区。接着在 `UART2_DMA_Init()` 函数,我们首先使能 DMA 时钟,并配置 DMA 句柄。然后我们将 DMA 句柄关联到 UART2 句柄上,并使能 UART2 时钟,最后配置 UART2 句柄。最后,我们通过调用 `HAL_UART_Receive_DMA()` 函数开启 UART2 接收 DMA。在 DMA 传输完成后,`HAL_UART_RxCpltCallback()` 函数会被调用,我们可以在这个回调函数处理接收到的数据。如果 DMA 传输出现错误,`HAL_UART_ErrorCallback()` 函数会被调用。需要注意的是,这里的 DMA 模式是循环模式,即 DMA 缓冲区满后会自动重新从缓冲区头开始填充,因此我们需要在处理数据时注意判断 DMA 缓冲区指针的位置。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值