cancel
Showing results for 
Search instead for 
Did you mean: 

HAL_UART_DMAStop does not stop DMA on UART RX

Jack3
Senior II

Using a Nucleo-G431KB developoment board, I played a bit with UART RX DMA.

I started a DMA RX on a linear buffer, and would stop and then restart the DMA.

I just observed that after receiving a couple of bytes, HAL_UART_DMAStop no longer stops the DMA on UART RX. The HAL_UART_DMAStop call does not return an error, but HAL_UART_Receive_DMA does, as the tranfer never stopped. I also observed characters would still be received into the DMA UART RX buffer, confirming the DMA didn't stop.

It happens only after I received a couple of bytes in the buffer. A single byte caused no problems.

I couldn't find the root cause.

Anyway, starting HAL_UART_Receive_DMA only once on a circular buffer and checking __HAL_DMA_GET_COUNTER is a better approach for incoming UART data of random size.

I'm just wondering if somebody noticed this too.

Is it a bug in HAL?

Unfortunately, I no longer have the code but it was just straight forward, like calling in a loop, something along these lines:

uint8_t DataRX[256];
 
while (1)
{
  HAL_UART_Receive_DMA(&huart1, DataRX, sizeof(DataRX))
 
  //Checking length and print buffer (on a different UART)
  uint16_t Last_Byte_Index = (uint16_t)(sizeof(DataRX) - __HAL_DMA_GET_COUNTER(huart1.hdmarx));
  // ... printing the buffer on another UART
 
  HAL_UART_DMAStop(&huart1);
}

Kind regards,

Jack

1 ACCEPTED SOLUTION

Accepted Solutions
Jack3
Senior II

Hi Sarra,

thank you for bearing with me. I was curious, so I wanted to repeat it, but this time all was running fine.

I must have been blind for some stupid mistake. I didn't keep that code so I had to redo it, and then didn't seem to hit any problems. I must be getting old a bit.

Nevertheless, enabling the uart interrupt appears to be a best practice anyway, if would it only to clear error flags, like framing errors which easily occur when connecting and disconnecting the lines.

I thought I would share some straight forward examples for other (co)developers to play with.

During the blocking HAL_Delay, characters are fetched using DMA on UART RX.

We can either use the UART RTO interrupt (Receive TimeOut in number of bits) to detect an end of the data reception (perhaps when receiving Modbus, 35 bits is a 3.5 character timout), or pol for received data, or recognize some specific received data.

Note, blocking calls should be avoided at all times, I believe. This is just a simple demo example.

Also as much as possible should be done using the hardware, so using DMA as much as possible is mostly a good thing. The MCU can then perform other tasks in the mean time.

First: Mode = DMA_CIRCULAR, start once (I think the best solution, as no characters will be missed)

Second: DMA_NORMAL or DMA_CIRCULAR, start/stop

  /* Mode = DMA_CIRCULAR, start once */
 
  /* Interrupt Enable */
  HAL_NVIC_EnableIRQ(USART1_IRQn);
 
  uint8_t DataRX[256];
  if (HAL_UART_Receive_DMA(&huart1, DataRX, sizeof(DataRX)) != HAL_OK)
  {
    /* Error */
    char Error[] = "Error HAL_UART_Receive_DMA\n";
    HAL_UART_Transmit(&huart2, (uint8_t*)Error, strlen(Error), 100); /* To USB serial port */
  }
 
  uint16_t First_Byte_Index = 0;
  uint16_t Last_Byte_Index_Previous = 0;
  while (1)
  {
    /* Show what's in the DataRX */
    uint16_t Last_Byte_Index = (uint16_t)(sizeof(DataRX) - __HAL_DMA_GET_COUNTER(huart1.hdmarx));
    uint16_t DataRX_Length = Last_Byte_Index - Last_Byte_Index_Previous;
    First_Byte_Index = Last_Byte_Index - DataRX_Length;
    /* If the new Index is greater than Index_old, we received something */
    if (DataRX_Length)
    {
      char Received[] = "Received: ";
      HAL_UART_Transmit(&huart2, (uint8_t*)Received, strlen(Received), 100); /* To USB serial port */
      for (uint16_t i = 0; i < DataRX_Length; i++)
      {
        uint16_t DataRX_Index = i + First_Byte_Index;
        /* Roll over DataRX_index if necessary */
        if (DataRX_Index > sizeof(DataRX))
        {
          DataRX_Index -= sizeof(DataRX);
        }
        HAL_UART_Transmit(&huart2, &DataRX[DataRX_Index], 1, 100); /* To USB serial port */
      }
      char CR[] = "\n";
      HAL_UART_Transmit(&huart2, (uint8_t*)CR, strlen(CR), 100); /* To USB serial port */
 
      /* Reset Index_old to latest Index */
      Last_Byte_Index_Previous = Last_Byte_Index;
    }
 
    HAL_UART_Transmit(&huart2, (uint8_t*)Test, strlen(Test), 100); /* To USB serial port */
 
    HAL_Delay(1000);
    HAL_IWDG_Refresh(&hiwdg); /* Don't forget to take good care of your watch-dog, feed it! */
  }
  /* Mode = DMA_NORMAL or DMA_CIRCULAR, start/stop */
 
  /* Interrupt Enable */
  HAL_NVIC_EnableIRQ(USART1_IRQn);
 
  uint8_t DataRX[256];
  while (1)
  {
    if (HAL_UART_Receive_DMA(&huart1, DataRX, sizeof(DataRX)) != HAL_OK)
    {
      /* Error */
      char Error[] = "Error HAL_UART_Receive_DMA\n";
      HAL_UART_Transmit(&huart2, (uint8_t*)Error, strlen(Error), 100); /* To USB serial port */
    }
 
    HAL_Delay(1000);
 
    /*Checking length and print buffer (on a different UART) */
    uint16_t Last_Byte_Index = (uint16_t)(sizeof(DataRX) - __HAL_DMA_GET_COUNTER(huart1.hdmarx));
 
    /* printing the buffer on another UART */
    if (Last_Byte_Index)
    {
      char Received[] = "Received: ";
      HAL_UART_Transmit(&huart2, (uint8_t*)Received, strlen(Received), 100); /* To USB serial port */
      for (uint16_t i = 0; i < Last_Byte_Index; i++)
      {
        HAL_UART_Transmit(&huart2, &DataRX[i], 1, 100); /* To USB serial port */
      }
      char CR[] = "\n";
      HAL_UART_Transmit(&huart2, (uint8_t*)CR, strlen(CR), 100); /* To USB serial port */
    }
 
    if (HAL_UART_DMAStop(&huart1) != HAL_OK)
    {
      /* Error */
      char Error[] = "Error HAL_UART_DMAStop\n";
      HAL_UART_Transmit(&huart2, (uint8_t*)Error, strlen(Error), 100); /* To USB serial port */
    }
 
    HAL_IWDG_Refresh(&hiwdg); /* Don't forget to take good care of your watch-dog, feed it! */
  }

In stm32g4xx_it.c we clear some error flags, would they occur. We could just clear them without checking, but maybe someone would maintain framing and parity error counters.

/**
  * @brief This function handles USART1 global interrupt / USART1 wake-up interrupt through EXTI line 25.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
 
  /* Noise immunity feature enabled for RS (Modbus-RTU) interfaces */
  /* Clear the errors if they occurred */
 
  /* UART parity error detected */
  if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_PE) == SET)
  {
    __HAL_UART_CLEAR_PEFLAG(&huart1);
  }
  /* UART frame error detected or break character is detected */
  if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_FE) == SET)
  {
    __HAL_UART_CLEAR_FEFLAG(&huart1);
  }
  /* UART noise error detected */
  if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_NE) == SET)
  {
    __HAL_UART_CLEAR_NEFLAG(&huart1);
  }
  /* UART overrun error detected */
  if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_ORE) == SET)
  {
    __HAL_UART_CLEAR_OREFLAG(&huart1);
  }
  /* UART idle flag */
  if((__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET))
  {
    __HAL_UART_CLEAR_IDLEFLAG(&huart1);
  }
 
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
 
  /* USER CODE END USART1_IRQn 1 */
}

Hope this is useful.

Any feedback is always welcome.

Warm regards to all of you!

View solution in original post

2 REPLIES 2
Sarra.S
ST Employee

Hello @Jack​,

In some cases, HAL_UART_DMAStop may not stop the DMA transfer when a couple of bytes have been sent, it's likely because the UART is not configured correctly and is not interrupting the CPU when the last byte has been received.

To fix that, you should configure the UART to interrupt on the last byte transmitted.

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.

Jack3
Senior II

Hi Sarra,

thank you for bearing with me. I was curious, so I wanted to repeat it, but this time all was running fine.

I must have been blind for some stupid mistake. I didn't keep that code so I had to redo it, and then didn't seem to hit any problems. I must be getting old a bit.

Nevertheless, enabling the uart interrupt appears to be a best practice anyway, if would it only to clear error flags, like framing errors which easily occur when connecting and disconnecting the lines.

I thought I would share some straight forward examples for other (co)developers to play with.

During the blocking HAL_Delay, characters are fetched using DMA on UART RX.

We can either use the UART RTO interrupt (Receive TimeOut in number of bits) to detect an end of the data reception (perhaps when receiving Modbus, 35 bits is a 3.5 character timout), or pol for received data, or recognize some specific received data.

Note, blocking calls should be avoided at all times, I believe. This is just a simple demo example.

Also as much as possible should be done using the hardware, so using DMA as much as possible is mostly a good thing. The MCU can then perform other tasks in the mean time.

First: Mode = DMA_CIRCULAR, start once (I think the best solution, as no characters will be missed)

Second: DMA_NORMAL or DMA_CIRCULAR, start/stop

  /* Mode = DMA_CIRCULAR, start once */
 
  /* Interrupt Enable */
  HAL_NVIC_EnableIRQ(USART1_IRQn);
 
  uint8_t DataRX[256];
  if (HAL_UART_Receive_DMA(&huart1, DataRX, sizeof(DataRX)) != HAL_OK)
  {
    /* Error */
    char Error[] = "Error HAL_UART_Receive_DMA\n";
    HAL_UART_Transmit(&huart2, (uint8_t*)Error, strlen(Error), 100); /* To USB serial port */
  }
 
  uint16_t First_Byte_Index = 0;
  uint16_t Last_Byte_Index_Previous = 0;
  while (1)
  {
    /* Show what's in the DataRX */
    uint16_t Last_Byte_Index = (uint16_t)(sizeof(DataRX) - __HAL_DMA_GET_COUNTER(huart1.hdmarx));
    uint16_t DataRX_Length = Last_Byte_Index - Last_Byte_Index_Previous;
    First_Byte_Index = Last_Byte_Index - DataRX_Length;
    /* If the new Index is greater than Index_old, we received something */
    if (DataRX_Length)
    {
      char Received[] = "Received: ";
      HAL_UART_Transmit(&huart2, (uint8_t*)Received, strlen(Received), 100); /* To USB serial port */
      for (uint16_t i = 0; i < DataRX_Length; i++)
      {
        uint16_t DataRX_Index = i + First_Byte_Index;
        /* Roll over DataRX_index if necessary */
        if (DataRX_Index > sizeof(DataRX))
        {
          DataRX_Index -= sizeof(DataRX);
        }
        HAL_UART_Transmit(&huart2, &DataRX[DataRX_Index], 1, 100); /* To USB serial port */
      }
      char CR[] = "\n";
      HAL_UART_Transmit(&huart2, (uint8_t*)CR, strlen(CR), 100); /* To USB serial port */
 
      /* Reset Index_old to latest Index */
      Last_Byte_Index_Previous = Last_Byte_Index;
    }
 
    HAL_UART_Transmit(&huart2, (uint8_t*)Test, strlen(Test), 100); /* To USB serial port */
 
    HAL_Delay(1000);
    HAL_IWDG_Refresh(&hiwdg); /* Don't forget to take good care of your watch-dog, feed it! */
  }
  /* Mode = DMA_NORMAL or DMA_CIRCULAR, start/stop */
 
  /* Interrupt Enable */
  HAL_NVIC_EnableIRQ(USART1_IRQn);
 
  uint8_t DataRX[256];
  while (1)
  {
    if (HAL_UART_Receive_DMA(&huart1, DataRX, sizeof(DataRX)) != HAL_OK)
    {
      /* Error */
      char Error[] = "Error HAL_UART_Receive_DMA\n";
      HAL_UART_Transmit(&huart2, (uint8_t*)Error, strlen(Error), 100); /* To USB serial port */
    }
 
    HAL_Delay(1000);
 
    /*Checking length and print buffer (on a different UART) */
    uint16_t Last_Byte_Index = (uint16_t)(sizeof(DataRX) - __HAL_DMA_GET_COUNTER(huart1.hdmarx));
 
    /* printing the buffer on another UART */
    if (Last_Byte_Index)
    {
      char Received[] = "Received: ";
      HAL_UART_Transmit(&huart2, (uint8_t*)Received, strlen(Received), 100); /* To USB serial port */
      for (uint16_t i = 0; i < Last_Byte_Index; i++)
      {
        HAL_UART_Transmit(&huart2, &DataRX[i], 1, 100); /* To USB serial port */
      }
      char CR[] = "\n";
      HAL_UART_Transmit(&huart2, (uint8_t*)CR, strlen(CR), 100); /* To USB serial port */
    }
 
    if (HAL_UART_DMAStop(&huart1) != HAL_OK)
    {
      /* Error */
      char Error[] = "Error HAL_UART_DMAStop\n";
      HAL_UART_Transmit(&huart2, (uint8_t*)Error, strlen(Error), 100); /* To USB serial port */
    }
 
    HAL_IWDG_Refresh(&hiwdg); /* Don't forget to take good care of your watch-dog, feed it! */
  }

In stm32g4xx_it.c we clear some error flags, would they occur. We could just clear them without checking, but maybe someone would maintain framing and parity error counters.

/**
  * @brief This function handles USART1 global interrupt / USART1 wake-up interrupt through EXTI line 25.
  */
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
 
  /* Noise immunity feature enabled for RS (Modbus-RTU) interfaces */
  /* Clear the errors if they occurred */
 
  /* UART parity error detected */
  if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_PE) == SET)
  {
    __HAL_UART_CLEAR_PEFLAG(&huart1);
  }
  /* UART frame error detected or break character is detected */
  if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_FE) == SET)
  {
    __HAL_UART_CLEAR_FEFLAG(&huart1);
  }
  /* UART noise error detected */
  if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_NE) == SET)
  {
    __HAL_UART_CLEAR_NEFLAG(&huart1);
  }
  /* UART overrun error detected */
  if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_ORE) == SET)
  {
    __HAL_UART_CLEAR_OREFLAG(&huart1);
  }
  /* UART idle flag */
  if((__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE) != RESET))
  {
    __HAL_UART_CLEAR_IDLEFLAG(&huart1);
  }
 
  /* USER CODE END USART1_IRQn 0 */
  HAL_UART_IRQHandler(&huart1);
  /* USER CODE BEGIN USART1_IRQn 1 */
 
  /* USER CODE END USART1_IRQn 1 */
}

Hope this is useful.

Any feedback is always welcome.

Warm regards to all of you!