Search topics...
Driver DesignDriver Patternsfoundational

Design a simple UART driver API — what functions would you expose?

0 upvotes
Practice with AISoon
Study the fundamentals first — Driver Design topic page

A production UART driver API should provide five core functions that separate configuration from data flow and support both blocking and non-blocking operation:

c
typedef struct {
uint32_t baudrate;
uint8_t word_length; // 8 or 9
uint8_t parity; // NONE, EVEN, ODD
uint8_t stop_bits; // 1 or 2
} uart_config_t;
int uart_init(uint8_t port, const uart_config_t *cfg);
int uart_send(uint8_t port, const uint8_t *data, uint16_t len);
void uart_register_rx_callback(uint8_t port,
void (*cb)(const uint8_t *data, uint16_t len));
int uart_get_error(uint8_t port);
void uart_deinit(uint8_t port);

uart_init() configures the peripheral, sets up DMA channels and interrupts, and allocates internal ring buffers. It returns an error code if the port is invalid or already initialized. uart_send() is non-blocking — it copies data into a transmit ring buffer and starts the DMA or interrupt-driven transfer. It returns immediately, allowing the caller to continue processing. If the TX buffer is full, it returns an error rather than blocking. uart_register_rx_callback() lets the application provide a function pointer that is called from the RX half-transfer or idle-line ISR with a pointer to the received data and its length.

uart_get_error() returns a bitmask of error flags (framing error, overrun, parity error, buffer overflow) accumulated since the last call, and clears them. This is better than returning errors from individual send/receive calls because errors often occur asynchronously. uart_deinit() disables the peripheral, releases DMA channels, and frees resources — essential for low-power modes where you need to shut down unused peripherals. The key design principle is that the API header contains no hardware-specific types — no UART_HandleTypeDef, no register addresses, no STM32-specific enums. This makes the API portable: swapping MCU families requires only a new implementation file, not changes to every file that calls the driver.

Source: Driver Design Q&A