Skip to main content
Explorer
May 22, 2025
Solved

STM32U5 UART - loopback test

  • May 22, 2025
  • 4 replies
  • 1197 views

Hi! I have a STM32U585 board and I'm trying to make a UART loopback test. I tried two different modes:

  • Asynchronous (Full-Duplex) - wire between the TX and RX
  • Single-Wire (Half-Duplex) - wire looping to itself

Both worked in similar ways. I successfully was able to handle 1 byte, so this is the code that worked fine

/* USER CODE BEGIN PV */
uint8_t tx = 0;
uint8_t rx = 0;
/* USER CODE END PV */

and inside the main function I had

while(1)
{
 printf("Sending byte: %d\r\n", tx);
 HAL_UART_Transmit(&huart3, &tx, 1, HAL_MAX_DELAY);

 HAL_UART_Receive(&huart3, &rx, 1, HAL_MAX_DELAY);
 printf("Received byte: %d\r\n", rx);

 tx++;
 HAL_Delay(1000);
}

I could see in the terminal that it was successfully incrementing both tx and rx variables.

 

Now, it all went wrong when I tried to use an array instead of a single byte. This is what I tried:

/* USER CODE BEGIN PV */
uint8_t tx[4] = {0x55, 0x12, 0x00, 0xFF};
uint8_t rx[4] = {0};
/* USER CODE END PV */

and the main function had

while(1)
{
 printf("Sending bytes:\r\n");
 for(int i=0; i<4; i++)
 printf(" 0x%02X", tx[i]);
 printf("\r\n");

 HAL_UART_Transmit(&huart3, tx, 4, HAL_MAX_DELAY);

 HAL_UART_Receive(&huart3, rx, 4, HAL_MAX_DELAY);

 printf("Received bytes:\r\n");
 for(int i=0; i<4; i++)
 printf(" 0x%02X", rx[i]);
 printf("\r\n");

 tx[2]++;
 HAL_Delay(2000);
}

From this in the terminal shows that it got stuck at HAL_UART_Transmit(), this was the full output

    Sending bytes:  0x55 0x12 0x00 0xFF

 

By running it in debug mode I was able to realize that it got stuck on this while loop inside the stm32u5xx_hal_uart.c

HAL_StatusTypeDef UART_WaitOnFlagUntilTimeout(UART_HandleTypeDef *huart, uint32_t Flag, FlagStatus status, uint32_t Tickstart, uint32_t Timeout)
{
 /* Wait until flag is set */
 while ((__HAL_UART_GET_FLAG(huart, Flag) ? SET : RESET) == Status)
 {
 ...

Can anyone help me out? Is there any info about my test that you need me to provide?

Thanks in advance!

    This topic has been closed for replies.
    Best answer by Tesla DeLorean

    There's no buffer depth, so don't use blocking functions.

    At a polling/loop level you could check for TXE and RXNE flags in the status register and advance your transmit and receive buffer pointers.

    Or look to use HAL_UART_ReceiveIT to capture reception as it occurs.

    4 replies

    Graduate II
    May 22, 2025

    There's no buffer depth, so don't use blocking functions.

    At a polling/loop level you could check for TXE and RXNE flags in the status register and advance your transmit and receive buffer pointers.

    Or look to use HAL_UART_ReceiveIT to capture reception as it occurs.

    Explorer
    May 23, 2025

    That worked very well, for anyone wondering I activated the NVIC global interrupts in UART and this was the logic I implemented:

    /* USER CODE BEGIN 0 */
    uint8_t tx_buf[4] = {0x55, 0x12, 0x29, 0xFF};
    uint8_t rx_buf[4] = {0};
    volatile uint8_t rx_done = 0;
    
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
     if(huart == &huart3) {
     rx_done = 1;
     }
    }
    /* USER CODE END 0 */

    and in main function

    while(1)
    {
     rx_done = 0;
    
     printf("Sending: ");
     for(int i=0; i<4; i++) printf("0x%02X ", tx_buf[i]);
     printf("\r\n");
    
     if(HAL_UART_Receive_IT(&huart3, rx_buf, 4) != HAL_OK)
     printf("RX start failed\r\n");
    
     HAL_Delay(1);
    
     if(HAL_UART_Transmit_IT(&huart3, tx_buf, 4) != HAL_OK)
     printf("TX start failed\r\n");
    
     while(!rx_done) {}
    
     printf("Received: ");
     for(int i=0; i<4; i++) printf("0x%02X ", rx_buf[i]);
     printf("\r\n");
    
     tx_buf[2]++;
     HAL_Delay(3000);
    }

    And this was the output:

        Starting...
        Sending: 0x55 0x12 0x29 0xFF  
        Received: 0x55 0x12 0x29 0xFF  
        Sending: 0x55 0x12 0x2A 0xFF  
        Received: 0x55 0x12 0x2A 0xFF  
        Sending: 0x55 0x12 0x2B 0xFF  
        Received: 0x55 0x12 0x2B 0xFF

    Graduate II
    May 22, 2025

    Don't use HAL_MAX_DELAY. That is ~49 days before it'll timeout. For 4 bytes, 100ms will suffice. That why it seems like you're stuck in the UART_WaitOnFlagUntilTimeout function

     

    You should always read the return status when using HAL_UART_Transmit and HAL_UART_Receive.

    You should get either HAL_ERROR, HAL_OK, HAL_BUSY or HAL_TIMEOUT returned. In your case it'll probably be HAL_TIMEOUT

     

    HAL_StatusTypeDef hal_status;
     
    hal_status = HAL_UART_Transmit(&huart3, tx, 4, 100); // same for HAL_UART_Receive
    if(hal_status != HAL_OK)
    {
    	if(hal_status == HAL_TIMEOUT)
    	{
    		// do something
    	}
    	else if(hal_status == HAL_BUSY)
    	{
    		// do something
    	}
    	else if(hal_status == HAL_ERROR)
    	{
    		// do something
    	}
    	return;// do not continue, until errors fixed
    }
    // continue

     

    Super User
    May 23, 2025

    @Karl Yamashita wrote:

    Don't use HAL_MAX_DELAY. That is ~49 days before it'll timeout.


    Actually, I think the HAL_UART_Receive takes HAL_MAX_DELAY to mean infinite timeout; ie, it will wait forever?

    The name (or, at least, the usage) is misleading.

     

    PS:

    found it:

    https://community.st.com/t5/stm32-mcus-products/ultrasonic-a02yyuw-not-working-in-stm32l443vct6/m-p/731949/highlight/true#M263525

    Graduate II
    May 27, 2025

    Yes, you are right. I just looked at the 32 bit value but didn't look to see what UART_WaitOnFlagUntilTimeout actually did.

    Super User
    May 22, 2025

    You need to be actively receiving characters as they come in. At least, when you're receiving more than 1 of them. There's no way to do a loopback like this in blocking mode. You'll have to use HAL_UART_Receive_IT or HAL_UART_Receive_DMA.

    > Single-Wire (Half-Duplex) - wire looping to itself

    Loopback on half-duplex makes no sense.

    Super User
    May 27, 2025

    I'm not sure the distinction between 49 days and forever matters a whole heck of a lot on an MCU, but the behavior is documented in various places.

    TDK_0-1748306136162.png

    Waiting forever is better than silently ignoring errors most of the time IMO, especially during the debugging phase. Maybe it changes when you move into production.