API Design Guidelines¶
This comprehensive guide covers API design principles, patterns, and best practices for the Nexus Embedded Platform.
Overview¶
Well-designed APIs are crucial for a successful embedded platform. This guide provides detailed guidelines for:
API design principles
Naming conventions
Function signatures
Error handling
Memory management
Thread safety
Documentation requirements
Versioning and compatibility
Good API design makes the platform:
Easy to use: Intuitive and consistent
Hard to misuse: Type-safe and well-documented
Maintainable: Clear and well-structured
Extensible: Easy to add new features
Portable: Works across platforms
Design Principles¶
Fundamental Principles¶
1. Consistency
Maintain consistency across all APIs:
Naming conventions
Parameter ordering
Return value conventions
Error handling patterns
Documentation style
Example - Consistent Naming:
/* Good: Consistent naming pattern */
hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin, const hal_gpio_config_t* config);
hal_status_t hal_uart_init(hal_uart_id_t id, const hal_uart_config_t* config);
hal_status_t hal_spi_init(hal_spi_id_t id, const hal_spi_config_t* config);
/* Bad: Inconsistent naming */
hal_status_t gpio_initialize(hal_gpio_port_t port, uint8_t pin, const hal_gpio_config_t* config);
hal_status_t InitUART(hal_uart_id_t id, const hal_uart_config_t* config);
hal_status_t spi_setup(hal_spi_id_t id, const hal_spi_config_t* config);
2. Simplicity
Keep APIs simple and focused:
One function, one purpose
Minimal parameters
Clear semantics
No hidden behavior
Example - Simple vs Complex:
/* Good: Simple, focused function */
hal_status_t hal_gpio_write(hal_gpio_port_t port, uint8_t pin, hal_gpio_level_t level);
/* Bad: Too many responsibilities */
hal_status_t hal_gpio_write_with_delay_and_toggle(
hal_gpio_port_t port,
uint8_t pin,
hal_gpio_level_t level,
uint32_t delay_ms,
bool toggle_after,
uint32_t toggle_count
);
3. Orthogonality
Functions should be independent:
No unexpected side effects
No hidden dependencies
Composable operations
Predictable behavior
Example - Orthogonal Design:
/* Good: Independent operations */
hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin, const hal_gpio_config_t* config);
hal_status_t hal_gpio_write(hal_gpio_port_t port, uint8_t pin, hal_gpio_level_t level);
hal_status_t hal_gpio_read(hal_gpio_port_t port, uint8_t pin, hal_gpio_level_t* level);
/* Bad: Coupled operations */
hal_status_t hal_gpio_init_and_write(hal_gpio_port_t port, uint8_t pin,
const hal_gpio_config_t* config,
hal_gpio_level_t initial_level);
4. Discoverability
Make APIs easy to discover:
Logical grouping
Clear naming
Comprehensive documentation
Examples and tutorials
5. Safety
Design for safety:
Type safety
Null pointer checks
Bounds checking
Resource management
Error handling
Naming Conventions¶
Module Prefix¶
All public APIs must use module prefix:
Format: <module>_<component>_<action>
Examples:
/* HAL module */
hal_gpio_init()
hal_uart_send()
hal_spi_transfer()
/* OSAL module */
osal_task_create()
osal_mutex_lock()
osal_queue_send()
/* Framework modules */
log_init()
shell_register_command()
config_set_i32()
Function Names¶
Format: <module>_<noun>_<verb> or <module>_<verb>_<noun>
Verbs:
init/deinit- Initialize / deinitializecreate/destroy- Create / destroy objectopen/close- Open / close resourcestart/stop- Start / stop operationenable/disable- Enable / disable featureset/get- Set / get valueread/write- Read / write datasend/receive- Send / receive messagelock/unlock- Lock / unlock mutexwait/signal- Wait / signal event
Examples:
/* Initialization */
hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin, const hal_gpio_config_t* config);
hal_status_t hal_gpio_deinit(hal_gpio_port_t port, uint8_t pin);
/* Configuration */
hal_status_t hal_gpio_set_mode(hal_gpio_port_t port, uint8_t pin, hal_gpio_mode_t mode);
hal_status_t hal_gpio_get_mode(hal_gpio_port_t port, uint8_t pin, hal_gpio_mode_t* mode);
/* Operations */
hal_status_t hal_gpio_write(hal_gpio_port_t port, uint8_t pin, hal_gpio_level_t level);
hal_status_t hal_gpio_read(hal_gpio_port_t port, uint8_t pin, hal_gpio_level_t* level);
hal_status_t hal_gpio_toggle(hal_gpio_port_t port, uint8_t pin);
Type Names¶
Format: <module>_<name>_t
Examples:
/* Status types */
typedef enum {
HAL_OK = 0,
HAL_ERROR,
HAL_ERROR_PARAM,
} hal_status_t;
/* Configuration structures */
typedef struct {
hal_gpio_mode_t mode;
hal_gpio_pull_t pull;
hal_gpio_speed_t speed;
} hal_gpio_config_t;
/* Handle types */
typedef void* osal_task_handle_t;
typedef void* osal_mutex_handle_t;
Enumeration Values¶
Format: <MODULE>_<TYPE>_<VALUE>
Examples:
typedef enum {
HAL_GPIO_MODE_INPUT = 0,
HAL_GPIO_MODE_OUTPUT_PP,
HAL_GPIO_MODE_OUTPUT_OD,
HAL_GPIO_MODE_AF_PP,
HAL_GPIO_MODE_AF_OD,
} hal_gpio_mode_t;
typedef enum {
HAL_GPIO_PULL_NONE = 0,
HAL_GPIO_PULL_UP,
HAL_GPIO_PULL_DOWN,
} hal_gpio_pull_t;
Macro Names¶
Format: <MODULE>_<NAME>
Examples:
/* Constants */
#define HAL_GPIO_PORT_MAX 16
#define HAL_GPIO_PIN_MAX 16
#define HAL_UART_BAUDRATE_MAX 921600
/* Configuration macros */
#define HAL_TIMEOUT_DEFAULT 1000
#define HAL_TIMEOUT_INFINITE 0xFFFFFFFF
Function Signatures¶
Parameter Order¶
Standard Order:
Handle/Context: Object or context pointer
Input Parameters: Data to be processed
Output Parameters: Results (pointers)
Options/Flags: Optional parameters
Examples:
/* Good: Consistent parameter order */
hal_status_t hal_uart_send(
hal_uart_id_t id, /* 1. Handle */
const uint8_t* data, /* 2. Input */
size_t length, /* 2. Input */
uint32_t timeout /* 4. Options */
);
hal_status_t hal_uart_receive(
hal_uart_id_t id, /* 1. Handle */
uint8_t* buffer, /* 3. Output */
size_t buffer_size, /* 2. Input */
size_t* received_length, /* 3. Output */
uint32_t timeout /* 4. Options */
);
/* Bad: Inconsistent parameter order */
hal_status_t hal_uart_send(
const uint8_t* data,
hal_uart_id_t id,
uint32_t timeout,
size_t length
);
Return Values¶
Always Return Status:
All functions that can fail must return a status code:
/* Good: Returns status */
hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin, const hal_gpio_config_t* config);
/* Bad: No error indication */
void hal_gpio_init(hal_gpio_port_t port, uint8_t pin, const hal_gpio_config_t* config);
Status Code Convention:
typedef enum {
HAL_OK = 0, /* Success - always 0 */
HAL_ERROR, /* General error */
HAL_ERROR_PARAM, /* Invalid parameter */
HAL_ERROR_STATE, /* Invalid state */
HAL_ERROR_TIMEOUT, /* Operation timeout */
HAL_ERROR_NO_MEMORY, /* Out of memory */
HAL_ERROR_NOT_SUPPORTED, /* Not supported */
HAL_ERROR_BUSY, /* Resource busy */
} hal_status_t;
Output Parameters:
Use output parameters for returning data:
/* Good: Status return, data via output parameter */
hal_status_t hal_gpio_read(hal_gpio_port_t port, uint8_t pin, hal_gpio_level_t* level);
/* Bad: Data return, no error indication */
hal_gpio_level_t hal_gpio_read(hal_gpio_port_t port, uint8_t pin);
Input Parameters¶
Const Correctness:
Mark input parameters as const:
/* Good: Const input parameters */
hal_status_t hal_uart_send(hal_uart_id_t id, const uint8_t* data, size_t length, uint32_t timeout);
hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin, const hal_gpio_config_t* config);
/* Bad: Missing const */
hal_status_t hal_uart_send(hal_uart_id_t id, uint8_t* data, size_t length, uint32_t timeout);
Pointer Parameters:
Always validate pointer parameters:
hal_status_t hal_gpio_read(hal_gpio_port_t port, uint8_t pin, hal_gpio_level_t* level) {
/* Validate pointer */
if (level == NULL) {
return HAL_ERROR_PARAM;
}
/* Implementation */
*level = /* read value */;
return HAL_OK;
}
Array Parameters:
Always include size parameter:
/* Good: Size parameter included */
hal_status_t hal_uart_send(hal_uart_id_t id, const uint8_t* data, size_t length, uint32_t timeout);
/* Bad: No size parameter */
hal_status_t hal_uart_send(hal_uart_id_t id, const uint8_t* data, uint32_t timeout);
Output Parameters¶
Pointer Convention:
Output parameters must be pointers:
/* Good: Output via pointer */
hal_status_t hal_gpio_read(hal_gpio_port_t port, uint8_t pin, hal_gpio_level_t* level);
hal_status_t hal_uart_receive(hal_uart_id_t id, uint8_t* buffer, size_t buffer_size,
size_t* received_length, uint32_t timeout);
Validation:
Always validate output pointers:
hal_status_t hal_uart_receive(hal_uart_id_t id, uint8_t* buffer, size_t buffer_size,
size_t* received_length, uint32_t timeout) {
/* Validate output pointers */
if (buffer == NULL || received_length == NULL) {
return HAL_ERROR_PARAM;
}
/* Validate buffer size */
if (buffer_size == 0) {
return HAL_ERROR_PARAM;
}
/* Implementation */
*received_length = /* actual received */;
return HAL_OK;
}
Optional Parameters¶
Use NULL for Optional Pointers:
/**
* \brief Initialize UART with optional configuration
* \param[in] id: UART instance ID
* \param[in] config: Configuration (NULL for default)
* \return HAL_OK on success
*/
hal_status_t hal_uart_init(hal_uart_id_t id, const hal_uart_config_t* config) {
hal_uart_config_t default_config;
/* Use default if config is NULL */
if (config == NULL) {
default_config.baudrate = 115200;
default_config.wordlen = HAL_UART_WORDLEN_8;
default_config.stopbits = HAL_UART_STOPBITS_1;
default_config.parity = HAL_UART_PARITY_NONE;
config = &default_config;
}
/* Implementation */
return HAL_OK;
}
Use Special Values for Optional Integers:
/* Use 0 or MAX value for "default" or "infinite" */
#define HAL_TIMEOUT_DEFAULT 0
#define HAL_TIMEOUT_INFINITE 0xFFFFFFFF
hal_status_t hal_uart_send(hal_uart_id_t id, const uint8_t* data, size_t length, uint32_t timeout);
Data Structures¶
Structure Design¶
Configuration Structures:
Use structures for configuration:
/**
* \brief GPIO configuration structure
*/
typedef struct {
hal_gpio_mode_t mode; /**< GPIO mode */
hal_gpio_pull_t pull; /**< Pull-up/down configuration */
hal_gpio_speed_t speed; /**< Output speed */
hal_gpio_level_t init_level; /**< Initial output level */
} hal_gpio_config_t;
/* Usage */
hal_gpio_config_t config = {
.mode = HAL_GPIO_MODE_OUTPUT_PP,
.pull = HAL_GPIO_PULL_NONE,
.speed = HAL_GPIO_SPEED_LOW,
.init_level = HAL_GPIO_LEVEL_LOW
};
hal_gpio_init(HAL_GPIO_PORT_A, 5, &config);
Opaque Handles:
Use opaque pointers for handles:
/* Public header - opaque type */
typedef void* osal_task_handle_t;
typedef void* osal_mutex_handle_t;
/* Implementation file - actual structure */
typedef struct {
/* Internal fields */
void* native_handle;
uint32_t priority;
char name[32];
} osal_task_internal_t;
Benefits:
Hide implementation details
Allow implementation changes
Prevent direct access to internals
Type safety
Structure Initialization¶
Designated Initializers:
Always use designated initializers:
/* Good: Designated initializers */
hal_gpio_config_t config = {
.mode = HAL_GPIO_MODE_OUTPUT_PP,
.pull = HAL_GPIO_PULL_NONE,
.speed = HAL_GPIO_SPEED_LOW,
.init_level = HAL_GPIO_LEVEL_LOW
};
/* Bad: Positional initializers */
hal_gpio_config_t config = {
HAL_GPIO_MODE_OUTPUT_PP,
HAL_GPIO_PULL_NONE,
HAL_GPIO_SPEED_LOW,
HAL_GPIO_LEVEL_LOW
};
Default Values:
Provide helper functions for default initialization:
/**
* \brief Get default GPIO configuration
* \param[out] config: Configuration structure to initialize
*/
void hal_gpio_get_default_config(hal_gpio_config_t* config) {
if (config == NULL) {
return;
}
config->mode = HAL_GPIO_MODE_INPUT;
config->pull = HAL_GPIO_PULL_NONE;
config->speed = HAL_GPIO_SPEED_LOW;
config->init_level = HAL_GPIO_LEVEL_LOW;
}
/* Usage */
hal_gpio_config_t config;
hal_gpio_get_default_config(&config);
config.mode = HAL_GPIO_MODE_OUTPUT_PP; /* Override specific fields */
hal_gpio_init(HAL_GPIO_PORT_A, 5, &config);
Enumeration Design¶
Explicit Values:
Always specify explicit values:
/* Good: Explicit values */
typedef enum {
HAL_GPIO_MODE_INPUT = 0,
HAL_GPIO_MODE_OUTPUT_PP = 1,
HAL_GPIO_MODE_OUTPUT_OD = 2,
HAL_GPIO_MODE_AF_PP = 3,
HAL_GPIO_MODE_AF_OD = 4,
} hal_gpio_mode_t;
/* Bad: Implicit values */
typedef enum {
HAL_GPIO_MODE_INPUT,
HAL_GPIO_MODE_OUTPUT_PP,
HAL_GPIO_MODE_OUTPUT_OD,
} hal_gpio_mode_t;
Size Specification:
Specify enum size when needed:
/* Specify size for packed structures */
typedef enum {
HAL_GPIO_LEVEL_LOW = 0,
HAL_GPIO_LEVEL_HIGH = 1,
} hal_gpio_level_t;
/* Ensure 8-bit size */
_Static_assert(sizeof(hal_gpio_level_t) <= sizeof(uint8_t),
"hal_gpio_level_t must fit in uint8_t");
Error Handling¶
Status Codes¶
Consistent Status Codes:
All modules use consistent status code pattern:
/* HAL status codes */
typedef enum {
HAL_OK = 0, /* Success */
HAL_ERROR, /* General error */
HAL_ERROR_PARAM, /* Invalid parameter */
HAL_ERROR_STATE, /* Invalid state */
HAL_ERROR_TIMEOUT, /* Operation timeout */
HAL_ERROR_NO_MEMORY, /* Out of memory */
HAL_ERROR_NOT_SUPPORTED, /* Not supported */
HAL_ERROR_BUSY, /* Resource busy */
} hal_status_t;
/* OSAL status codes */
typedef enum {
OSAL_OK = 0, /* Success */
OSAL_ERROR, /* General error */
OSAL_ERROR_PARAM, /* Invalid parameter */
OSAL_ERROR_TIMEOUT, /* Operation timeout */
OSAL_ERROR_NO_MEMORY, /* Out of memory */
} osal_status_t;
Success is Always Zero:
/* Good: Success is 0 */
if (hal_gpio_init(port, pin, &config) == HAL_OK) {
/* Success */
}
/* Also works */
if (hal_gpio_init(port, pin, &config) != HAL_OK) {
/* Error */
}
Error Checking¶
Always Check Return Values:
/* Good: Check return value */
hal_status_t status = hal_gpio_init(HAL_GPIO_PORT_A, 5, &config);
if (status != HAL_OK) {
LOG_ERROR("GPIO init failed: %d", status);
return status;
}
/* Bad: Ignore return value */
hal_gpio_init(HAL_GPIO_PORT_A, 5, &config);
Error Propagation:
Propagate errors up the call stack:
hal_status_t initialize_peripherals(void) {
hal_status_t status;
/* Initialize GPIO */
status = hal_gpio_init(HAL_GPIO_PORT_A, 5, &gpio_config);
if (status != HAL_OK) {
LOG_ERROR("GPIO init failed: %d", status);
return status;
}
/* Initialize UART */
status = hal_uart_init(HAL_UART_1, &uart_config);
if (status != HAL_OK) {
LOG_ERROR("UART init failed: %d", status);
/* Cleanup GPIO before returning */
hal_gpio_deinit(HAL_GPIO_PORT_A, 5);
return status;
}
return HAL_OK;
}
Parameter Validation¶
Validate All Parameters:
hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin,
const hal_gpio_config_t* config) {
/* Validate port */
if (port >= HAL_GPIO_PORT_MAX) {
return HAL_ERROR_PARAM;
}
/* Validate pin */
if (pin >= HAL_GPIO_PIN_MAX) {
return HAL_ERROR_PARAM;
}
/* Validate config pointer */
if (config == NULL) {
return HAL_ERROR_PARAM;
}
/* Validate config values */
if (config->mode >= HAL_GPIO_MODE_MAX) {
return HAL_ERROR_PARAM;
}
/* Implementation */
return HAL_OK;
}
Validation Order:
Null pointer checks
Range checks
State checks
Resource availability checks
State Validation¶
Check State Before Operations:
hal_status_t hal_uart_send(hal_uart_id_t id, const uint8_t* data,
size_t length, uint32_t timeout) {
/* Validate parameters */
if (id >= HAL_UART_MAX_INSTANCES) {
return HAL_ERROR_PARAM;
}
if (data == NULL || length == 0) {
return HAL_ERROR_PARAM;
}
/* Check if UART is initialized */
if (!uart_is_initialized(id)) {
return HAL_ERROR_STATE;
}
/* Check if UART is busy */
if (uart_is_busy(id)) {
return HAL_ERROR_BUSY;
}
/* Implementation */
return HAL_OK;
}
Error Messages¶
Provide Helpful Error Messages:
hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin,
const hal_gpio_config_t* config) {
if (port >= HAL_GPIO_PORT_MAX) {
LOG_ERROR("Invalid GPIO port: %d (max: %d)", port, HAL_GPIO_PORT_MAX - 1);
return HAL_ERROR_PARAM;
}
if (pin >= HAL_GPIO_PIN_MAX) {
LOG_ERROR("Invalid GPIO pin: %d (max: %d)", pin, HAL_GPIO_PIN_MAX - 1);
return HAL_ERROR_PARAM;
}
if (config == NULL) {
LOG_ERROR("GPIO config is NULL");
return HAL_ERROR_PARAM;
}
return HAL_OK;
}
Memory Management¶
Ownership Rules¶
Clear Ownership:
Always document who owns memory:
/**
* \brief Create GPIO device (caller owns returned pointer)
* \param[in] port: GPIO port
* \param[in] pin: GPIO pin
* \return GPIO device pointer (must be freed with nx_factory_gpio_release)
*/
nx_gpio_t* nx_factory_gpio(uint8_t port, uint8_t pin);
/**
* \brief Release GPIO device (frees memory)
* \param[in] gpio: GPIO device pointer
*/
void nx_factory_gpio_release(nx_gpio_t* gpio);
Allocation Patterns:
Caller Allocates: Caller provides buffer
/* Caller allocates buffer */ hal_status_t hal_uart_receive(hal_uart_id_t id, uint8_t* buffer, size_t buffer_size, size_t* received_length, uint32_t timeout); /* Usage */ uint8_t buffer[256]; size_t received; hal_uart_receive(HAL_UART_1, buffer, sizeof(buffer), &received, 1000);
Callee Allocates: Function allocates and returns
/* Function allocates memory */ nx_gpio_t* nx_factory_gpio(uint8_t port, uint8_t pin); /* Usage */ nx_gpio_t* gpio = nx_factory_gpio(0, 5); /* Use gpio */ nx_factory_gpio_release(gpio); /* Caller must free */
Static Allocation: Function returns static data
/* Returns pointer to static data */ const char* hal_get_version(void); /* Usage */ const char* version = hal_get_version(); /* No need to free */
Buffer Management¶
Size Parameters:
Always include buffer size:
/* Good: Buffer size included */
hal_status_t hal_uart_receive(hal_uart_id_t id, uint8_t* buffer,
size_t buffer_size, size_t* received_length,
uint32_t timeout);
/* Bad: No size parameter */
hal_status_t hal_uart_receive(hal_uart_id_t id, uint8_t* buffer,
size_t* received_length, uint32_t timeout);
Bounds Checking:
Always check buffer bounds:
hal_status_t hal_uart_receive(hal_uart_id_t id, uint8_t* buffer,
size_t buffer_size, size_t* received_length,
uint32_t timeout) {
/* Validate buffer */
if (buffer == NULL || buffer_size == 0) {
return HAL_ERROR_PARAM;
}
/* Check available data */
size_t available = uart_get_available_data(id);
if (available > buffer_size) {
/* Buffer too small */
return HAL_ERROR_NO_MEMORY;
}
/* Copy data with bounds check */
for (size_t i = 0; i < available && i < buffer_size; i++) {
buffer[i] = uart_read_byte(id);
}
*received_length = available;
return HAL_OK;
}
String Handling:
Use safe string functions:
/* Good: Size-limited string copy */
hal_status_t config_get_str(const char* key, char* buffer, size_t buffer_size) {
if (buffer == NULL || buffer_size == 0) {
return CONFIG_ERROR_PARAM;
}
const char* value = config_find_value(key);
if (value == NULL) {
return CONFIG_ERROR_NOT_FOUND;
}
/* Safe string copy with null termination */
strncpy(buffer, value, buffer_size - 1);
buffer[buffer_size - 1] = '\0';
return CONFIG_OK;
}
/* Bad: Unsafe string copy */
hal_status_t config_get_str(const char* key, char* buffer) {
const char* value = config_find_value(key);
strcpy(buffer, value); /* Buffer overflow risk! */
return CONFIG_OK;
}
Resource Cleanup¶
RAII Pattern:
Use init/deinit pairs:
/* Initialize resource */
hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin, const hal_gpio_config_t* config);
/* Cleanup resource */
hal_status_t hal_gpio_deinit(hal_gpio_port_t port, uint8_t pin);
/* Usage */
hal_status_t status = hal_gpio_init(HAL_GPIO_PORT_A, 5, &config);
if (status != HAL_OK) {
return status;
}
/* Use GPIO */
/* Always cleanup */
hal_gpio_deinit(HAL_GPIO_PORT_A, 5);
Error Cleanup:
Clean up on error:
hal_status_t initialize_system(void) {
hal_status_t status;
/* Initialize GPIO */
status = hal_gpio_init(HAL_GPIO_PORT_A, 5, &gpio_config);
if (status != HAL_OK) {
return status;
}
/* Initialize UART */
status = hal_uart_init(HAL_UART_1, &uart_config);
if (status != HAL_OK) {
/* Cleanup GPIO before returning */
hal_gpio_deinit(HAL_GPIO_PORT_A, 5);
return status;
}
/* Initialize SPI */
status = hal_spi_init(HAL_SPI_1, &spi_config);
if (status != HAL_OK) {
/* Cleanup UART and GPIO */
hal_uart_deinit(HAL_UART_1);
hal_gpio_deinit(HAL_GPIO_PORT_A, 5);
return status;
}
return HAL_OK;
}
Thread Safety¶
Reentrancy¶
Document Thread Safety:
Always document thread safety guarantees:
/**
* \brief Write to GPIO pin (thread-safe)
* \param[in] port: GPIO port
* \param[in] pin: GPIO pin
* \param[in] level: Output level
* \return HAL_OK on success
* \note This function is thread-safe
*/
hal_status_t hal_gpio_write(hal_gpio_port_t port, uint8_t pin, hal_gpio_level_t level);
/**
* \brief Initialize GPIO pin (NOT thread-safe)
* \param[in] port: GPIO port
* \param[in] pin: GPIO pin
* \param[in] config: Configuration
* \return HAL_OK on success
* \note This function is NOT thread-safe. Call only during initialization.
*/
hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin, const hal_gpio_config_t* config);
Thread-Safe Implementation:
Use mutexes for shared resources:
/* Global mutex for GPIO access */
static osal_mutex_handle_t gpio_mutex;
hal_status_t hal_gpio_write(hal_gpio_port_t port, uint8_t pin, hal_gpio_level_t level) {
hal_status_t status;
/* Lock mutex */
if (osal_mutex_lock(gpio_mutex, OSAL_WAIT_FOREVER) != OSAL_OK) {
return HAL_ERROR;
}
/* Critical section */
status = platform_gpio_write(port, pin, level);
/* Unlock mutex */
osal_mutex_unlock(gpio_mutex);
return status;
}
Atomic Operations¶
Use Atomic Operations:
For simple operations, use atomic operations:
#include <stdatomic.h>
/* Atomic counter */
static atomic_uint_fast32_t request_counter = ATOMIC_VAR_INIT(0);
uint32_t get_next_request_id(void) {
return atomic_fetch_add(&request_counter, 1);
}
Lock-Free Queues:
For high-performance scenarios, consider lock-free data structures:
/* Lock-free ring buffer for interrupt-to-task communication */
typedef struct {
uint8_t buffer[256];
atomic_uint_fast32_t head;
atomic_uint_fast32_t tail;
} lockfree_ringbuf_t;
bool ringbuf_push(lockfree_ringbuf_t* rb, uint8_t data) {
uint32_t head = atomic_load(&rb->head);
uint32_t next_head = (head + 1) % 256;
if (next_head == atomic_load(&rb->tail)) {
return false; /* Buffer full */
}
rb->buffer[head] = data;
atomic_store(&rb->head, next_head);
return true;
}
Interrupt Safety¶
Document Interrupt Safety:
/**
* \brief Write to GPIO pin (interrupt-safe)
* \param[in] port: GPIO port
* \param[in] pin: GPIO pin
* \param[in] level: Output level
* \return HAL_OK on success
* \note This function is interrupt-safe and can be called from ISR
*/
hal_status_t hal_gpio_write(hal_gpio_port_t port, uint8_t pin, hal_gpio_level_t level);
/**
* \brief Send data via UART (NOT interrupt-safe)
* \param[in] id: UART instance
* \param[in] data: Data buffer
* \param[in] length: Data length
* \param[in] timeout: Timeout in milliseconds
* \return HAL_OK on success
* \note This function is NOT interrupt-safe. Do not call from ISR.
*/
hal_status_t hal_uart_send(hal_uart_id_t id, const uint8_t* data, size_t length, uint32_t timeout);
ISR-Safe Implementation:
/* ISR-safe function - no blocking, no allocation */
hal_status_t hal_gpio_write_isr(hal_gpio_port_t port, uint8_t pin, hal_gpio_level_t level) {
/* Direct hardware access, no locks */
platform_gpio_write_direct(port, pin, level);
return HAL_OK;
}
/* Regular function - may block */
hal_status_t hal_gpio_write(hal_gpio_port_t port, uint8_t pin, hal_gpio_level_t level) {
/* Use mutex for thread safety */
osal_mutex_lock(gpio_mutex, OSAL_WAIT_FOREVER);
platform_gpio_write(port, pin, level);
osal_mutex_unlock(gpio_mutex);
return HAL_OK;
}
Documentation Requirements¶
Doxygen Comments¶
File Headers:
Every file must have a header comment:
/**
* \file hal_gpio.h
* \brief GPIO Hardware Abstraction Layer
* \author Nexus Team
*/
Function Documentation:
All public functions must be documented:
/**
* \brief Initialize GPIO pin
* \param[in] port: GPIO port (HAL_GPIO_PORT_A to HAL_GPIO_PORT_K)
* \param[in] pin: GPIO pin number (0-15)
* \param[in] config: Pointer to configuration structure
* \return HAL_OK on success, error code otherwise
* \retval HAL_OK: Success
* \retval HAL_ERROR_PARAM: Invalid parameter
* \retval HAL_ERROR_STATE: GPIO already initialized
* \note Pin must be deinitialized before re-initialization
* \warning This function is NOT thread-safe
* \see hal_gpio_deinit
*/
hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin,
const hal_gpio_config_t* config);
Structure Documentation:
Document all structures and fields:
/**
* \brief GPIO configuration structure
*/
typedef struct {
hal_gpio_mode_t mode; /**< GPIO mode (input/output/alternate) */
hal_gpio_pull_t pull; /**< Pull-up/down configuration */
hal_gpio_speed_t speed; /**< Output speed (low/medium/high/very high) */
hal_gpio_level_t init_level; /**< Initial output level (low/high) */
} hal_gpio_config_t;
Enumeration Documentation:
Document all enumerations and values:
/**
* \brief GPIO mode enumeration
*/
typedef enum {
HAL_GPIO_MODE_INPUT = 0, /**< Input mode */
HAL_GPIO_MODE_OUTPUT_PP, /**< Output push-pull mode */
HAL_GPIO_MODE_OUTPUT_OD, /**< Output open-drain mode */
HAL_GPIO_MODE_AF_PP, /**< Alternate function push-pull */
HAL_GPIO_MODE_AF_OD, /**< Alternate function open-drain */
HAL_GPIO_MODE_ANALOG, /**< Analog mode */
} hal_gpio_mode_t;
Usage Examples¶
Provide Examples:
Include usage examples in documentation:
/**
* \brief Initialize GPIO pin
* \param[in] port: GPIO port
* \param[in] pin: GPIO pin number
* \param[in] config: Configuration structure
* \return HAL_OK on success
*
* \par Example:
* \code
* // Configure PA5 as output
* hal_gpio_config_t config = {
* .mode = HAL_GPIO_MODE_OUTPUT_PP,
* .pull = HAL_GPIO_PULL_NONE,
* .speed = HAL_GPIO_SPEED_LOW,
* .init_level = HAL_GPIO_LEVEL_LOW
* };
*
* hal_status_t status = hal_gpio_init(HAL_GPIO_PORT_A, 5, &config);
* if (status != HAL_OK) {
* // Handle error
* }
*
* // Write to pin
* hal_gpio_write(HAL_GPIO_PORT_A, 5, HAL_GPIO_LEVEL_HIGH);
* \endcode
*/
hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin,
const hal_gpio_config_t* config);
Complex Examples:
For complex APIs, provide detailed examples:
/**
* \brief Send data via UART with DMA
*
* \par Example: Basic Usage
* \code
* uint8_t data[] = "Hello, World!";
* size_t sent;
* hal_status_t status = hal_uart_send_dma(HAL_UART_1, data, sizeof(data), &sent, 1000);
* if (status == HAL_OK) {
* printf("Sent %zu bytes\n", sent);
* }
* \endcode
*
* \par Example: Error Handling
* \code
* hal_status_t status = hal_uart_send_dma(HAL_UART_1, data, sizeof(data), &sent, 1000);
* switch (status) {
* case HAL_OK:
* printf("Success\n");
* break;
* case HAL_ERROR_TIMEOUT:
* printf("Timeout\n");
* break;
* case HAL_ERROR_BUSY:
* printf("UART busy\n");
* break;
* default:
* printf("Error: %d\n", status);
* break;
* }
* \endcode
*/
hal_status_t hal_uart_send_dma(hal_uart_id_t id, const uint8_t* data,
size_t length, size_t* sent, uint32_t timeout);
Versioning and Compatibility¶
API Versioning¶
Semantic Versioning:
Use semantic versioning for APIs:
Major version: Breaking changes
Minor version: New features (backward compatible)
Patch version: Bug fixes (backward compatible)
Version Macros:
/* Version information */
#define HAL_VERSION_MAJOR 1
#define HAL_VERSION_MINOR 2
#define HAL_VERSION_PATCH 3
/* Version as single number */
#define HAL_VERSION ((HAL_VERSION_MAJOR << 16) | \
(HAL_VERSION_MINOR << 8) | \
HAL_VERSION_PATCH)
/* Version string */
#define HAL_VERSION_STRING "1.2.3"
/**
* \brief Get HAL version
* \return Version as 32-bit number (major.minor.patch)
*/
uint32_t hal_get_version(void);
/**
* \brief Get HAL version string
* \return Version string (e.g., "1.2.3")
*/
const char* hal_get_version_string(void);
Backward Compatibility¶
Deprecation Process:
Mark as deprecated in documentation
Add deprecation warning
Provide migration path
Remove in next major version
Deprecation Example:
/**
* \brief Old function (DEPRECATED)
* \deprecated Use hal_gpio_init() instead
* \see hal_gpio_init
*/
__attribute__((deprecated("Use hal_gpio_init() instead")))
hal_status_t hal_gpio_configure(hal_gpio_port_t port, uint8_t pin,
const hal_gpio_config_t* config);
/**
* \brief New function
*/
hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin,
const hal_gpio_config_t* config);
API Evolution:
When adding new features, maintain backward compatibility:
/* Version 1.0 - Original API */
typedef struct {
hal_gpio_mode_t mode;
hal_gpio_pull_t pull;
} hal_gpio_config_t;
/* Version 1.1 - Extended API (backward compatible) */
typedef struct {
hal_gpio_mode_t mode;
hal_gpio_pull_t pull;
hal_gpio_speed_t speed; /* New field with default */
hal_gpio_level_t init_level; /* New field with default */
} hal_gpio_config_t;
/* Helper for backward compatibility */
void hal_gpio_get_default_config(hal_gpio_config_t* config) {
config->mode = HAL_GPIO_MODE_INPUT;
config->pull = HAL_GPIO_PULL_NONE;
config->speed = HAL_GPIO_SPEED_LOW; /* Default for new field */
config->init_level = HAL_GPIO_LEVEL_LOW; /* Default for new field */
}
ABI Stability¶
Maintain ABI Stability:
Don’t change structure layout
Don’t change enum values
Don’t change function signatures
Don’t remove public symbols
Breaking Changes:
If breaking changes are necessary:
Increment major version
Document all breaking changes
Provide migration guide
Consider compatibility layer
Example Migration Guide:
Migration from v1.x to v2.0
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Breaking Changes**:
1. ``hal_gpio_configure()`` renamed to ``hal_gpio_init()``
2. ``hal_gpio_config_t`` structure changed
3. Return type changed from ``int`` to ``hal_status_t``
**Migration Steps**:
Old code (v1.x):
.. code-block:: c
hal_gpio_config_t config = {
.mode = GPIO_MODE_OUTPUT,
.pull = GPIO_PULL_NONE
};
int result = hal_gpio_configure(GPIO_PORT_A, 5, &config);
New code (v2.0):
.. code-block:: c
hal_gpio_config_t config = {
.mode = HAL_GPIO_MODE_OUTPUT_PP,
.pull = HAL_GPIO_PULL_NONE,
.speed = HAL_GPIO_SPEED_LOW,
.init_level = HAL_GPIO_LEVEL_LOW
};
hal_status_t status = hal_gpio_init(HAL_GPIO_PORT_A, 5, &config);
Performance Considerations¶
Efficiency Guidelines¶
Minimize Overhead:
Keep API overhead minimal:
/* Good: Minimal overhead */
static inline hal_status_t hal_gpio_write_fast(hal_gpio_port_t port,
uint8_t pin,
hal_gpio_level_t level) {
/* Direct register access */
if (level == HAL_GPIO_LEVEL_HIGH) {
GPIO_PORTS[port]->BSRR = (1U << pin);
} else {
GPIO_PORTS[port]->BSRR = (1U << (pin + 16));
}
return HAL_OK;
}
/* Bad: Excessive overhead */
hal_status_t hal_gpio_write_slow(hal_gpio_port_t port,
uint8_t pin,
hal_gpio_level_t level) {
/* Validate parameters */
if (port >= HAL_GPIO_PORT_MAX) return HAL_ERROR_PARAM;
if (pin >= HAL_GPIO_PIN_MAX) return HAL_ERROR_PARAM;
/* Lock mutex */
osal_mutex_lock(gpio_mutex, OSAL_WAIT_FOREVER);
/* Log operation */
LOG_DEBUG("GPIO write: port=%d, pin=%d, level=%d", port, pin, level);
/* Write value */
if (level == HAL_GPIO_LEVEL_HIGH) {
GPIO_PORTS[port]->BSRR = (1U << pin);
} else {
GPIO_PORTS[port]->BSRR = (1U << (pin + 16));
}
/* Unlock mutex */
osal_mutex_unlock(gpio_mutex);
return HAL_OK;
}
Inline Functions:
Use inline for performance-critical functions:
/* Inline for performance */
static inline uint32_t hal_get_tick(void) {
return SysTick->VAL;
}
/* Regular function for complex operations */
hal_status_t hal_delay_ms(uint32_t ms);
Avoid Allocations:
Minimize dynamic memory allocation:
/* Good: Caller provides buffer */
hal_status_t hal_uart_receive(hal_uart_id_t id, uint8_t* buffer,
size_t buffer_size, size_t* received_length,
uint32_t timeout);
/* Bad: Function allocates memory */
uint8_t* hal_uart_receive_alloc(hal_uart_id_t id, size_t* length,
uint32_t timeout);
Batch Operations¶
Provide Batch APIs:
For operations on multiple items:
/* Single operation */
hal_status_t hal_gpio_write(hal_gpio_port_t port, uint8_t pin,
hal_gpio_level_t level);
/* Batch operation - more efficient */
hal_status_t hal_gpio_write_multiple(hal_gpio_port_t port,
uint16_t pin_mask,
uint16_t value_mask);
/* Usage */
/* Set pins 0, 2, 4 high; pins 1, 3, 5 low */
hal_gpio_write_multiple(HAL_GPIO_PORT_A,
0x003F, /* Pins 0-5 */
0x0015); /* Pins 0, 2, 4 high */
DMA Support:
Provide DMA APIs for bulk transfers:
/* Blocking transfer */
hal_status_t hal_uart_send(hal_uart_id_t id, const uint8_t* data,
size_t length, uint32_t timeout);
/* DMA transfer - more efficient for large data */
hal_status_t hal_uart_send_dma(hal_uart_id_t id, const uint8_t* data,
size_t length, hal_uart_callback_t callback,
void* user_data);
Zero-Copy APIs¶
Avoid Unnecessary Copies:
/* Good: Zero-copy API */
hal_status_t hal_uart_send_dma(hal_uart_id_t id, const uint8_t* data,
size_t length, hal_uart_callback_t callback,
void* user_data);
/* Bad: Copies data internally */
hal_status_t hal_uart_send_copy(hal_uart_id_t id, const uint8_t* data,
size_t length);
Buffer Ownership:
Document buffer ownership clearly:
/**
* \brief Send data via UART using DMA
* \param[in] id: UART instance
* \param[in] data: Data buffer (must remain valid until callback)
* \param[in] length: Data length
* \param[in] callback: Completion callback
* \param[in] user_data: User data for callback
* \return HAL_OK on success
* \note Buffer must remain valid until callback is invoked
* \warning Do not modify or free buffer until transfer completes
*/
hal_status_t hal_uart_send_dma(hal_uart_id_t id, const uint8_t* data,
size_t length, hal_uart_callback_t callback,
void* user_data);
Caching Considerations¶
Cache-Aligned Buffers:
For DMA operations, ensure proper alignment:
/* Cache line size */
#define CACHE_LINE_SIZE 32
/* Aligned buffer for DMA */
__attribute__((aligned(CACHE_LINE_SIZE)))
static uint8_t dma_buffer[256];
/**
* \brief Send data via UART using DMA
* \param[in] data: Data buffer (must be cache-aligned for DMA)
* \note Buffer must be aligned to CACHE_LINE_SIZE
*/
hal_status_t hal_uart_send_dma(hal_uart_id_t id, const uint8_t* data,
size_t length, hal_uart_callback_t callback,
void* user_data);
Cache Operations:
Provide cache management APIs:
/**
* \brief Clean data cache for DMA
* \param[in] addr: Buffer address
* \param[in] size: Buffer size
*/
void hal_cache_clean(void* addr, size_t size);
/**
* \brief Invalidate data cache after DMA
* \param[in] addr: Buffer address
* \param[in] size: Buffer size
*/
void hal_cache_invalidate(void* addr, size_t size);
Testing Requirements¶
Unit Testing¶
Testable Design:
Design APIs for testability:
/* Good: Dependency injection for testing */
typedef struct {
hal_status_t (*read)(uint32_t addr, uint8_t* data);
hal_status_t (*write)(uint32_t addr, uint8_t data);
} hal_flash_ops_t;
hal_status_t hal_flash_init(const hal_flash_ops_t* ops);
/* Test with mock operations */
hal_status_t mock_flash_read(uint32_t addr, uint8_t* data) {
*data = test_data[addr];
return HAL_OK;
}
hal_flash_ops_t mock_ops = {
.read = mock_flash_read,
.write = mock_flash_write
};
hal_flash_init(&mock_ops);
Test Coverage:
Ensure comprehensive test coverage:
Normal operation
Error conditions
Boundary conditions
Invalid parameters
State transitions
Concurrent access
Example Test Cases:
/* Test normal operation */
void test_gpio_write_normal(void) {
hal_gpio_config_t config = {
.mode = HAL_GPIO_MODE_OUTPUT_PP,
.pull = HAL_GPIO_PULL_NONE,
.speed = HAL_GPIO_SPEED_LOW,
.init_level = HAL_GPIO_LEVEL_LOW
};
assert(hal_gpio_init(HAL_GPIO_PORT_A, 5, &config) == HAL_OK);
assert(hal_gpio_write(HAL_GPIO_PORT_A, 5, HAL_GPIO_LEVEL_HIGH) == HAL_OK);
assert(hal_gpio_deinit(HAL_GPIO_PORT_A, 5) == HAL_OK);
}
/* Test invalid parameters */
void test_gpio_write_invalid_port(void) {
assert(hal_gpio_write(HAL_GPIO_PORT_MAX, 5, HAL_GPIO_LEVEL_HIGH) == HAL_ERROR_PARAM);
}
/* Test invalid state */
void test_gpio_write_not_initialized(void) {
assert(hal_gpio_write(HAL_GPIO_PORT_A, 5, HAL_GPIO_LEVEL_HIGH) == HAL_ERROR_STATE);
}
Integration Testing¶
Hardware Testing:
Test on actual hardware:
/* Hardware integration test */
void test_uart_loopback(void) {
uint8_t tx_data[] = "Hello";
uint8_t rx_data[16];
size_t received;
/* Configure UART in loopback mode */
hal_uart_config_t config = {
.baudrate = 115200,
.wordlen = HAL_UART_WORDLEN_8,
.stopbits = HAL_UART_STOPBITS_1,
.parity = HAL_UART_PARITY_NONE,
.mode = HAL_UART_MODE_LOOPBACK
};
assert(hal_uart_init(HAL_UART_1, &config) == HAL_OK);
/* Send data */
assert(hal_uart_send(HAL_UART_1, tx_data, sizeof(tx_data), 1000) == HAL_OK);
/* Receive data */
assert(hal_uart_receive(HAL_UART_1, rx_data, sizeof(rx_data), &received, 1000) == HAL_OK);
/* Verify data */
assert(received == sizeof(tx_data));
assert(memcmp(tx_data, rx_data, sizeof(tx_data)) == 0);
hal_uart_deinit(HAL_UART_1);
}
Stress Testing:
Test under stress conditions:
/* Stress test - rapid operations */
void test_gpio_stress(void) {
hal_gpio_config_t config = {
.mode = HAL_GPIO_MODE_OUTPUT_PP,
.pull = HAL_GPIO_PULL_NONE,
.speed = HAL_GPIO_SPEED_HIGH,
.init_level = HAL_GPIO_LEVEL_LOW
};
assert(hal_gpio_init(HAL_GPIO_PORT_A, 5, &config) == HAL_OK);
/* Toggle 10000 times */
for (int i = 0; i < 10000; i++) {
assert(hal_gpio_toggle(HAL_GPIO_PORT_A, 5) == HAL_OK);
}
hal_gpio_deinit(HAL_GPIO_PORT_A, 5);
}
Common Patterns¶
Initialization Pattern¶
Standard Init/Deinit:
/* Initialize resource */
hal_status_t hal_xxx_init(hal_xxx_id_t id, const hal_xxx_config_t* config);
/* Deinitialize resource */
hal_status_t hal_xxx_deinit(hal_xxx_id_t id);
/* Usage */
hal_xxx_config_t config;
hal_xxx_get_default_config(&config);
config.param1 = value1;
if (hal_xxx_init(HAL_XXX_1, &config) == HAL_OK) {
/* Use resource */
hal_xxx_deinit(HAL_XXX_1);
}
Global Init:
For modules requiring global initialization:
/**
* \brief Initialize HAL module
* \return HAL_OK on success
* \note Must be called before any other HAL functions
*/
hal_status_t hal_init(void);
/**
* \brief Deinitialize HAL module
* \return HAL_OK on success
*/
hal_status_t hal_deinit(void);
Configuration Pattern¶
Configuration Structure:
/* Configuration structure */
typedef struct {
uint32_t param1;
uint32_t param2;
bool enable_feature;
} hal_xxx_config_t;
/* Get default configuration */
void hal_xxx_get_default_config(hal_xxx_config_t* config);
/* Initialize with configuration */
hal_status_t hal_xxx_init(hal_xxx_id_t id, const hal_xxx_config_t* config);
/* Runtime configuration */
hal_status_t hal_xxx_set_config(hal_xxx_id_t id, const hal_xxx_config_t* config);
hal_status_t hal_xxx_get_config(hal_xxx_id_t id, hal_xxx_config_t* config);
Callback Pattern¶
Callback Registration:
/* Callback function type */
typedef void (*hal_xxx_callback_t)(hal_xxx_id_t id, hal_xxx_event_t event,
void* user_data);
/**
* \brief Register callback
* \param[in] id: Instance ID
* \param[in] callback: Callback function (NULL to unregister)
* \param[in] user_data: User data passed to callback
* \return HAL_OK on success
*/
hal_status_t hal_xxx_register_callback(hal_xxx_id_t id,
hal_xxx_callback_t callback,
void* user_data);
/* Usage */
void my_callback(hal_xxx_id_t id, hal_xxx_event_t event, void* user_data) {
/* Handle event */
}
hal_xxx_register_callback(HAL_XXX_1, my_callback, &my_context);
Multiple Callbacks:
For multiple event types:
/* Callback structure */
typedef struct {
void (*on_complete)(hal_xxx_id_t id, void* user_data);
void (*on_error)(hal_xxx_id_t id, hal_status_t error, void* user_data);
void (*on_timeout)(hal_xxx_id_t id, void* user_data);
} hal_xxx_callbacks_t;
/**
* \brief Register callbacks
* \param[in] id: Instance ID
* \param[in] callbacks: Callback structure (NULL to unregister all)
* \param[in] user_data: User data passed to callbacks
* \return HAL_OK on success
*/
hal_status_t hal_xxx_register_callbacks(hal_xxx_id_t id,
const hal_xxx_callbacks_t* callbacks,
void* user_data);
Iterator Pattern¶
Iteration API:
/* Iterator handle */
typedef void* hal_xxx_iterator_t;
/**
* \brief Get first item
* \param[out] iterator: Iterator handle
* \param[out] item: First item
* \return HAL_OK if item found, HAL_ERROR_NOT_FOUND if empty
*/
hal_status_t hal_xxx_get_first(hal_xxx_iterator_t* iterator,
hal_xxx_item_t* item);
/**
* \brief Get next item
* \param[in,out] iterator: Iterator handle
* \param[out] item: Next item
* \return HAL_OK if item found, HAL_ERROR_NOT_FOUND if end
*/
hal_status_t hal_xxx_get_next(hal_xxx_iterator_t* iterator,
hal_xxx_item_t* item);
/* Usage */
hal_xxx_iterator_t iter;
hal_xxx_item_t item;
if (hal_xxx_get_first(&iter, &item) == HAL_OK) {
do {
/* Process item */
} while (hal_xxx_get_next(&iter, &item) == HAL_OK);
}
Factory Pattern¶
Object Creation:
/**
* \brief Create GPIO device
* \param[in] port: GPIO port
* \param[in] pin: GPIO pin
* \return GPIO device pointer (NULL on error)
* \note Caller must free with nx_factory_gpio_release()
*/
nx_gpio_t* nx_factory_gpio(uint8_t port, uint8_t pin);
/**
* \brief Release GPIO device
* \param[in] gpio: GPIO device pointer
*/
void nx_factory_gpio_release(nx_gpio_t* gpio);
/* Usage */
nx_gpio_t* gpio = nx_factory_gpio(0, 5);
if (gpio != NULL) {
nx_gpio_write(gpio, NX_GPIO_LEVEL_HIGH);
nx_factory_gpio_release(gpio);
}
Builder Pattern¶
Complex Object Construction:
/* Builder handle */
typedef struct hal_xxx_builder* hal_xxx_builder_t;
/**
* \brief Create builder
* \return Builder handle (NULL on error)
*/
hal_xxx_builder_t hal_xxx_builder_create(void);
/**
* \brief Set parameter
* \param[in] builder: Builder handle
* \param[in] value: Parameter value
* \return HAL_OK on success
*/
hal_status_t hal_xxx_builder_set_param1(hal_xxx_builder_t builder, uint32_t value);
hal_status_t hal_xxx_builder_set_param2(hal_xxx_builder_t builder, uint32_t value);
/**
* \brief Build object
* \param[in] builder: Builder handle
* \param[out] obj: Created object
* \return HAL_OK on success
*/
hal_status_t hal_xxx_builder_build(hal_xxx_builder_t builder, hal_xxx_t* obj);
/**
* \brief Destroy builder
* \param[in] builder: Builder handle
*/
void hal_xxx_builder_destroy(hal_xxx_builder_t builder);
/* Usage */
hal_xxx_builder_t builder = hal_xxx_builder_create();
hal_xxx_builder_set_param1(builder, 100);
hal_xxx_builder_set_param2(builder, 200);
hal_xxx_t obj;
if (hal_xxx_builder_build(builder, &obj) == HAL_OK) {
/* Use object */
}
hal_xxx_builder_destroy(builder);
Anti-Patterns to Avoid¶
Boolean Parameters¶
Avoid Boolean Parameters:
/* Bad: Boolean parameter is unclear */
hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin, bool output);
/* Usage - unclear what true means */
hal_gpio_init(HAL_GPIO_PORT_A, 5, true);
/* Good: Use enum for clarity */
hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin,
hal_gpio_mode_t mode);
/* Usage - clear and explicit */
hal_gpio_init(HAL_GPIO_PORT_A, 5, HAL_GPIO_MODE_OUTPUT_PP);
Output Parameters First¶
Don’t Put Output Parameters First:
/* Bad: Output parameter first */
hal_status_t hal_gpio_read(hal_gpio_level_t* level, hal_gpio_port_t port,
uint8_t pin);
/* Good: Input parameters first, output last */
hal_status_t hal_gpio_read(hal_gpio_port_t port, uint8_t pin,
hal_gpio_level_t* level);
Magic Numbers¶
Avoid Magic Numbers:
/* Bad: Magic numbers */
hal_status_t hal_uart_init(hal_uart_id_t id, uint32_t config);
hal_uart_init(HAL_UART_1, 0x00001C00); /* What does this mean? */
/* Good: Named constants */
typedef struct {
uint32_t baudrate;
hal_uart_wordlen_t wordlen;
hal_uart_stopbits_t stopbits;
hal_uart_parity_t parity;
} hal_uart_config_t;
hal_uart_config_t config = {
.baudrate = 115200,
.wordlen = HAL_UART_WORDLEN_8,
.stopbits = HAL_UART_STOPBITS_1,
.parity = HAL_UART_PARITY_NONE
};
hal_uart_init(HAL_UART_1, &config);
Inconsistent Naming¶
Maintain Consistent Naming:
/* Bad: Inconsistent naming */
hal_status_t gpio_init(hal_gpio_port_t port, uint8_t pin, const hal_gpio_config_t* config);
hal_status_t hal_uart_initialize(hal_uart_id_t id, const hal_uart_config_t* config);
hal_status_t SPI_Init(hal_spi_id_t id, const hal_spi_config_t* config);
/* Good: Consistent naming */
hal_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin, const hal_gpio_config_t* config);
hal_status_t hal_uart_init(hal_uart_id_t id, const hal_uart_config_t* config);
hal_status_t hal_spi_init(hal_spi_id_t id, const hal_spi_config_t* config);
Global State¶
Avoid Hidden Global State:
/* Bad: Hidden global state */
static hal_gpio_port_t current_port;
void hal_gpio_set_port(hal_gpio_port_t port) {
current_port = port;
}
hal_status_t hal_gpio_write(uint8_t pin, hal_gpio_level_t level) {
/* Uses hidden global state */
return platform_gpio_write(current_port, pin, level);
}
/* Good: Explicit parameters */
hal_status_t hal_gpio_write(hal_gpio_port_t port, uint8_t pin,
hal_gpio_level_t level) {
return platform_gpio_write(port, pin, level);
}
String Return Values¶
Don’t Return Strings Directly:
/* Bad: Returns pointer to internal buffer */
char* hal_get_error_string(hal_status_t status) {
static char buffer[64];
sprintf(buffer, "Error: %d", status);
return buffer; /* Not thread-safe! */
}
/* Good: Caller provides buffer */
hal_status_t hal_get_error_string(hal_status_t status, char* buffer,
size_t buffer_size) {
if (buffer == NULL || buffer_size == 0) {
return HAL_ERROR_PARAM;
}
snprintf(buffer, buffer_size, "Error: %d", status);
return HAL_OK;
}
/* Also good: Return const string literal */
const char* hal_get_error_string(hal_status_t status) {
switch (status) {
case HAL_OK: return "Success";
case HAL_ERROR: return "Error";
case HAL_ERROR_PARAM: return "Invalid parameter";
default: return "Unknown error";
}
}
Excessive Parameters¶
Avoid Too Many Parameters:
/* Bad: Too many parameters */
hal_status_t hal_uart_init(hal_uart_id_t id, uint32_t baudrate,
hal_uart_wordlen_t wordlen,
hal_uart_stopbits_t stopbits,
hal_uart_parity_t parity,
hal_uart_flow_t flow,
bool enable_dma,
uint32_t timeout);
/* Good: Use configuration structure */
typedef struct {
uint32_t baudrate;
hal_uart_wordlen_t wordlen;
hal_uart_stopbits_t stopbits;
hal_uart_parity_t parity;
hal_uart_flow_t flow;
bool enable_dma;
uint32_t timeout;
} hal_uart_config_t;
hal_status_t hal_uart_init(hal_uart_id_t id, const hal_uart_config_t* config);
Platform-Specific Considerations¶
Endianness¶
Handle Endianness:
/* Endianness conversion macros */
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define CPU_TO_BE16(x) __builtin_bswap16(x)
#define CPU_TO_BE32(x) __builtin_bswap32(x)
#define BE16_TO_CPU(x) __builtin_bswap16(x)
#define BE32_TO_CPU(x) __builtin_bswap32(x)
#else
#define CPU_TO_BE16(x) (x)
#define CPU_TO_BE32(x) (x)
#define BE16_TO_CPU(x) (x)
#define BE32_TO_CPU(x) (x)
#endif
/* Usage */
uint32_t network_value = CPU_TO_BE32(local_value);
uint32_t local_value = BE32_TO_CPU(network_value);
Alignment¶
Handle Alignment Requirements:
/* Aligned structure */
typedef struct {
uint32_t field1;
uint16_t field2;
uint8_t field3;
uint8_t padding; /* Explicit padding */
} __attribute__((aligned(4))) hal_xxx_data_t;
/* Check alignment */
_Static_assert(sizeof(hal_xxx_data_t) % 4 == 0,
"hal_xxx_data_t must be 4-byte aligned");
/* Unaligned access helpers */
static inline uint32_t read_unaligned_u32(const uint8_t* ptr) {
uint32_t value;
memcpy(&value, ptr, sizeof(value));
return value;
}
static inline void write_unaligned_u32(uint8_t* ptr, uint32_t value) {
memcpy(ptr, &value, sizeof(value));
}
Bit Fields¶
Avoid Bit Fields for Portability:
/* Bad: Bit fields are not portable */
typedef struct {
uint32_t field1 : 8;
uint32_t field2 : 8;
uint32_t field3 : 16;
} hal_xxx_reg_t;
/* Good: Use bit masks and shifts */
#define HAL_XXX_FIELD1_POS 0
#define HAL_XXX_FIELD1_MASK (0xFF << HAL_XXX_FIELD1_POS)
#define HAL_XXX_FIELD2_POS 8
#define HAL_XXX_FIELD2_MASK (0xFF << HAL_XXX_FIELD2_POS)
#define HAL_XXX_FIELD3_POS 16
#define HAL_XXX_FIELD3_MASK (0xFFFF << HAL_XXX_FIELD3_POS)
/* Helper macros */
#define HAL_XXX_SET_FIELD1(reg, val) \
((reg) = ((reg) & ~HAL_XXX_FIELD1_MASK) | \
(((val) << HAL_XXX_FIELD1_POS) & HAL_XXX_FIELD1_MASK))
#define HAL_XXX_GET_FIELD1(reg) \
(((reg) & HAL_XXX_FIELD1_MASK) >> HAL_XXX_FIELD1_POS)
Integer Sizes¶
Use Fixed-Width Types:
/* Good: Fixed-width types */
#include <stdint.h>
typedef struct {
uint8_t byte_field;
uint16_t word_field;
uint32_t dword_field;
uint64_t qword_field;
} hal_xxx_data_t;
/* Bad: Variable-width types */
typedef struct {
char byte_field; /* Size varies */
short word_field; /* Size varies */
int dword_field; /* Size varies */
long qword_field; /* Size varies */
} hal_xxx_data_t;
Platform Abstraction¶
Abstract Platform Differences:
/* Platform-specific implementation */
#if defined(PLATFORM_STM32)
#include "stm32_gpio.h"
#define PLATFORM_GPIO_WRITE(port, pin, level) stm32_gpio_write(port, pin, level)
#elif defined(PLATFORM_NRF52)
#include "nrf52_gpio.h"
#define PLATFORM_GPIO_WRITE(port, pin, level) nrf52_gpio_write(port, pin, level)
#else
#error "Unsupported platform"
#endif
/* Platform-independent API */
hal_status_t hal_gpio_write(hal_gpio_port_t port, uint8_t pin,
hal_gpio_level_t level) {
PLATFORM_GPIO_WRITE(port, pin, level);
return HAL_OK;
}
Examples and Case Studies¶
Example 1: GPIO API¶
Complete GPIO API Design:
/* hal_gpio.h */
/**
* \file hal_gpio.h
* \brief GPIO Hardware Abstraction Layer
* \author Nexus Team
*/
#ifndef HAL_GPIO_H
#define HAL_GPIO_H
#include <stdint.h>
#include <stdbool.h>
/*---------------------------------------------------------------------------*/
/* Type Definitions */
/*---------------------------------------------------------------------------*/
/**
* \brief GPIO status codes
*/
typedef enum {
HAL_GPIO_OK = 0, /**< Success */
HAL_GPIO_ERROR, /**< General error */
HAL_GPIO_ERROR_PARAM, /**< Invalid parameter */
HAL_GPIO_ERROR_STATE, /**< Invalid state */
} hal_gpio_status_t;
/**
* \brief GPIO port enumeration
*/
typedef enum {
HAL_GPIO_PORT_A = 0, /**< Port A */
HAL_GPIO_PORT_B, /**< Port B */
HAL_GPIO_PORT_C, /**< Port C */
HAL_GPIO_PORT_MAX /**< Maximum port number */
} hal_gpio_port_t;
/**
* \brief GPIO mode enumeration
*/
typedef enum {
HAL_GPIO_MODE_INPUT = 0, /**< Input mode */
HAL_GPIO_MODE_OUTPUT_PP, /**< Output push-pull */
HAL_GPIO_MODE_OUTPUT_OD, /**< Output open-drain */
} hal_gpio_mode_t;
/**
* \brief GPIO pull configuration
*/
typedef enum {
HAL_GPIO_PULL_NONE = 0, /**< No pull */
HAL_GPIO_PULL_UP, /**< Pull-up */
HAL_GPIO_PULL_DOWN, /**< Pull-down */
} hal_gpio_pull_t;
/**
* \brief GPIO level enumeration
*/
typedef enum {
HAL_GPIO_LEVEL_LOW = 0, /**< Low level */
HAL_GPIO_LEVEL_HIGH, /**< High level */
} hal_gpio_level_t;
/**
* \brief GPIO configuration structure
*/
typedef struct {
hal_gpio_mode_t mode; /**< GPIO mode */
hal_gpio_pull_t pull; /**< Pull configuration */
hal_gpio_level_t init_level; /**< Initial level */
} hal_gpio_config_t;
/*---------------------------------------------------------------------------*/
/* Public Functions */
/*---------------------------------------------------------------------------*/
/**
* \brief Get default GPIO configuration
* \param[out] config: Configuration structure
*/
void hal_gpio_get_default_config(hal_gpio_config_t* config);
/**
* \brief Initialize GPIO pin
* \param[in] port: GPIO port
* \param[in] pin: Pin number (0-15)
* \param[in] config: Configuration structure
* \return HAL_GPIO_OK on success
*/
hal_gpio_status_t hal_gpio_init(hal_gpio_port_t port, uint8_t pin,
const hal_gpio_config_t* config);
/**
* \brief Deinitialize GPIO pin
* \param[in] port: GPIO port
* \param[in] pin: Pin number
* \return HAL_GPIO_OK on success
*/
hal_gpio_status_t hal_gpio_deinit(hal_gpio_port_t port, uint8_t pin);
/**
* \brief Write to GPIO pin
* \param[in] port: GPIO port
* \param[in] pin: Pin number
* \param[in] level: Output level
* \return HAL_GPIO_OK on success
*/
hal_gpio_status_t hal_gpio_write(hal_gpio_port_t port, uint8_t pin,
hal_gpio_level_t level);
/**
* \brief Read from GPIO pin
* \param[in] port: GPIO port
* \param[in] pin: Pin number
* \param[out] level: Read level
* \return HAL_GPIO_OK on success
*/
hal_gpio_status_t hal_gpio_read(hal_gpio_port_t port, uint8_t pin,
hal_gpio_level_t* level);
/**
* \brief Toggle GPIO pin
* \param[in] port: GPIO port
* \param[in] pin: Pin number
* \return HAL_GPIO_OK on success
*/
hal_gpio_status_t hal_gpio_toggle(hal_gpio_port_t port, uint8_t pin);
#endif /* HAL_GPIO_H */
Example 2: UART API with DMA¶
UART API with Async Operations:
/* hal_uart.h */
/**
* \file hal_uart.h
* \brief UART Hardware Abstraction Layer
* \author Nexus Team
*/
#ifndef HAL_UART_H
#define HAL_UART_H
#include <stdint.h>
#include <stddef.h>
/*---------------------------------------------------------------------------*/
/* Type Definitions */
/*---------------------------------------------------------------------------*/
/**
* \brief UART instance ID
*/
typedef enum {
HAL_UART_1 = 0, /**< UART instance 1 */
HAL_UART_2, /**< UART instance 2 */
HAL_UART_MAX /**< Maximum UART instances */
} hal_uart_id_t;
/**
* \brief UART status codes
*/
typedef enum {
HAL_UART_OK = 0, /**< Success */
HAL_UART_ERROR, /**< General error */
HAL_UART_ERROR_PARAM, /**< Invalid parameter */
HAL_UART_ERROR_TIMEOUT, /**< Operation timeout */
HAL_UART_ERROR_BUSY, /**< UART busy */
} hal_uart_status_t;
/**
* \brief UART event types
*/
typedef enum {
HAL_UART_EVENT_TX_COMPLETE = 0, /**< Transmission complete */
HAL_UART_EVENT_RX_COMPLETE, /**< Reception complete */
HAL_UART_EVENT_ERROR, /**< Error occurred */
} hal_uart_event_t;
/**
* \brief UART callback function type
* \param[in] id: UART instance ID
* \param[in] event: Event type
* \param[in] user_data: User data
*/
typedef void (*hal_uart_callback_t)(hal_uart_id_t id,
hal_uart_event_t event,
void* user_data);
/**
* \brief UART configuration structure
*/
typedef struct {
uint32_t baudrate; /**< Baudrate (e.g., 115200) */
uint8_t wordlen; /**< Word length (7, 8, 9 bits) */
uint8_t stopbits; /**< Stop bits (1, 2) */
uint8_t parity; /**< Parity (none, even, odd) */
} hal_uart_config_t;
/*---------------------------------------------------------------------------*/
/* Public Functions */
/*---------------------------------------------------------------------------*/
/**
* \brief Get default UART configuration
* \param[out] config: Configuration structure
*/
void hal_uart_get_default_config(hal_uart_config_t* config);
/**
* \brief Initialize UART
* \param[in] id: UART instance ID
* \param[in] config: Configuration structure
* \return HAL_UART_OK on success
*/
hal_uart_status_t hal_uart_init(hal_uart_id_t id,
const hal_uart_config_t* config);
/**
* \brief Deinitialize UART
* \param[in] id: UART instance ID
* \return HAL_UART_OK on success
*/
hal_uart_status_t hal_uart_deinit(hal_uart_id_t id);
/**
* \brief Send data (blocking)
* \param[in] id: UART instance ID
* \param[in] data: Data buffer
* \param[in] length: Data length
* \param[in] timeout: Timeout in milliseconds
* \return HAL_UART_OK on success
*/
hal_uart_status_t hal_uart_send(hal_uart_id_t id, const uint8_t* data,
size_t length, uint32_t timeout);
/**
* \brief Receive data (blocking)
* \param[in] id: UART instance ID
* \param[out] buffer: Receive buffer
* \param[in] buffer_size: Buffer size
* \param[out] received: Bytes received
* \param[in] timeout: Timeout in milliseconds
* \return HAL_UART_OK on success
*/
hal_uart_status_t hal_uart_receive(hal_uart_id_t id, uint8_t* buffer,
size_t buffer_size, size_t* received,
uint32_t timeout);
/**
* \brief Send data using DMA (non-blocking)
* \param[in] id: UART instance ID
* \param[in] data: Data buffer (must remain valid until callback)
* \param[in] length: Data length
* \param[in] callback: Completion callback
* \param[in] user_data: User data for callback
* \return HAL_UART_OK on success
* \note Buffer must remain valid until callback is invoked
*/
hal_uart_status_t hal_uart_send_dma(hal_uart_id_t id, const uint8_t* data,
size_t length,
hal_uart_callback_t callback,
void* user_data);
/**
* \brief Receive data using DMA (non-blocking)
* \param[in] id: UART instance ID
* \param[out] buffer: Receive buffer (must remain valid until callback)
* \param[in] buffer_size: Buffer size
* \param[in] callback: Completion callback
* \param[in] user_data: User data for callback
* \return HAL_UART_OK on success
* \note Buffer must remain valid until callback is invoked
*/
hal_uart_status_t hal_uart_receive_dma(hal_uart_id_t id, uint8_t* buffer,
size_t buffer_size,
hal_uart_callback_t callback,
void* user_data);
#endif /* HAL_UART_H */
Example 3: Configuration API¶
Type-Safe Configuration API:
/* config.h */
/**
* \file config.h
* \brief Configuration System API
* \author Nexus Team
*/
#ifndef CONFIG_H
#define CONFIG_H
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
/*---------------------------------------------------------------------------*/
/* Type Definitions */
/*---------------------------------------------------------------------------*/
/**
* \brief Configuration status codes
*/
typedef enum {
CONFIG_OK = 0, /**< Success */
CONFIG_ERROR, /**< General error */
CONFIG_ERROR_NOT_FOUND, /**< Key not found */
CONFIG_ERROR_TYPE, /**< Type mismatch */
CONFIG_ERROR_RANGE, /**< Value out of range */
} config_status_t;
/*---------------------------------------------------------------------------*/
/* Public Functions */
/*---------------------------------------------------------------------------*/
/**
* \brief Initialize configuration system
* \return CONFIG_OK on success
*/
config_status_t config_init(void);
/**
* \brief Set integer value
* \param[in] key: Configuration key
* \param[in] value: Integer value
* \return CONFIG_OK on success
*/
config_status_t config_set_i32(const char* key, int32_t value);
/**
* \brief Get integer value
* \param[in] key: Configuration key
* \param[out] value: Integer value
* \return CONFIG_OK on success
*/
config_status_t config_get_i32(const char* key, int32_t* value);
/**
* \brief Set string value
* \param[in] key: Configuration key
* \param[in] value: String value
* \return CONFIG_OK on success
*/
config_status_t config_set_str(const char* key, const char* value);
/**
* \brief Get string value
* \param[in] key: Configuration key
* \param[out] buffer: Output buffer
* \param[in] buffer_size: Buffer size
* \return CONFIG_OK on success
*/
config_status_t config_get_str(const char* key, char* buffer,
size_t buffer_size);
/**
* \brief Set boolean value
* \param[in] key: Configuration key
* \param[in] value: Boolean value
* \return CONFIG_OK on success
*/
config_status_t config_set_bool(const char* key, bool value);
/**
* \brief Get boolean value
* \param[in] key: Configuration key
* \param[out] value: Boolean value
* \return CONFIG_OK on success
*/
config_status_t config_get_bool(const char* key, bool* value);
/**
* \brief Check if key exists
* \param[in] key: Configuration key
* \return true if key exists
*/
bool config_has_key(const char* key);
/**
* \brief Delete key
* \param[in] key: Configuration key
* \return CONFIG_OK on success
*/
config_status_t config_delete(const char* key);
/**
* \brief Save configuration to storage
* \return CONFIG_OK on success
*/
config_status_t config_save(void);
/**
* \brief Load configuration from storage
* \return CONFIG_OK on success
*/
config_status_t config_load(void);
#endif /* CONFIG_H */
See Also¶
External Resources¶
Summary¶
This guide provides comprehensive API design guidelines for the Nexus Embedded Platform:
Design Principles: Consistency, simplicity, orthogonality, discoverability, safety
Naming Conventions: Module prefixes, function names, type names, enumerations
Function Signatures: Parameter order, return values, input/output parameters
Data Structures: Configuration structures, opaque handles, enumerations
Error Handling: Status codes, parameter validation, error propagation
Memory Management: Ownership rules, buffer management, resource cleanup
Thread Safety: Reentrancy, atomic operations, interrupt safety
Documentation: Doxygen comments, usage examples, API documentation
Versioning: Semantic versioning, backward compatibility, deprecation
Performance: Efficiency, batch operations, zero-copy APIs
Testing: Unit testing, integration testing, testable design
Common Patterns: Initialization, configuration, callbacks, iterators
Anti-Patterns: Boolean parameters, magic numbers, global state
Platform Considerations: Endianness, alignment, portability
Examples: Complete API designs for GPIO, UART, configuration
Following these guidelines ensures consistent, maintainable, and high-quality APIs across the Nexus platform.