2024-11-14 12:21 AM
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:
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
}
}
Solved! Go to Solution.
2024-11-14 12:40 AM
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.
2024-11-14 12:40 AM
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.
2024-11-14 12:57 AM
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
}
}
2024-11-14 02:23 AM
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
2024-11-14 03:42 AM
@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.
2024-11-14 04:39 AM
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.
2024-11-14 05:01 AM
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
2024-11-14 02:36 PM
@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
2024-11-14 04:15 PM
@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.
2024-11-14 08:09 PM
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