cancel
Showing results for 
Search instead for 
Did you mean: 

[Bug report] HAL_UART_TxCpltCallback() doesn't get called in DMA normal (instead of circular) mode

SwineOne
Associate II

I believe I may have found a bug in the latest version (1.24.1) of the STM32F4 HAL library.

The HAL documentation states this about using UART in DMA mode:

DMA mode IO operation

  • Send an amount of data in non blocking mode (DMA) using HAL_UART_Transmit_DMA()
  • ...
  • At transmission end of transfer HAL_UART_TxCpltCallback is executed and user can add his own code by customization of function pointer HAL_UART_TxCpltCallback

However, here is the code called when a UART DMA Tx is completed:

static void UART_DMATransmitCplt(DMA_HandleTypeDef *hdma)
{
  UART_HandleTypeDef *huart = (UART_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;
  /* DMA Normal mode*/
  if ((hdma->Instance->CR & DMA_SxCR_CIRC) == 0U)
  {
    huart->TxXferCount = 0x00U;
 
    /* Disable the DMA transfer for transmit request by setting the DMAT bit
       in the UART CR3 register */
    CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAT);
 
    /* Enable the UART Transmit Complete Interrupt */
    SET_BIT(huart->Instance->CR1, USART_CR1_TCIE);
 
  }
  /* DMA Circular mode */
  else
  {
#if (USE_HAL_UART_REGISTER_CALLBACKS == 1)
    /*Call registered Tx complete callback*/
    huart->TxCpltCallback(huart);
#else
    /*Call legacy weak Tx complete callback*/
    HAL_UART_TxCpltCallback(huart);
#endif /* USE_HAL_UART_REGISTER_CALLBACKS */
  }
}

As can be seen, the callback is only called when DMA is in circular mode. This isn't mentioned in the documentation, and moreover, it makes no sense to me: if I fire a UART transfer in normal mode, this callback is exactly what I'd like to use to monitor when the transfer ends. I don't see why this callback should be restricted to circular mode.

Also, if you look at, say, the SPI code, it doesn't distinguish between DMA normal and circular mode; it calls the callback either way. The code for I2C is way more complex, but I also don't see it making a distinction between DMA normal and circular mode. So, at a minimum, the UART behavior is inconsistent.

I suggest the call to the callback be taken out of the else clause, so it gets called regardless of whether DMA is in normal or circular mode.

4 REPLIES 4

For non-circular DMA its called after the last byte has been transmitted, which is not when the DMA throws the transfer complete interrupt.

After DMA finishes, i.e. in its TC interrupt, Cube/HAL driver enables the USART's TC interrupt (see line 14 of the code yourself quoted above) and then when that interrupt happens it calls the callback. Find it yourself.

I don't Cube.

JW

You're right. It's even documented at the beginning of the file, which I didn't notice (in the last item):

(##) DMA Configuration if you need to use DMA process (HAL_UART_Transmit_DMA()

       and HAL_UART_Receive_DMA() APIs):

      (+++) Declare a DMA handle structure for the Tx/Rx stream.

      (+++) Enable the DMAx interface clock.

      (+++) Configure the declared DMA handle structure with the required

         Tx/Rx parameters.

      (+++) Configure the DMA Tx/Rx stream.

      (+++) Associate the initialized DMA handle to the UART DMA Tx/Rx handle.

      (+++) Configure the priority and enable the NVIC for the transfer complete

         interrupt on the DMA Tx/Rx stream.

      (+++) Configure the USARTx interrupt priority and enable the NVIC USART IRQ handle

         (used for last byte sending completion detection in DMA non circular mode)

I wonder if there's a particular reason for doing this differently for UART? I assume it's to make sure the interrupt is only called after the transmission is actually complete, rather than when the last byte is queued. But then, how is this situation different with SPI and I2C?

I always thought declaring the DMA interrupt was enough for the callbacks, but in this case it's also necessary to enable the UART interrupt. I don't like how one needs to remember these tiny differences between peripherals -- you risk forgetting it and then introducing a bug in your code.

Ahmad M.Nejad
Associate III

This method of second stage interrupt is very suitable for RS485 transmission. The second Interrupt can be used for toggle of DE pin of RS485 driver.

Refer to AN3070 application note of ST:

https://www.st.com/resource/en/application_note/cd00249778.pdf

Wojciech Dembinski
Associate

In the G4 series HAL source file (stm32g4xx_uart_hal.c) this is not mentioned. I would suggest to update documentation there, it could save time of your customers in the future.