UART + DMA and IDLE line interrupt with HAL layer does not work as expected
Hello,
I am trying to update some code to use UART with DMA for receiving instead of the polling method.
I want the implementation to work for messages of unknown length so I use the HAL_UARTEx_ReceiveToIdle_DMA function.
I know that the maximum message length i can receive is MAX_LEN (=1024) so I call HAL_UARTEx_ReceiveToIdle_DMA(&huart2, my_buffer, MAX_LEN)
The problem I noticed is that sometimes when I get in the HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) , the value of "Size" is equal to 1 even though I send 5 bytes in the UART. From what i understand, that this is because in the HAL_UART_IRQHandler (that is called when the line goes IDLE), there is this code:
[...]
/* Check if DMA mode is enabled in UART */
if (HAL_IS_BIT_SET(huart->Instance->CR3, USART_CR3_DMAR))
{
/* DMA mode enabled */
/* Check received length : If all expected data are received, do nothing,
(DMA cplt callback will be called).
Otherwise, if at least one data has already been received, IDLE event is to be notified to user */
uint16_t nb_remaining_rx_data = (uint16_t) __HAL_DMA_GET_COUNTER(huart->hdmarx);
[...]For some reason, when the line 10 is reached, __HAL_DMA_GET_COUNTER(huart->hdmarx); is equal to MAX_LEN - 1 which means that the DMA transferred only 1 byte of the 5 that were sent. I managed to solve this problem by addig a HAL_DELAY(1) on top of line 10 so that the DMA has time to finish the transfer of the 5 bytes and the callback is hence called with 5 as size instead of 1.
However, I would like to achieve the same result without modifying the HAL layer, do you have any suggestion on how I could achieve that ?
Here is the code that I implemented, the goal is to have a function that I called UART_DMA_RECV(...) that replaces HAL_UART_Receive(...) but that relies on the DMA so that if the MCU is interrupted during the exection of the function, no bytes are missed
uint32_t received_size = 0;
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
if (huart->Instance == USART2)
{
received_size = Size;
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, UART_BUFFER, MAX_RECV_SIZE);
__HAL_DMA_DISABLE_IT(&hdma_usart2_rx, DMA_IT_HT);
}
}
/************************************************************************************//**
* \brief Replaces HAL_UART_Receive(huart, pData, Size, Timeout)
****************************************************************************************/
static int UART_DMA_RECV(uint8_t* data, uint32_t size, uint32_t timeout)
{
received_size = 0;
int ret = 0;
uint32_t start_tick = HAL_GetTick();
while (received_size == 0 && HAL_GetTick() - start_tick < timeout)
{
/*wait*/
}
/*If we did not receive --> timeout */
if (received_size == 0)
{
ret = 1;
}
else
{
/* if we received less than expected --> copy what we received to data */
if (received_size < size)
{
memcpy(data, UART_BUFFER, received_size);
}
/* else copy size bytes from what we received to data */
else
{
memcpy(data, UART_BUFFER, size);
}
}
return ret;
}
int main(void)
{
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART2_UART_Init();
/* Infinite loop */
HAL_UARTEx_ReceiveToIdle_DMA(&huart2, UART_BUFFER, MAX_RECV_SIZE);
__HAL_DMA_DISABLE_IT(&hdma_usart2_rx, DMA_IT_HT); //Don't need Half transfer interrupt
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
uint32_t timeout = 100;
uint32_t message_size = 5;
UART_DMA_RECV(rx, message_size, timeout);
}
/* USER CODE END 3 */
}