In embedded systems, timing plays a crucial role. Whether you are sending bytes over UART, transferring data through I2C, or synchronizing using SPI, a stalled communication can freeze your entire firmware. This is where the concept of a driver timeout becomes essential.
This article explains how to implement and apply timeout functions in embedded drivers for UART, I2C, and SPI communication. By the end, you will understand how a well-structured communication timeout makes your firmware reliable and fault-tolerant.
A driver timeout is a mechanism that prevents an embedded driver from waiting endlessly for a hardware response that may never occur. It ensures that the system can recover safely from unresponsive peripherals or communication errors.
Timeouts improve the reliability and predictability of firmware, especially in resource-limited embedded environments.
Peripheral drivers continuously wait for hardware flags, signals, or transfer completions. Without a timeout function, this waiting may never end, causing the firmware to hang or crash.
By using driver timeouts, your system can detect these issues, return an error, retry the operation, or safely reset the interface. This ensures that communication failures never stop the entire system.
#include "timeout.h"
bool uart_read_byte_timeout(uint8_t *out, uint32_t max_ms) {
timeout_t to = timeout_start(max_ms, clock_ms());
while (!timeout_expired(&to, clock_ms())) {
if (UARTx->SR & UART_SR_RXNE) {
*out = (uint8_t)UARTx->DR;
return true;
}
}
return false;
}
This function exits when either a byte is received or the driver timeout expires. It keeps your firmware responsive even if the UART line stays inactive.
For multi-byte transfers, you can apply two approaches:
bool uart_read_buf_timeout(uint8_t *buf, size_t len, uint32_t max_ms) {
timeout_t to = timeout_start(max_ms, clock_ms());
size_t i = 0;
while (i < len && !timeout_expired(&to, clock_ms())) { if (UARTx->SR & UART_SR_RXNE) {
buf[i++] = (uint8_t)UARTx->DR;
}
}
return (i == len);
}
The I2C bus may lock up when a slave device holds SDA low after a reset or fault. Without an I2C timeout, the master can loop forever waiting for a signal that never appears.
#include "timeout.h"
static volatile uint8_t i2c_done;
void I2C_IRQHandler(void) {
if (I2C->SR & I2C_SR_TC) {
i2c_done = 1;
I2C->ICR = I2C_ICR_CLEAR_TC;
}
}
bool i2c_transfer_timeout(uint32_t max_ms) {
i2c_done = 0;
start_i2c_transfer();
timeout_t to = timeout_start(max_ms, clock_ms());
while (!timeout_expired(&to, clock_ms())) {
if (i2c_done) return true;
__WFI();
}
abort_i2c_transfer();
return false;
}
This communication timeout ensures the system exits safely even when the transfer cannot complete.
bool spi_transfer_byte_timeout(uint8_t tx, uint8_t *rx, uint32_t max_ms) {
timeout_t to = timeout_start(max_ms, clock_ms());
while (!timeout_expired(&to, clock_ms())) {
if (SPIx->SR & SPI_SR_TXE) {
SPIx->DR = tx;
break;
}
}
if (timeout_expired(&to, clock_ms())) return false;
while (!timeout_expired(&to, clock_ms())) {
if (SPIx->SR & SPI_SR_RXNE) {
*rx = (uint8_t)SPIx->DR;
return true;
}
}
return false;
}
This approach prevents the embedded driver from freezing during incomplete data exchanges.
typedef enum {
TO_OK = 0,
TO_EXPIRED,
TO_ERROR
} to_status_t;
This allows higher-level code to decide whether to retry, reset, or handle the communication timeout in a specific way.
Testing confirms that your timeout functions behave reliably in every situation.
| Feature | Description |
| Purpose | Prevent indefinite waiting during communication |
| Applies To | UART timeout, I2C timeout, SPI timeout |
| Managed By | Firmware using the timeout function |
| Prevents | System hangs, data loss, communication lockups |
| Use Case | Reliable and safe embedded driver operation |
| Errors Avoided | Bus lock, missing data, infinite loops |
Timeouts are a critical part of building robust and reliable embedded drivers. Whether it is a UART timeout, an I2C timeout, or a SPI timeout, each ensures your system remains stable even under communication failure conditions. Implementing a reusable timeout function helps your drivers stay predictable, safe, and easy to debug. For bare metal embedded systems, this is one of the most effective techniques for achieving long-term reliability.
If the hardware fails to signal, the interrupt will never occur. A driver timeout provides a controlled way to exit.
Choose a value based on the worst-case hardware timing plus a small safety margin.
No. They only add a simple check that has minimal performance impact.
No. Timeouts protect individual drivers, while watchdogs protect the entire system.
Use fault injection, disconnect lines, or observe GPIO signals to confirm timeout handling.
Indian Institute of Embedded Systems – IIES