Skip to main content
Explorer II
November 14, 2024
Solved

STM32L412KB UART Data Corruption After Random Periods

  • November 14, 2024
  • 3 replies
  • 1844 views

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

 

    This topic has been closed for replies.
    Best answer by gbm

    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.

    3 replies

    gbmAnswer
    Graduate
    November 14, 2024

    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.

    Explorer II
    November 14, 2024

    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

    Visitor II
    November 14, 2024

    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
     }
    }
    Graduate II
    November 14, 2024

    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

    Graduate
    November 14, 2024

    @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.

    Explorer
    November 14, 2024

    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.