cancel
Showing results for 
Search instead for 
Did you mean: 

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

Remle
Visitor

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.

1 ACCEPTED SOLUTION

Accepted Solutions

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

View solution in original post

13 REPLIES 13
Saket_Om
ST Employee

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
TDK
Super User

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".

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 

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. 

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

Do I have to close/open any solder bridges on the STM32H747I-DISCO  to enable UART communication over the STMod connector?

Yes, when using PD5 PD6 ,see the data sheet

KnarfB_0-1752765309249.png

I would suggest using UART8 with pins on the Arduino connector:

KnarfB_1-1752765368207.png

what core is your code for M4? M7?

hth

KnarfB

Wow thank you! I completely missed that, I guess the reference manual of the board was the wrong place to look at. Ill modify the bridges and then try it again. My code is currently running on the M7 core. Is there any major difference when running on the M4 core?

I tried a various number of baud rates (from low to high) and the physical connection seems pretty solid. That being said, I cant expect anything to work if the solder bridges are not modified ^^ Thanks to @KnarfB, I now know that.

> Is there any major difference when running on the M4 core?

No, as long as you keep everything consistent.

Just tested an empty project on M7 on that board using UART8 with PJ8 and PJ9 and enabling interrupt in STM32CubeMX. Added a loopback wire and some code in CM7/main.c:

/* USER CODE BEGIN 0 */

uint8_t rx_buff[256]; 
int rx_index = 0;

void HAL_UART_RxCpltCallback(UART_HandleTypeDef * huart){
	if(huart->Instance == huart8.Instance) {
    if(++rx_index < sizeof(rx_buff)) {
      HAL_UART_Receive_IT(&huart8, &rx_buff[rx_index], 1);
    }
  }
}


uint8_t tx_buff[] = "The quick brown fox jumped over the lazy dog.";
int tx_index = 0;

void HAL_UART_TxCpltCallback(UART_HandleTypeDef * huart){
	if(huart->Instance == huart8.Instance) {
    if(++tx_index < sizeof(tx_buff)) {
      HAL_UART_Transmit_IT(&huart8, &tx_buff[tx_index], 1);
    }
  }
}

/* USER CODE END 0 */

and in main():

  /* USER CODE BEGIN 2 */

  HAL_UART_Receive_IT(&huart8, rx_buff, 1);
  HAL_UART_Transmit_IT(&huart8, tx_buff, 1);
  
  /* USER CODE END 2 */

This will copy the string as expected.

Note that HAL can handle more than just 1 char, this was only done for testing.

hth

KnarfB