cancel
Showing results for 
Search instead for 
Did you mean: 

HAL_UART_Receive_DMA calls RxCpltCallback, but receives no data

Lars Beiderbecke
Senior III

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?

17 REPLIES 17
Lars Beiderbecke
Senior III

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?

T J
Lead

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;
}

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.

Are you invalidating the cache for the receive buffer?

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

JW

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

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?

Bob S
Principal

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.

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.