cancel
Showing results for 
Search instead for 
Did you mean: 

STM32H563 UART DMA idle not triggering

mipi
Associate

Hello everyone,

I'm working on a custom board with an STM32H563 and using CubeMX, HAL, and ThreadX.

I am trying to receive variable-length UART data via DMA using HAL_UARTEx_ReceiveToIdle_DMA(). My problem is that the HAL_UARTEx_RxEventCallback never triggers for the HAL_UART_RXEVENT_IDLE event. It only triggers for HAL_UART_RXEVENT_HT (Half Transfer) and HAL_UART_RXEVENT_TC (Transfer Complete).

Setup

  • Function Call: HAL_UARTEx_ReceiveToIdle_DMA()
  • RX Buffer Size: 1000 bytes
  • DMA Mode: Circular
  • Data Source: A peripheral sends packets of variable size (approx. 2048 - 4096 bytes) at 1,000,000 bits/s.
  • Idle Time: There is a guaranteed idle time of > 45ms between packets. This should be more than enough to trigger the UART IDLE event.

Here is the .ioc

kasjdlfkjsadfjl.png

Here is the RX line on the scope:

WhatsApp Image 2025-11-08 at 06.23.47.jpeg

Problem

Expected Behavior: Given the long idle time, I expect HAL_UARTEx_RxEventCallback to be called with an event type of HAL_UART_RXEVENT_IDLE after each packet.

Actual Behavior: The IDLE event never fires. I only get Half Transfer and Transfer Complete events (so when the buffer is half full or full).

Here is my RxEventCallback handler:

// This is the callback I EXPECT to fire for IDLE
void Radar_Handler_HandleRxEvent(UART_HandleTypeDef *huart, uint16_t Size)
{
    if (huart->Instance == _radar1_handle.huart_data->Instance)
    {
        switch (HAL_UARTEx_GetRxEventType(huart))
        {
        case HAL_UART_RXEVENT_TC:
            io_handler_send_usb_message("Transmission completed\n");
            break;
        case HAL_UART_RXEVENT_IDLE:
            // THIS CASE NEVER EXECUTES
            io_handler_send_usb_message("Transmission idle\n");
            break;
        case HAL_UART_RXEVENT_HT:
            io_handler_send_usb_message("Transmission half complete\n");
            break;
        default:
            io_handler_send_usb_message("Unknown UART RX event\n");
            break;
        }
        snprintf(msg, sizeof(msg), "%u bytes\r\n", Size);
        io_handler_send_usb_message(msg);
    }
}

This produces the following debug output, showing only HT and TC events:

Transmission half complete
500 bytes
Transmission completed
1000 bytes
Transmission half complete
500 bytes
Transmission completed
1000 bytes
...

Initialization

Here is how I initialize the DMA.

HAL_StatusTypeDef Radar_Handler_Init(UART_HandleTypeDef *huart_control,
                                     UART_HandleTypeDef *huart_data,
                                     GPIO_TypeDef *nreset_port,
                                     uint16_t nreset_pin)
{
    // ... (internal init)

    // Start the DMA reception
    HAL_UARTEx_ReceiveToIdle_DMA(_radar1_handle.huart_data, 
                                 _radar1_handle.data_rx_buffer, 
                                 sizeof(_radar1_handle.data_rx_buffer));

    // This is a workaround but it is commented out.
    // Calling it causes the "Weird Behavior" described below.
    // UART_CheckIdleState(_radar1_handle.huart_data); 

    // ... (semaphore create)
    return status;
}

Callbacks in main.c:

/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  Radar_Handler_HandleRxInterrupt(huart);
}

void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
  Radar_Handler_HandleRxEvent(huart, Size);
}

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
  uint32_t error = huart->ErrorCode;
  io_handler_send_usb_message("UART1 error occurred\n");
  if (error & HAL_UART_ERROR_ORE)
    __HAL_UART_CLEAR_OREFLAG(huart);
}

/* USER CODE END 4 */

Weird behavior

This is the part that makes me suspect a HAL or config bug.

If I uncomment the line UART_CheckIdleState(_radar1_handle.huart_data); in my Init function, the entire interrupt behavior changes:

HAL_UARTEx_RxEventCallback() stops triggering entirely.

The HAL_UART_RxCpltCallback() starts triggering instead.

Inside the handler for HAL_UART_RxCpltCallback, I added a test function to manually check the IDLE flag.

Test function called by HAL_UART_RxCpltCallback:

void Radar_Handler_HandleRxInterrupt(UART_HandleTypeDef *huart)
{
    if (huart->Instance == _radar1_handle.huart_data->Instance)
    {
        // Manually check the IDLE flag
        if (__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE))
        {
            io_handler_send_usb_message("IDLE Interruption (from RxCplt)\n");
        }
    }
}

This produces the following output. It seems the IDLE flag is being set, but only the interruption callback fires, not the rx event one, and it appears to fire only when the buffer is full (at TC).

IDLE Interruption (from RxCplt)
IDLE Interruption (from RxCplt)
...

Simply calling UART_CheckIdleState() (which I assume is a read-only check) should not change which callback (RxEvent vs. RxCplt) is active. This seems to indicate a state problem.

What I have tried

  • Changing GPDMA peripheral and channel: No change.
  • Enabling/Disabling NVIC on the USART1: Enabling it (with or without UART_CheckIdleState) stops all UART interrupts (if the DMA is active, if i turn the DMA off it works normally). Disabling it gives the behavior described above.
  • Disabling Overrun on UART settings in CubeMX: No change.
  • Disabling Circular Mode on GPDMA config in CubeMX: No change, except the DMA stops after the first 1000 bytes (as expected).
  • Handling the interruptions and init directly on main.c: No change.
  • Disabling HT and TC: No change.


I appreciate any tip or advice, I don't think the problem is with my custom board as everything else, I2S, USB, HSE, works without a problem.

 

1 ACCEPTED SOLUTION

Accepted Solutions

Hello @Guenael Cadier,

Thank you for your response. Your suggestion to investigate the USART1 IRQ was correct and led me to the solution.

The issue is now resolved.

The Solution (TL;DR)

  1. I enabled the USART1 global interrupt in CubeMX, as you suggested this is mandatory for IDLE detection.

  2. This initially caused the UART to enter an error state on startup (details below), which is why all interrupts seemed to stop.

  3. I fixed this by implementing HAL_UART_ErrorCallback() to re-arm the DMA reception immediately after an error is detected.

Here is the code added to main.c:

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART1)
  {
    // Re-start the DMA reception.
    if (HAL_UARTEx_ReceiveToIdle_DMA(huart, huart->pRxBuffPtr, huart->RxXferSize) != HAL_OK)
    {
      // Handle the case where re-arming fails
    }
  }
}

Detailed Explanation

After enabling the USART1 IRQ, I observed that HAL_UART_ErrorCallback  was being triggered immediately on startup, mostly with HAL_UART_ERROR_NE (Noise Error).

My peripheral sends a continuous stream of data bursts, and it appears one of these bursts was active during initialization, immediately placing the UART peripheral into an error state. By default, the HAL driver does not automatically recover from this state, which is why enabling the IRQ seemed to "stop all interrupts."

By implementing HAL_UART_ErrorCallback and re-starting the HAL_UARTEx_ReceiveToIdle_DMA transfer, the driver now correctly handles this initial error, recovers, and successfully detects the IDLE line events as expected.

Follow-up Question: As an optimization, is there a better method to clear the error state and resume reception within the error callback, other than calling the full HAL_UARTEx_ReceiveToIdle_DMA function?

Thank you for your help.

View solution in original post

2 REPLIES 2
Guenael Cadier
ST Employee

Hello @mipi 

In order to have HAL_UARTEx_ReceiveToIdle_DMA() working as expected, it is mandatory to have USART1 IRQ enabled : IDLE event will be detected using IDLE interrupt at UART level.
If you enable it using MX, USART1_IRQn should be enabled durint HAL_UART_Init() call so prior any communications.

GuenaelCadier_0-1762762519208.png

Now, if you mentioned "Enabling it stops all UART interrupts", I think it is the part that needs to be investigated.

What do you mean ? no reception at all ?
Could you check content of USART1_ISR when it occurs ? (to check if OverRun error flag is raised).
Regards

 

 

Hello @Guenael Cadier,

Thank you for your response. Your suggestion to investigate the USART1 IRQ was correct and led me to the solution.

The issue is now resolved.

The Solution (TL;DR)

  1. I enabled the USART1 global interrupt in CubeMX, as you suggested this is mandatory for IDLE detection.

  2. This initially caused the UART to enter an error state on startup (details below), which is why all interrupts seemed to stop.

  3. I fixed this by implementing HAL_UART_ErrorCallback() to re-arm the DMA reception immediately after an error is detected.

Here is the code added to main.c:

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART1)
  {
    // Re-start the DMA reception.
    if (HAL_UARTEx_ReceiveToIdle_DMA(huart, huart->pRxBuffPtr, huart->RxXferSize) != HAL_OK)
    {
      // Handle the case where re-arming fails
    }
  }
}

Detailed Explanation

After enabling the USART1 IRQ, I observed that HAL_UART_ErrorCallback  was being triggered immediately on startup, mostly with HAL_UART_ERROR_NE (Noise Error).

My peripheral sends a continuous stream of data bursts, and it appears one of these bursts was active during initialization, immediately placing the UART peripheral into an error state. By default, the HAL driver does not automatically recover from this state, which is why enabling the IRQ seemed to "stop all interrupts."

By implementing HAL_UART_ErrorCallback and re-starting the HAL_UARTEx_ReceiveToIdle_DMA transfer, the driver now correctly handles this initial error, recovers, and successfully detects the IDLE line events as expected.

Follow-up Question: As an optimization, is there a better method to clear the error state and resume reception within the error callback, other than calling the full HAL_UARTEx_ReceiveToIdle_DMA function?

Thank you for your help.