Skip to main content
Visitor II
October 24, 2018
Question

HAL_UART_Receive_DMA calls RxCpltCallback, but receives no data

  • October 24, 2018
  • 10 replies
  • 5047 views

In my project, I'm using UART1 with DMA enabled, similar to this:

MX_GPIO_Init();
MX_DMA_Init();
MX_UART7_Init(); // initializes, and I assume, correctly
 
void DMA1_Stream1_IRQHandler(void) {
 HAL_DMA_IRQHandler(&hdma_uart7_tx);
}
 
void DMA1_Stream3_IRQHandler(void) {
 HAL_DMA_IRQHandler(&hdma_uart7_rx);
}
 
void UART7_IRQHandler(void) {
 HAL_UART_IRQHandler(&huart7);
}
 
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
 if (huart->Instance == UART7)
 complete = 1;
}
 
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
 error();
}

The setup was with CubeMX, and I included the IR-related stuff. The DMA is non-circular, with auto-increase for memory but not for the peripheral.

The code that receives data looks like this:

complete = 0;
HAL_UART_Receive_DMA(&huart7, buffer, size);
HAL_DELAY(5000); // <-- simplified
if (complete)
 return buffer;
 

But when doing so, the callback HAL_UART_RxCpltCallback is called (i.e., complete is set to 1), but the buffer contains no (new) data. Also, the IR doesn't fire immediately (this cannot be observed with this snippet here). The sending of data works.

What am I missing here? Did I forget some action or configuration? Could this be an interrupt issue, as I'm using various priorities here?

    This topic has been closed for replies.

    10 replies

    Visitor II
    October 24, 2018

    The answer seems to be that I need to add

    HAL_UART_DeInit(&huart7);
    HAL_UART_Init(&huart7);

    between calls of HAL_UART_Transmit_DMA and HAL_UART_Receive_DMA.

    Seriously? I have never read that anywhere, except for the outdated HAL examples where I found it now.

    Also, looking at the code of Init and DeInit, which line is the critical one?

    Visitor II
    November 29, 2020

    Hi Lars,

    I've found the same issue and this solution works. However, I am wondering if you were able to find a better solution than DeInit() then Init()?

    Best regards,

    Visitor II
    October 25, 2018

    wow, that is a little upset... this is how I work it: RxDMA

    do you use the cube ?

    I had to enable the RxDMA to Circular buffer and TxDMA to Normal.

    then I have to enable the Uart interrupt too.

    all done in the cube...

    initialize before while(1)
     
    U1RxBufferPtrOUT = 0;
    HAL_UART_Receive_DMA(&huart1, (uint8_t *)Usart1RxDMABuffer, U1RxBufSize); 
     
    Then in your code you can Peek at the buffer level ;
    U1RxBufferPtrIN this is pointing to the next Rx byte position.
     
    char readableU1(void) { // returns length in DMA not read.
     U1RxBufferPtrIN = U1RxBufSize - huart1.hdmarx->Instance->CNDTR;
     return U1RxBufferPtrIN - U1RxBufferPtrOUT;
    }
     
    If you want to read some bytes you use U1RxBufferPtrOUT. 
    As you read bytes from the buffer, you must increment your U1RxBufferPtrOUT and wrap it at the DMA circular buffer length.
    My method to check the current receive buffer length is:
     
    RxLength = readableU1(); // reads zero after initialization
     
    Example: just after initialization;
     
     U1RxBufferPtrOUT is still zero.
     
     if you receive 10 bytes;
     
    RxLength = readableU1(); // returns length in DMA not read.
     U1RxBufferPtrIN is calculated from the DMA position register as 10
     RxLength is calculated as U1RxBufferPtrIN - U1RxBufferPtrOUT
     RxLength becomes 10.
     
    You can poll this value readableU1() for your required packet size without reading a byte.
     
    -> As you read each byte from that packet, you increment U1RxBufferPtrOUT and wrap at U1RxBufSize
     
    char readU1(void) {
     char readByte = Usart1RxDMABuffer[U1RxBufferPtrOUT++];
     if (U1RxBufferPtrOUT >= U1RxBufSize) U1RxBufferPtrOUT = 0;
     return readByte;
    }

    Visitor II
    October 25, 2018

    Thanks, but your solution seems more complex than I need.

    I want to receive a fixed amount of data, and when that data is complete, I want an interrupt. Again, I get the interrupt, but there is no data, unless I do as above.

    Graduate II
    October 25, 2018

    Are you invalidating the cache for the receive buffer?

    Visitor II
    October 25, 2018

    Are you referring to the DCache/ICache? I'm not using those, but ART.

    Super User
    October 25, 2018

    Read out and post the content of UART registers before and after the reinit.

    JW

    Visitor II
    October 25, 2018

    Good idea! There are 4 register changes for Tx, and 1 for Rx:

    0x58 S3CR went from 0A00040E before the reinit to 0A00041F after. That means that EN (stream enable) went from 0 to 1, and TCIE (tx complete IR enable) went also from 0 to 1.

    That makes sense, but why were those bits not set to begin with? In fact, could it be that HAL_UART_Receive_DMA would set those? I'll check if that function sets S1M0AR correctly.

    Anyway, I guess that as a workaround I'm going to set those bits manually instead of using Init/DeInit.

    Any comments?

    Super User
    October 25, 2018

    Because with DMA in non-circular mode, calling HAL_UART_Receive_DMA(&huart7, buffer, size) will cause the HAL code to disable the serial RX function after it receives "size" bytes. One of the joys of HAL programming. If you want to continue receiving data (and not miss any data) then use circular mode, which keeps the UART RX/DMA running continually... EXCEPT when it sees a UART error (framing error, parity, noise, etc.), which will stop reception and disable the DMA, then call the UART error callback function.

    Visitor II
    October 25, 2018

    Thanks for that great explanation, but in my case I want to receive only one buffer length, so a circular buffer won't do any good. But I guess that explains the bits described above.

    Visitor II
    October 25, 2018

    Errr, I think I just had an idea. I didn't mention what kind of STM32 I'm using, but its a F722. And that buffer is most likely located in DTCM RAM.

    Now if I read that F722 architecture diagram correctly, then DMA cannot possibly write to DTCM RAM. Which kind of solves the mystery, although I would've expected an error here.

    Does this make sense?

    Visitor II
    October 30, 2018

    I did some further experiments. My latest assumption that DMA cannot write into DTCM is not the cause of the missing data, since locating the buffer in SRAM still doesn't yield any data.

    The DeInit/Init sequence still helps, but only for the first Receive. For subsequent Receives, the Error callback is called.

    The bit comparison also didn't provide any clues.

    Are there any other suggestions?

    Super User
    October 30, 2018

    Use DMA in circular mode. Yes, I know you only want to receive a fixed size buffer. But using circular mode means the DMA and UART Rx remain enabled all the time and don't need to be re-enabled/closed/re-opened/etc. Unless you are putting the CPU into a deep sleep/power-down mode between communication bursts, I don't see the need to have peripherals started and then stopped (even though that is the way the HAL library is designed).

    You can poll your DMA buffer for the proper # of bytes from your main polling loop (if you have one), or from a periodic timer interrupt. Or even design your buffer size to use the "half full" DMA interrupt. There are several other posts on this forum about using UARTS and DMA and how to tell how many bytes you have received. A quick search should find them.

    Visitor II
    October 30, 2018

    Thanks! Put this way, it makes a lot of sense for me. I'll try it out!

    Visitor II
    October 30, 2018

    No, unfortunately the result buffer is still untouched after switching to a circular RX buffer.

    Super User
    October 30, 2018

    Do you *EVER* get data in the buffer? I can't find it in this thread, but I thought when this started you said it worked for the first "message", but any following messages you got the "complete" flag but the data had not changed. Is that correct, and is that still the case (it works once then not again)?

    Can you simplify your program to just the UART code (send and receive) and basic system init and verify that the problem still exists? If so, post THAT code (all of it). And an exact and detailed description of what happens (ex. program starts, receives one message, sends a response, gets the "complete" flag for the 2nd message but buffer contains XXXX, and the DMA registers contain *** (specifically the CNDTR and DMA_CCR registers). And how do you know the 2nd message is actually different than the first message (i.e. what is generating the messages)? I have lots of questions that would be easier answered if we could see the source.

    Visitor II
    October 30, 2018

    which error flag is being set ?