天天看點

HAL庫的序列槽基礎學習(包含序列槽接收不定長資料的實作)

HAL庫的序列槽基礎學習(1)

HAL庫有一個特點就是對于許多外設的初始化以及功能操作,都提供有一個weak版本的函數,這是充分的展現出庫名字的含義(Hardware Abstraction Layer)硬體抽象層。

例如序列槽的HAL_UART_MspInit()函數和HAL_UART_MspDeInit()函數等,這些都可以供使用者在需要時在stm32f1xx_hal_msp.c中進行重寫實作功能。

用序列槽初始化來舉例子,用Cube配置UART1使能并生成代碼後可以看到有三個關鍵函數:

1、void MX_USART1_UART_Init(void)
           

這個函數是Cube配置完成後自動幫我們生成的,存在于使用者檔案usart.c中(如果Cube中有配置選項生成使用者.c檔案,否則在main函數中)。裡面主要是将USART的結構體初始化成USART1以及我們設定好的參數),并且在最後有一個語句:if (HAL_UART_Init(&huart1) != HAL_OK)通過這個來調用HAL_UART_Init函數。

2、HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart)
           

這個函數在HAL庫檔案stm32f1xx_hal_uart.c中,在上一個函數 MX_USART1_UART_Init(void)中有被調用,這個函數将初始化UART1,使能UART1,而重點是裡面有調用一個函數HAL_UART_MspInit(huart)

3、__weak void HAL_UART_MspInit(UART_HandleTypeDef *huart)
           

這個函數也在HAL庫檔案stm32f1xx_hal_uart.c中,它是weak修飾的,也就是可以重寫。我們發現在Cube生成的使用者檔案usart.c檔案中有對這個函數進行重寫,主要實作對UART1的TX、RX引腳的IO口配置,以及開啟序列槽中斷等設定。

由此,HAL庫的序列槽外設初始化操作就清晰了,也就是說如果我們使用Cube生成代碼,要完成對序列槽的初始化,就要自行對序列槽的結構體參數進行配置,并對HAL_UART_MspInit函數進行重寫。到這裡,也能看出HAL庫的優越性,使用者隻需在msp.c中對要實作的函數進行重寫,調用相關函數既可實作。

序列槽初始化完成後,下一步就是實作序列槽的收發資料,輪詢發送很簡單,這裡主要研究下序列槽的中斷接收,和中斷接收有關的函數有下面幾個:

1、HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)
           

函數有是三個參數,分别是序列槽結構體,要傳輸的資料指針以及待傳輸資料的大小,這其實是對中斷接收的一個配置函數,指定當接收到Size大小的位元組後就産生一次中斷進入中斷處理。

對比以前的庫函數的序列槽接收協定,可以将這裡的Size設定為1,也就是每次接收一個位元組就産生一次中斷,并将資料存儲到pData指向的位址中。這就類似于庫函數中的每次中斷将資料指派給臨時變量res。需要注意的是,這個函數調用一次隻适用于一次中斷,如果要連續接收資料的話應該在重寫函數中調用它。

如果資料定長接收的話也可以在設定中斷時直接使用設定長度,如接收定長的size為8就可以設定HAL_UART_Receive_IT(&huart, &DataBuff, 8)設定後序列槽接收回調函數就在接收到8位元組後進入。

如果序列槽接收的資料為不定長資料可以通過三種方法來實作,定時器和接收中斷配合、DMA和序列槽中斷配合、空閑中斷實作。稍後會線介紹下空閑中斷的實作方法。

2、USART1_IRQHandler(void)    和    HAL_UART_IRQHandler(&huart1)
           

第一個是在it.c中的通用的硬體中斷入口函數,其中在裡面調用了HAL_UART_IRQHandler(&huart1),也就進入了UART1的中斷處理,HAL_UART_IRQHandler函數在stm32f0xx_hal_uart.c檔案中,主要進行中斷标志的判定,當判定接收到資料時,調用UART_Receive_IT(huart)函數。

3、HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
           

這個函數也在stm32f0xx_hal_uart.c中,這才是真正的處理資料的函數,讀取接收寄存器,将資料指派給結構體huart的pRxBuffer并清除中斷标志,當接收資料完成并把序列槽的接收标志位huart->RxState = HAL_UART_STATE_READY;這個狀态很重要,這個狀态會直接影響下次中斷的使能,因為在HAL_UART_Receive_IT函數中會對此狀态進行判斷成立時才會對中斷進行使能。随後調用了一個函數HAL_UART_RxCpltCallback(huart);

4、__weak void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
           

這個是使用者真正可以進行重寫實作自己所需功能的函數,在msp.c中将其重寫。前面我們設定了參數使得每接收一個位元組就産生中斷,于是可以重寫這個函數,在裡面将每次接收到的資料存放在自己定義的UART1RXBuffer中。另外,上面有提到配置中斷參數的函數 HAL_UART_Receive_IT()每次調用僅适用于一次中斷,如果要實作連續采用中斷接收資料,也要這個函數中調用HAL_UART_Receive_IT()。因為這個回調函數是每次中斷發生都會調用,也就相當于每次發生中斷處理完資料後又将中斷打開,進而實作連續中斷接收資料。

下面介紹一下利用空閑中斷實作不定長資料的接收。

1、在HAL_UART_MspInit函數中使能中斷。HAL_UART_Receive_IT( &huart1, aucUsart1RecBuff, DATA_FIRST_LEN );

DATA_FIRST_LEN 資料長度可以是接收資料緩沖區的大小,不用考慮接收資料的長度,隻要保證此數值大于接收資料的可能長度即可。

2、在stm32f1xx_hal_uart.c檔案中找到UART_Receive_IT函數(在接收到資料時會在HAL_UART_IRQHandler中調用)添加__HAL_UART_ENABLE_IT(huart,UART_IT_IDLE);

static HAL_StatusTypeDef UART_Receive_IT(UART_HandleTypeDef *huart)
{
  uint16_t* tmp;
  
  /* Check that a Rx process is ongoing */
  if(huart->RxState == HAL_UART_STATE_BUSY_RX) 
  {
    //Add for kong 20190106
    __HAL_UART_ENABLE_IT(huart,UART_IT_IDLE);
      
    if(huart->Init.WordLength == UART_WORDLENGTH_9B)
    {
      tmp = (uint16_t*) huart->pRxBuffPtr;
      if(huart->Init.Parity == UART_PARITY_NONE)
      {
        *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x01FF);
        huart->pRxBuffPtr += 2U;
      }
      else
      {
        *tmp = (uint16_t)(huart->Instance->DR & (uint16_t)0x00FF);
        huart->pRxBuffPtr += 1U;
      }
    }
    else
    {
      if(huart->Init.Parity == UART_PARITY_NONE)
      {
        *huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x00FF);
      }
      else
      {
        *huart->pRxBuffPtr++ = (uint8_t)(huart->Instance->DR & (uint8_t)0x007F);
      }
    }

    if(--huart->RxXferCount == 0U)
    {
      /* Disable the IRDA Data Register not empty Interrupt */
      __HAL_UART_DISABLE_IT(huart, UART_IT_RXNE);

      /* Disable the UART Parity Error Interrupt */
      __HAL_UART_DISABLE_IT(huart, UART_IT_PE);
        /* Disable the UART Error Interrupt: (Frame error, noise error, overrun error) */
        __HAL_UART_DISABLE_IT(huart, UART_IT_ERR);

      /* Rx process is completed, restore huart->RxState to Ready */
      huart->RxState = HAL_UART_STATE_READY;

      HAL_UART_RxCpltCallback(huart);

      return HAL_OK;
    }
    return HAL_OK;
  }
  else
  {
    return HAL_BUSY;
  }
}
           

3、在uart.c檔案中添加void HAL_UART_IDLECallback( UART_HandleTypeDef* huart )回調函數

void HAL_UART_IDLECallback( UART_HandleTypeDef* huart )
{
    __HAL_UART_CLEAR_IDLEFLAG(&huart1);                                     //清除空閑中斷flag
    __HAL_UART_DISABLE_IT(&huart1,UART_IT_IDLE);
    __HAL_UART_DISABLE_IT(&huart1,UART_IT_RXNE);
    
    huart1.RxState = HAL_UART_STATE_READY;                                  //此狀态在整個接收資料緩沖區滿的時候會被置位,但是空閑中斷響應的時候很可能資料緩沖區還沒有被填充滿,如果沒有此操作會引起下次開中斷的失敗
    
   /*
  添加使用者程式*/
    HAL_UART_Receive_IT( &huart1, aucUsart1RecBuff, DATA_FIRST_LEN );
}
           

4、修改中斷函數,也可以修改HAL_UART_IRQHandler(UART_HandleTypeDef *huart)在函數中添加此判斷。

void USART1_IRQHandler(void)
{
    uint32_t isrflags   = READ_REG(huart1.Instance->SR);
    uint32_t cr1its     = READ_REG(huart1.Instance->CR1);
    
    if(((isrflags & USART_SR_IDLE) != RESET) && ((cr1its & USART_CR1_IDLEIE) != RESET))
    {
        HAL_UART_IDLECallback(&huart1);  
    }
    
	HAL_UART_IRQHandler(&huart1);
}
           

以上就是利用序列槽空閑中斷完成的接收不定長資料的程式。

繼續閱讀