Architecture Design¶
This document describes the architecture and design decisions of the Nexus Embedded Platform.
Overview¶
Nexus follows a layered architecture with clear separation of concerns. This document explains:
System architecture
Design principles
Design patterns
Module interactions
Design decisions and rationale
Design Principles¶
Core Principles¶
1. Separation of Concerns
Each layer has a specific responsibility:
HAL: Hardware abstraction
OSAL: OS abstraction
Framework: High-level services
Platform: Hardware-specific implementation
2. Abstraction
Hide implementation details behind clean interfaces:
Public APIs are stable and well-documented
Internal implementation can change without affecting users
Platform differences are hidden from applications
3. Portability
Minimize platform-specific code:
Write once, run on multiple platforms
Platform-specific code isolated in platform layer
Consistent behavior across platforms
4. Testability
Design for testing:
Native platform for unit testing
Mock-friendly interfaces
Dependency injection
Property-based testing support
5. Resource Efficiency
Optimize for embedded systems:
Minimal memory footprint
Low CPU overhead
Configurable features via Kconfig
Static allocation options
SOLID Principles¶
Single Responsibility Principle
Each module has one reason to change:
HAL modules handle only hardware abstraction
OSAL modules handle only OS abstraction
Framework modules provide specific services
Open/Closed Principle
Open for extension, closed for modification:
New platforms can be added without modifying core
New peripherals can be added via factory pattern
Behavior can be extended via configuration
Liskov Substitution Principle
Derived types must be substitutable:
All GPIO implementations follow same interface
All UART implementations behave consistently
Platform-specific code respects contracts
Interface Segregation Principle
Clients shouldn't depend on unused interfaces:
Separate read/write GPIO interfaces
Separate TX/RX UART interfaces
Optional features are truly optional
Dependency Inversion Principle
Depend on abstractions, not concretions:
Applications depend on HAL interfaces
HAL depends on platform abstractions
Platform implements interfaces
System Architecture¶
Layer Overview¶
┌─────────────────────────────────────────────────────────────┐
│ Application Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Blinky │ │ Shell Demo │ │ Config Demo │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Framework Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Log │ │ Shell │ │ Config │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ OSAL Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Task │ │ Mutex │ │ Queue │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ HAL Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ GPIO │ │ UART │ │ SPI │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
├─────────────────────────────────────────────────────────────┤
│ Platform Layer │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ STM32F4 │ │ STM32H7 │ │ Native │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
Dependency Rules¶
Strict Layering:
Upper layers depend on lower layers
Lower layers never depend on upper layers
Same-layer components are independent
Platform layer is the only layer with hardware access
Allowed Dependencies:
Application → Framework → OSAL → HAL → Platform
Application → OSAL → HAL → Platform
Application → HAL → Platform
Forbidden Dependencies:
HAL ✗→ Framework
Platform ✗→ HAL
GPIO ✗→ UART (same layer)
HAL Architecture¶
Design Goals¶
Unified API across platforms
Type-safe interfaces
Resource management
Error handling
Testability
Factory Pattern¶
HAL uses factory pattern for device creation:
Benefits:
Centralized resource management
Platform-specific instantiation
Lifecycle management
Resource tracking
Implementation:
/* Factory function */
nx_gpio_write_t* nx_factory_gpio_write(char port, uint8_t pin) {
/* 1. Validate parameters */
if (!is_valid_port(port) || !is_valid_pin(pin)) {
return NULL;
}
/* 2. Check resource availability */
if (!is_gpio_available(port, pin)) {
return NULL;
}
/* 3. Create platform-specific instance */
nx_gpio_write_t* gpio = platform_create_gpio_write(port, pin);
if (gpio == NULL) {
return NULL;
}
/* 4. Register resource */
register_gpio_resource(port, pin, gpio);
return gpio;
}
/* Release function */
void nx_factory_gpio_release(nx_gpio_t* gpio) {
if (gpio == NULL) {
return;
}
/* 1. Unregister resource */
unregister_gpio_resource(gpio);
/* 2. Deinitialize device */
if (gpio->deinit != NULL) {
gpio->deinit(gpio);
}
/* 3. Free memory */
platform_free_gpio(gpio);
}
Interface Pattern¶
Devices expose interfaces with function pointers:
Benefits:
Polymorphism in C
Platform-specific implementations
Runtime dispatch
Testability (mocking)
Implementation:
/* Interface definition */
typedef struct nx_gpio_write {
/* Base interface */
nx_gpio_t base;
/* Write operations */
nx_status_t (*write)(struct nx_gpio_write* self, uint8_t pin,
nx_gpio_level_t level);
nx_status_t (*toggle)(struct nx_gpio_write* self, uint8_t pin);
nx_status_t (*read)(struct nx_gpio_write* self, uint8_t pin,
nx_gpio_level_t* level);
/* Configuration */
nx_status_t (*set_mode)(struct nx_gpio_write* self, uint8_t pin,
nx_gpio_mode_t mode);
/* Private data */
void* private_data;
} nx_gpio_write_t;
/* Platform implementation */
static nx_status_t stm32_gpio_write(nx_gpio_write_t* self, uint8_t pin,
nx_gpio_level_t level) {
stm32_gpio_private_t* priv = (stm32_gpio_private_t*)self->private_data;
/* Platform-specific implementation */
HAL_GPIO_WritePin(priv->port, 1 << pin,
level == NX_GPIO_LEVEL_HIGH ? GPIO_PIN_SET : GPIO_PIN_RESET);
return NX_OK;
}
Resource Management¶
HAL manages hardware resources:
Resource Registry:
/* Resource entry */
typedef struct {
uint8_t port;
uint8_t pin;
nx_gpio_t* device;
bool in_use;
} gpio_resource_t;
/* Resource registry */
static gpio_resource_t gpio_resources[MAX_GPIO_RESOURCES];
/* Register resource */
static nx_status_t register_gpio_resource(uint8_t port, uint8_t pin,
nx_gpio_t* device) {
for (int i = 0; i < MAX_GPIO_RESOURCES; i++) {
if (!gpio_resources[i].in_use) {
gpio_resources[i].port = port;
gpio_resources[i].pin = pin;
gpio_resources[i].device = device;
gpio_resources[i].in_use = true;
return NX_OK;
}
}
return NX_ERR_NO_MEMORY;
}
/* Check availability */
static bool is_gpio_available(uint8_t port, uint8_t pin) {
for (int i = 0; i < MAX_GPIO_RESOURCES; i++) {
if (gpio_resources[i].in_use &&
gpio_resources[i].port == port &&
gpio_resources[i].pin == pin) {
return false;
}
}
return true;
}
Error Handling¶
Consistent error handling across HAL:
Status Codes:
typedef enum {
NX_OK = 0, /* Success */
NX_ERR_FAIL, /* General failure */
NX_ERR_PARAM, /* Invalid parameter */
NX_ERR_STATE, /* Invalid state */
NX_ERR_TIMEOUT, /* Operation timeout */
NX_ERR_NO_MEMORY, /* Out of memory */
NX_ERR_NOT_SUPPORTED, /* Not supported */
NX_ERR_BUSY, /* Resource busy */
} nx_status_t;
Error Propagation:
nx_status_t hal_operation(void) {
nx_status_t status;
/* Check preconditions */
if (!is_initialized()) {
return NX_ERR_STATE;
}
/* Perform operation */
status = platform_operation();
if (status != NX_OK) {
/* Log error */
LOG_ERROR("Operation failed: %d", status);
return status;
}
/* Verify postconditions */
if (!verify_result()) {
return NX_ERR_FAIL;
}
return NX_OK;
}
OSAL Architecture¶
Design Goals¶
OS-agnostic API
Multiple backend support
Minimal overhead
Consistent behavior
Adapter Pattern¶
OSAL uses adapter pattern for OS abstraction:
Benefits:
Support multiple RTOS
Consistent API
Easy to add new backends
Testability
Implementation:
/* OSAL API */
osal_status_t osal_mutex_create(osal_mutex_handle_t* handle);
/* FreeRTOS adapter */
osal_status_t osal_mutex_create(osal_mutex_handle_t* handle) {
SemaphoreHandle_t mutex = xSemaphoreCreateMutex();
if (mutex == NULL) {
return OSAL_ERR_NO_MEMORY;
}
*handle = (osal_mutex_handle_t)mutex;
return OSAL_OK;
}
/* Bare-metal adapter */
osal_status_t osal_mutex_create(osal_mutex_handle_t* handle) {
baremetal_mutex_t* mutex = malloc(sizeof(baremetal_mutex_t));
if (mutex == NULL) {
return OSAL_ERR_NO_MEMORY;
}
mutex->locked = false;
*handle = (osal_mutex_handle_t)mutex;
return OSAL_OK;
}
Backend Selection¶
Backend selected at compile time via Kconfig:
#if defined(CONFIG_OSAL_FREERTOS)
#include "osal/adapters/freertos/freertos_adapter.h"
#elif defined(CONFIG_OSAL_BAREMETAL)
#include "osal/adapters/baremetal/baremetal_adapter.h"
#elif defined(CONFIG_OSAL_RTTHREAD)
#include "osal/adapters/rtthread/rtthread_adapter.h"
#else
#error "No OSAL backend selected"
#endif
Framework Architecture¶
Design Goals¶
Reusable components
Minimal dependencies
Configurable features
Easy integration
Logging Framework¶
Architecture:
┌─────────────────────────────────────────┐
│ Application Code │
│ LOG_INFO("Message: %d", value) │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ Log Frontend │
│ - Level filtering │
│ - Module filtering │
│ - Format string processing │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ Log Backend │
│ ┌──────────┐ ┌──────────┐ │
│ │ Console │ │ UART │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────┘
Key Features:
Multiple log levels (ERROR, WARN, INFO, DEBUG)
Module-based filtering
Multiple backends (console, UART, file)
Asynchronous logging option
Minimal overhead when disabled
Shell Framework¶
Architecture:
┌─────────────────────────────────────────┐
│ User Input │
│ "led green on" │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ Shell Core │
│ - Command parsing │
│ - History management │
│ - Tab completion │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ Command Registry │
│ ┌──────────┐ ┌──────────┐ │
│ │ led │ │ button │ │
│ └──────────┘ └──────────┘ │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ Command Handlers │
│ cmd_led(argc, argv) │
└─────────────────────────────────────────┘
Key Features:
Command registration
Argument parsing
Command history
Tab completion
Help system
Configuration Framework¶
Architecture:
┌─────────────────────────────────────────┐
│ Application Code │
│ config_set_i32("key", value) │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ Config Manager │
│ - Key-value storage │
│ - Type safety │
│ - Namespace support │
└─────────────────┬───────────────────────┘
│
┌─────────────────▼───────────────────────┐
│ Storage Backend │
│ ┌──────────┐ ┌──────────┐ │
│ │ Memory │ │ Flash │ │
│ └──────────┘ └──────────┘ │
└─────────────────────────────────────────┘
Key Features:
Key-value storage
Multiple data types
Namespace isolation
JSON/binary import/export
Persistence support
Platform Architecture¶
Design Goals¶
Minimal platform-specific code
Easy to add new platforms
Consistent initialization
Resource management
Platform Abstraction¶
Each platform implements required interfaces:
Required Implementations:
HAL peripheral drivers
OSAL backend (if using RTOS)
System initialization
Clock configuration
Interrupt handling
Platform Structure:
platforms/stm32/
├── src/
│ ├── stm32f4/
│ │ ├── gpio_stm32f4.c # GPIO implementation
│ │ ├── uart_stm32f4.c # UART implementation
│ │ └── system_stm32f4.c # System init
│ ├── stm32h7/
│ │ └── ...
│ └── common/
│ └── stm32_common.c # Common code
├── include/
│ └── stm32_hal.h # Platform header
├── Kconfig # Platform config
└── CMakeLists.txt # Build config
Initialization Sequence¶
Platform initialization follows a defined sequence:
int main(void) {
/* 1. Platform initialization */
platform_early_init(); /* Clock, power */
/* 2. HAL initialization */
nx_hal_init(); /* HAL subsystem */
/* 3. OSAL initialization */
osal_init(); /* OS/scheduler */
/* 4. Framework initialization */
log_init(NULL); /* Logging */
shell_init(&shell_config); /* Shell */
config_init(NULL); /* Configuration */
/* 5. Application initialization */
app_init();
/* 6. Start scheduler (if using RTOS) */
osal_start_scheduler();
/* 7. Main loop (bare-metal) */
while (1) {
app_main_loop();
}
return 0;
}
Design Patterns¶
Factory Pattern¶
Usage: Device creation in HAL
Benefits:
Centralized resource management
Platform-specific instantiation
Lifecycle management
Example: nx_factory_gpio_write()
Adapter Pattern¶
Usage: OSAL backend adaptation
Benefits:
Support multiple RTOS
Consistent API
Easy to add backends
Example: FreeRTOS adapter, bare-metal adapter
Strategy Pattern¶
Usage: Configurable behavior
Benefits:
Runtime behavior selection
Easy to add strategies
Testability
Example: Log backends, config storage backends
Observer Pattern¶
Usage: Event notification
Benefits:
Loose coupling
Multiple observers
Event-driven architecture
Example: Interrupt callbacks, event handlers
Singleton Pattern¶
Usage: Global resources
Benefits:
Single instance
Global access point
Lazy initialization
Example: HAL manager, OSAL scheduler
Design Decisions¶
Decision Records¶
Major design decisions are documented as Architecture Decision Records (ADRs).
ADR Template:
# ADR-001: Use Factory Pattern for HAL Devices
## Status
Accepted
## Context
Need a way to create and manage HAL device instances across
different platforms with resource tracking.
## Decision
Use factory pattern with centralized resource management.
## Consequences
**Positive:**
- Centralized resource management
- Platform-specific instantiation
- Easy to track resources
**Negative:**
- Additional indirection
- Slightly more complex API
## Alternatives Considered
1. Direct instantiation - rejected due to resource management issues
2. Singleton pattern - rejected due to inflexibility
Key Decisions¶
1. Layered Architecture
Decision: Use strict layered architecture
Rationale: Clear separation of concerns, testability
Trade-offs: Some indirection, but worth it for maintainability
2. Factory Pattern for HAL
Decision: Use factory pattern for device creation
Rationale: Resource management, platform abstraction
Trade-offs: Additional API layer, but provides flexibility
3. Adapter Pattern for OSAL
Decision: Use adapter pattern for OS abstraction
Rationale: Support multiple RTOS, consistent API
Trade-offs: Some overhead, but enables portability
4. Kconfig for Configuration
Decision: Use Kconfig for compile-time configuration
Rationale: Proven system, powerful, well-documented
Trade-offs: Learning curve, but very flexible
5. Property-Based Testing
Decision: Use property-based testing for HAL
Rationale: Better coverage, finds edge cases
Trade-offs: More complex tests, but higher quality
Performance Considerations¶
Memory Usage¶
Static Allocation:
Predictable memory usage
No fragmentation
Suitable for safety-critical systems
Dynamic Allocation:
Flexible memory usage
Potential fragmentation
Requires careful management
Optimization Strategies:
Use Kconfig to disable unused features
Static allocation for resource-constrained systems
Pool allocation for predictable patterns
CPU Usage¶
Optimization Strategies:
Minimize function call overhead
Use inline functions for hot paths
Avoid unnecessary copies
Efficient algorithms
Profiling:
Use profiling tools to identify bottlenecks
Optimize hot paths
Balance readability and performance
Code Size¶
Optimization Strategies:
Use
MinSizeRelbuild typeDisable unused features via Kconfig
Link-time optimization (LTO)
Remove debug code in release builds
Future Enhancements¶
Planned Features¶
Security: Secure boot, TLS 1.3, crypto engine
Cloud Integration: AWS IoT, Azure IoT
TinyML: TensorFlow Lite Micro support
More Platforms: ESP32, nRF52
More RTOS: Zephyr, RT-Thread
Architecture Evolution¶
Middleware Layer: Add more middleware components
Component System: Plugin architecture for extensions
Service Layer: High-level services (networking, file system)
See Also¶
API Design Guidelines - API design principles
Porting Guide - Platform porting guide
编码规范 - Coding standards
架构 - User-facing architecture guide