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 Uart3 is for GNSS and Uart1 is 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

LayerPurposeRecommended Names
Static DriverConcrete implementation for hardwareUartDebug, UartModem, SpiSensor, GpioLed
Type-Erased HandleRuntime-bound wrapperUartHandle, SpiHandle, GpioHandle
Concept / TraitCompile-time constraintUartConcept, SpiConcept, IsUart, IsSpi
PolicyBehavior strategyBlockingTxPolicy, DmaTxPolicy
Adapter (Virtual)Bridge static → virtual interfaceAdapterUart, AdapterSpi
Registry / DriversAggregate of handlesHardwareDrivers, 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