STM32 Uart 接收不定長數據

      前面講了Uart三種不同的方式接收數據,請參照《STM32 Uart及其配置》《STM32 Uart中斷接收》《STM32 Uart DMA方式接收數據》,但是,它們都需要指定數據的長度,但實際應用中,會出現不定長度的數據,比如,某些模塊的@命令,那麼,如何接收不定長度的數據呢?今天,我們就來扒一扒STM32 Uart 變長數據的接收。

      問題來了,變長數據包,我們如何確定數據包的長度?

      帶着問題思考,我們可以得出以下幾種思路:

      1. 制定嚴格的通信協議,帶有一串特殊字符的數據包頭、長度、校驗、包尾等,比如,一開始收到:0xAA-0xBB-0xCC-0xDD-0xEE,表示數據包頭,接下來兩個字節是數據包長度,接下來是數據包內容,接下來是校驗值,接下來是結尾。

      2. 使用定時器,判定在指定時間內沒有收到數據,就算一個數據包結束。可以在收到一個數據後,開啓定時器,定時器的超時時間設定爲1~2個數據之間的時間,若在此期間有數據,則判定爲數據包未結束,重載定時器,若此期間無數據,則判定爲數據包結束,關閉定時器。

      這篇章裏不討論1和2,因爲STM32有更方便的處理方式,STM32能夠檢測空閒。

      看RM0033,空閒,可以理解爲,下一個起始位之前的全部1,也就是Rx線上高電平。

      會不會和數據0xFF衝突呢?不會,因爲數據0xFF有起始位,空閒沒有。

      看下來這個手畫的圖,中間那一段高電平,就是空閒。

      

      當空閒被檢測到時,如果IDLEIE位設置,就會產生一個IDLE中斷。

      我們捕獲這個中斷,並處理它就行了。

      在空閒中斷產生之前,我們收到的數據,怎麼處理呢?先收着,存進一個Buffer裏面!

      如前面講的《STM32 Uart中斷接收》《STM32 Uart DMA方式接收數據》,接收數據,也可以用中斷和DMA兩種不同的方式。我們把兩種方式都寫進代碼裏面,用兩個不同的宏定義區分開來。

      我們就寫一個,收到數據,往串口發送出收到的數據長度,以及完整的數據吧。

      理論講完,就開始實踐吧!

      參照《STM32 Uart DMA方式接收數據》,建立工程,生成代碼。

   在 usart.c 裏面寫這樣一段代碼,#define RCV_RXNEIDLE_PROCESS 表示用RX中斷方式接收數據,#define RCV_DMAIDLE_PROCESS 表示用DMA方式接收數據。這段代碼主要用於選擇接收數據的方式。同一時間只能使用一種方式,#error這裏做了個限制,不允許同時打開這兩個宏定義,否則會編譯出錯。

// select oneof two method
//#define RCV_RXNEIDLE_PROCESS
#define RCV_DMAIDLE_PROCESS

#if defined(RCV_DMAIDLE_PROCESS)&&defined(RCV_RXNEIDLE_PROCESS)
    #error "Don't Allow #define Two Micro at the same time, Check RCV_RXNEIDLE_PROCESS && RCV_DMAIDLE_PROCESS"
#endif

      接收數據,就定義一個數組來存放數據,就申請1024個字節的數組吧;

      數據要變長,就定義一個變量來表示接收到的數據長度;

      最後,再定義一個指針,用來指向存放數據的數組。

      在 usart.c 裏面,這一斷代碼定義了所需要的數據。

uint8_t uart4Rx[UART4_BUF_MAX];      // 存放接收到的數據
uint8_t *pUart4Rx;    // 指向存認數據的數組
uint16_t uart4RxLength;     // 接收到數據的長度

      整個事件的流程就是,接收一段數據後,產生IDLE中斷,IDLE中斷服務例程裏處理數據。

      那我們就看一下stm32f2xx_it.c裏面UART4_IRQHandler()中斷服務例程裏面,對Idle中斷是怎麼處理的?

      看一下 HAL_UART_IRQHandler() 這個函數,它對Idle中斷沒有處理!那怎麼辦呢?自己寫一段咯。

      在HAL_UART_IRQHandler() 函數加上這一段。

/* UART in mode Idle -------------------------------------------------*/
if(((isrflags & USART_SR_IDLE) != RESET) && ((cr1its & USART_CR1_IDLEIE) != RESET))
{
    HAL_UART_IdleCpltCallback(huart);      
    return;
}  

      這個回調函數HAL_UART_IdleCpltCallback(),仿照着在stm32f2xx_hal_uart.c裏面加一個回調函數。

      後面,我們在 usart.c 裏面重寫它。

/**
  * @brief  Idle callbacks.
  * @param  huart pointer to a UART_HandleTypeDef structure that contains
  *                the configuration information for the specified UART module.
  * @retval None
  */
__weak void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
{
  /* Prevent unused argument(s) compilation warning */
  UNUSED(huart);
  /* NOTE: This function Should not be modified, when the callback is needed,
           the HAL_UART_TxCpltCallback could be implemented in the user file
   */
}

 

      整個HAL_UART_IRQHandler()函數,是這樣的。

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 */
            HAL_UART_ErrorCallback(huart);
          }
        }
        else
        {
          /* Call user error callback */
          HAL_UART_ErrorCallback(huart);
        }
      }
      else
      {
        /* Non Blocking error : transfer could go on.
           Error is notified to user through user error callback */
        HAL_UART_ErrorCallback(huart);
        huart->ErrorCode = HAL_UART_ERROR_NONE;
      }
    }
    return;
  } /* End if some error occurs */

// 這裏,就是對Idle中斷的處理啊啊啊啊啊!!!
     /* UART in mode Idle -------------------------------------------------*/
    if(((isrflags & USART_SR_IDLE) != RESET) && ((cr1its & USART_CR1_IDLEIE) != RESET))
    {
        HAL_UART_IdleCpltCallback(huart);      
      return;
    }  

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

如《STM32 Uart中斷接收》所寫,要響應RX中斷接收,必須使能RX中斷;

如《STM32 Uart DMA方式接收數據》所寫,要響應DMA接收,必須使能DMA;

要響應IDLE中斷,必須使能 IDLE 中斷。

看這個USR_UartInit()函數:

void USR_UartInit(void)
{
// 這裏是RX中斷處理
#ifdef RCV_RXNEIDLE_PROCESS
    pUart4Rx = uart4Rx;      // 指針指向數組
    uart4RxLength = 0;      // 長度初始爲0
    HAL_UART_Receive_IT(&huart4, pUart4Rx, 1);    // 使能Rx中斷,並設置長度爲1,數據置於uart4Rx中,準備接收第一個數據
#endi

// 這裏是DMA中斷處理;
#ifdef RCV_DMAIDLE_PROCESS
    uart4RxLength = 0;     // 長度初始爲0
    HAL_UART_Receive_DMA(&huart4, uart4Rx, UART4_BUF_MAX);    // 使能DMA,最長爲1024字節,數據置於uart4Rx中,準備接收一串數據
#endif
    
    __HAL_UART_ENABLE_IT(&huart4, UART_IT_IDLE);       // 使能 IDLE中斷
}

      這個函數在main()裏面,初始化串口和DMA之後調用。

  MX_DMA_Init();
  MX_UART4_Init();
  /* USER CODE BEGIN 2 */
    USR_UartInit();
  /* USER CODE END 2 */

      在《STM32 Uart中斷接收》裏面講了,響應RX中斷,最終會到HAL_UART_RxCpltCallback裏面處理;

      在《STM32 Uart DMA方式接收數據》裏面講了,響應DMA中斷,最終也會到HAL_UART_RxCpltCallback裏面處理。

      RX中斷接收:來一次中斷,指針要後移,指向下一個數據,上一個數據已經接收,數據長度自加,然後再次使能Rx中斷,準備接收下一個數據。

      DMA接收:再次使能DMA,準備接收下一串數據。

      DMA產生中斷的條件,1.接收滿UART_BUF_MAX,2.設置DMA Disable。

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
#ifdef RCV_RXNEIDLE_PROCESS
    pUart4Rx++;    // 指針指向下一個數據,上一個數據接收了,纔會產生這次中斷
    uart4RxLength++;   // 接收到上一個數據,長度要+1
    HAL_UART_Receive_IT(&huart4, pUart4Rx, 1);    // 繼續使能RX中斷,準備接收。
#endif

#ifdef RCV_DMAIDLE_PROCESS
    HAL_UART_Receive_DMA(&huart4, uart4Rx, UART4_BUF_MAX);   // DMA產生Complete中斷條件:1.接收滿UART_BUF_MAX,2.設置DMA Disable。再次使能DMA
#endif
}

 

      接收完一串數據,接下來,輪到 Idle 中斷出手了。

      Idle中斷處理,會調用回調函數HAL_UART_IdleCpltCallback(),我們就實現它。

uint8_t RxLenHi, RxLenlo;    // 長度高位,低位
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart)
{
        __HAL_UART_CLEAR_IDLEFLAG(huart);

// RX
#ifdef RCV_RXNEIDLE_PROCESS
    RxLenHi = (uint8_t)(uart4RxLength>>8);
    RxLenlo =  (uint8_t)(uart4RxLength);           
    HAL_UART_Transmit(huart, &RxLenHi, 1, 1000);
    HAL_UART_Transmit(huart, &RxLenlo, 1, 1000);
    HAL_UART_Transmit(huart, uart4Rx, uart4RxLength, 1000);
    pUart4Rx = uart4Rx;       // 接收完一條命令,指針指向數組頭,重新接收
    uart4RxLength = 0;         // 長度設置爲0
#endif

// DMA
#ifdef RCV_DMAIDLE_PROCESS
    uart4RxLength = UART4_BUF_MAX-__HAL_DMA_GET_COUNTER(&hdma_uart4_rx);     // 獲取長度
    RxLenHi = (uint8_t)(uart4RxLength>>8);
    RxLenlo =  (uint8_t)(uart4RxLength);
    HAL_UART_Transmit(huart, &RxLenHi, 1, 1000);
    HAL_UART_Transmit(huart, &RxLenlo, 1, 1000);
    HAL_UART_Transmit(huart, uart4Rx, uart4RxLength, 1000);
    __HAL_DMA_DISABLE(&hdma_uart4_rx);       // DMA計數重載,會產生DMA完成中斷,
                                             // 參考RM0033-9.3.13,當DMA關閉時,會產生DMA完成中斷
                                            // 參考RM0033-DMA_SxNDTR,當DMA關閉時,重載計數器
#endif
}

 

      現在,捋一下整個過程:

      RX變長數據接收:

        1. 初始化時使能RX中斷,時刻準備着接收數據。

        2. 第一個數據接收,觸發RX中斷,執行HAL_UART_RxCpltCallback(),函數裏,接收的數據指向數組下一個存儲空間,接收的長度+1,使能RX中斷,準備下一數據接收。

      下一個數據接收,觸發RX中斷,執行HAL_UART_RxCpltCallback(),函數裏,接收的數據指向數組下一個存儲空間,接收的長度+1,使能RX中斷,準備下一數據接收。

    …… ……

      3. 整條命令接收完成,觸發空閒中斷,執行行HAL_UART_IdleCpltCallback(),函數裏,處理接收到的數據,指針指向uart4Rx[]的第一個數據。

      DMA變長數據接收:

       1. 初始化時使能DMA中斷,時刻準備着接收數據。

       2. 有數據,觸發DMA事件,有數據,觸發DMA事件,有數據,觸發DMA事件... ... ...這個過程是全自動的,不需要我們參與,DMA自動把數據接收了,存進uart4Rx[]裏。

    3. 整條命令接收完成,觸發空閒中斷,執行行HAL_UART_IdleCpltCallback(),函數裏,處理接收到的數據, __HAL_DMA_DISABLE()關閉DMA,關閉DMA會引起DMA計數重載,且觸發DMA Complete 中斷,中斷服務例程會執行HAL_UART_IdleCpltCallback(),函數裏,使能DMA,準備接收下一條命令。


      重新編譯,燒錄,運行便可;

      返回的第1個字節,長度高位 0x00,返回的第2個字節,長度低位 0x0B,0x000B = 11,我們發的,正好11字節;

      後面緊跟着 11 22 33 44 55 66 77 88 ff ff 99,就是發送區發的數據。

      STM32收到的數據,是這樣處理的。

    HAL_UART_Transmit(huart, &RxLenHi, 1, 1000);      // 發送長度高位
    HAL_UART_Transmit(huart, &RxLenlo, 1, 1000);      //  發送長度低位
    HAL_UART_Transmit(huart, uart4Rx, uart4RxLength, 1000);    // 發送接收到的數據

        等等!這樣有沒有感覺超麻煩?有沒有一種簡單的方法,像C語言的printf,直接在串口調試助手輸出字符串呢?

       請看下一篇《STM32 Uart 實現printf函數》。

    

     整個工程及代碼呢,請上百度網盤上下載:

     鏈接:https://pan.baidu.com/s/19usUcgZPX8cCRTKt_NPcfg

     密碼:07on

     文件夾:\Stm32CubeMx\Code\UartRx_Any.rar

 

上一篇:《STM32 Uart DMA方式接收數據

下一篇:《STM32 Uart 實現printf函數

回目錄:《前言

      

   

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章