STM32H743 解決串口同時收發遇到的問題

博主在使用1.2版本的HAL庫開發STM32H743的串口7設備的時候,遇到了如下問題:
數據發送使用HAL_UART_Transmit進行發送,單獨測試發送的時候,發送正常。
接收則是HAL_UART_Receive_IT,逐字節進行接收並存放至數組,配合定時器進行不定長數據接收,單獨測試接收的時候,接收也正常。
然後博主這裏就把TX和RX短接,按理說在發送完成後的50ms以內就會打印接收到的數據(定時器設置的50ms溢出,即不定長數據間隔爲50ms),但是這裏並沒有看到輸出。

研究了下源碼,可以發現,發送的時候,會把串口狀態huart->gState標記爲發送忙HAL_UART_STATE_BUSY_TX並上鎖。

HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
  uint16_t* tmp;
  uint32_t tickstart = 0U;

  /* 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->ErrorCode = HAL_UART_ERROR_NONE;
    huart->gState = HAL_UART_STATE_BUSY_TX;

    /* Init tickstart for timeout managment*/
    tickstart = HAL_GetTick();

    huart->TxXferSize = Size;
    huart->TxXferCount = Size;
    while(huart->TxXferCount > 0U)
    {
      huart->TxXferCount--;
      if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TXE, RESET, tickstart, Timeout) != HAL_OK)
      {
        return HAL_TIMEOUT;
      }
      if ((huart->Init.WordLength == UART_WORDLENGTH_9B) && (huart->Init.Parity == UART_PARITY_NONE))
      {
        tmp = (uint16_t*) pData;
        huart->Instance->TDR = (*tmp & (uint16_t)0x01FFU);
        pData += 2U;
      }
      else
      {
        huart->Instance->TDR = (*pData++ & (uint8_t)0xFFU);
      }
    }
    if(UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, tickstart, Timeout) != HAL_OK)
    {
      return HAL_TIMEOUT;
    }

    /* At end of Tx process, restore huart->gState to Ready */
    huart->gState = HAL_UART_STATE_READY;

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

而接收中斷觸發後,中斷向量入口UART7_IRQHandler會直接調用HAL_UART_IRQHandler(&huart7)分析中斷請求,根據接收數據完成請求函數調用UART_Receive_IT(huart),注意這裏會把huart->RxState設置爲準備接收HAL_UART_STATE_READY並上鎖。

HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
{
  /* 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->RxXferCount = Size;

    /* Computation of UART mask to apply to RDR register */
    UART_MASK_COMPUTATION(huart);

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

    /* Process Unlocked */
    __HAL_UNLOCK(huart);

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

    /* Enable the UART Parity Error interupt and RX FIFO Threshold interrupt
       (if FIFO mode is enabled) or Data Register Not Empty interrupt
       (if FIFO mode is disabled).
    */
    if (READ_BIT(huart->Instance->CR1, USART_CR1_FIFOEN) != RESET)
    {
      SET_BIT(huart->Instance->CR1, USART_CR1_PEIE);
      SET_BIT(huart->Instance->CR3, USART_CR3_RXFTIE);
    }
    else
    {
      SET_BIT(huart->Instance->CR1, USART_CR1_PEIE | USART_CR1_RXNEIE);
    }

    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}

查看一下定義,可以發現這裏的一個設計缺陷,gState代表發送狀態,RxState代表接收狀態,通過這個狀態機來保證接收和發送過程的完整性,但是,只設計了一把鎖Lock,而鎖是用來保護髮送或者接收單個字節的完整性的,這樣就導致了在連續發送的過程中,處理接收來的數據發現串口設備被上鎖了,返回busy,從而丟失了發送的數據。

typedef struct
{
  USART_TypeDef            *Instance;        /*!< UART registers base address        */

  UART_InitTypeDef         Init;             /*!< UART communication parameters      */

  UART_AdvFeatureInitTypeDef AdvancedInit;   /*!< UART Advanced Features initialization parameters */

  uint8_t                  *pTxBuffPtr;      /*!< Pointer to UART Tx transfer Buffer */

  uint16_t                 TxXferSize;       /*!< UART Tx Transfer size              */

  __IO uint16_t            TxXferCount;      /*!< UART Tx Transfer Counter           */

  uint8_t                  *pRxBuffPtr;      /*!< Pointer to UART Rx transfer Buffer */

  uint16_t                 RxXferSize;       /*!< UART Rx Transfer size              */

  __IO uint16_t            RxXferCount;      /*!< UART Rx Transfer Counter           */

  uint16_t                 Mask;             /*!< UART Rx RDR register mask          */

  DMA_HandleTypeDef        *hdmatx;          /*!< UART Tx DMA Handle parameters      */

  DMA_HandleTypeDef        *hdmarx;          /*!< UART Rx DMA Handle parameters      */

  HAL_LockTypeDef           Lock;            /*!< Locking object                     */

  __IO HAL_UART_StateTypeDef    gState;      /*!< UART state information related to global Handle management
                                                  and also related to Tx operations.
                                                  This parameter can be a value of @ref HAL_UART_StateTypeDef */

  __IO HAL_UART_StateTypeDef    RxState;     /*!< UART state information related to Rx operations.
                                                  This parameter can be a value of @ref HAL_UART_StateTypeDef */

  __IO uint32_t             ErrorCode;       /*!< UART Error code                    */

}UART_HandleTypeDef;

因爲串口本身是全雙工,一個比較合理的設計方法就是再設計一把鎖進行保護,而網上的大部分解決方案是註釋掉調用鎖這個過程,個人是不想改動HAL庫的,於是自己寫發送函數和接收函數。

首先是串口初始化,沒有使用HAL_UART_Receive_IT設置接收buffer,而是直接使能接收中斷。

void MX_UART7_Init(void)
{

    huart7.Instance = UART7;
    huart7.Init.BaudRate = 115200;
    huart7.Init.WordLength = UART_WORDLENGTH_8B;
    huart7.Init.StopBits = UART_STOPBITS_1;
    huart7.Init.Parity = UART_PARITY_NONE;
    huart7.Init.Mode = UART_MODE_TX_RX;
    huart7.Init.HwFlowCtl = UART_HWCONTROL_NONE;
    huart7.Init.OverSampling = UART_OVERSAMPLING_16;
    huart7.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
    huart7.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
    if (HAL_UART_Init(&huart7) != HAL_OK)
    {
        Error_Handler();
    }
    __HAL_UART_ENABLE_IT(&huart7, UART_IT_RXNE);	//使能接收中斷

中斷服務函數這裏,因爲我只使能了接收中斷,所以只對接收中斷進行處理,其他中斷源直接清除。

void UART7_IRQHandler(void)
{
    char ch;
    if ((__HAL_UART_GET_FLAG(&huart7, UART_FLAG_RXNE) != RESET) &&
            (__HAL_UART_GET_IT_SOURCE(&huart7, UART_IT_RXNE) != RESET))
    {
        ch = huart7.Instance->RDR;
		__HAL_TIM_SET_COUNTER(&htim6, 0);
        if (USART7_RX_STA & 0x8000) return;         //上次接收完成的未處理,直接退出
        if (USART7_RX_STA == 0)                 	//長度爲0,接收到的是第一個字節,啓動定時器
        {
            __HAL_TIM_CLEAR_FLAG(&htim6, TIM_FLAG_UPDATE);
            HAL_TIM_Base_Start_IT(&htim6);
        }
        USART7_RX_BUF[USART7_RX_STA & 0x3FFF] = ch;
        USART7_RX_STA++;
        if (USART7_RX_STA > (USART_REC_LEN - 1))USART7_RX_STA = 0;
    }
    else
    {
        if (__HAL_UART_GET_FLAG(&huart7, UART_FLAG_ORE) != RESET)
        {
            __HAL_UART_CLEAR_OREFLAG(&huart7);
        }
        if (__HAL_UART_GET_FLAG(&huart7, UART_FLAG_NE) != RESET)
        {
            __HAL_UART_CLEAR_NEFLAG(&huart7);
        }
        if (__HAL_UART_GET_FLAG(&huart7, UART_FLAG_FE) != RESET)
        {
            __HAL_UART_CLEAR_FEFLAG(&huart7);
        }
        if (__HAL_UART_GET_FLAG(&huart7, UART_FLAG_PE) != RESET)
        {
            __HAL_UART_CLEAR_PEFLAG(&huart7);
        }
        if (__HAL_UART_GET_FLAG(&huart7, UART_FLAG_CTS) != RESET)
        {
            __HAL_UART_CLEAR_IT(&huart7, UART_FLAG_CTS);
        }
        if (__HAL_UART_GET_FLAG(&huart7, UART_FLAG_TXE) != RESET)
        {
            __HAL_UART_CLEAR_IT(&huart7, UART_FLAG_TXE);
        }
        if (__HAL_UART_GET_FLAG(&huart7, UART_FLAG_TC) != RESET)
        {
            __HAL_UART_CLEAR_IT(&huart7, UART_FLAG_TC);
        }
        if (__HAL_UART_GET_FLAG(&huart7, UART_FLAG_RXNE) != RESET)
        {
            __HAL_UART_CLEAR_IT(&huart7, UART_FLAG_RXNE);
        }
    }
}

發送過程就比較簡單了,等待發送完成繼續發送就行了。

void u7_printf(char* s)
{
	int i=0;
	while(s[i])
	{
		huart7.Instance->TDR = s[i];
		while (__HAL_UART_GET_FLAG(&huart7, UART_FLAG_TC) == RESET);
		i++;
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章