Hello! Welcome to the final lesson of our module on UART communication.
In our previous lessons, we've built a powerful UART driver from the ground up. You've implemented interrupt-driven reception with a circular buffer, created a command-line interface, and even redirected printf for easy debugging. Our system works perfectly under ideal conditions. But in the real world, communication links are rarely perfect.
This lesson addresses the final piece of building robust UART firmware, which is a critical skill for any professional engineer. We will focus on the learning outcome: Detect and handle UART communication errors like overruns. You'll learn what causes these errors and how to modify your interrupt service routine to catch them, ensuring your system can gracefully handle unexpected communication faults.
1. The Reality of Asynchronous Communication: Common Errors
Asynchronous communication, by its nature, relies on both the transmitter and receiver being perfectly synchronized in time. When this timing is disrupted by electrical noise, or if the receiver CPU is too busy to process incoming data, errors occur.
To understand these errors conceptually, please read the following material. It provides an excellent high-level overview of the most common issues you'll encounter.
The article 'Understanding UART' by Pantech.ai gives a clear, hardware-agnostic explanation of Framing, Parity, and Overrun errors. This will provide the theoretical foundation for our practical implementation.
Read the section 'Error Detection and Handling in UART'. Focus on understanding the cause, symptoms, and general detection method for each of the three main errors: Framing Errors Parity Errors Overrun Errors
Let's summarize the key errors:
- Framing Error (FE): The receiver doesn't see the 'stop' bit where it expects it. This is most often caused by a baud rate mismatch between the two devices or significant noise on the communication line.

-
Parity Error (PE): If parity checking is enabled, this error means a bit in the data frame was flipped during transmission (e.g., by electrical noise), and the calculated parity no longer matches the received parity bit. We aren't using parity, but it's important to know about.
-
Overrun Error (ORE): This is arguably the most important software-related error to handle. It occurs when a new byte arrives from the sender, but the receiver's hardware buffer (the Data Register) hasn't been read yet. The CPU was too busy to service the previous
RXNEinterrupt in time, and as a result, the older, unread byte is overwritten and lost.
2. How the STM32 Detects and Flags Errors
The STM32's USART peripheral has built-in hardware to detect these errors automatically. When an error occurs, the hardware sets a corresponding flag bit in the USART Status Register (USART_SR).
The following reading dives into the specifics for the STM32.
STM32 UART (USART) Tutorial + Examples (DMA, Interrupt)
The 'STM32 UART Tutorial' from DeepBlueEmbedded provides precise details on how the STM32 hardware flags these errors and, crucially, how to clear those flags.
Read the subsection named 'STM32 USART Errors'. Pay close attention to the descriptions of the Overrun Error (ORE), Noise Error (NE), and Framing Error (FE). For each error, note which bit is set and the specific sequence required to reset the flag. This sequence is very important!
As you read, you'll discover a critical, non-obvious detail about STM32 peripherals: error flags are not cleared by writing a '1' to them. Instead, they are cleared by a specific sequence of operations: a read to the USART_SR register followed by a read to the USART_DR register.
If you don't perform this exact sequence, the error flag will remain set, and your interrupt will keep firing continuously, locking up the CPU.
3. Handling Errors Efficiently with Interrupts
We could poll the USART_SR register in our main loop to check for these flags, but that's inefficient. A much better approach is to enable the USART's Error Interrupt. This allows the same USARTx_IRQHandler we use for receiving data to also handle error conditions.
To do this, we need to set the Error Interrupt Enable (EIE) bit in the USART_CR3 control register.
Once the EIE bit is set, a framing error, noise error, or overrun error will trigger the USART2_IRQHandler. Our job is to update the handler to check for these error flags in addition to the RXNE flag we already handle.
4. Hands-On: Making Your UART Driver Error-Proof
Let's update your project to handle these errors. We'll focus on the Overrun Error as it's the easiest to reproduce reliably for testing.
Step 1: Enable the Error Interrupt
In your main.c, find the function where you initialize your UART peripheral (uart2_tx_rx_init or similar). Add the following line at the end of the function to enable the error interrupt.
// In your UART initialization function...
// Enable the error interrupt (for ORE, FE, NE)
USART2->CR3 |= USART_CR3_EIE;
Step 2: Update the Interrupt Service Routine
Now, modify your USART2_IRQHandler to check for the Overrun Error flag. The new logic should first check for errors and then check for the receive flag. This is because an error condition often accompanies a received byte, and the flag clearing sequence involves reading the data register anyway.
Here is the updated ISR. Note the addition of the if (USART2->SR & USART_SR_ORE) block.
/* In main.c, inside the /* USER CODE BEGIN 4 */ section */
void USART2_IRQHandler(void)
{
// Check for Overrun Error
if (USART2->SR & USART_SR_ORE)
{
// ORE flag is set. This means we lost some data.
// The flag is cleared by a read to SR followed by a read to DR.
// We can simply read DR and discard the value.
(void)USART2->DR; // Read DR to clear the ORE flag.
// Let the user know an error occurred.
printf("UART Overrun Error!\r\n");
}
// Check if the RXNE (Receive Not Empty) flag is set
if (USART2->SR & USART_SR_RXNE)
{
// A byte has been received.
uint8_t received_byte = USART2->DR; // Reading DR also clears the RXNE flag
// Add the byte to your circular buffer
cb_write(&rx_buffer, received_byte);
}
}
Step 3: Forcing an Overrun Error for Testing
How can we test our error handler? We need to make the CPU too busy to read the UART data register before the next byte arrives. We can simulate this with a simple for loop that freezes the processor for a short time.
Add this code snippet to the while(1) loop in your main function, just before you process your command-line interface.
/* In main(), inside the while(1) loop */
// This is a temporary test block to induce a UART Overrun Error.
// It will pause the CPU for a noticeable period.
if (HAL_GPIO_ReadPin(B1_GPIO_Port, B1_Pin) == GPIO_PIN_SET)
{
printf("Inducing CPU stall...\r\n");
// Waste CPU time to prevent the ISR from running
for (volatile uint32_t i = 0; i < 500000; ++i) {}
printf("Stall finished.\r\n");
}
// ... your existing CLI processing code ...
// if (cb_has_data(&rx_buffer)) { ... }
Note: This code assumes you have the blue user button on your board configured as B1. If you press and hold the button, this will trigger the stall.
Your task:
- Add the line to enable the Error Interrupt (
EIE). - Replace your existing
USART2_IRQHandlerwith the new version that checks forORE. - Add the test block to your
mainloop. - Compile and flash the code to your Blue Pill.
- Open your serial terminal.
- Press and hold down the blue user button. While holding it, you'll see the "Inducing CPU stall..." message.
- QUICKLY type a dozen or more characters into the terminal.
- Release the button.
You should see the "UART Overrun Error!" message printed by your ISR, followed by the "Stall finished" message. You successfully detected and handled an overrun error! You can now remove the temporary test block from your main loop.
Conclusion
Congratulations on completing this module! You have now built a feature-complete and robust UART driver. By adding error handling, you've taken a significant step from academic projects to professional-grade firmware. Reliable communication is non-negotiable in commercial products, especially in the automotive field.
Key Takeaways:
- Real-world UART communication is susceptible to Overrun, Framing, and Noise errors.
- The STM32 hardware automatically detects these errors and sets flags (
ORE,FE,NE) in theUSART_SRregister. - Error flags on STM32 are cleared with a special sequence: a read to the status register followed by a read to the data register.
- Using the Error Interrupt (EIE) is the most efficient way to handle these faults without bogging down the CPU with polling.
Preview of the Next Lesson:
With our mastery of asynchronous serial communication solidified, we now move to a new category of peripherals. In the next module, we will explore synchronous communication, starting with the Serial Peripheral Interface (SPI). You'll learn how SPI uses a dedicated clock line to achieve much higher data rates and avoid the types of timing-related errors we've just discussed. We'll start by configuring the SPI peripheral in master mode and preparing to communicate with an external device.