STM32 HAL USART receive by interrupt

53,837

Solution 1

HAL_UART_Receive_IT() is not meant to be called from an interrupt handler that way, but to initiate receiving a fixed number of bytes via interrupt.

A possible workaround is to check your input buffer after HAL_UART_IRQHandler() completes, i.e. in the /* USER CODE BEGIN USART1_IRQn 1 */ section. When a command is processed, you can reset pRxBuffPtr and RxXferCount in the handle structure to their original values to start from the start of the buffer again.

Another horrible possible workaround would be to call HAL_UART_Receive_IT() with a buffer size of 1, and set up a HAL_UART_RxCpltCallback() handler that checks the received byte each time, and calls HAL_UART_Receive_IT() again when necessary.

Of course you could do it without HAL, as PeterJ and others (always) suggest.

  • You've already implemented pin and interrupt setup, leave them unchanged at first.
  • Calculate the UART->BRR value according to the reference manual, or copy the relevant code from hal.
  • set UART->CR1=USART_CR1_RE|USART_CR1_TE|USART_CR1_UE|USART_CR1_RXNEIE; Now, you are getting interrupts.
  • In the interrupt function, read UART->SR into a temporary variable, and examine it.
  • Read UART->DR when there is a received byte waiting, do the error handling otherwise (later).
  • Get rid of the rest of the HAL calls when the above is working.

Interrupt response and processing time is often critical in embedded applications, and the HAL just wastes a lot of that.

Solution 2

The normal HAL library is not useful for continuous reception or commands with different length.

If you have the complete HAL package installed, you could look at the examples for the LowLevel interface.

Projects\STM32F411RE-Nucleo\Examples_LL\USART\USART_Communication_Rx_IT_Continuous

The main thing is to set you usart to continuous reception:

    void Configure_USART(void) {    
        /* (1) Enable GPIO clock and configures the USART pins *********************/

        /* Enable the peripheral clock of GPIO Port */
        USARTx_GPIO_CLK_ENABLE();

        /* Configure Tx Pin as : Alternate function, High Speed, Push pull, Pull up */
        LL_GPIO_SetPinMode(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_MODE_ALTERNATE);
        USARTx_SET_TX_GPIO_AF();
        LL_GPIO_SetPinSpeed(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_SPEED_FREQ_HIGH);
        LL_GPIO_SetPinOutputType(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_OUTPUT_PUSHPULL);
        LL_GPIO_SetPinPull(USARTx_TX_GPIO_PORT, USARTx_TX_PIN, LL_GPIO_PULL_UP);

        /* Configure Rx Pin as : Alternate function, High Speed, Push pull, Pull up */
        LL_GPIO_SetPinMode(USARTx_RX_GPIO_PORT, USARTx_RX_PIN, LL_GPIO_MODE_ALTERNATE);
        USARTx_SET_RX_GPIO_AF();
        LL_GPIO_SetPinSpeed(USARTx_RX_GPIO_PORT, USARTx_RX_PIN, LL_GPIO_SPEED_FREQ_HIGH);
        LL_GPIO_SetPinOutputType(USARTx_RX_GPIO_PORT, USARTx_RX_PIN, LL_GPIO_OUTPUT_PUSHPULL);
        LL_GPIO_SetPinPull(USARTx_RX_GPIO_PORT, USARTx_RX_PIN, LL_GPIO_PULL_UP);

        /* (2) NVIC Configuration for USART interrupts */
        /*  - Set priority for USARTx_IRQn */
        /*  - Enable USARTx_IRQn */
        NVIC_SetPriority(USARTx_IRQn, 0);  
        NVIC_EnableIRQ(USARTx_IRQn);

        /* (3) Enable USART peripheral clock and clock source ***********************/
        USARTx_CLK_ENABLE();

        /* (4) Configure USART functional parameters ********************************/
        /* TX/RX direction */
        LL_USART_SetTransferDirection(USARTx_INSTANCE, LL_USART_DIRECTION_TX_RX);

        /* 8 data bit, 1 start bit, 1 stop bit, no parity */
        LL_USART_ConfigCharacter(USARTx_INSTANCE, LL_USART_DATAWIDTH_8B, LL_USART_PARITY_NONE, LL_USART_STOPBITS_1);

        /* No Hardware Flow control */
        /* Reset value is LL_USART_HWCONTROL_NONE */
        // LL_USART_SetHWFlowCtrl(USARTx_INSTANCE, LL_USART_HWCONTROL_NONE);

        /* Oversampling by 16 */
        /* Reset value is LL_USART_OVERSAMPLING_16 */
        // LL_USART_SetOverSampling(USARTx_INSTANCE, LL_USART_OVERSAMPLING_16);

        /* Set Baudrate to 115200 using APB frequency set to 100000000/APB_Div Hz */
        /* Frequency available for USART peripheral can also be calculated through LL RCC macro */
        /* Ex :
            Periphclk = LL_RCC_GetUSARTClockFreq(Instance); or 
            LL_RCC_GetUARTClockFreq(Instance); depending on USART/UART instance
  
            In this example, Peripheral Clock is expected to be equal to 
            100000000/APB_Div Hz => equal to SystemCoreClock/APB_Div
        */
        LL_USART_SetBaudRate(USARTx_INSTANCE, SystemCoreClock/APB_Div, LL_USART_OVERSAMPLING_16, 115200); 

        /* (5) Enable USART *********************************************************/
        LL_USART_Enable(USARTx_INSTANCE);
    }

The USART IT Handler should look like

    void USARTx_IRQHandler(void)
    {
      /* Check RXNE flag value in SR register */
      if(LL_USART_IsActiveFlag_RXNE(USARTx_INSTANCE) && LL_USART_IsEnabledIT_RXNE(USARTx_INSTANCE))
      {
        /* RXNE flag will be cleared by reading of DR register (done in call) */
        /* Call function in charge of handling Character reception */
        USART_CharReception_Callback();
      }
      else
      {
        /* Call Error function */
        Error_Callback();
      }
    }

The last thing to set up is the Callback

void USART_CharReception_Callback(void);

Where you could put the bytes into an buffer and handle it in the main loop or where you want.

Solution 3

Since I stumbled over the problem today and could not find a good solution to it, I like to present a very simple one, using most of the HAL but avoiding the problems described...

Short verison of my approach is:

In the last user code section (for the appropriate USART instance, if using more than one) of void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle), enable the IRQ with:

__HAL_UART_ENABLE_IT(&huartx, UART_IT_RXNE);

Then put the desired code in your interrupt function:

void USART3_IRQHandler(void)  {    
    /* USER CODE BEGIN USART3_IRQn 0 */
    CallMyCodeHere();
    return;  // To avoid calling the default HAL handler at all 
             // (in case you want to save the time)
    /* USER CODE END USART3_IRQn 0 */
    HAL_UART_IRQHandler(&huart3);  // This is the CubeMX generated HAL handler
    /* USER CODE BEGIN USART3_IRQn 1 */
    /* USER CODE END USART3_IRQn 1 */ 
}

Don't use HAL_UART_Receive_IT anywhere, it will disable the IRQ and you need to re-enable it, if you want to get called with every reception.

The long version could be found in my post here...

Solution 4

Here is the full example of receiving data and idle line detection by interrupts:

  1. Enable the receive interrupts and idle line detection in main.c:
  /* USER CODE BEGIN USART2_Init 2 */
  __HAL_UART_ENABLE_IT(&huart2, UART_IT_RXNE);  // enable receive intterupts 
  __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);  // enable idle line detection 
  /* USER CODE END USART2_Init 2 */
  1. Sort out the idle line event from within USARTx_IRQHandler in stm32f4xx_it.c:
void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */
  if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) { 
    __HAL_UART_CLEAR_IDLEFLAG(&huart2); // taken from https://electronics.stackexchange.com/questions/471272/setting-up-stm32-timer-for-uart-idle-detection#comment1353999_480556
    uart2_idleHandler();
  } else {
    uart2_handler();
  }
  return;
  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */
  /* USER CODE END USART2_IRQn 1 */
}

Testing:

  1. Create the following handlers:
char my_uart_buffer[256];
int my_uart_buffer_index = 0;

void uart2_handler(void){
  char buff; 
  HAL_UART_Receive (&huart2, (uint8_t *)&buff, 1, 400);
  my_uart_buffer[my_uart_buffer_index++] = buff;
}

void uart2_idleHandler(){
  my_uart_buffer_index = 0; 
}
  1. Open a serial port client in your PC, setup as 115200 baud, 8N1.
  2. Set a breakpoint to uart2_idleHandler().
  3. Send "Hello world".
  4. You can examine your buffer by p/c *my_uart_buffer@20 when breakpoint hits.

Example Project

Here is the full example that runs on STM32F407 Discovery board.

Solution 5

You can make it work using HAL! It may not be as elegant as other implementations but it is doable.

You have to create an error handler function and then the function that calls the HAL_UART_RCV_IT must flush the UART RX whenever there is an overrun error.

In addition, I work with two buffers. While the Interrupt is filling one buffer the main loop is emptying the other.

Here it how it is working well for me:

typedef enum
{
  UARTREADY = 0,
    UARTBUSY  = 1,
    UARTDATA  = 2,
    UARTERROR = 3
} enumUartStatus;

while(1){
    if(UARTREADY == isUsart3RxReady()){

        Usart3RxBuffer((char *)&SOMRxBytesBuffer[use_buffer_index], RCV_BUFFER_BANK_SIZE);   // receive bytes in the raw buffer

        if(use_buffer_index == RCV_BUFFER_BANK1_INDEX){         
            use_buffer_index = RCV_BUFFER_BANK2_INDEX;
            rxb1_stats++;
        }else{  
            use_buffer_index = RCV_BUFFER_BANK1_INDEX;
            rxb2_stats++;
        }

    }
}

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
    if(huart == NULL){
        return;
    }

    if(huart->Instance == USART3){

        if(HAL_UART_GetError(huart) == HAL_UART_ERROR_FE){
            Usart3Ready = UARTREADY;
        }else{      
            Usart3Ready = UARTERROR;
        }
    }
}

void Usart3RxBuffer(char *buffer, unsigned short rxbuffersize){
  /* Reset transmission flag */

    if(Usart3Ready != UARTREADY)
    {
        return;
    }

    if(HAL_UART_GetState(&huart3) == HAL_UART_STATE_READY){

        /*##-3- Put UART peripheral in reception process ###########################*/
        if (HAL_UART_Receive_IT(&huart3, (uint8_t *)buffer, rxbuffersize) != HAL_OK)
        {
            // TODO: Error_Handler();
            DEBUG_TRACE(DEBUG_MSK_MAIN, "UART3 error starting receiver!\r\n");
        }else{

            // An interrupt HAL_UART_ErrorCallback hit right here !!!!
            // There is an overrun error happening here so we have to retry
            // this is because we are using the Receive_IT in a continous communication and there is no handshake or flow control
            if(Usart3Ready != UARTERROR){
                /* Busy waiting to receive bytes */
                Usart3Ready = UARTBUSY;
            }
        }
    }

    if(Usart3Ready == UARTERROR){
        HAL_UART_AbortReceive_IT(&huart3);
        Usart3Ready = UARTREADY;
    }
}
Share:
53,837
HansPeterLoft
Author by

HansPeterLoft

Updated on February 05, 2022

Comments

  • HansPeterLoft
    HansPeterLoft over 2 years

    I have some trouble to receive data over the USART. What I actually want to achieve ist, that I can receive a command over USART with no specific length (only a maximum possible length). So I use the interrupt routine to check each character received, but I somehow still cannot achieve what I want. The routine is called each time I receive a new character, but somehow HAL_UART_Receive_IT(&huart1,rx_data,buff_size_rx) does not upgrade in realtime, then I don't see the received character when I check rx_data[pointer], but a few time later it is in the rx_data buffer.

    What I have so far:

    int pointer =0;
    
    ...
    
    void USART1_IRQHandler(void)
    {
      /* USER CODE BEGIN USART1_IRQn 0 */
        if ( USART1->ISR & UART_IT_TXE) {
    
        }
    
        if ( USART1->ISR & UART_IT_RXNE) {
            HAL_UART_Receive_IT(&huart1,rx_data,buff_size_rx);
            if(rx_data[pointer]=='\0') {
                  pointer=0;
                  readCommand(rx_data);
                  clearBuffer(rx_data,buff_size_rx);
            } else {
              pointer++;
              if(pointer>=buff_size_rx) {
                  pointer=0;
              }
            }
        }
        /* USER CODE END USART1_IRQn 0 */
        HAL_UART_IRQHandler(&huart1);
        /* USER CODE BEGIN USART1_IRQn 1 */
    
    
    
      /* USER CODE END USART1_IRQn 1 */
    }