Skip to main content
Malte Moers
Associate
June 15, 2017
Solved

STM32L4 UART DMA TX Callback not called

  • June 15, 2017
  • 16 replies
  • 6381 views
Posted on June 15, 2017 at 13:00

Dear community,

I used CubeMX to setup a project for UART DMA transfers RX and TX. Apart from my protocol logic, I added two callbacks:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *UartHandle)
{
 rx_cplt=true;
}
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *UartHandle)
{
 tx_cplt=true;
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

and start transfers with calls to

HAL_UART_Receive_DMA(&huart2, (uint8_t *)buf, len);
HAL_UART_Transmit_DMA(&huart2, (uint8_t *)x, len)�?�?�?�?

Both work fine in the sense that the data is transmitted, as I can see from logic analyzer and host side. But the problem is, that only the Rx Callback is called after transfer has finished.

I walked through the source code with debugger and found that the interrupt for TX complete is actually triggered, but the callback function is never called from HAL implementation. I checked the file 'stm32l4xx_hal_uart.c' and found the following function, which is called after the UART DMA TX transfer has completed:

static void UART_DMATransmitCplt(DMA_HandleTypeDef *hdma)
{
 UART_HandleTypeDef* huart = (UART_HandleTypeDef*)(hdma->Parent);
 
 /* DMA Normal mode */
 if ( HAL_IS_BIT_CLR(hdma->Instance->CCR, DMA_CCR_CIRC) )
 {
 huart->TxXferCount = 0;
 /* Disable the DMA transfer for transmit request by resetting 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
 {
 HAL_UART_TxCpltCallback(huart);
 }
}�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

Essentially, this tells me, that when in normal mode (not circular), the registered callback is never executed. Is this a bug in the HAL implementation?

After adding the callback in the code executed if normal mode is activated, everything works like a charm. I would prefer not to change the HAL library code, though.

Has anybody experienced this problem before? Am I using the driver correctly?

You can find the complete source code of my application in the attachment.

Cheers,

Malte

#uart-tx #uart #dma
This topic has been closed for replies.
Best answer by Zt Liu
Posted on June 15, 2017 at 16:05

You should turn on global UART IRQ in cubeMX, this handler is not seen in your code.

so that in your stm32l4xx_it.c, you should see like this

void USART2_IRQHandler(void)

{

   /* USER CODE BEGIN USART2_IRQn 0 */

   /* USER CODE END USART2_IRQn 0 */

   HAL_UART_IRQHandler(&huart2);

   /* USER CODE BEGIN USART2_IRQn 1 */

   /* USER CODE END USART2_IRQn 1 */

}

In the HAL_UART_IRQHander(),

your HAL_UART_TxCpltCallback() would be called in the UART_EndTransmit_IT();

You can also see the examples in your downloaded CUBE example concerning UART_DMA

Good Luck!

16 replies

Zt Liu
Zt LiuBest answer
Senior III
June 15, 2017
Posted on June 15, 2017 at 16:05

You should turn on global UART IRQ in cubeMX, this handler is not seen in your code.

so that in your stm32l4xx_it.c, you should see like this

void USART2_IRQHandler(void)

{

   /* USER CODE BEGIN USART2_IRQn 0 */

   /* USER CODE END USART2_IRQn 0 */

   HAL_UART_IRQHandler(&huart2);

   /* USER CODE BEGIN USART2_IRQn 1 */

   /* USER CODE END USART2_IRQn 1 */

}

In the HAL_UART_IRQHander(),

your HAL_UART_TxCpltCallback() would be called in the UART_EndTransmit_IT();

You can also see the examples in your downloaded CUBE example concerning UART_DMA

Good Luck!

Malte Moers
Associate
June 15, 2017
Posted on June 15, 2017 at 16:56

Thanks for the quick response. Your suggestion to activate the global UART IRQ worked perfectly!

schperplata
Associate III
August 12, 2017
Posted on August 13, 2017 at 00:28

Hello,

Ihave a similar problem withUART DMA transfer complete/half transfer callback function using CubeMx and HAL.

I use UART with DMA on UART RX. I have enabled DMA transfer complete and half transfer interrupts in usart.c:

/* USART1 DMA Init */
/* USART1_RX Init */
hdma_usart1_rx.Instance = DMA1_Channel3;
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
{
 _Error_Handler(__FILE__, __LINE__);
}
__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?
HAL_DMA_RegisterCallback(&hdma_usart1_rx, HAL_DMA_XFER_HALFCPLT_CB_ID, handle_uart_dma_data); 
HAL_DMA_RegisterCallback(&hdma_usart1_rx, HAL_DMA_XFER_CPLT_CB_ID, handle_uart_dma_data);
 
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_rx, DMA_FLAG_HT3); // DMA CH3
__HAL_DMA_ENABLE_IT(&hdma_usart1_rx, DMA_IT_HT); // enable Half transfer flag
__HAL_DMA_CLEAR_FLAG(&hdma_usart1_rx, DMA_FLAG_TC3); // DMA CH3
__HAL_DMA_ENABLE_IT(&hdma_usart1_rx, DMA_IT_TC); // enable Transfer Complete interrupt�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?�?

I defined my handle_uart_dma_data function in separate file:

.h

#include ''stm32f0xx_hal.h''
#include ''stm32f0xx_hal_dma.h''
 ...
void handle_uart_dma_data(DMA_HandleTypeDef *hdma);
 ...�?�?�?�?�?

.c

void handle_uart_dma_data(DMA_HandleTypeDef *hdma){
 LED_1_ON();
}�?�?�?

Problem is, this function is never executed.

HAL_DMA_RegisterCallback executeswith HAL_OK status, while debugger shows different pointer, to UART __weak functions. 0690X00000607qDQAQ.png

Why are called this functions instead of my registered callback?

void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);�?�?�?�?�?

Zt Liu
Senior III
August 17, 2017
Posted on August 17, 2017 at 11:06

If you did call HAL_UART_Receive_DMA(...), then the HAL library do the following

...

/* Set the UART DMA transfer complete callback */

huart->hdmarx->XferCpltCallback = UART_DMAReceiveCplt;

/* Set the UART DMA Half transfer complete callback */

huart->hdmarx->XferHalfCpltCallback = UART_DMARxHalfCplt;

/* Set the DMA error callback */

huart->hdmarx->XferErrorCallback = UART_DMAError;

/* Set the DMA abort callback */

huart->hdmarx->XferAbortCallback = NULL;

 ...

schperplata
Associate III
August 17, 2017
Posted on August 17, 2017 at 11:18

Hello,

yes, I figured it out, but I don't see the point - are there any good  reasons that HAL library re-sets this callback to this predefined callback?

Thank you!

Zt Liu
Senior III
August 17, 2017
Posted on August 17, 2017 at 11:32

in static void UART_DMAReceiveCplt(DMA_HandleTypeDef *hdma)

If your dma is not in circular mode,

The library clear Error flags and disable UART DMA Receiver, also set the handle's RxState to HAL_UART_STATE_READY. and clear its RxXferCount.

static void UART_DMAReceiveCplt(DMA_HandleTypeDef *hdma)

{

UART_HandleTypeDef* huart = ( UART_HandleTypeDef* )((DMA_HandleTypeDef* )hdma)->Parent;

/* DMA Normal mode*/

if((hdma->Instance->CR & DMA_SxCR_CIRC) == 0U)

{

   huart->RxXferCount = 0U;

   /* Disable RXNE, PE and ERR (Frame error, noise error, overrun error) interrupts */

   CLEAR_BIT(huart->Instance->CR1, USART_CR1_PEIE);

   CLEAR_BIT(huart->Instance->CR3, USART_CR3_EIE);

   /* Disable the DMA transfer for the receiver request by setting the DMAR bit

   in the UART CR3 register */

   CLEAR_BIT(huart->Instance->CR3, USART_CR3_DMAR);

   /* At end of Rx process, restore huart->RxState to Ready */

      huart->RxState = HAL_UART_STATE_READY;

   }

   HAL_UART_RxCpltCallback(huart);

}

What's the problem using void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)??

schperplata
Associate III
August 17, 2017
Posted on August 17, 2017 at 11:39

There is no problem, except that I couldn't find anywhere those things clearly written - why and when to use which handler. Some things in HAL works out of the box with ease, but other (like this) - you must know the inner working of functions and connections beetween hardware, like UART <-> DMA. 

If there is anywhere a document describing how all this functions are called and connected, I am happy to read it, let me know. 

Anyway, thank you for reply.
Zt Liu
Senior III
August 17, 2017
Posted on August 17, 2017 at 11:55

In each webpage of STM32FX Cube, there are user manuals for HAL and LL...

For example, in F4 Cube, there is

http://www.st.com/content/ccc/resource/technical/document/user_manual/2f/71/ba/b8/75/54/47/cf/DM00105879.pdf/files/DM001…

However.....I rarely read them for I guess it's just generated from the src files using Doxygen.....

So I always give a quick read on the source files.

Also, the go to definition or go to reference Function in your IDE may help you give insights on the source.

Good Luck!

Greg Anderson
Visitor II
February 10, 2018
Posted on February 10, 2018 at 20:49

Since no one has mentioned Transmit Complete Interrupt Enable bit yet, I will do so, because the HAL Library function UART_DMATransmitCplt (DMA_HandleTypeDef *hdma) has what I view as a flaw. Inside that function there is a line (shown below) to set the UART CR1 TCIE IRQ enable flag (Transmit Complete Interrupt Enable). The issue that can occur (and perhaps does happen in your code) is that a higher priority (by design) interrupt happens sometime after the DMA has transferred the last data byte into the USARTs Transmit Data Register, but before the TCIE bit is set. If the higher priority interrupt lasts long enough so that the USART has completely shifted out the last TX byte, then the TCIE bit is being set too late to take effect. I'd recommend leaving the HAL function as-is, but use the macro provided to set the CR1.TCIE control bit immediately after the transmission is started. Doing so should not lead to any premature callback since the trigger for transmit complete is a combination of the last bit of the USART TX shift register becoming empty AND the USART TDR register is also empty; with a DMA this event should not occur until all data bytes have been shipped out. I suppose if there is concurrent DMA activity going on, there may be a chance that a premature Transmit Complete IRQ could happen; so at least consider that possibility as well. Hope this helps. I'm assuming you have long since found your workaround, but maybe this will help someone else in the future.

static void UART_DMATransmitCplt(DMA_HandleTypeDef *hdma)
 /* Enable the UART Transmit Complete Interrupt */
 SET_BIT(huart->Instance->CR1, USART_CR1_TCIE);

Allen Huffman
Associate II
March 12, 2018
Posted on March 12, 2018 at 15:10

 ,

 ,

I am trying to track down something that changed after the 1.1.2 version of the HAL which makes our existing UART code (DMA_NORMAL) not work.

In the old UART code, it was:

 , , , /* Enable the UART Transmit Complete Interrupt */

 ,

 , , , __HAL_UART_ENABLE_IT(huart, UART_IT_TC),

And currently it is the code you mention.

♯ define UART_IT_TC , , , , , , , , , , , , , , , , , , , , , , ((uint32_t)(UART_CR1_REG_INDEX <,<, 28U | USART_CR1_TCIE))

versus

♯ define USART_CR1_TCIE_Pos , , , , , , , , , , , (6U) , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,

 ,

♯ define USART_CR1_TCIE_Msk , , , , , , , , , , , (0x1U <,<, USART_CR1_TCIE_Pos) , , , , , , , , , , , , /*!<, 0x00000040 */

 ,

♯ define USART_CR1_TCIE , , , , , , , , , , , , , , , USART_CR1_TCIE_Msk , , , , , , , , , , , , , , , , , , , , , , /*!<,Transmission Complete Interrupt Enable */

Since the situation we are seeing involves the IRQ handler never getting called, I have been looking specifically at UART and DMA IRQ code changes. I will look into this one further. Thanks.

We can get our code working (seemingly) fine just by using the old versions of ,stm32f2xx_hal_uart.h and stm32f2xx_hal_uart.c. There is something in the 1.1.3 Release notes that says '

The following changes done on the HAL drivers require an update on the application code based on HAL V1.1.2

' with specific notes for UART, but the steps they describe seem to be what was already being done. There are notes regarding DMA and UART, so I expect I am just not understanding something. My code matches an F2 eval board DMA example, as well.

Or we are the only folks ever to use this particular UART/DMA config.

Any pointers would be greatly appreciated.

schperplata
Associate III
February 25, 2018
Posted on February 25, 2018 at 22:21

ganderson

‌, great for sharingthis!

Anyway, yes, I've found my workaround a long time ago. Here it is: I used Low Layer drivers. I found that (UART specifically) HAL drivers are much to complicated and 'undeterministic', and since Low Layer is actually very simple and straight forward, I managed to rewrite whole communication part of my project in a matter of few hours. I even made my own simple data protocol for communication with PC master.

Here you can read more about it:

https://damogranlabs.com/2017/12/sdp-simple-data-protocol/

Note: If I conclude by myself, there might be a certain 'fear' of Low Layer drivers - since they are described and recommended only for experienced users... Well, good news, I think they are even simpler than HAL - simpler in terms of transparency and what is actually going on with peripheral. Really straightforward... So, to all of you who are not sure if LL is the right path: GO FOR IT! You can always return to HAL.