2025-07-17 2:34 AM - last edited on 2025-07-17 4:01 AM by Amel NASRI
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:
main() calls HAL_UART_Transmit_IT() to send the first byte.
The HAL_UART_TxCpltCallback() is successfully triggered.
Inside the TxCpltCallback, I call HAL_UART_Receive_IT() to arm the receiver.
The byte is received, and HAL_UART_RxCpltCallback() is successfully triggered. The LED toggles as expected.
Inside the RxCpltCallback, I increment my data and call HAL_UART_Transmit_IT() to send the next byte.
The HAL_UART_TxCpltCallback() is triggered for the second time, which is correct.
Inside this second TxCpltCallback, I again call HAL_UART_Receive_IT() to arm the receiver for the next byte.
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.
Solved! Go to Solution.
2025-07-17 8:58 AM
Another question: Shouldnt the loopback connection work when im connecting a wire from TX to RX on the PMOD connector? Im having trouble with understanding the schematic you sent me. Im not really sure what bridge I have to open or close for the UART2 signal to be available on the STMod connector.
2025-07-17 9:42 AM - edited 2025-07-17 9:44 AM
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
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
2025-07-17 9:48 AM
Thank you very much! I just modified the bridges and the loopback works. After two day of nearly loosing my mind I am finally at peace :D
2025-07-17 9:51 AM - edited 2025-07-17 9:52 AM
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