How do you make a driver portable across MCU families?
The standard pattern is a platform-independent API header paired with per-platform implementation files, selected at compile time by the build system. The API header declares the driver interface using only standard C types — no vendor-specific types, no register addresses, no #include of any hardware header:
// gpio_driver.h — platform-independentint gpio_init(uint8_t port, uint8_t pin, gpio_mode_t mode);void gpio_write(uint8_t port, uint8_t pin, uint8_t value);int gpio_read(uint8_t port, uint8_t pin);
Each supported platform has its own implementation file — gpio_stm32f4.c, gpio_nrf52.c, gpio_esp32.c — that implements these functions using the platform's registers or vendor SDK. The build system (CMake, Makefile) selects the correct file based on a target variable:
ifeq ($(PLATFORM),stm32f4)SRCS += gpio_stm32f4.celse ifeq ($(PLATFORM),nrf52)SRCS += gpio_nrf52.cendif
The tricky part is defining the API at the right level of abstraction. Too low (exposing register-level details) and every platform implementation becomes trivial but the API is not truly portable. Too high (abstracting away all hardware differences) and you lose the ability to use platform-specific features or optimize for specific hardware. The sweet spot is an API that maps to common peripheral capabilities — init, read, write, configure interrupt — with a platform-specific "extras" extension point for features that only some hardware supports (e.g., STM32's GPIO BSRR atomic set/reset, or nRF52's GPIO SENSE for wake-from-sleep).
A common mistake is trying to abstract away differences that are fundamentally incompatible — for example, I2C on STM32 uses a peripheral with built-in state machine and interrupts, while bit-banged I2C on a tiny 8-bit MCU is purely software-driven. The driver API can be the same, but the implementations are so different that sharing code between them provides no real benefit. Portability is a spectrum: aim for API compatibility (same function signatures) rather than code reuse across wildly different platforms.
Source: Driver Design Q&A
