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處理空閑中斷,但是沒用找到,因此代碼稍顯的強侵入了.如果哪位朋友有更好的辦法,不妨留言交流一下.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM