2025-11-08 2:25 AM
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
Here is the .ioc
Here is the RX line on the scope:
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
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.
Solved! Go to Solution.
2025-11-12 5:03 PM - edited 2025-11-12 5:04 PM
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)
I enabled the USART1 global interrupt in CubeMX, as you suggested this is mandatory for IDLE detection.
This initially caused the UART to enter an error state on startup (details below), which is why all interrupts seemed to stop.
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.
2025-11-10 12:21 AM
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.
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
2025-11-12 5:03 PM - edited 2025-11-12 5:04 PM
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)
I enabled the USART1 global interrupt in CubeMX, as you suggested this is mandatory for IDLE detection.
This initially caused the UART to enter an error state on startup (details below), which is why all interrupts seemed to stop.
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.