cancel
Showing results for 
Search instead for 
Did you mean: 

STM32H7 UART (DMA with IDLE line) unknown length problem

Inigo
Associate II

Hello,

I am trying to use UART with DMA with IDLE line for unkown length data reception and I am getting the following error:

request for member 'NDTR' in something not a structure or union main.c

I coded a couple of years ago a HAL_UART_RxCpltCallback() function that needs NDTR to calculate the buffer position.

rxBufPos = UART_BUFFER_SIZE - (uint16_t) huart->hdmarx->Instance->NDTR;

It works works on STM32F4 series microcontrollers but it does not work on STM32H7 series microcontrollers.

I have compared F4 and H7 DMA_HandleTypeDef struct and I have found that F4 *Instance type is DMA_Stream_TypeDef and H7 *Instance type is void.

4 REPLIES 4
Guenael Cadier
ST Employee

Hi @Inigo​ 

First, I think H7 is handling classic DMA and Basic DMA (BDMA). All are managed within same HAL APIs, that's why on H7 Instance field of DMA_HandleTypeDef structure is a (void*) element, that will be later casted on either (DMA_Stream_TypeDef *) type or on (BDMA_Channel_TypeDef *) type, depending on the used channel.

In order to retrieve the number of remaining data units in the current DMA Stream transfer, I think you should be able to use __HAL_DMA_GET_COUNTER() macro.

This should work on both platforms.

Secondly, if you want to use HAL API for unknown lengths reception on UART, another option for using IDLE event for data reception would be to implement something similar to what is shown in UART_ReceptionToIdle_CircularDMA example provided in STM32 Firmware packages.

These new APIs (ReceptionToIdle) have been added recently. Advantage is that you will not have to deal with such DMA structure changes between F4 and H7.

A dedicated example for STM32F4 (but easily portable on other STM32) could be found here.

This example shows use of HAL_UARTEx_ReceiveToIdle_DMA() API.

Using a circular DMA, this API allows user to get notified thanks to a HAL_UARTEx_RxEventCallback callback on 3 types of events :

  - HT (Half Transfer) : Half of Rx buffer is filled

  - TC (Transfer Complete) : Rx buffer is full.

    (In case of Circular DMA, reception could go on, and next reception data will be stored

    in index 0 of reception buffer by DMA).

  - Idle Event on Rx line : Triggered when RX line has been in idle state (normally high state)

    for 1 frame time, after last received byte.

Hope this helps.

Regards

emha1969
Associate

I was trying to do the same (transfer from Windows PC) to the H7 CPU, however the general problem with receive to idle seems to be, that the idle is triggered too fast, resulting in reception of 1 or just several bytes. For the embedded senders the idle works because you can deterministically control the byte spacing, so that the idle time is not exceeded, however this is not the case with Windows. I am sending a serial message from Qt application with serialPort->write(msg) which sends the message as a block, but what Winz are doing cannot be controlled.

After long struggle, I came around this problem by using HAL_UART_Receive_IT() with full buffer length. I am checking huart.RxXferCount within periodic timer interrupt, which is used anyway for other tasks.

If huart.RxXferCount does not change for a while (e.g. 100ms) and it has different value than full buffer length, I am assuming reception / sending from PC is finished. So far this is working much more reliable than idle detection.

The code looks something like this:

volatile uint8_t msgReceived = 0;
main() {
// Start reception
HAL_UART_Receive_IT(&huart1, serRxBuffer, SER_BUF_LEN);
...
while() {
    if (msgReceived) {
       msgReceived = 0;
       HAL_UART_AbortReceive(&huart1); // --> Abort current reception
       process_rx_message();
       HAL_UART_Receive_IT(&huart1, serRxBuffer, SER_BUF_LEN); // --> restart for the next msg
    }
  }
}

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    static uint16_t serRxBytes = SER_BUF_LEN;
	
    if (huart1.RxXferCount != serRxBytes) {
	serRxBytes = huart1.RxXferCount;
    }
    else {
	if ((huart1.RxXferCount == serRxBytes) && (serRxBytes != SER_BUF_LEN)) {
            msgReceived = 1;
	}
    }
}



The H7 is more problematic due to cache coherency, and not being able to use CM7 side DTCMRAM for DMA.

One can make a circular DMA buffer with 16-bit wide slots, and readily mark areas taken as dirty, and periodically sweep that buffer in your data processing task. Size the buffer based on baud rates and ability to consume.

Most STM32 interrupt for every byte received/transmitted via UART, you can track that via the IRQ Handler, and manage post call-in to HAL. All the library source is available for inspection,and can be adapted / exploited as necessary. Or you can code things that work much better, and as desired.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

>the general problem with receive to idle seems to be, that the idle is triggered too fast, resulting in reception of 1 or just several bytes. 

Because of this several STM32s have "receive timeout" feature (RTO), this is what you usually want instead of "idle". However it is not supported by the HAL library (yet?).

Unfortunately again, besides of HAL_UARTEx_ReceiveToIdle_DMA there is no practically useful UART receive function - but, as Tesla wrote, DMA has  complications with cache management and it cannot access certain memory areas. So people resort to all kinds of pathetic hacks with HAL_UART_Receive_IT.