Portable Firmware: Modernizing Embedded Systems Architecture
The embedded systems landscape has undergone a dramatic transformation. Today’s microcontrollers pack features that were once exclusive to full computers - multi-core processors, sophisticated peripherals, wireless connectivity, AI accelerators, and gigabytes of memory. Yet, many firmware projects still rely on outdated, monolithic architectures that were designed for simple 8-bit controllers with kilobytes of memory.
This mismatch between hardware capability and firmware architecture is holding back innovation, increasing development costs, and creating maintenance nightmares. Modern embedded systems demand a new approach: portable firmware architecture that can scale from simple sensors to complex edge computing devices.
The Legacy Firmware Problem
Traditional Firmware Architecture (1990s-2000s)
Early embedded firmware was designed for resource-constrained environments:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // Traditional monolithic firmware structure
#include "pic16f877a.h" // Hardware-specific header
void main() {
// Direct register manipulation
TRISD = 0x00; // Port D as output
PORTD = 0x00; // Clear Port D
while(1) {
// Hardware-specific polling
if(RC0 == 1) {
RD0 = 1; // Turn on LED directly
__delay_ms(500);
RD0 = 0;
}
// ADC reading - direct register access
ADCON0 = 0x41; // Select channel and start conversion
while(ADCON0bits.GO_nDONE);
int adc_result = (ADRESH << 8) + ADRESL;
}
}
|
Characteristics of Legacy Firmware:
- Direct hardware manipulation - Register-level programming
- Vendor lock-in - Code tied to specific microcontroller families
- Monolithic structure - Everything in main() loop
- No abstraction layers - Hardware details scattered throughout code
- Limited scalability - Difficult to add features or port to new hardware
Why Legacy Approaches Fail Modern Systems
1. Hardware Complexity Explosion
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // Modern MCU complexity (STM32H7 example)
// - Multiple clock domains
// - Complex power management
// - Advanced peripherals (CAN-FD, Ethernet, USB3.0)
// - Multi-core architecture
// - Memory management units
// - Cryptographic accelerators
// Legacy approach becomes unmaintainable:
void configure_clocks() {
// 200+ lines of clock configuration
RCC->CR |= RCC_CR_HSEON;
while(!(RCC->CR & RCC_CR_HSERDY));
RCC->PLLCFGR = (4 << RCC_PLLCFGR_PLLM_Pos) |
(200 << RCC_PLLCFGR_PLLN_Pos) |
(2 << RCC_PLLCFGR_PLLP_Pos);
// ... hundreds more lines of register manipulation
}
|
2. Development Time Explosion
- Port to new hardware: 3-6 months of rewriting
- Add new features: Risk breaking existing functionality
- Debug issues: Hardware-specific knowledge required
- Team scalability: Only hardware experts can contribute
3. Maintenance Nightmare
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // Typical legacy codebase issues
#ifdef STM32F4
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
#elif defined(PIC32MX)
TRISA &= ~(1 << 5);
LATA |= (1 << 5);
#elif defined(ATMEGA328)
DDRB |= (1 << PB5);
PORTB |= (1 << PB5);
#endif
// Multiply this by hundreds of peripheral configurations...
|
Modern Embedded System Requirements
Hardware Evolution: From Simple to Sophisticated
8-bit Era:
- 4-8 KB Flash, 256B RAM
- Simple GPIO, timers, UART
- Single-threaded, polling-based
32-bit Revolution:
- 32-512 KB Flash, 8-64 KB RAM
- Advanced peripherals (SPI, I2C, ADC, PWM)
- Real-time operating systems
Modern SoC Era
- 2MB+ Flash, 1MB+ RAM
- Multi-core processors
- AI accelerators, GPU cores
- Wireless connectivity (WiFi, Bluetooth, LoRa)
- Advanced security features
- Multiple voltage domains
Application Complexity Growth
Traditional Applications:
1
2
3
4
5
6
7
8
9
10
| // Simple control loop
void simple_controller() {
while(1) {
sensor_value = read_sensor();
if(sensor_value > threshold) {
activate_relay();
}
delay_ms(100);
}
}
|
Modern Applications:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // Modern IoT device requirements
void modern_iot_device() {
// Multi-threaded operation
create_task(sensor_acquisition_task, HIGH_PRIORITY);
create_task(data_processing_task, MEDIUM_PRIORITY);
create_task(wireless_communication_task, MEDIUM_PRIORITY);
create_task(cloud_sync_task, LOW_PRIORITY);
create_task(security_monitoring_task, HIGH_PRIORITY);
create_task(power_management_task, LOW_PRIORITY);
// Machine learning inference
initialize_ai_model();
// Over-the-air updates
initialize_ota_client();
// Security and encryption
initialize_crypto_engine();
start_scheduler();
}
|
Portable Firmware Architecture Principles
1. Hardware Abstraction Layer (HAL)
A proper HAL isolates hardware-specific code from application logic:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // HAL Interface Definition (hardware-agnostic)
typedef enum {
GPIO_OUTPUT,
GPIO_INPUT,
GPIO_INPUT_PULLUP,
GPIO_INPUT_PULLDOWN
} gpio_mode_t;
typedef enum {
GPIO_LOW = 0,
GPIO_HIGH = 1
} gpio_state_t;
// Generic HAL API
int hal_gpio_init(uint32_t pin, gpio_mode_t mode);
int hal_gpio_write(uint32_t pin, gpio_state_t state);
gpio_state_t hal_gpio_read(uint32_t pin);
|
Platform-Specific Implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| // STM32 Implementation
int hal_gpio_init(uint32_t pin, gpio_mode_t mode) {
GPIO_TypeDef* port = get_gpio_port(pin);
uint32_t pin_num = get_pin_number(pin);
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = (1 << pin_num);
switch(mode) {
case GPIO_OUTPUT:
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
break;
case GPIO_INPUT:
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
break;
// ... other modes
}
HAL_GPIO_Init(port, &GPIO_InitStruct);
return 0;
}
// ESP32 Implementation
int hal_gpio_init(uint32_t pin, gpio_mode_t mode) {
gpio_config_t io_conf = {};
io_conf.pin_bit_mask = (1ULL << pin);
switch(mode) {
case GPIO_OUTPUT:
io_conf.mode = GPIO_MODE_OUTPUT;
break;
case GPIO_INPUT:
io_conf.mode = GPIO_MODE_INPUT;
break;
// ... other modes
}
return gpio_config(&io_conf);
}
|
2. Component-Based Architecture
Break monolithic firmware into reusable components:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // Component interface definition
typedef struct {
int (*init)(void* config);
int (*start)(void);
int (*stop)(void);
int (*process)(void);
int (*cleanup)(void);
} component_interface_t;
// Sensor component
typedef struct {
component_interface_t interface;
float (*read_temperature)(void);
float (*read_humidity)(void);
int (*calibrate)(void);
} sensor_component_t;
// Communication component
typedef struct {
component_interface_t interface;
int (*send_data)(uint8_t* data, size_t len);
int (*receive_data)(uint8_t* buffer, size_t max_len);
int (*connect)(void);
int (*disconnect)(void);
} comm_component_t;
|
3. Configuration Management
Modern firmware needs flexible configuration systems:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| // Configuration structure
typedef struct {
// Hardware configuration
struct {
uint32_t cpu_frequency;
uint8_t enable_fpu;
uint8_t enable_cache;
} hardware;
// Peripheral configuration
struct {
uint32_t uart_baudrate;
uint8_t i2c_address;
uint32_t spi_frequency;
} peripherals;
// Application configuration
struct {
uint16_t sensor_sample_rate;
float temperature_threshold;
uint32_t transmission_interval;
} application;
// Security configuration
struct {
uint8_t enable_encryption;
uint8_t key_length;
char device_certificate[1024];
} security;
} system_config_t;
// Configuration loading from multiple sources
int load_configuration(system_config_t* config) {
// Try loading from flash memory
if(load_config_from_flash(config) == 0) {
return 0;
}
// Fallback to default configuration
if(load_default_config(config) == 0) {
return 0;
}
return -1;
}
|
Modern Firmware Architecture Patterns
1. Layered Architecture
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| ┌─────────────────────────────────────────────────────┐
│ Application Layer │
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ IoT Services │ │ User Interface │ │
│ │ - MQTT Client │ │ - Web Server │ │
│ │ - HTTP Client │ │ - Command Interface │ │
│ │ - CoAP Client │ │ - Status LEDs │ │
│ └─────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Middleware Layer │
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ RTOS │ │ Libraries │ │
│ │ - Task Mgmt │ │ - JSON Parser │ │
│ │ - Semaphores │ │ - Crypto Library │ │
│ │ - Queues │ │ - Protocol Stacks │ │
│ └─────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Hardware Abstraction Layer │
│ ┌─────────────────┐ ┌─────────────────────────────┐ │
│ │ Peripheral │ │ System Services │ │
│ │ Drivers │ │ - Clock Management │ │
│ │ - GPIO, UART │ │ - Power Management │ │
│ │ - SPI, I2C │ │ - Memory Management │ │
│ └─────────────────┘ └─────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ Hardware Layer │
│ ARM Cortex-M / ESP32 / etc. │
└─────────────────────────────────────────────────────┘
|
2. Event-Driven Architecture
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| // Event system for decoupled communication
typedef enum {
EVENT_SENSOR_DATA_READY,
EVENT_NETWORK_CONNECTED,
EVENT_NETWORK_DISCONNECTED,
EVENT_LOW_BATTERY,
EVENT_FIRMWARE_UPDATE_AVAILABLE,
EVENT_USER_BUTTON_PRESSED
} event_type_t;
typedef struct {
event_type_t type;
uint32_t timestamp;
void* data;
size_t data_size;
} event_t;
// Event handler function type
typedef void (*event_handler_t)(const event_t* event);
// Event system API
int event_subscribe(event_type_t type, event_handler_t handler);
int event_publish(event_type_t type, void* data, size_t size);
// Component implementation using events
void sensor_task(void* parameters) {
while(1) {
float temperature = read_temperature_sensor();
// Publish sensor data event
sensor_data_t data = {
.temperature = temperature,
.timestamp = get_system_time()
};
event_publish(EVENT_SENSOR_DATA_READY, &data, sizeof(data));
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void data_logger_handler(const event_t* event) {
if(event->type == EVENT_SENSOR_DATA_READY) {
sensor_data_t* data = (sensor_data_t*)event->data;
log_sensor_data(data);
}
}
void communication_handler(const event_t* event) {
if(event->type == EVENT_SENSOR_DATA_READY) {
sensor_data_t* data = (sensor_data_t*)event->data;
send_data_to_cloud(data);
}
}
|
3. Plugin Architecture
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| // Plugin interface for extensible firmware
typedef struct plugin {
const char* name;
const char* version;
int (*init)(struct plugin* self, void* config);
int (*process)(struct plugin* self);
int (*cleanup)(struct plugin* self);
void* private_data;
struct plugin* next;
} plugin_t;
// Plugin registry
typedef struct {
plugin_t* plugins;
uint32_t count;
uint32_t max_plugins;
} plugin_registry_t;
// Plugin management
int register_plugin(plugin_registry_t* registry, plugin_t* plugin) {
if(registry->count >= registry->max_plugins) {
return -1;
}
// Add to linked list
plugin->next = registry->plugins;
registry->plugins = plugin;
registry->count++;
return plugin->init(plugin, NULL);
}
// Example plugins
plugin_t temperature_sensor_plugin = {
.name = "Temperature Sensor",
.version = "1.0.0",
.init = temperature_sensor_init,
.process = temperature_sensor_process,
.cleanup = temperature_sensor_cleanup
};
plugin_t wifi_communication_plugin = {
.name = "WiFi Communication",
.version = "2.1.0",
.init = wifi_init,
.process = wifi_process,
.cleanup = wifi_cleanup
};
|
1. Zephyr RTOS
Zephyr provides a comprehensive portable firmware framework:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| // Zephyr device tree configuration
/ {
aliases {
led0 = &green_led;
sw0 = &user_button;
};
leds {
compatible = "gpio-leds";
green_led: led_0 {
gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
label = "Green LED";
};
};
buttons {
compatible = "gpio-keys";
user_button: button_0 {
gpios = <&gpio0 11 GPIO_ACTIVE_LOW>;
label = "User Button";
};
};
};
// Application code (platform-independent)
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#define LED_NODE DT_ALIAS(led0)
#define BUTTON_NODE DT_ALIAS(sw0)
const struct device *led_dev = DEVICE_DT_GET(LED_NODE);
const struct device *button_dev = DEVICE_DT_GET(BUTTON_NODE);
void main(void) {
// Configure GPIO
gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
gpio_pin_configure_dt(&button, GPIO_INPUT);
while(1) {
if(gpio_pin_get_dt(&button)) {
gpio_pin_toggle_dt(&led);
}
k_msleep(100);
}
}
|
2. ESP-IDF Framework
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| // ESP-IDF component structure
├── components/
│ ├── sensor/
│ │ ├── include/sensor.h
│ │ ├── sensor.c
│ │ └── CMakeLists.txt
│ ├── communication/
│ │ ├── include/comm.h
│ │ ├── wifi_comm.c
│ │ ├── bluetooth_comm.c
│ │ └── CMakeLists.txt
│ └── data_processing/
│ ├── include/processor.h
│ ├── processor.c
│ └── CMakeLists.txt
// Component interface
// components/sensor/include/sensor.h
#ifndef SENSOR_H
#define SENSOR_H
typedef struct {
float temperature;
float humidity;
uint32_t timestamp;
} sensor_data_t;
esp_err_t sensor_init(void);
esp_err_t sensor_read(sensor_data_t* data);
esp_err_t sensor_calibrate(void);
#endif
|
3. ARM Mbed Framework
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
| // Mbed OS portable application
#include "mbed.h"
#include "platform/mbed_thread.h"
// Platform-independent hardware abstraction
DigitalOut led(LED1);
DigitalIn button(USER_BUTTON);
Serial pc(USBTX, USBRX);
// Network interface (automatically selects WiFi/Ethernet)
NetworkInterface *net = NetworkInterface::get_default_instance();
void sensor_thread() {
while(true) {
float temperature = read_temperature();
printf("Temperature: %.2f°C\n", temperature);
// Send to cloud
if(net->get_connection_status() == NSAPI_STATUS_GLOBAL_UP) {
send_to_cloud(temperature);
}
ThisThread::sleep_for(1000ms);
}
}
int main() {
// Connect to network
net->connect();
// Start sensor thread
Thread sensor_task(osPriorityNormal, 4096);
sensor_task.start(sensor_thread);
// Main loop
while(true) {
if(button == 0) {
led = !led;
ThisThread::sleep_for(200ms);
}
ThisThread::sleep_for(100ms);
}
}
|
Building Your Own Portable Firmware Framework
1. Define Hardware Abstraction Interfaces
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| // hal/include/hal_gpio.h
#ifndef HAL_GPIO_H
#define HAL_GPIO_H
#include <stdint.h>
typedef enum {
HAL_GPIO_MODE_INPUT,
HAL_GPIO_MODE_OUTPUT,
HAL_GPIO_MODE_ALTERNATE,
HAL_GPIO_MODE_ANALOG
} hal_gpio_mode_t;
typedef enum {
HAL_GPIO_PULL_NONE,
HAL_GPIO_PULL_UP,
HAL_GPIO_PULL_DOWN
} hal_gpio_pull_t;
typedef struct {
uint32_t pin;
hal_gpio_mode_t mode;
hal_gpio_pull_t pull;
uint32_t alternate_function;
} hal_gpio_config_t;
// HAL API
int hal_gpio_init(const hal_gpio_config_t* config);
int hal_gpio_write(uint32_t pin, uint8_t value);
uint8_t hal_gpio_read(uint32_t pin);
int hal_gpio_toggle(uint32_t pin);
#endif
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
| // hal/stm32/hal_gpio_stm32.c
#include "hal_gpio.h"
#include "stm32h7xx_hal.h"
static GPIO_TypeDef* get_gpio_port(uint32_t pin) {
switch(pin >> 4) {
case 0: return GPIOA;
case 1: return GPIOB;
case 2: return GPIOC;
// ... other ports
default: return NULL;
}
}
static uint16_t get_gpio_pin(uint32_t pin) {
return 1 << (pin & 0x0F);
}
int hal_gpio_init(const hal_gpio_config_t* config) {
GPIO_TypeDef* port = get_gpio_port(config->pin);
uint16_t pin = get_gpio_pin(config->pin);
if(!port) return -1;
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = pin;
switch(config->mode) {
case HAL_GPIO_MODE_INPUT:
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
break;
case HAL_GPIO_MODE_OUTPUT:
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
break;
// ... other modes
}
switch(config->pull) {
case HAL_GPIO_PULL_UP:
GPIO_InitStruct.Pull = GPIO_PULLUP;
break;
case HAL_GPIO_PULL_DOWN:
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
break;
default:
GPIO_InitStruct.Pull = GPIO_NOPULL;
break;
}
HAL_GPIO_Init(port, &GPIO_InitStruct);
return 0;
}
int hal_gpio_write(uint32_t pin, uint8_t value) {
GPIO_TypeDef* port = get_gpio_port(pin);
uint16_t gpio_pin = get_gpio_pin(pin);
HAL_GPIO_WritePin(port, gpio_pin, value ? GPIO_PIN_SET : GPIO_PIN_RESET);
return 0;
}
|
3. Build System Integration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
| # CMakeLists.txt for portable firmware
cmake_minimum_required(VERSION 3.15)
project(portable_firmware)
# Platform selection
set(TARGET_PLATFORM "stm32h7" CACHE STRING "Target platform")
set(AVAILABLE_PLATFORMS "stm32h7;esp32;nrf52;rp2040")
# Validate platform
if(NOT TARGET_PLATFORM IN_LIST AVAILABLE_PLATFORMS)
message(FATAL_ERROR "Unsupported platform: ${TARGET_PLATFORM}")
endif()
# Common source files
set(COMMON_SOURCES
src/main.c
src/application.c
src/system_init.c
)
# HAL abstraction
set(HAL_SOURCES
hal/common/hal_system.c
hal/common/hal_timer.c
)
# Platform-specific sources
if(TARGET_PLATFORM STREQUAL "stm32h7")
list(APPEND HAL_SOURCES
hal/stm32/hal_gpio_stm32.c
hal/stm32/hal_uart_stm32.c
hal/stm32/hal_spi_stm32.c
)
include(cmake/stm32h7.cmake)
elseif(TARGET_PLATFORM STREQUAL "esp32")
list(APPEND HAL_SOURCES
hal/esp32/hal_gpio_esp32.c
hal/esp32/hal_uart_esp32.c
hal/esp32/hal_spi_esp32.c
)
include(cmake/esp32.cmake)
endif()
# Create executable
add_executable(${PROJECT_NAME}
${COMMON_SOURCES}
${HAL_SOURCES}
)
# Platform-specific configuration
target_compile_definitions(${PROJECT_NAME} PRIVATE
TARGET_PLATFORM_${TARGET_PLATFORM}
)
target_include_directories(${PROJECT_NAME} PRIVATE
hal/include
hal/${TARGET_PLATFORM}
src/include
)
|
Advanced Portability Techniques
1. Runtime Hardware Detection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
| // Hardware capability detection
typedef struct {
uint8_t has_fpu;
uint8_t has_dsp;
uint8_t has_crypto;
uint32_t flash_size;
uint32_t ram_size;
uint32_t max_cpu_freq;
uint8_t num_cores;
} hardware_capabilities_t;
void detect_hardware_capabilities(hardware_capabilities_t* caps) {
// CPU feature detection
#ifdef __ARM_FP
caps->has_fpu = 1;
#endif
#ifdef ARM_MATH_DSP
caps->has_dsp = 1;
#endif
// Memory size detection
#if defined(STM32H7)
caps->flash_size = (*((uint16_t*)FLASHSIZE_BASE)) * 1024;
caps->ram_size = get_total_heap_size();
#elif defined(ESP32)
caps->flash_size = spi_flash_get_chip_size();
caps->ram_size = heap_caps_get_total_size(MALLOC_CAP_8BIT);
#endif
// CPU frequency
caps->max_cpu_freq = HAL_RCC_GetSysClockFreq();
}
// Adaptive algorithm selection based on capabilities
void select_optimal_algorithms(const hardware_capabilities_t* caps) {
if(caps->has_fpu) {
use_hardware_float_operations();
} else {
use_fixed_point_operations();
}
if(caps->has_dsp) {
enable_simd_optimizations();
}
if(caps->has_crypto) {
use_hardware_encryption();
} else {
use_software_encryption();
}
}
|
2. Configuration-Driven Development
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| // device_config.json
{
"hardware": {
"platform": "stm32h7",
"cpu_frequency": 400000000,
"enable_cache": true,
"enable_fpu": true
},
"peripherals": {
"uart": [
{
"instance": 1,
"baudrate": 115200,
"pins": {"tx": "PA9", "rx": "PA10"}
}
],
"spi": [
{
"instance": 1,
"frequency": 10000000,
"pins": {"sck": "PA5", "mosi": "PA7", "miso": "PA6"}
}
],
"gpio": [
{"pin": "PC13", "mode": "output", "name": "status_led"},
{"pin": "PA0", "mode": "input_pullup", "name": "user_button"}
]
},
"components": [
{
"name": "sensor_bme280",
"interface": "i2c1",
"address": "0x76"
},
{
"name": "wifi_esp32",
"interface": "uart2",
"ssid": "MyNetwork",
"password": "MyPassword"
}
]
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| // Configuration parser and system generator
typedef struct {
char name[32];
uint32_t pin;
gpio_mode_t mode;
} gpio_config_t;
typedef struct {
gpio_config_t* gpios;
size_t gpio_count;
// ... other peripheral configs
} system_config_t;
int parse_configuration(const char* json_config, system_config_t* config) {
cJSON* json = cJSON_Parse(json_config);
if(!json) return -1;
// Parse GPIO configuration
cJSON* gpio_array = cJSON_GetObjectItem(json, "gpio");
config->gpio_count = cJSON_GetArraySize(gpio_array);
config->gpios = malloc(sizeof(gpio_config_t) * config->gpio_count);
for(int i = 0; i < config->gpio_count; i++) {
cJSON* gpio_item = cJSON_GetArrayItem(gpio_array, i);
strncpy(config->gpios[i].name,
cJSON_GetStringValue(cJSON_GetObjectItem(gpio_item, "name")),
sizeof(config->gpios[i].name));
config->gpios[i].pin = parse_pin_name(
cJSON_GetStringValue(cJSON_GetObjectItem(gpio_item, "pin")));
const char* mode_str = cJSON_GetStringValue(cJSON_GetObjectItem(gpio_item, "mode"));
config->gpios[i].mode = parse_gpio_mode(mode_str);
}
cJSON_Delete(json);
return 0;
}
|
3. Middleware Integration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| // Middleware component interface
typedef struct middleware_component {
const char* name;
const char* version;
int (*init)(struct middleware_component* self, void* config);
int (*start)(struct middleware_component* self);
int (*stop)(struct middleware_component* self);
int (*process)(struct middleware_component* self, void* data);
struct middleware_component* next;
} middleware_component_t;
// MQTT middleware component
int mqtt_component_init(middleware_component_t* self, void* config) {
mqtt_config_t* mqtt_cfg = (mqtt_config_t*)config;
// Initialize MQTT client based on available network stack
#ifdef ESP_IDF
esp_mqtt_client_config_t esp_config = {
.uri = mqtt_cfg->broker_uri,
.username = mqtt_cfg->username,
.password = mqtt_cfg->password
};
self->private_data = esp_mqtt_client_init(&esp_config);
#elif defined(LWIP_VERSION)
// Initialize lwIP-based MQTT client
mqtt_client_t* client = mqtt_client_new();
mqtt_connect_client(client, mqtt_cfg->broker_ip, mqtt_cfg->port,
mqtt_connection_cb, mqtt_cfg);
self->private_data = client;
#endif
return 0;
}
// HTTP server middleware
int http_server_init(middleware_component_t* self, void* config) {
http_config_t* http_cfg = (http_config_t*)config;
#ifdef ESP_IDF
httpd_config_t esp_config = HTTPD_DEFAULT_CONFIG();
esp_config.server_port = http_cfg->port;
httpd_handle_t server = NULL;
httpd_start(&server, &esp_config);
self->private_data = server;
#elif defined(LWIP_VERSION)
// Initialize lwIP-based HTTP server
struct http_server* server = http_server_create(http_cfg->port);
self->private_data = server;
#endif
return 0;
}
|
Testing and Validation Strategies
1. Hardware-in-the-Loop Testing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
| // Test framework for portable firmware
typedef struct {
const char* test_name;
int (*setup)(void);
int (*test_function)(void);
int (*teardown)(void);
} test_case_t;
// Platform-independent test cases
int test_gpio_operations(void) {
// Test GPIO HAL across platforms
hal_gpio_config_t led_config = {
.pin = TEST_LED_PIN,
.mode = HAL_GPIO_MODE_OUTPUT,
.pull = HAL_GPIO_PULL_NONE
};
assert(hal_gpio_init(&led_config) == 0);
assert(hal_gpio_write(TEST_LED_PIN, 1) == 0);
assert(hal_gpio_read(TEST_LED_PIN) == 1);
assert(hal_gpio_write(TEST_LED_PIN, 0) == 0);
assert(hal_gpio_read(TEST_LED_PIN) == 0);
return 0;
}
int test_uart_communication(void) {
hal_uart_config_t uart_config = {
.baudrate = 115200,
.data_bits = 8,
.stop_bits = 1,
.parity = HAL_UART_PARITY_NONE
};
assert(hal_uart_init(TEST_UART_PORT, &uart_config) == 0);
const char* test_message = "Hello, World!";
assert(hal_uart_send(TEST_UART_PORT, test_message, strlen(test_message)) > 0);
char receive_buffer[64];
assert(hal_uart_receive(TEST_UART_PORT, receive_buffer, sizeof(receive_buffer)) > 0);
assert(strcmp(test_message, receive_buffer) == 0);
return 0;
}
// Test runner
test_case_t test_cases[] = {
{"GPIO Operations", NULL, test_gpio_operations, NULL},
{"UART Communication", NULL, test_uart_communication, NULL},
// ... more tests
};
void run_platform_tests(void) {
int passed = 0, failed = 0;
for(size_t i = 0; i < sizeof(test_cases)/sizeof(test_cases[0]); i++) {
printf("Running test: %s\n", test_cases[i].test_name);
if(test_cases[i].setup) {
test_cases[i].setup();
}
if(test_cases[i].test_function() == 0) {
printf("PASSED\n");
passed++;
} else {
printf("FAILED\n");
failed++;
}
if(test_cases[i].teardown) {
test_cases[i].teardown();
}
}
printf("Test Results: %d passed, %d failed\n", passed, failed);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| # .github/workflows/multi-platform-build.yml
name: Multi-Platform Build
on: [push, pull_request]
jobs:
build:
strategy:
matrix:
platform: [stm32h7, esp32, nrf52, rp2040]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup ARM GCC
if: matrix.platform != 'esp32'
uses: carlosperate/arm-none-eabi-gcc-action@v1
with:
release: '9-2020-q2'
- name: Setup ESP-IDF
if: matrix.platform == 'esp32'
uses: espressif/esp-idf-action@v1
with:
esp_idf_version: v4.4
- name: Build for $
run: |
mkdir build
cd build
cmake .. -DTARGET_PLATFORM=$
make -j$(nproc)
- name: Run Unit Tests
run: |
cd build
ctest --output-on-failure
- name: Archive Artifacts
uses: actions/upload-artifact@v2
with:
name: firmware-$
path: build/*.bin
|
1. Memory Management Strategies
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
| // Memory pool management for embedded systems
typedef struct memory_pool {
uint8_t* memory_start;
size_t total_size;
size_t block_size;
uint32_t* allocation_bitmap;
size_t num_blocks;
uint32_t allocated_blocks;
} memory_pool_t;
int memory_pool_init(memory_pool_t* pool, void* memory,
size_t total_size, size_t block_size) {
pool->memory_start = (uint8_t*)memory;
pool->total_size = total_size;
pool->block_size = block_size;
pool->num_blocks = total_size / block_size;
// Allocate bitmap (1 bit per block)
size_t bitmap_size = (pool->num_blocks + 31) / 32;
pool->allocation_bitmap = malloc(bitmap_size * sizeof(uint32_t));
if(!pool->allocation_bitmap) return -1;
// Clear bitmap
memset(pool->allocation_bitmap, 0, bitmap_size * sizeof(uint32_t));
pool->allocated_blocks = 0;
return 0;
}
void* memory_pool_alloc(memory_pool_t* pool) {
if(pool->allocated_blocks >= pool->num_blocks) {
return NULL; // Pool full
}
// Find free block
for(size_t i = 0; i < pool->num_blocks; i++) {
uint32_t word_index = i / 32;
uint32_t bit_index = i % 32;
if(!(pool->allocation_bitmap[word_index] & (1U << bit_index))) {
// Mark as allocated
pool->allocation_bitmap[word_index] |= (1U << bit_index);
pool->allocated_blocks++;
return pool->memory_start + (i * pool->block_size);
}
}
return NULL; // Should not reach here
}
int memory_pool_free(memory_pool_t* pool, void* ptr) {
if(!ptr || ptr < (void*)pool->memory_start ||
ptr >= (void*)(pool->memory_start + pool->total_size)) {
return -1; // Invalid pointer
}
size_t block_index = ((uint8_t*)ptr - pool->memory_start) / pool->block_size;
uint32_t word_index = block_index / 32;
uint32_t bit_index = block_index % 32;
if(!(pool->allocation_bitmap[word_index] & (1U << bit_index))) {
return -1; // Block not allocated
}
// Mark as free
pool->allocation_bitmap[word_index] &= ~(1U << bit_index);
pool->allocated_blocks--;
return 0;
}
|
2. Power Management Integration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
| // Power management abstraction
typedef enum {
POWER_MODE_RUN,
POWER_MODE_SLEEP,
POWER_MODE_DEEP_SLEEP,
POWER_MODE_STANDBY
} power_mode_t;
typedef struct {
power_mode_t (*get_current_mode)(void);
int (*enter_mode)(power_mode_t mode);
int (*configure_wakeup_source)(uint32_t sources);
uint32_t (*get_wakeup_reason)(void);
} power_manager_t;
// Platform-specific implementations
#ifdef STM32H7
int stm32_enter_power_mode(power_mode_t mode) {
switch(mode) {
case POWER_MODE_SLEEP:
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
break;
case POWER_MODE_DEEP_SLEEP:
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
break;
case POWER_MODE_STANDBY:
HAL_PWR_EnterSTANDBYMode();
break;
default:
return -1;
}
return 0;
}
#endif
#ifdef ESP32
int esp32_enter_power_mode(power_mode_t mode) {
switch(mode) {
case POWER_MODE_SLEEP:
esp_light_sleep_start();
break;
case POWER_MODE_DEEP_SLEEP:
esp_deep_sleep_start();
break;
default:
return -1;
}
return 0;
}
#endif
// Application power management
void application_power_management(void) {
// Determine optimal power mode based on system state
if(is_network_activity_required()) {
power_manager.enter_mode(POWER_MODE_RUN);
} else if(sensor_reading_pending()) {
power_manager.enter_mode(POWER_MODE_SLEEP);
} else {
power_manager.configure_wakeup_source(WAKEUP_TIMER | WAKEUP_GPIO);
power_manager.enter_mode(POWER_MODE_DEEP_SLEEP);
}
}
|
Future Trends and Considerations
1. WebAssembly for Embedded Systems
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| // WASM runtime integration for portable application logic
#include "wasm3.h"
typedef struct {
IM3Environment env;
IM3Runtime runtime;
IM3Module module;
IM3Function app_init;
IM3Function app_process;
} wasm_app_context_t;
int load_wasm_application(wasm_app_context_t* ctx, const uint8_t* wasm_binary, size_t size) {
ctx->env = m3_NewEnvironment();
ctx->runtime = m3_NewRuntime(ctx->env, 1024, NULL);
M3Result result = m3_ParseModule(ctx->env, &ctx->module, wasm_binary, size);
if(result) return -1;
result = m3_LoadModule(ctx->runtime, ctx->module);
if(result) return -1;
// Link native functions
m3_LinkRawFunction(ctx->module, "env", "hal_gpio_write", "i(ii)", &wasm_hal_gpio_write);
m3_LinkRawFunction(ctx->module, "env", "hal_gpio_read", "i(i)", &wasm_hal_gpio_read);
// Get application functions
m3_FindFunction(&ctx->app_init, ctx->runtime, "app_init");
m3_FindFunction(&ctx->app_process, ctx->runtime, "app_process");
return 0;
}
// WASM-native function bindings
const void* wasm_hal_gpio_write(IM3Runtime runtime, IM3ImportContext ctx, uint64_t* args) {
uint32_t pin = args[0];
uint32_t value = args[1];
int result = hal_gpio_write(pin, value);
m3_Return(result);
}
|
2. AI/ML Integration
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| // AI model abstraction for edge inference
typedef struct {
void* model_data;
size_t model_size;
int (*init)(void* model_data, size_t size);
int (*inference)(float* input, float* output);
int (*cleanup)(void);
} ai_model_t;
// TensorFlow Lite Micro integration
#ifdef USE_TFLITE_MICRO
#include "tensorflow/lite/micro/micro_interpreter.h"
int tflite_model_init(void* model_data, size_t size) {
// Initialize TFLite Micro interpreter
static tflite::MicroInterpreter* interpreter;
static uint8_t tensor_arena[TENSOR_ARENA_SIZE];
const tflite::Model* model = tflite::GetModel(model_data);
static tflite::MicroMutableOpResolver<10> resolver;
// Add required operations
resolver.AddFullyConnected();
resolver.AddRelu();
resolver.AddSoftmax();
static tflite::MicroInterpreter static_interpreter(
model, resolver, tensor_arena, TENSOR_ARENA_SIZE, nullptr);
interpreter = &static_interpreter;
return interpreter->AllocateTensors() == kTfLiteOk ? 0 : -1;
}
#endif
// Edge Impulse integration
#ifdef USE_EDGE_IMPULSE
#include "edge-impulse-sdk/classifier/ei_run_classifier.h"
int edge_impulse_inference(float* input, float* output) {
signal_t signal;
signal.total_length = EI_CLASSIFIER_DSP_INPUT_FRAME_SIZE;
signal.get_data = &get_signal_data;
ei_impulse_result_t result;
EI_IMPULSE_ERROR res = run_classifier(&signal, &result, false);
if(res == EI_IMPULSE_OK) {
memcpy(output, result.classification, sizeof(result.classification));
return 0;
}
return -1;
}
#endif
|
Real-World HAL Implementation Example
A excellent reference for portable firmware architecture is the Apress book “Reusable Firmware Development” by Jacob Beningo. The accompanying GitHub repository provides concrete examples of HAL implementation for embedded systems.
HAL Structure Overview
The repository demonstrates a well-structured HAL with these key components:
1
2
3
4
5
6
7
8
9
10
11
12
| hal/
├── drivers/ # Hardware abstraction implementations
│ ├── dio.c/dio.h # Digital I/O abstraction
│ ├── uart.c/uart.h # UART communication abstraction
│ ├── spi.c/spi.h # SPI communication abstraction
│ ├── tmr.c/tmr.h # Timer abstraction
│ ├── flash.c/flash.h # Flash memory abstraction
│ └── wdt.c/wdt.h # Watchdog timer abstraction
└── config/ # Platform-specific configuration
├── dio_cfg.c/dio_cfg.h
├── uart_cfg.c/uart_cfg.h
└── spi_cfg.c/spi_cfg.h
|
Digital I/O HAL Example
The DIO (Digital Input/Output) module demonstrates clean abstraction:
Interface Definition (dio.h):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| // Hardware-agnostic API
typedef enum {
DIO_LOW, // Logic low state
DIO_HIGH, // Logic high state
DIO_PIN_STATE_MAX
} DioPinState_t;
typedef enum {
PORT1_0, PORT1_1, PORT1_2, // Pin enumeration
PORT1_3, PORT1_4, PORT1_5,
// ... more pins
DIO_MAX_PIN_NUMBER
} DioChannel_t;
// Public API functions
void Dio_Init(const DioConfig_t * const Config);
DioPinState_t Dio_ChannelRead(DioChannel_t Channel);
void Dio_ChannelWrite(DioChannel_t Channel, DioPinState_t State);
void Dio_ChannelToggle(DioChannel_t Channel);
|
Platform-Specific Implementation (dio.c):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| // Hardware register access tables
static TYPE volatile * const DataIn[NUM_PORTS] = {
(TYPE*)®ISTER1, (TYPE*)®ISTER2
};
static TYPE volatile * const DataOut[NUM_PORTS] = {
(TYPE*)®ISTER1, (TYPE*)®ISTER2
};
// Implementation uses register pointers for hardware access
DioPinState_t Dio_ChannelRead(DioChannel_t Channel) {
DioPinState_t PortState = (DioPinState_t)*DataIn[Channel/NUM_PINS_PER_PORT];
DioPinState_t PinMask = (DioPinState_t)(1UL<<(Channel%NUM_PINS_PER_PORT));
return ((PortState & PinMask) ? DIO_HIGH : DIO_LOW);
}
void Dio_ChannelWrite(DioChannel_t Channel, DioPinState_t State) {
if (State == DIO_HIGH) {
*DataOut[Channel/NUM_PINS_PER_PORT] |= (1UL<<(Channel%NUM_PINS_PER_PORT));
} else {
*DataOut[Channel/NUM_PINS_PER_PORT] &= ~(1UL<<(Channel%NUM_PINS_PER_PORT));
}
}
|
UART Communication HAL
Configuration-Driven Approach:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // uart_cfg.h - Configuration structure
typedef struct {
UartChannel_t UartChannel; // Hardware channel
UartEnable_t UartEnable; // Enable/disable
UartMode_t UartMode; // UART mode
UartClkSrc_t UartClockSource; // Clock source
uint32_t UartBaudRate; // Baud rate
UartLoopback_t UartLoopback; // Loopback mode
UartBitOrder_t UartBitDirection;// Bit ordering
UartComm_t UartDataBits; // Data bits (8/9)
uint8_t UartStopBits; // Stop bits
UartParity_t UartParity; // Parity setting
uint8_t UartDelimiter; // Frame delimiter
UartEnable_t UartInterruptEnable; // Interrupt enable
} UartConfig_t;
// uart_cfg.c - Configuration table
const UartConfig_t UartConfig[] = {
{UART_0, ENABLED, UART, SUBMCLK, 115200, DISABLED,
UART_LSB_FIRST, BITS_EIGHT, 1, DISABLED, 1, DISABLED}
};
|
Hardware Abstraction Implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| // Register pointer arrays for different UART channels
static TYPE volatile * const uartctl1[NUM_UART_CHANNELS] = {
(TYPE*)®ISTER1, (TYPE*)®ISTER2
};
static TYPE volatile * const uarttx[NUM_UART_CHANNELS] = {
(TYPE*)®ISTER1, (TYPE*)®ISTER2
};
static TYPE volatile * const uartrx[NUM_UART_CHANNELS] = {
(TYPE*)®ISTER1, (TYPE*)®ISTER2
};
// Baud rate calculation table
const UartBaud_t UartBaudTable[] = {
// Clock_Freq Baud_Rate UCBRx UCBRSx UCBRFx Oversample
{1000000, 9600, 6, 0, 8, ENABLED},
{4000000, 115200, 2, 3, 2, ENABLED},
{16000000, 115200, 8, 0, 11, ENABLED}
};
|
SPI Communication HAL
Transfer-Based API:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| // spi.h - Transfer configuration
typedef struct {
SpiChannel_t Channel; // SPI channel
DioChannel_t ChipSelect; // Chip select pin
uint16_t *TxBuffer; // Transmit buffer
uint16_t *RxBuffer; // Receive buffer
uint16_t Length; // Transfer length
SpiPolarity_t Polarity; // Clock polarity
SpiPhase_t Phase; // Clock phase
SpiBitOrder_t Direction; // Bit order
} SpiTransfer_t;
// API functions
void Spi_Init(const SpiConfig_t *Config);
void Spi_Transfer(const SpiTransfer_t *Config);
void Spi_ChipSelectSet(DioChannel_t Channel);
void Spi_ChipSelectClear(DioChannel_t Channel);
|
Key Design Principles Demonstrated
1. Configuration Tables:
- Centralized configuration in
_cfg.c files - Compile-time configuration for different targets
- Clear separation of configuration and implementation
2. Register Pointer Arrays:
1
2
3
4
| // Scalable approach for multi-channel peripherals
static TYPE volatile * const peripheral_reg[NUM_CHANNELS] = {
(TYPE*)&CHANNEL0_REG, (TYPE*)&CHANNEL1_REG
};
|
3. Enumerated Hardware Resources:
1
2
3
4
5
6
7
| // Platform-specific pin definitions
typedef enum {
PORT1_0, // Maps to actual hardware pin
PORT1_1, // Platform-specific mapping
// ...
DIO_MAX_PIN_NUMBER
} DioChannel_t;
|
4. Function Callback Support:
1
2
3
| // Extensible callback mechanism
typedef void (*UartCallback_t)(UartChannel_t Channel, uint8_t Data);
void Uart_CallbackRegister(UartCallback_t Function);
|
5. Direct Register Access:
1
2
3
| // Low-level access for advanced users
void Uart_RegisterWrite(uint32_t Address, uint32_t Value);
uint32_t Uart_RegisterRead(uint32_t Address);
|
Application Usage Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| #include "dio.h"
#include "uart.h"
int main(void) {
// Initialize HAL components
const DioConfig_t *DioConfig = Dio_ConfigGet();
const UartConfig_t *UartConfig = Uart_ConfigGet();
Dio_Init(DioConfig);
Uart_Init(UartConfig);
// Application logic using HAL
while(1) {
if(Dio_ChannelRead(USER_BUTTON) == DIO_LOW) {
Dio_ChannelToggle(STATUS_LED);
Uart_CharPut(UART_0, 'B'); // Send button press indicator
}
if(Uart_IsDataPresent(UART_0)) {
char received = Uart_CharGet(UART_0);
// Process received data
}
}
return 0;
}
|
This example demonstrates how proper HAL design enables:
- Hardware Independence: Same application code works across platforms
- Configuration Flexibility: Easy customization via configuration tables
- Scalability: Support for multiple channels/instances
- Maintainability: Clean separation of concerns
- Reusability: Components can be reused across projects