Device Drivers
Drivers hide hardware details behind clean interfaces. Learn driver layers and APIs, initialisation and error handling, interrupt-driven I/O with ring buffers, sizing buffers from data rates, blocking vs non-blocking designs, RTOS-aware drivers, Linux kernel/device-tree basics, and testing drivers.
Good drivers make applications portable and robust: the application calls uart_write() without caring whether the bytes go out by polling, interrupt or DMA, on STM32 or NXP. Bad drivers lose data, block forever or corrupt memory.
An analogy
A driver is a translator between the application (who speaks 'send this message') and the hardware (who speaks 'write bit 7 of register 0x40011004').
Driver structure
init(config), read/write, control (ioctl-like), status/error queries, and deinit. Hide registers in the .c file; expose a small, documented API in the .h file.
Buffers and concurrency
Ring buffers decouple ISRs from applications. Size them from data rate × worst-case delay. Use volatile indices, single-writer rules, or critical sections; with an RTOS, signal tasks with semaphores/queues from ISRs.
Error handling
Detect and report overruns, framing/parity errors, timeouts and NACKs. Return error codes, never block forever — use timeouts. Provide recovery (peripheral reset, bus unlock for I²C).
OS drivers and testing
Linux drivers register with subsystems (char, I²C, SPI, IIO, net) and get hardware info from the device tree. Zephyr uses devicetree and a generic driver model. Test with hardware-in-the-loop, mocks for host unit tests, and fault injection.
Formulas
- Buffer sizing:
N ≥ R × t_block (round up to 2^k) - Ring index wrap:
i = (i + 1) & (N − 1) (N power of two) - UART byte rate:
R = baud / 10 (8N1) - Occupancy:
count = (head − tail) & (N − 1)
Worked example: Buffer size
Given: 115 200 baud 8N1; Main loop may block up to 20 ms
Find: RX buffer size.
- R = 11 520 B/s
- N ≥ 11 520 × 0.02 = 230 B → 256
Answer: 256 bytes
Worked example: Ring occupancy
Given: N = 256, head = 10, tail = 250
Find: Bytes available.
- (10 − 250) & 255 = 16
Answer: 16 bytes
Interrupt-driven UART RX driver with a ring buffer (C)
#include <stdint.h>
#include <stdbool.h>
#define RX_SIZE 256u /* power of two */
static volatile uint8_t rx_buf[RX_SIZE];
static volatile uint16_t rx_head, rx_tail; /* ISR writes head, app reads tail */
static volatile uint32_t rx_overruns;
void USART2_IRQHandler(void) { /* producer */
uint8_t byte = (uint8_t)USART2->DR; /* reading DR clears RXNE */
uint16_t next = (rx_head + 1u) & (RX_SIZE - 1u);
if (next == rx_tail) { rx_overruns++; return; } /* full: count and drop */
rx_buf[rx_head] = byte;
rx_head = next;
}
bool uart_read(uint8_t *out) { /* consumer, non-blocking */
if (rx_tail == rx_head) return false; /* empty */
*out = rx_buf[rx_tail];
rx_tail = (rx_tail + 1u) & (RX_SIZE - 1u);
return true;
}
uint16_t uart_available(void) { return (rx_head - rx_tail) & (RX_SIZE - 1u); }A single-producer/single-consumer ring buffer needs no locks: only the ISR changes head and only the application changes tail. Overruns are counted instead of silently lost, making problems diagnosable.
Common mistakes
- Wrong: Blocking forever waiting for hardware. — Right: Always use timeouts.
- Wrong: Silently dropping data. — Right: Count and report overruns.
Applications
- Serial consoles & modems: Reliable UART drivers.
- Sensor drivers: I²C/SPI device drivers with error recovery.
- Linux BSPs: Kernel drivers and device trees.
Key takeaways
- Small API, hidden registers.
- N ≥ rate × worst delay.
- SPSC ring buffers without locks.
- Timeouts, error counts, recovery.
Going further
- Zero-copy drivers: DMA directly into application buffers.
- Power management: Suspend/resume hooks.
- HIL testing: Automated hardware test rigs.