Skip to main content
Explorer
July 17, 2025
Solved

UART Loopback with Interrupts Halts After Second Transmission on STM32H747I-DISCO

  • July 17, 2025
  • 3 replies
  • 1056 views

Title: UART Loopback with Interrupts Halts After Second Transmission on STM32H747I-DISCO

Hello everyone,

I'm working on a simple UART loopback test on an STM32H747I-DISCO board using the HAL libraries, and I've run into an issue where my interrupt-driven communication halts unexpectedly.

The goal is to have USART2 continuously transmit a byte, receive it back via a physical loopback (TX and RX pins are connected), increment the value, and repeat the process. I'm using an LED to visually confirm when a byte is successfully received.

The process starts correctly but stops after the first full cycle. Here is the exact sequence of events I'm observing with the debugger:

  1. main() calls HAL_UART_Transmit_IT() to send the first byte.

  2. The HAL_UART_TxCpltCallback() is successfully triggered.

  3. Inside the TxCpltCallback, I call HAL_UART_Receive_IT() to arm the receiver.

  4. The byte is received, and HAL_UART_RxCpltCallback() is successfully triggered. The LED toggles as expected.

  5. Inside the RxCpltCallback, I increment my data and call HAL_UART_Transmit_IT() to send the next byte.

  6. The HAL_UART_TxCpltCallback() is triggered for the second time, which is correct.

  7. Inside this second TxCpltCallback, I again call HAL_UART_Receive_IT() to arm the receiver for the next byte.

  8. The process halts here. The HAL_UART_RxCpltCallback() is never called again, and the system sits idle.

The Error_Handler() is not being called, and there are no HAL errors returned from the functions. It seems like the receive interrupt is not being triggered after the second transmit cycle.

Here is the summary of the event chain: Transmit_IT() -> Tx_Callback() -> Receive_IT() -> Rx_Callback() -> Transmit_IT() -> Tx_Callback() -> HALT

My Code: Here is my complete main.c file. The relevant logic is in main() and the HAL_UART_RxCpltCallback/HAL_UART_TxCpltCallback functions at the bottom.

// Includes
#include "main.h"

// Global defines
UART_HandleTypeDef huart1;
UART_HandleTypeDef huart2;

// Define the TX and RX buffers
uint8_t rx_buff = 10;
uint8_t tx_buff = 20;

// Global function defines
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_USART2_UART_Init(void);


// Main function
int main(void)
{
 // Initialize peripherals, clock and HAL
 HAL_Init();
 SystemClock_Config();
 MX_GPIO_Init();
 MX_USART1_UART_Init();
 MX_USART2_UART_Init();

 // Turn on LED first (Note: My LED is on with SET, off with RESET)
 HAL_GPIO_WritePin(GPIOI,GPIO_PIN_12,GPIO_PIN_SET);

 // Initial Transmit - Kicks off the loop
 HAL_UART_Transmit_IT(&huart2, &rx_buff,1);

 while (1)
 {
 // Loop forever, everything is handled by interrupts
 }

}


// Clock config
void SystemClock_Config(void)
{
 RCC_OscInitTypeDef RCC_OscInitStruct = {0};
 RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

 /** Supply configuration update enable
 */
 HAL_PWREx_ConfigSupply(PWR_DIRECT_SMPS_SUPPLY);

 /** Configure the main internal regulator output voltage
 */
 __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

 while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}

 /** Initializes the RCC Oscillators according to the specified parameters
 * in the RCC_OscInitTypeDef structure.
 */
 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_HSE;
 RCC_OscInitStruct.HSEState = RCC_HSE_ON;
 RCC_OscInitStruct.HSIState = RCC_HSI_DIV1;
 RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
 RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
 RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
 RCC_OscInitStruct.PLL.PLLM = 2;
 RCC_OscInitStruct.PLL.PLLN = 32;
 RCC_OscInitStruct.PLL.PLLP = 2;
 RCC_OscInitStruct.PLL.PLLQ = 10;
 RCC_OscInitStruct.PLL.PLLR = 2;
 RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_3;
 RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
 RCC_OscInitStruct.PLL.PLLFRACN = 0;
 if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
 {
 Error_Handler();
 }

 /** Initializes the CPU, AHB and APB buses clocks
 */
 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
 |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
 |RCC_CLOCKTYPE_D3PCLK1|RCC_CLOCKTYPE_D1PCLK1;
 RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
 RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
 RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV1;
 RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
 RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
 RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
 RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;

 if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
 {
 Error_Handler();
 }
 HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_HSI, RCC_MCODIV_1);
}

// UART 1 init for communication of the STM32H747I-DISCO and the PC over a serial monitor
static void MX_USART1_UART_Init(void)
{

 huart1.Instance = USART1;
 huart1.Init.BaudRate = 115200;
 huart1.Init.WordLength = UART_WORDLENGTH_8B;
 huart1.Init.StopBits = UART_STOPBITS_1;
 huart1.Init.Parity = UART_PARITY_NONE;
 huart1.Init.Mode = UART_MODE_TX_RX;
 huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
 huart1.Init.OverSampling = UART_OVERSAMPLING_16;
 huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
 huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
 huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
 if (HAL_UART_Init(&huart1) != HAL_OK)
 {
 Error_Handler();
 }
 if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
 {
 Error_Handler();
 }
 if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
 {
 Error_Handler();
 }
 if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK)
 {
 Error_Handler();
 }

}

// UART2 init for a loopback/ESP communication test
static void MX_USART2_UART_Init(void)
{


 huart2.Instance = USART2;
 huart2.Init.BaudRate = 9600;
 huart2.Init.WordLength = UART_WORDLENGTH_8B;
 huart2.Init.StopBits = UART_STOPBITS_1;
 huart2.Init.Parity = UART_PARITY_NONE;
 huart2.Init.Mode = UART_MODE_TX_RX;
 huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
 huart2.Init.OverSampling = UART_OVERSAMPLING_16;
 huart2.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
 huart2.Init.ClockPrescaler = UART_PRESCALER_DIV1;
 huart2.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
 if (HAL_UART_Init(&huart2) != HAL_OK)
 {
 Error_Handler();
 }
 if (HAL_UARTEx_SetTxFifoThreshold(&huart2, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
 {
 Error_Handler();
 }
 if (HAL_UARTEx_SetRxFifoThreshold(&huart2, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
 {
 Error_Handler();
 }
 if (HAL_UARTEx_DisableFifoMode(&huart2) != HAL_OK)
 {
 Error_Handler();
 }

}

// GPIO Init
static void MX_GPIO_Init(void)
{
 GPIO_InitTypeDef GPIO_InitStruct = {0};

 /* GPIO Ports Clock Enable */
 __HAL_RCC_GPIOD_CLK_ENABLE();
 __HAL_RCC_GPIOC_CLK_ENABLE();
 __HAL_RCC_GPIOA_CLK_ENABLE();
 __HAL_RCC_GPIOH_CLK_ENABLE();

 // Init additional GPIO clocks
 __HAL_RCC_GPIOF_CLK_ENABLE();
 __HAL_RCC_GPIOI_CLK_ENABLE();
 __HAL_RCC_GPIOJ_CLK_ENABLE();

 /*Configure GPIO pin : CEC_CK_MCO1_Pin for oscillator feedback*/
 GPIO_InitStruct.Pin = CEC_CK_MCO1_Pin;
 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
 GPIO_InitStruct.Pull = GPIO_NOPULL;
 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
 GPIO_InitStruct.Alternate = GPIO_AF0_MCO;
 HAL_GPIO_Init(CEC_CK_MCO1_GPIO_Port, &GPIO_InitStruct);

 // Configure the rest of the GPIOs

 // ESP chip enable
 GPIO_InitStruct.Pin = GPIO_PIN_4;
 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
 GPIO_InitStruct.Pull = GPIO_NOPULL;
 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
 HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

 // ESP reset
 GPIO_InitStruct.Pin = GPIO_PIN_13;
 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
 GPIO_InitStruct.Pull = GPIO_NOPULL;
 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
 HAL_GPIO_Init(GPIOJ, &GPIO_InitStruct);

 // LED1
 GPIO_InitStruct.Pin = GPIO_PIN_12;
 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
 GPIO_InitStruct.Pull = GPIO_NOPULL;
 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
 HAL_GPIO_Init(GPIOI, &GPIO_InitStruct);

 // ESP GPIO0
 GPIO_InitStruct.Pin = GPIO_PIN_6;
 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
 GPIO_InitStruct.Pull = GPIO_NOPULL;
 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
 HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

 //ESP GPIO2
 GPIO_InitStruct.Pin = GPIO_PIN_8;
 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
 GPIO_InitStruct.Pull = GPIO_NOPULL;
 GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
 HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);

}

// Here the callback functions for the interrupt based loopback communication of UART2 are defined
// Rx callback
void HAL_UART_RxCpltCallback(UART_HandleTypeDef * huart){
	if(huart->Instance == huart2.Instance){
		//When a byte is received turn off the LED 
		HAL_GPIO_WritePin(GPIOI,GPIO_PIN_12,GPIO_PIN_RESET);
		//Increment tx_buff before next transmit
		tx_buff++;
		// Transmit again
		HAL_UART_Transmit_IT(&huart2,&tx_buff,1);
	}
}


// Tx callback
void HAL_UART_TxCpltCallback(UART_HandleTypeDef * huart){
	if(huart->Instance == huart2.Instance){
		// Transmit is finished, now arm the receive interrupt
		HAL_UART_Receive_IT(&huart2,&rx_buff,1);
	}
}

// Error Callback
void HAL_UART_ErrorCallback(UART_HandleTypeDef * huart){
	if(huart->Instance == huart2.Instance){
		// Trap execution here if there is a UART error
 while(1){}
	}
}

// Error handler
void Error_Handler(void)
{

 __disable_irq();
 while (1)
 {
 }

}

I suspect there might be a race condition where the looped-back byte arrives before the MCU has fully finished the TxCpltCallback and armed the Receive_IT. Is this a known issue?

Any help or suggestions would be greatly appreciated!

Thanks in advance.

    This topic has been closed for replies.
    Best answer by KnarfB

    In the schematics cutout, the red crosses are unpopulated solder bridges.

    Like the one that could connect pin PD5 to PMOD#2. The numbers are hard to guess, indeed.

    Found a better one for rev. D04 here: https://www.st.com/resource/en/schematic_pack/mb1248-h747i-d04-schematic.pdf

    KnarfB_0-1752769625330.png

    so you have to open SB34 and to close SB33 for that.

    Similar, open S36 and close SB35 for PD6 connection to PMOD#3.

    Unfortunately, they are not mentioned in the UM2411 user manual, couldn't locate them on the board.

    Double check with the board docs before turinng the soldering iron on.

    hth

    KnarfB

    3 replies

    Technical Moderator
    July 17, 2025

    Hello @Remle 


    @Remle wrote:

    The Error_Handler() is not being called, and there are no HAL errors returned from the functions. It seems like the receive interrupt is not being triggered after the second transmit cycle.


    Have you tried placing a breakpoint inside the IRQ handler to verify whether it is being triggered after the second transmit cycle ? This is a crucial first step to confirm if the interrupt is firing as expected.

    Additionally, I recommend checking the error flags that lead to the invocation of Error_Handler(). Understanding which flags are set when the error occurs can help pinpoint the root cause.

    Regarding the UART communication, it might be more effective to call  HAL_UART_Transmit_IT() and HAL_UART_Receive_IT(). inside main function. You can set flags within the callback functions HAL_UART_TxCpltCallback() and HAL_UART_RxCpltCallback().

    Please see the implementation below: 

    #include "stm32fxxx_hal.h" // Replace with your specific device header
    
    UART_HandleTypeDef huart1; // UART handle (adjust as per your UART instance)
    
    // Flags to indicate completion of RX
    volatile uint8_t uartRxCompleteFlag = 0;
    
    // Buffers for TX and RX
    uint8_t txBuffer[] = "Hello UART Interrupt!\r\n";
    uint8_t rxBuffer[20]; // Adjust size as needed
    
    int main(void)
    {
     HAL_Init();
     // SystemClock_Config(); // Configure system clock if needed
     // MX_USART1_UART_Init(); // Initialize UART peripheral (generated by CubeMX or manually)
    
     if (HAL_UART_Receive_IT() != HAL_OK)
     {
     // Handle error
     Error_Handler();
     }
     if (HAL_UART_Transmit_IT() != HAL_OK)
     {
     // Handle error
     Error_Handler();
     }
    
     while (1)
     {
     // Example: Check if RX completed
     if (uartRxCompleteFlag)
     {
     uartRxCompleteFlag = 0;
    
     if (HAL_UART_Receive_IT() != HAL_OK)
     {
     // Handle error
     Error_Handler();
     }
     if (HAL_UART_Transmit_IT() != HAL_OK)
     {
     // Handle error
     Error_Handler();
     }
     }
     }
    }
    
    // Callback called by HAL when transmission is complete
    void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
    {
    }
    
    // Callback called by HAL when reception is complete
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
     if (huart->Instance == huart1.Instance)
     {
     uartRxCompleteFlag = 1;
     }
    }
    
    // Optional: Error callback to catch UART errors
    void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
    {
     if (huart->Instance == huart1.Instance)
     {
     // You can check error flags here and call Error_Handler if needed
     Error_Handler();
     }
    }
    
    void Error_Handler(void)
    {
     // User can add error handling code here
     while (1)
     {
     // Stay here
     }
    }
    RemleAuthor
    Explorer
    July 17, 2025

    Hello,

    thank you for your answer. I have implemented the code as you suggested. This code saddly does not work also.

    Regarding: "Additionally, I recommend checking the error flags that lead to the invocation of Error_Handler(). Understanding which flags are set when the error occurs can help pinpoint the root cause." Every error flag that is thrown is an ErrorCode of 4. 

    Im suspecting my issue is on the hardware side. Do I have to close/open any solder bridges on the STM32H747I-DISCO  to enable UART communication over the STMod connector? Im pretty sure my configuration in CubeMX is correct. 

    Technical Moderator
    July 17, 2025

    Hello @Remle 

    The ErrorCode 4 corresponds to HAL_UART_ERROR_FE.

    Could you try with lower baud rate please? 

    Could you check the quality of your hardware connection? 

    Super User
    July 17, 2025

    You should be ready to receive the character prior to when it's sent. After it's sent, it's too late.

    Super User
    July 17, 2025

    exactly. HAL_UART_Receive_IT is typically called at the end of HAL_UART_RxCpltCallback to immediately get ready for more input to come.

    You can do the same for TX, incrementing a static index variable and stop when the end of the message was reached.

    hth

    KnarfB 

    Graduate II
    July 17, 2025

    This is wrong. The data has already been transmitted and done before you armed the Rx interrupt.

    void HAL_UART_TxCpltCallback(UART_HandleTypeDef * huart){
    	if(huart->Instance == huart2.Instance){
    		// Transmit is finished, now arm the receive interrupt
    		HAL_UART_Receive_IT(&huart2,&rx_buff,1);
    	}
    }

     

    you need to call HAL_UART_Receive_IT before your main while loop and before your 1st transmit.

    // Main function
    int main(void)
    {
     // Initialize peripherals, clock and HAL
     HAL_Init();
     SystemClock_Config();
     MX_GPIO_Init();
     MX_USART1_UART_Init();
     MX_USART2_UART_Init();
    
     // Turn on LED first (Note: My LED is on with SET, off with RESET)
     HAL_GPIO_WritePin(GPIOI,GPIO_PIN_12,GPIO_PIN_SET);
    
     HAL_UART_Receive_IT(&huart2,&rx_buff,1);
    
     // Initial Transmit - Kicks off the loop
     HAL_UART_Transmit_IT(&huart2, &rx_buff,1);
    
     while (1)
     {
     if(rxDataRdy)
     {
     rxDataRdy = false;
    
     // Need some delay/processing or UART interupts will be firing back to back and may keep other interrupts from working reliably
     HAL_UART_Transmit_IT(&huart2, &rx_buff,1);
     
     }
    
     // Loop forever, everything is handled by interrupts
     }
    
    }

    Then in your callback, you need to set a flag to indicate you have new data. You also call HAL_UART_Receive_IT

    bool rxDataRdy = false;
    
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef * huart){
    	if(huart->Instance == huart2.Instance){
    		//When a byte is received turn off the LED 
    		HAL_GPIO_WritePin(GPIOI,GPIO_PIN_12,GPIO_PIN_RESET);
    		//Increment tx_buff before next transmit
    		tx_buff++;
     HAL_UART_Receive_IT(&huart2,&rx_buff,1);
    
    		rxDataRdy = true
    	}
    }

     

    and you don't need HAL_UART_TxCpltCallback