cancel
Showing results for 
Search instead for 
Did you mean: 

STM32L412KB UART Data Corruption After Random Periods

edensheiko
Associate

Hello, I’m working with an STM32L412KB on an evaluation board, and I’m facing an issue with receiving UART data. Here’s a quick overview:

  1. Setup: The device receives 5-byte packets over UART every 30 ms using the HAL UART interrupt mode (HAL_UART_Receive_IT).
  2. Issue: After running for a few minutes, the received packet occasionally gets corrupted—typically with the first and last byte becoming swapped or confused.
  3. Attempted Fixes: I’ve cleared error flags in the HAL_UART_ErrorCallback, and I’m restarting UART with DMA after an error, but the corruption persists.
  4. I have tried DMA and circular buffer, but the results are the same
  5. Below is a code example showing the logic (I cannot share the actual code due to NDA constraints):.

 

HAL_UART_Receive_IT(&UART, packet, BUFF_SIZE);  // Initiate UART receive in interrupt mode

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    flag = 1;  // Signal main loop to process packet
    HAL_UART_Receive_IT(&huart1, packet, BUFF_SIZE);  // Re-initiate UART receive
}

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
    __HAL_UART_CLEAR_PEFLAG(huart);  // Clear UART error flags
    HAL_UART_Receive_IT(huart, packet, BUFF_SIZE);  // Restart UART in DMA mode
}

void system_main(void) {
    for (;;) {
        if (flag) {
            // Process each byte in the packet
            process_value(packet[0], DEFAULT_PACKET);
            process_value(packet[1], DEFAULT_PACKET);
            process_value(packet[2], DEFAULT_PACKET);
            process_value(packet[3], DEFAULT_PACKET);
            process_value(packet[4], BUTTON_PACKET);

            // Clear the buffer and reset the flag
            memset(packet, 0, sizeof(packet));
            flag = 0;
        }
     // here some work with hal_gpio API and dealys
    }
}​

 

1 ACCEPTED SOLUTION

Accepted Solutions
gbm
Lead III

Do not assume that you will get the correct, aligned 5-byte packet. Receive data byte by byte and assemble into packets. HAL solution for UART reception is almost useless, as it stops the reception after completion and requires retriggering by calling Receive again and again. You should always be ready to receive a byte and interpret it, without calling Receive every time and HAL does not support this very basic and useful model at all.

Maybe something like "receivetoidle" HAL functions will get you closer to the working code.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice

View solution in original post

9 REPLIES 9
gbm
Lead III

Do not assume that you will get the correct, aligned 5-byte packet. Receive data byte by byte and assemble into packets. HAL solution for UART reception is almost useless, as it stops the reception after completion and requires retriggering by calling Receive again and again. You should always be ready to receive a byte and interpret it, without calling Receive every time and HAL does not support this very basic and useful model at all.

Maybe something like "receivetoidle" HAL functions will get you closer to the working code.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice
liaifat85
Senior III

You can adjust your error handling and processing logic to improve reliability.

HAL_UART_Receive_IT(&UART, packet, BUFF_SIZE);  // Initiate UART receive in interrupt mode

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    flag = 1;  // Signal main loop to process packet
    HAL_UART_Receive_IT(&huart1, packet, BUFF_SIZE);  // Re-initiate UART receive
}

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
    __HAL_UART_CLEAR_PEFLAG(huart);  // Clear parity error
    __HAL_UART_CLEAR_FEFLAG(huart);  // Clear framing error
    __HAL_UART_CLEAR_NEFLAG(huart);  // Clear noise error
    __HAL_UART_CLEAR_OREFLAG(huart); // Clear overrun error

    error_flag = 1;  // Signal main loop to restart UART
}

void system_main(void) {
    for (;;) {
        if (flag) {
            // Process each byte in the packet
            process_value(packet[0], DEFAULT_PACKET);
            process_value(packet[1], DEFAULT_PACKET);
            process_value(packet[2], DEFAULT_PACKET);
            process_value(packet[3], DEFAULT_PACKET);
            process_value(packet[4], BUTTON_PACKET);

            // Clear the buffer and reset the flag
            memset(packet, 0, sizeof(packet));
            flag = 0;
        }

        if (error_flag) {
            HAL_UART_Abort(&huart1);  // Abort any ongoing UART activity
            HAL_UART_Receive_IT(&huart1, packet, BUFF_SIZE);  // Restart UART reception
            error_flag = 0;  // Clear error flag
        }

        // Other tasks
        HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);  // Example GPIO toggling
        HAL_Delay(10);  // Ensure tasks don’t block
    }
}

Having the HAL_UART_Receive_IT() in the callback is not a good idea. Put it in the main loop.

HAL_UART_Receive_IT(&UART, packet, BUFF_SIZE);  // Initiate UART receive in interrupt mode

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    flag = 1;  // Signal main loop to process packet
}

void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) {
    __HAL_UART_CLEAR_PEFLAG(huart);  // Clear parity error
    __HAL_UART_CLEAR_FEFLAG(huart);  // Clear framing error
    __HAL_UART_CLEAR_NEFLAG(huart);  // Clear noise error
    __HAL_UART_CLEAR_OREFLAG(huart); // Clear overrun error

    error_flag = 1;  // Signal main loop to restart UART
}

void system_main(void) {
    for (;;) {
        if (flag) {
            // Process each byte in the packet
            process_value(packet[0], DEFAULT_PACKET);
            process_value(packet[1], DEFAULT_PACKET);
            process_value(packet[2], DEFAULT_PACKET);
            process_value(packet[3], DEFAULT_PACKET);
            process_value(packet[4], BUTTON_PACKET);

            // Clear the buffer and reset the flag
            memset(packet, 0, sizeof(packet));
            flag = 0;
            HAL_UART_Receive_IT(&huart1, packet, BUFF_SIZE);  // Re-initiate UART receive
        }

        if (error_flag) {
            HAL_UART_Abort(&huart1);  // Abort any ongoing UART activity
            HAL_UART_Receive_IT(&huart1, packet, BUFF_SIZE);  // Restart UART reception
            error_flag = 0;  // Clear error flag
        }

        // Other tasks
        HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);  // Example GPIO toggling
        HAL_Delay(10);  // Ensure tasks don’t block
    }
}

 

I hope this helps.

Kind regards
Pedro

AI = Artificial Intelligence, NI = No Intelligence, RI = Real Intelligence.

@PGump.1 wrote:

Having the HAL_UART_Receive_IT() in the callback is not a good idea. Put it in the main loop.

It's not a good idea but it's the best idea. Unfortunately, if this broken HAL mechanism is used, Receive_IT should be called from ISR. The real problem is that it must be called to re-enable the reception which should be permanently enabled (no other way is possible with HAL). Thus it should be called as soon as possible = from the ISR callback.

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice
Ozone
Lead II

Questions like this seem to come up quite regularly.

And mainly for two reasons. First, RS232 / UART is not a very robust or "safe" transport layer as such. And second, the awkward way this is usually handled in HAL / Cube code.

Characters can get lost or corrupted, and you need to deal with that on protocol level. Implement some redundancy to check each transmission for correctness - at minimum designated start/stop character, and/or a fixed length.

With DMA, it is troublesome to deal with corrupted/lost characters, you might be out of sync until the next restart.

The most robust option is single-character Rx interrupt processing.
Use a receive buffer for more than one package/transmission. Upon receiving the start character, reet the character counter, and discard non-finished transmissions. Upon receiving the stop character, check length and consistency, and move it to an application buffer for processing in the main loop if it is fine.
If you have a fixed package length, discard any extraneous characters and discard the current package.

This way, the application will immediately resynchronize with the next properly received package.
I suppose you know, but generally, you need to keep the time spent in interrupt handler at a minimum. Extensive interrupt callbacks are a sure way to sabotage and kill the application sooner or later.

Thank you for the suggestion!

I ran some tests using the HAL_UARTEx_ReceiveToIdle API, and it looks promising so far. This approach seems to handle the data better.

Thanks again for pointing me in this direction!

Best,
Eden

@gbm It is NOT "the best idea" to rearm the Receiver before you have read the current buffer content - that will cause buffer overruns. Overruns will make it appear as characters are missing, or out of sequence - isn't that what @edensheiko is complaining about... If you are going to encourage this bad idea, you should explain how to double buffer...

@Ozone- "UART is not a very robust or safe" - all communication interfaces have their limitations. It's understanding those limitations that sets you up to make good choices.

HAL is rarely a good choice...

Kind regards
Pedro

 

AI = Artificial Intelligence, NI = No Intelligence, RI = Real Intelligence.

@PGump.1 wrote:

@gbm It is NOT "the best idea" to rearm the Receiver before you have read the current buffer content - that will cause buffer overruns. Overruns will make it appear as characters are missing, or out of sequence - isn't that what @edensheiko is complaining about... If you are going to encourage this bad idea, you should explain how to double buffer...

@Ozone- "UART is not a very robust or safe" - all communication interfaces have their limitations. It's understanding those limitations that sets you up to make good choices.

HAL is rarely a good choice...

Kind regards
Pedro

 


As @gbm indicated, it is the BEST advice to enable the interrupt from inside the callback, but only after you've saved the data.

With your approach, if you enable interrupt in main while loop, when tending to other tasks before you can enable the UART interrupt, you could easily get overruns.

A better approach, if you have a large amount of data to receive, is to point HAL to which buffer and index to save the data to. So when you get an interrupt, all you do is increment the pointer. No copying needed since HAL driver already did that for you before the callback.

Tips and Tricks with TimerCallback https://www.youtube.com/@eebykarl
If you find my solution useful, please click the Accept as Solution so others see the solution.

As @gbm indicated, it is the BEST advice to enable the interrupt from inside the callback, but only after you've saved the data.

With your approach, if you enable interrupt in main while loop, when tending to other tasks before you can enable the UART interrupt, you could easily get overruns.

A better approach, if you have a large amount of data to receive, is to point HAL to which buffer and index to save the data to. So when you get an interrupt, all you do is increment the pointer. No copying needed since HAL driver already did that for you before the callback.


@Karl Yamashita- my point exactly - there was NO mention of saving the data until I pointed it out.

What other tasks? There is NO mention of other tasks in the spec - just a 30mS interval between bursts - plenty of time for the UART Receiver to be rearmed. This not my approach, I was only fixing a problem for @edensheiko .

I don't like HAL, and I rarely use it - because of all the grief it causes...

Kind regards
Pedro

AI = Artificial Intelligence, NI = No Intelligence, RI = Real Intelligence.