cancel
Showing results for 
Search instead for 
Did you mean: 

UART + DMA and IDLE line interrupt with HAL layer does not work as expected

BEnge.1
Associate III

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 */
}

1 ACCEPTED SOLUTION

Accepted Solutions
BEnge.1
Associate III

Hi,

I managed to solve my problem by using the DMA in circular mode.

By doing that, the DMA transfer is not stopped when entering in the HAL_UART_IRQHandler(...)

So if I received 5 bytes and enter in the HAL_UARTEx_RxEventCallback(...) with Size=1, I will enter again in the the HAL_UARTEx_RxEventCallback(...) when the DMA transfer will be finished with Size=5.

I just had to handle overflow in the HAL_UARTEx_RxEventCallback(...) function and my UART_DMA_RECV(...) functions to keep track of where to read in the DMA buffer.

View solution in original post

5 REPLIES 5
KnarfB
Principal III

What MCU are you using? Could the missing chars be in a FIFO? This could explain a difference of 4 and your fix.

hth

KnarfB

BEnge.1
Associate III

@KnarfB​ Thank you for replying, I am using the STM32L486RG.

@KnarfB​ What FIFO are you referring to?

BEnge.1
Associate III

Hi,

I managed to solve my problem by using the DMA in circular mode.

By doing that, the DMA transfer is not stopped when entering in the HAL_UART_IRQHandler(...)

So if I received 5 bytes and enter in the HAL_UARTEx_RxEventCallback(...) with Size=1, I will enter again in the the HAL_UARTEx_RxEventCallback(...) when the DMA transfer will be finished with Size=5.

I just had to handle overflow in the HAL_UARTEx_RxEventCallback(...) function and my UART_DMA_RECV(...) functions to keep track of where to read in the DMA buffer.

Piranha
Chief II

Don't waste your time on a broken bloatware...

https://github.com/MaJerle/stm32-usart-uart-dma-rx-tx