Skip to main content
mgrunwald9
Associate
October 25, 2016
Question

Long wait for UART_FLAG_TC after DMA transfer

  • October 25, 2016
  • 4 replies
  • 2480 views
Posted on October 25, 2016 at 10:51

Hello,

While implementing a full duplex uart (115kBaud) communication on an STM32F303, I'm struggling quite a bit.

My current problem is that the DMA1_Channel2_IRQn takes 170μs. I'm using DMA to TX data from the STM. Receiving data is handled via interrupt. In the 170μs, two bytes can be received. Since the DMA1_Channel2_IRQn takes so long, the UART Interrupt for receiving is called after these two bytes which results in an overrun error :(

I have attached an oszillogram: 

Ignore yellow(top), 

Red is the duration of UART_WaitOnFlagUntilTimeout(huart, UART_FLAG_TC, RESET, HAL_UART_TXDMA_TIMEOUTVALUE), which is called from DMA1_Channel2_IRQHandler

Blue is Rx data

Green is Tx data

What I don't understand: Why is DMA TC arriving so early (while TXing byte 0x82)? And why does it take so long until the UART TC flag is set?

As you can see on the right side, a DMA_TC is happening while data is received. The RX interrupt can be called only after a byte is lost. How can I handle this? Would nested interrupts solve this problem?

I used the code that has been generated by cube to implement my firmware. I attached the generated code as a ZIP. It's not exactly the same, but I'm not aware of any important changes regarding the DMA/UART.

Any help would be greatly appreciated!

cu

Markus
    This topic has been closed for replies.

    4 replies

    ColdWeather
    Senior
    October 25, 2016
    Posted on October 25, 2016 at 13:10

    Imagine, your system wanna send only two bytes via UART using DMA.

    The UART has actually a small FIFO: the ''visible'' data register DR and a ''shadow'' shift register, that makes the bit sequence at the TX pin.

    The DMA writes the first byte to DR. The byte goes to the yet ''empty'' shadow register immediately, and the serialization starts. DR gets free at this moment and is ready for the next byte. DMA sees this and writes the second byte to DR. So, the DMA has made its job and sets TC (transfer complete!), while the UART is still handling two bytes written: the first is still on the way via TX (think on baud rates that are very very slow comparative to the CPU/DMA speed), while the second byte is waiting in DR. The UART TC bit is set when both shadow and DR registers get empty, and it takes time.

    The solution is (I do this way): in the DMA TC interrupt disable it but enable the UART TC interrupt. Process the ''packet complete'' in the UART TC interrupt and disable it, prepare the new packet by reinitializing the DMA and enable its TC interrupt again, and so on. This would never block the RX interrupt of the UART.

    The next hint: make the RX interrupt priority higher than TC interrupts of DMA and UART.

    Tesla DeLorean
    Guru
    October 25, 2016
    Posted on October 25, 2016 at 16:45

    Don't use DMA TC interrupt to manage USART TC. Have your USART IRQ Handler manage RXNE and TC

    USART TXE asserts, byte N-1 hits the wire

    DMA writes last byte to USART DR, byte N

    DMA TC asserts

    .. one byte time

    USART TXE asserts, byte N hits the wire

    No DMA service, TXE remains asserted

    .. one byte time

    USART TC asserts

    Tips, Buy me a coffee, or three.. PayPal VenmoUp vote any posts that you find helpful, it shows what's working..
    mgrunwald9
    Associate
    October 26, 2016
    Posted on October 26, 2016 at 14:08

    Hello ColdWeather and clive1

    many thanks for your explanations. Now I have been able to get the communication running with very short interrupt times :)

    My implementation still lacks elegance, so to say. Here's what I do:

    When sending data, I use this function because it offers a bit of convenience:

      halStatus = HAL_UART_Transmit_DMA(&huart3, (uint8_t*) buffer, size);

    One (now unwanted part) of the convenience is, that the

    usart->XferCpltCallback is set to a default value. After your

    explanation, I don't want the default. So what I do when the DMA_IT_TC

    asserts, is this:

    void DMA1_Channel2_IRQHandler( void )

    {

      hdma_usart3_tx.XferCpltCallback = 0;

      //hdma_usart3_tx.XferErrorCallback= 0;

      //hdma_usart3_tx.XferHalfCpltCallback = 0;

      HAL_DMA_IRQHandler(&hdma_usart3_tx);

    }

    The callback won't be called if it is 0. But this is kind of ugly. The

    alternative would be to use HAL_DMA_Start_IT() directly, but there's

    so much glue that HAL_UART_Transmit_DMA has already implemented...

    UART_IT_TC is then handled in USART3_IRQHandler.

    This works perfectly now. But do you have a suggestion how to remove

    the callbacks more nicely?

    cu

    Markus

    Walid FTITI_O
    Visitor II
    October 26, 2016
    Posted on October 26, 2016 at 16:03

    Hi The Grue, 

    Avoid the declaration of local buffer variable and use global one for the buffer to be transmitted through USART/DMA. The function call will extracted from other user file generated on the project.

    Give a try with this an tell me if this resolve your problem.

    -Hannibal-