HAL UART DMA 数据收发

UART使用DMA进行数据收发,实现功能,串口2发送指令到上位机,上位机返回数据给串口2,串口2收到数据后由串口1进行转发,该功能为实验功能

1、UART与DMA通道进行绑定

  1 void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
  2 {
  3 
  4     GPIO_InitTypeDef GPIO_InitStruct = {0};
  5     if(uartHandle->Instance==USART1)
  6     {
  7         /* USER CODE BEGIN USART1_MspInit 0 */
  8 
  9         /* USER CODE END USART1_MspInit 0 */
 10         /* USART1 clock enable */
 11         __HAL_RCC_USART1_CLK_ENABLE();
 12 
 13         __HAL_RCC_GPIOA_CLK_ENABLE();
 14         /**USART1 GPIO Configuration
 15         PA9     ------> USART1_TX
 16         PA10     ------> USART1_RX
 17         */
 18         GPIO_InitStruct.Pin = GPIO_PIN_9;
 19         GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
 20         GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
 21         HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 22 
 23         GPIO_InitStruct.Pin = GPIO_PIN_10;
 24         GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
 25         GPIO_InitStruct.Pull = GPIO_NOPULL;
 26         HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 27 
 28         /* USART1 DMA Init */
 29         /* USART1_TX Init */
 30         hdma_usart1_tx.Instance = DMA1_Channel4;
 31         hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
 32         hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
 33         hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
 34         hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
 35         hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
 36         hdma_usart1_tx.Init.Mode = DMA_NORMAL;
 37         hdma_usart1_tx.Init.Priority = DMA_PRIORITY_LOW;
 38         if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)
 39         {
 40             Error_Handler();
 41         }
 42 
 43         __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx);
 44 
 45         /* USART1 interrupt Init */
 46         HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
 47         HAL_NVIC_EnableIRQ(USART1_IRQn);
 48         /* USER CODE BEGIN USART1_MspInit 1 */
 49 
 50         /* USER CODE END USART1_MspInit 1 */
 51     }
 52     else if(uartHandle->Instance==USART2)
 53     {
 54         /* USER CODE BEGIN USART2_MspInit 0 */
 55 
 56         /* USER CODE END USART2_MspInit 0 */
 57         /* USART2 clock enable */
 58         __HAL_RCC_USART2_CLK_ENABLE();
 59 
 60         __HAL_RCC_GPIOA_CLK_ENABLE();
 61         /**USART2 GPIO Configuration
 62         PA2     ------> USART2_TX
 63         PA3     ------> USART2_RX
 64         */
 65         GPIO_InitStruct.Pin = GPIO_PIN_2;
 66         GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
 67         GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
 68         HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 69 
 70         GPIO_InitStruct.Pin = GPIO_PIN_3;
 71         GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
 72         GPIO_InitStruct.Pull = GPIO_NOPULL;
 73         HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 74 
 75         /* USART2 DMA Init */
 76         /* USART2_RX Init */
 77         hdma_usart2_rx.Instance = DMA1_Channel6;
 78         hdma_usart2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
 79         hdma_usart2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
 80         hdma_usart2_rx.Init.MemInc = DMA_MINC_ENABLE;
 81         hdma_usart2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
 82         hdma_usart2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
 83         hdma_usart2_rx.Init.Mode = DMA_NORMAL;
 84         hdma_usart2_rx.Init.Priority = DMA_PRIORITY_LOW;
 85         if (HAL_DMA_Init(&hdma_usart2_rx) != HAL_OK)
 86         {
 87             Error_Handler();
 88         }
 89 
 90         __HAL_LINKDMA(uartHandle,hdmarx,hdma_usart2_rx);
 91 
 92         /* USART2_TX Init */
 93         hdma_usart2_tx.Instance = DMA1_Channel7;
 94         hdma_usart2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
 95         hdma_usart2_tx.Init.PeriphInc = DMA_PINC_DISABLE;
 96         hdma_usart2_tx.Init.MemInc = DMA_MINC_ENABLE;
 97         hdma_usart2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
 98         hdma_usart2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
 99         hdma_usart2_tx.Init.Mode = DMA_NORMAL;
100         hdma_usart2_tx.Init.Priority = DMA_PRIORITY_LOW;
101         if (HAL_DMA_Init(&hdma_usart2_tx) != HAL_OK)
102         {
103             Error_Handler();
104         }
105 
106         __HAL_LINKDMA(uartHandle,hdmatx,hdma_usart2_tx);
107 
108         /* USART2 interrupt Init */
109         HAL_NVIC_SetPriority(USART2_IRQn, 0, 0);
110         HAL_NVIC_EnableIRQ(USART2_IRQn);
111         /* USER CODE BEGIN USART2_MspInit 1 */
112 
113         /* USER CODE END USART2_MspInit 1 */
114     }
115 }
__HAL_LINKDMA(uartHandle,hdmarx,hdma_usartx_xx)为UART与DMA绑定核心代码

2、数据发送与接收

2.1 DMA中断进行数据收发

2.1.1 DMA中断

在HAL使用DMA方式进行串口数据传输时,DMA全局中断模式是必需打开的,因此在DMA方式进行数据传输时(收,发),在数据传输过半,完成均会触发DMA中断

void DMA1_Channel6_IRQHandler(void)
{
    /* USER CODE BEGIN DMA1_Channel6_IRQn 0 */

    /* USER CODE END DMA1_Channel6_IRQn 0 */
    HAL_DMA_IRQHandler(&hdma_usart2_rx);
    /* USER CODE BEGIN DMA1_Channel6_IRQn 1 */

    /* USER CODE END DMA1_Channel6_IRQn 1 */
}

/**
  * @brief This function handles DMA1 channel7 global interrupt.
  */
void DMA1_Channel7_IRQHandler(void)
{
    /* USER CODE BEGIN DMA1_Channel7_IRQn 0 */

    /* USER CODE END DMA1_Channel7_IRQn 0 */
    HAL_DMA_IRQHandler(&hdma_usart2_tx);
    /* USER CODE BEGIN DMA1_Channel7_IRQn 1 */

    /* USER CODE END DMA1_Channel7_IRQn 1 */
}
HAL_DMA_IRQHandler(&hdma_usart2_tx)会根据中断标识,调用传输过半/完成/错误,回调函数,如下: 
void HAL_DMA_IRQHandler(DMA_HandleTypeDef *hdma)
{
  uint32_t flag_it = hdma->DmaBaseAddress->ISR;
  uint32_t source_it = hdma->Instance->CCR;
  
  /* Half Transfer Complete Interrupt management ******************************/
  if (((flag_it & (DMA_FLAG_HT1 << hdma->ChannelIndex)) != RESET) && ((source_it & DMA_IT_HT) != RESET))
  {
    /* Disable the half transfer interrupt if the DMA mode is not CIRCULAR */
    if((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U)
    {
      /* Disable the half transfer interrupt */
      __HAL_DMA_DISABLE_IT(hdma, DMA_IT_HT);
    }
    /* Clear the half transfer complete flag */
    __HAL_DMA_CLEAR_FLAG(hdma, __HAL_DMA_GET_HT_FLAG_INDEX(hdma));

    /* DMA peripheral state is not updated in Half Transfer */
    /* but in Transfer Complete case */

    if(hdma->XferHalfCpltCallback != NULL)
    {
      /* Half transfer callback */
      hdma->XferHalfCpltCallback(hdma);//DMA传输过半回调
    }
  }

  /* Transfer Complete Interrupt management ***********************************/
  else if (((flag_it & (DMA_FLAG_TC1 << hdma->ChannelIndex)) != RESET) && ((source_it & DMA_IT_TC) != RESET))
  {
    if((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U)
    {
      /* Disable the transfer complete and error interrupt */
      __HAL_DMA_DISABLE_IT(hdma, DMA_IT_TE | DMA_IT_TC);  

      /* Change the DMA state */
      hdma->State = HAL_DMA_STATE_READY;
    }
    /* Clear the transfer complete flag */
      __HAL_DMA_CLEAR_FLAG(hdma, __HAL_DMA_GET_TC_FLAG_INDEX(hdma));

    /* Process Unlocked */
    __HAL_UNLOCK(hdma);

    if(hdma->XferCpltCallback != NULL)
    {
      /* Transfer complete callback */
      hdma->XferCpltCallback(hdma);//DMA传输完成回调
    }
  }

  /* Transfer Error Interrupt management **************************************/
  else if (( RESET != (flag_it & (DMA_FLAG_TE1 << hdma->ChannelIndex))) && (RESET != (source_it & DMA_IT_TE)))
  {
    /* When a DMA transfer error occurs */
    /* A hardware clear of its EN bits is performed */
    /* Disable ALL DMA IT */
    __HAL_DMA_DISABLE_IT(hdma, (DMA_IT_TC | DMA_IT_HT | DMA_IT_TE));

    /* Clear all flags */
    hdma->DmaBaseAddress->IFCR = (DMA_ISR_GIF1 << hdma->ChannelIndex);

    /* Update error code */
    hdma->ErrorCode = HAL_DMA_ERROR_TE;

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

    /* Process Unlocked */
    __HAL_UNLOCK(hdma);

    if (hdma->XferErrorCallback != NULL)
    {
      /* Transfer error callback */
      hdma->XferErrorCallback(hdma);//传输错误回调
    }
  }
  return;
}

DMA传输中断的回调函数是在哪被设置的呢?被这个问题困扰了几个小时,原来是在DMA发送/接收数据函数中默认设置的,如下:

HAL_UART_Transmit_DMA(&huart2,cmd1,8);
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  uint32_t *tmp;

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

    /* Process Locked */
    __HAL_LOCK(huart);

    huart->pTxBuffPtr = pData;
    huart->TxXferSize = Size;
    huart->TxXferCount = Size;

    huart->ErrorCode = HAL_UART_ERROR_NONE;
    huart->gState = HAL_UART_STATE_BUSY_TX;

    /* Set the UART DMA transfer complete callback */
    huart->hdmatx->XferCpltCallback = UART_DMATransmitCplt;

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

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

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

    /* Enable the UART transmit DMA channel */
    tmp = (uint32_t *)&pData;
    HAL_DMA_Start_IT(huart->hdmatx, *(uint32_t *)tmp, (uint32_t)&huart->Instance->DR, Size);

    /* Clear the TC flag in the SR register by writing 0 to it */
    __HAL_UART_CLEAR_FLAG(huart, UART_FLAG_TC);

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

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

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

 

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;
    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;

    /* 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 channel */
    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;
  }
}

 

就是说:只要你使用DMA发送/接收数据,就会自动设置DMA中断回调函数,👍

转到 UART_DMATransmitCplt等三个函数的定义看一下

static void UART_DMATransmitCplt(DMA_HandleTypeDef *hdma)
{
  UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;
  /* DMA Normal mode*/
  if ((hdma->Instance->CCR & DMA_CCR_CIRC) == 0U)
  {
    huart->TxXferCount = 0x00U;

    /* Disable the DMA transfer for transmit request by setting the DMAT bit
       in the UART CR3 register */
    CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAT);

    /* Enable the UART Transmit Complete Interrupt */
    SET_BIT(huart->Instance->CR1, USART_CR1_TCIE);

  }
  /* DMA Circular mode */
  else
  {
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
    /*Call registered Tx complete callback*/
    huart->TxCpltCallback(huart);
#else
    /*Call legacy weak Tx complete callback*/
    HAL_UART_TxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
  }
}

可以看的对应传输完成中断,根据DMA数据传输模式的不同(正常/循环),会执行不同的代码操作.

正常模式(一次传输),在DMA传输完成后会触发UART的 USART_CR1_TCIE 中断

循环模式,会调用huart的或用户重写的 TxCpltCallback 函数

在我的程序中DMA都被配置为正常模式,因此要处理DMA数据发送和接收完成的事件(中断),应该在串口中断处理程序中进行.循环模式应该可以通过指定

huart->TxCpltCallback函数或重写HAL_UART_TxCpltCallback(huart)函数来处理发送/接收完成事件.

2.1.2 UART中断中处理数据接收/发送完成事件

void USART2_IRQHandler(void)
{
    /* USER CODE BEGIN USART2_IRQn 0 */

    /* USER CODE END USART2_IRQn 0 */
    HAL_UART_IRQHandler(&huart2);
    /* USER CODE BEGIN USART2_IRQn 1 */

    /* USER CODE END USART2_IRQn 1 */
}
HAL_UART_IRQHandler(&huart2) 串口2中断处理函数,内容如下:
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart)
{
  uint32_t isrflags   = READ_REG(huart->Instance->SR);
  uint32_t cr1its     = READ_REG(huart->Instance->CR1);
  uint32_t cr3its     = READ_REG(huart->Instance->CR3);
  uint32_t errorflags = 0x00U;
  uint32_t dmarequest = 0x00U;

  /* If no error occurs */
  errorflags = (isrflags & (uint32_t)(USART_SR_PE | USART_SR_FE | USART_SR_ORE | USART_SR_NE));
  if (errorflags == RESET)
  {
    /* UART in mode Receiver -------------------------------------------------*/
    if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
    {
      UART_Receive_IT(huart);
      return;
    }
  }

  /* If some errors occur */
  if ((errorflags != RESET) && (((cr3its & USART_CR3_EIE) != RESET) || ((cr1its & (USART_CR1_RXNEIE | USART_CR1_PEIE)) != RESET)))
  {
    /* UART parity error interrupt occurred ----------------------------------*/
    if (((isrflags & USART_SR_PE) != RESET) && ((cr1its & USART_CR1_PEIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_PE;
    }

    /* UART noise error interrupt occurred -----------------------------------*/
    if (((isrflags & USART_SR_NE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_NE;
    }

    /* UART frame error interrupt occurred -----------------------------------*/
    if (((isrflags & USART_SR_FE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_FE;
    }

    /* UART Over-Run interrupt occurred --------------------------------------*/
    if (((isrflags & USART_SR_ORE) != RESET) && ((cr3its & USART_CR3_EIE) != RESET))
    {
      huart->ErrorCode |= HAL_UART_ERROR_ORE;
    }

    /* Call UART Error Call back function if need be --------------------------*/
    if (huart->ErrorCode != HAL_UART_ERROR_NONE)
    {
      /* UART in mode Receiver -----------------------------------------------*/
      if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
      {
        UART_Receive_IT(huart);
      }

      /* If Overrun error occurs, or if any error occurs in DMA mode reception,
         consider error as blocking */
      dmarequest = HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR);
      if (((huart->ErrorCode & HAL_UART_ERROR_ORE) != RESET) || dmarequest)
      {
        /* Blocking error : transfer is aborted
           Set the UART state ready to be able to start again the process,
           Disable Rx Interrupts, and disable Rx DMA request, if ongoing */
        UART_EndRxTransfer(huart);

        /* Disable the UART DMA Rx request if enabled */
        if (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))
        {
          CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);

          /* Abort the UART DMA Rx channel */
          if (huart->hdmarx != NULL)
          {
            /* Set the UART DMA Abort callback :
               will lead to call HAL_UART_ErrorCallback() at end of DMA abort procedure */
            huart->hdmarx->XferAbortCallback = UART_DMAAbortOnError;
            if (HAL_DMA_Abort_IT(huart->hdmarx) != HAL_OK)
            {
              /* Call Directly XferAbortCallback function in case of error */
              huart->hdmarx->XferAbortCallback(huart->hdmarx);
            }
          }
          else
          {
            /* Call user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
            /*Call registered error callback*/
            huart->ErrorCallback(huart);
#else
            /*Call legacy weak error callback*/
            HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
          }
        }
        else
        {
          /* Call user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
          /*Call registered error callback*/
          huart->ErrorCallback(huart);
#else
          /*Call legacy weak error callback*/
          HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
        }
      }
      else
      {
        /* Non Blocking error : transfer could go on.
           Error is notified to user through user error callback */
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
        /*Call registered error callback*/
        huart->ErrorCallback(huart);
#else
        /*Call legacy weak error callback*/
        HAL_UART_ErrorCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */

        huart->ErrorCode = HAL_UART_ERROR_NONE;
      }
    }
    return;
  } /* End if some error occurs */

  /* UART in mode Transmitter ------------------------------------------------*/
  if (((isrflags & USART_SR_TXE) != RESET) && ((cr1its & USART_CR1_TXEIE) != RESET))
  {
    UART_Transmit_IT(huart);
    return;
  }

  /* UART in mode Transmitter end --------------------------------------------*/
  if (((isrflags & USART_SR_TC) != RESET) && ((cr1its & USART_CR1_TCIE) != RESET))
  {
    UART_EndTransmit_IT(huart);
    return;
  }
}

代码很多,实际仅是根据UART是接收模式还是发送模式来进行中断处理函数调用.

UART是如何切换接收/发送模式的,开始我也没用搞明白,后来想了下在调用DMA进行数据发送时,串口就应该时被设置为发送模式,相反调用DMA接收时,UART就切换为接收模式.

HAL_UART_Transmit_DMA(&huart2,cmd1,8);//DMA方式通过UART2发送数据,这样UART2就被切换到发送模式
HAL_UART_Receive_DMA(huart,Usart2_Rx_Buffer,USART2_RX_BUFFER_SIZE);//开启UART2的DMA数据接收,UART2切换为接收模式

2.1.3 UART接收/发送模式下,执行的处理程序

接收:UART_Receive_IT(huart);

发送:UART_EndTransmit_IT(huart);

UART_EndTransmit_IT 函数,如下:

static HAL_StatusTypeDef UART_EndTransmit_IT(UART_HandleTypeDef *huart)
{
  /* Disable the UART Transmit Complete Interrupt */
  __HAL_UART_DISABLE_IT(huart, UART_IT_TC);

  /* Tx process is ended, restore huart->gState to Ready */
  huart->gState = HAL_UART_STATE_READY;

#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
  /*Call registered Tx complete callback*/
  huart->TxCpltCallback(huart);
#else
  /*Call legacy weak Tx complete callback*/
  HAL_UART_TxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */

  return HAL_OK;
}

核心逻辑就是调用传输完成回调函数

因此对于DMA数据发送,中断发生及处理的过程如下:

 

 2.1.4 完整的DMA UART数据发送处理程序如下

main.c

int main(void)
{
  /* USER CODE BEGIN 1 */
    extern Worker_TypeDef Worker;
  /* USER CODE END 1 */
  

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
uint8_t cmd1[] = {0x01,0x04,0x00,0x00,0x00,0x05,0x30,0x09};
  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_TIM2_Init();
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
HAL_UART_Transmit_DMA(&huart2,cmd1,8);
                HAL_Delay(500);
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
View Code

uart.c

/* USER CODE BEGIN 1 */


/********************************************************************
串口发送完成中断回调
********************************************************************/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
   if(huart == &huart2)                                   //判断是否是串口1
   {
                //切换485芯片为接收模式
        HAL_GPIO_TogglePin(UART2_CTL_GPIO_Port, UART2_CTL_Pin);
                //切换uart2到接收模式
                HAL_UART_Receive_DMA(huart,Usart2_Rx_Buffer,USART2_RX_BUFFER_SIZE);
   }
}

/* USER CODE END 1 */

2.2 DMA数据接收及处理

这一部分功能的目的是在DMA模式下,UART2接收完数据后,马上将数据从UART1发出去

2.2.1 uart.c中定义DMA RX缓冲区

/* USER CODE BEGIN 0 */
#define USART2_RX_BUFFER_SIZE 40

uint8_t Usart2_Rx_Buffer[USART2_RX_BUFFER_SIZE] = {0x00};

/* USER CODE END 0 */

2.2.2 开启UART接收模式

在DMA数据发送完成后,会立即切换UART为接收模式

2.2.3 DMA UART 数据接收

DMA数据发送/接收过半,完成均会产生DMA终端,RX正在DMA通道6上引发中断,TX在通道7上引发中断,虽然通道不同,但中断逻辑与DMA中断处理程序完全一样.

只是发送/接收数据设置的中断回调函数不同.HAL_UART_Receive_DMA 接收函数中设置的中断回调函数为:

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

    /* 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;
UART_DMAReceiveCplt回调函数内容如下:
static void UART_DMAReceiveCplt(DMA_HandleTypeDef *hdma)
{
  UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;
  /* DMA Normal mode*/
  if ((hdma->Instance->CCR & DMA_CCR_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);

    /* At end of Rx process, restore huart->RxState to Ready */
    huart->RxState = HAL_UART_STATE_READY;
  }
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
  /*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 */
}

这里可以看到,接收模式下不区分是否为循环状态,也不会再次触发UART中断,而是直接调用 RxCpltCallback 接收完成回调函数

因此DMA数据接收及处理过程如下:

 

 2.2.4 完整的DMA数据接入代码如下:

uart.c

/* USER CODE BEGIN 1 */

/********************************************************************
串口发送完成中断回调
********************************************************************/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
   if(huart == &huart2)                                   //判断是否是串口1
   {
                //切换485芯片为接收模式
        HAL_GPIO_TogglePin(UART2_CTL_GPIO_Port, UART2_CTL_Pin);
                //切换uart2到接收模式
                HAL_UART_Receive_DMA(huart,Usart2_Rx_Buffer,USART2_RX_BUFFER_SIZE);
   }
}
/********************************************************************
串口接收完成中断回调
********************************************************************/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
        if(huart == &huart2)//判断是否是串口1
    {
        uint8_t temp  =  __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);// 获取DMA中未使用的字节个数
        uint8_t length = USART2_RX_BUFFER_SIZE - temp;
        HAL_UART_Transmit_DMA(&huart1,Usart2_Rx_Buffer,length);
    }
}
/* USER CODE END 1 */

仅添加红色部分代就可处理数据传输完成中断事件👍

 

3、DMA中断数据接收的问题

在进行DMA中断方式数据接收时,当数据接收完成时会调用 HAL_UART_RxCpltCallback 回调函数,但这里所谓的数据接收完成是 "Rx buffer接收缓冲区满" .

因此上面的接收的例子中,只有 Usart2_Rx_Buffer装满40个字节才会调用 HAL_UART_RxCpltCallback.很多时候,我们很难一次将缓冲区填满,除非进行固定长度的数据收发.对于任意长度的数据接收,上面的程序就不能满足需要了.

 要实现任意长度的数据接收(不超出Rx缓冲区大小),需要对以上程序进行如下改造:

3.1 屏蔽串口接收完成回调函数 HAL_UART_RxCpltCallback,因为需要在数据未填满缓冲区的情况下进行数据处理

3.2开启 UART 空闲中断,实现数据接收完成时触发空闲中断

main.c

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */
  

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_TIM2_Init();
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
  __HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);
  /* USER CODE END 2 */

3.3 编写自定义串口空闲中断处理程序,并在UART中断除程序中调用

stm32f1xx_it.c

void My_UART_IRQHandler(UART_HandleTypeDef *huart)
{
    if(huart == &huart2)                                   //判断是否是串口1
    {
        if(RESET != __HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE))   //判断是否是空闲中断
        {
            __HAL_UART_CLEAR_IDLEFLAG(huart);                     //清楚空闲中断标志(否则会一直不断进入中断)
                        HAL_UART_DMAStop(huart);
                        //HAL_UART_DMAResume
            uint8_t temp  =  __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);// 获取DMA中未传输的数据个数
            uint8_t length = USART2_RX_BUFFER_SIZE - temp;
            HAL_UART_Transmit_DMA(&huart1,Usart2_Rx_Buffer,length);
        }
    }
}
/**
  * @brief This function handles USART2 global interrupt.
  */
void USART2_IRQHandler(void)
{
    /* USER CODE BEGIN USART2_IRQn 0 */
        My_UART_IRQHandler(&huart2);
    /* USER CODE END USART2_IRQn 0 */
    HAL_UART_IRQHandler(&huart2);
    /* USER CODE BEGIN USART2_IRQn 1 */

    /* USER CODE END USART2_IRQn 1 */
}

3.4 总结

这样一来数据接收完成后,触发UART串口空闲中断,在 USART2_IRQHandler 中对空闲中断进行处理(获取数据转发出去).

这样虽然实现了任意长度的数据接收,但是本人觉得美中不足之处在于,UART空闲中断的处理方式上.HAL库在DMA中断的处理上采用了回调callback的方式,本来也想是否HAL_UART_IRQHandler(&huart2) 也支持callback处理空闲中断,但是没用找到,因此代码稍显的强侵入了.如果哪位朋友有更好的办法,不妨留言交流一下.

 

posted @ 2019-10-19 01:04  DW039  阅读(7694)  评论(2编辑  收藏  举报