Skip to main content
Associate
July 17, 2025
Solved

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

  • July 17, 2025
  • 3 replies
  • 1058 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.

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
 }
}
"To give better visibility on the answered topics, please click on ""Accept as Solution"" on the reply which solved your issue or answered your question.Saket_Om"
RemleAuthor
Associate
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? 

"To give better visibility on the answered topics, please click on ""Accept as Solution"" on the reply which solved your issue or answered your question.Saket_Om"
TDK
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.

"If you feel a post has answered your question, please click ""Accept as Solution""."
KnarfB
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 

Karl Yamashita
Principal
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 

 

If a reply has proven helpful, click on Accept as Solution so that it'll show at top of the post.CAN Jammer an open source CAN bus hacking toolCANableV3 Open Source