Peripheral Abstractions
NavHAL provides a uniform abstraction model across all hardware peripherals, enabling consistent interaction patterns while maintaining low-level control and minimal runtime overhead. Each peripheral module follows a common design structure that separates interface, implementation, and hardware mapping.
Design Pattern
All peripherals in NavHAL follow a consistent abstraction pattern:
Identifier → Macro Resolution → Register Access
Peripheral identifiers (e.g., GPIO pins, UART instances) are represented as compile-time constants. These identifiers encode sufficient information to determine the associated hardware resource.
At compile time, these identifiers are resolved through macros and inline functions into direct register-level operations. This eliminates the need for runtime lookup tables or dynamic dispatch mechanisms.
Reference Implementation: GPIO
The GPIO module serves as a representative example of this abstraction model.
GPIO pins are defined as encoded identifiers, where the port and pin number are embedded within a single value. Helper macros extract this information:
GPIO_GET_PORT(pin)→ resolves to the memory-mapped GPIO register blockGPIO_GET_PIN(pin)→ extracts the pin index within the port
This enables direct register access as shown below:
static inline void hal_gpio_digitalwrite(hal_gpio_pin pin,
hal_gpio_state state) {
if (state)
GPIO_GET_PORT(pin)->BSRR = (1U << GPIO_GET_PIN(pin));
else
GPIO_GET_PORT(pin)->BSRR = (1U << (GPIO_GET_PIN(pin) + 16));
}The resulting compiled code closely matches hand-written register-level implementations, ensuring minimal overhead.
More complex configuration operations, such as setting pin modes or alternate functions, are implemented as standard functions in the core layer, combining readability with efficiency.
Consistency Across Peripherals
This abstraction pattern is consistently applied across all supported peripherals, including UART, I2C, SPI, timers, and others. Each module:
Uses compile-time identifiers to represent hardware instances
Resolves hardware access through macros and inline functions
Provides a uniform API naming convention
Avoids runtime indirection and dynamic memory usage
This consistency reduces cognitive load and allows developers to interact with different peripherals using a predictable interface.
Generic Interface Selection
For certain peripherals, such as UART, NavHAL supports multiple variants of an operation (e.g., writing a single byte, buffer, or structured data). To maintain a clean and unified API, NavHAL leverages C11 _Generic macros to select the appropriate implementation at compile time.
This enables usage such as:
hal_uart_write(uart, data);where the underlying function is resolved based on the type of data. This approach avoids function overloading limitations in C while preserving type safety and eliminating runtime branching.
Performance Characteristics
The peripheral abstraction model is designed to introduce minimal runtime overhead. Key characteristics include:
Compile-time resolution of peripheral identifiers
Direct mapping to memory-mapped registers
Use of inline functions for performance-critical operations
Absence of dynamic dispatch or lookup tables
As a result, the generated machine code closely resembles manually written low-level implementations, making NavHAL suitable for performance-critical embedded applications.
Scalability
The abstraction model scales naturally to additional peripherals and hardware platforms. New peripherals can be integrated by following the same pattern, while new architectures or microcontrollers can be supported by extending the core and vendor layers without modifying the interface.