When working with embedded systems, naming might seem like a small detail, however it is not. Good naming conventions make code readable, maintainable, and portable. Poor naming, like Uart1 or Uart2, ties logic to hardware details, making future changes painful. Semantic names such as UartDebug or UartModem tell you what the peripheral in question does, not where it sits on the MCU.
In this post, I’ll explore why semantic naming matters, provide a naming table for all layers, one of which I will be using in upcoming examples when writing more about embedded C++, and present sample scenarios that clarify the benefits. These recommendations are based on my experience and general best practices in embedded C++ development.
Examples in Practice
Hardware API
struct Hardware
{
// UART
static UartDebug& get_debug_uart(); // For logging
static UartModem& get_modem_uart(); // For modem
static UartGnss& get_gnss_uart(); // For gnss
// SPI
// I2C
// GPIO
// etc...
};
Why Semantic Names Are Good
- Clarity:
Hardware::get_debug_uart()instantly tells you its purpose—debug logging—not just “UART number 1” - Portability: When you switch from Hardware A to Hardware B, you don’t need any rewrites. The mapping stays inside the hardware layer
- Maintainability: Semantic names reduce mental overhead. You don’t need to remember that
Uart3is for GNSS andUart1is for modem - Scalability: Works for UART, SPI, I2C, GPIO, and beyond
Before
Hardware::get_uart_1(); // Which UART is this? Debug? Modem?
After
Hardware::get_debug_uart(); // Clear intent, logical role, not hardware index
Naming Table
| Layer | Purpose | Recommended Names |
|---|---|---|
| Static Driver | Concrete implementation for hardware | UartDebug, UartModem, SpiSensor, GpioLed |
| Type-Erased Handle | Runtime-bound wrapper | UartHandle, SpiHandle, GpioHandle |
| Concept / Trait | Compile-time constraint | UartConcept, SpiConcept, IsUart, IsSpi |
| Policy | Behavior strategy | BlockingTxPolicy, DmaTxPolicy |
| Adapter (Virtual) | Bridge static → virtual interface | AdapterUart, AdapterSpi |
| Registry / Drivers | Aggregate of handles | HardwareDrivers, PlatformDrivers |
Again, why this matters:
- On MCU A,
get_debug_uart()might map to UART1. - On MCU B, it might map to UART3.
The user of the class doesn’t care—the mapping stays inside Hardware.
✅ Developer Checklist
- Use semantic names for all drivers (e.g.,
UartDebug,SpiSensor). - Provide both static and/or type-erased accessors in the hardware API (depending on the intention).
- Define concepts for compile-time constraints (
UartConcept). - Use adapters only at integration boundaries.
- Avoid leaking hardware indices into other parts of code.
Benefits and closing Thoughts
- Board portability: Logic stays the same across boards.
- Readability: Code explains intent without comments.
- Flexibility: Combine semantic names with type-erased handles for runtime wiring.
Semantic naming is a small change with a big impact. It makes your embedded C++ code future-proof, clear, and easy to maintain. Next time you start a project, skip Uart1 and go straight for UartDebug. Your future self will thank you.
-sorhanp