A Flexible Device Driver Architecture for Embedded Development

In embedded system development, we often need to manage various hardware peripherals, communication protocols, and sensor devices. The Factory Pattern provides a unified object creation interface, allowing the system to flexibly instantiate different types of objects without exposing the specific creation logic, greatly enhancing the maintainability and scalability of the code.

<strong>Core Concept of the Factory Pattern</strong>
The Factory Pattern is a creational design pattern that defines an interface for creating objects but lets subclasses decide which class to instantiate. The factory method delays the instantiation of a class to its subclasses.

<strong>Example 1: Peripheral Driver Creation</strong>
In embedded systems, there may be multiple instances of the same type of peripheral (e.g., UART, I2C, SPI). The Factory Pattern can uniformly manage the creation and initialization of these drivers.

// Peripheral type definition
typedef enum {
    PERIPH_UART,
    PERIPH_I2C,
    PERIPH_SPI,
    PERIPH_GPIO,
    PERIPH_ADC
} peripheral_type_t;

<strong>Example 2: Communication Protocols</strong>
Different application scenarios may require different communication protocols. The Factory Pattern can dynamically select protocol implementations based on configuration.

// Communication protocol types
typedef enum {
    PROTOCOL_MQTT,
    PROTOCOL_COAP,
    PROTOCOL_HTTP,
    PROTOCOL_WEBSOCKET,
    PROTOCOL_CUSTOM
} protocol_type_t;

<strong>Example 3: Sensor Instantiation</strong>
Faced with various sensor models and manufacturers, the Factory Pattern provides a unified sensor creation interface.

// Sensor types
typedef enum {
    SENSOR_TEMPERATURE,
    SENSOR_HUMIDITY,
    SENSOR_PRESSURE,
    SENSOR_ACCELEROMETER,
    SENSOR_GYROSCOPE,
    SENSOR_MAGNETOMETER
} sensor_category_t;

<strong>Zephyr RTOS Device Driver Model: Industrial Practice of the Factory Pattern</strong>
Zephyr RTOS is an open-source real-time operating system designed for resource-constrained devices, and its device driver model is a prime example of the Factory Pattern in the embedded field.

// Zephyr device driver core structure
struct device {
    const char *name;
    const void *config;
    const void *api;
    void *data;
    bool initialized;
};

// Device configuration structure
struct uart_config {
    uint32_t baudrate;
    uint8_t data_bits;
    uint8_t stop_bits;
    uint8_t parity;
    uint8_t flow_control;
};

// Device API interface
struct uart_driver_api {
    int (*poll_in)(const struct device *dev, unsigned char *p_char);
    int (*poll_out)(const struct device *dev, unsigned char out_char);
    int (*configure)(const struct device *dev, const struct uart_config *config);
    int (*config_get)(const struct device *dev, struct uart_config *config);
};

<strong>Complete Device Factory System Implementation</strong>
Below we demonstrate the application of the Factory Pattern in Zephyr RTOS through a complete device management system.

#include <zephyr.h>
#include <device.h>
#include <drivers/uart.h>
#include <drivers/i2c.h>
#include <drivers/spi.h>
#include <drivers/sensor.h>

// Device type definition
typedef enum {
    DEVICE_TYPE_UART,
    DEVICE_TYPE_I2C,
    DEVICE_TYPE_SPI,
    DEVICE_TYPE_SENSOR,
    DEVICE_TYPE_GPIO
} device_type_t;

// Device configuration union
typedef union {
    struct uart_config uart_cfg;
    struct i2c_config i2c_cfg;
    struct spi_config spi_cfg;
    struct sensor_config sensor_cfg;
} device_config_t;

// Device descriptor
typedef struct {
    const char *name;
    device_type_t type;
    device_config_t config;
    bool enabled;
} device_descriptor_t;

// Global device table
static struct device *device_table[MAX_DEVICES];
static device_descriptor_t device_descriptors[MAX_DEVICES];
static uint8_t device_count = 0;

// Device factory core function
struct device *device_factory_create(const char *name, device_type_t type, 
                                   const device_config_t *config) {
    struct device *dev = NULL;
    
    switch (type) {
        case DEVICE_TYPE_UART:
            dev = device_get_binding(name);
            if (dev != NULL && config != NULL) {
                const struct uart_driver_api *api = 
                    (const struct uart_driver_api *)dev->api;
                if (api->configure != NULL) {
                    api->configure(dev, &config->uart_cfg);
                }
            }
            break;
            
        case DEVICE_TYPE_I2C:
            dev = device_get_binding(name);
            if (dev != NULL && config != NULL) {
                // I2C configuration handling
                i2c_configure(dev, config->i2c_cfg.speed);
            }
            break;
            
        case DEVICE_TYPE_SPI:
            dev = device_get_binding(name);
            if (dev != NULL && config != NULL) {
                const struct spi_driver_api *api = 
                    (const struct spi_driver_api *)dev->api;
                if (api->configure != NULL) {
                    api->configure(dev, &config->spi_cfg);
                }
            }
            break;
            
        case DEVICE_TYPE_SENSOR:
            dev = device_get_binding(name);
            if (dev != NULL && config != NULL) {
                sensor_attr_set(dev, SENSOR_ATTR_CONFIG, &config->sensor_cfg);
            }
            break;
            
        default:
            printk("Unsupported device type: %d\n", type);
            return NULL;
    }
    
    return dev;
}

// Device registration function
int device_factory_register(const char *name, device_type_t type, 
                           const device_config_t *config) {
    if (device_count >= MAX_DEVICES) {
        return -ENOMEM;
    }
    
    // Create device
    struct device *dev = device_factory_create(name, type, config);
    if (dev == NULL) {
        printk("Failed to create device: %s\n", name);
        return -EIO;
    }
    
    // Save to device table
    device_table[device_count] = dev;
    
    // Save device descriptor
    device_descriptors[device_count].name = name;
    device_descriptors[device_count].type = type;
    if (config != NULL) {
        device_descriptors[device_count].config = *config;
    }
    device_descriptors[device_count].enabled = true;
    
    device_count++;
    
    printk("Registered device: %s (type: %d)\n", name, type);
    return 0;
}

// Device lookup function
struct device *device_factory_get(const char *name) {
    for (int i = 0; i < device_count; i++) {
        if (strcmp(device_descriptors[i].name, name) == 0 && 
            device_descriptors[i].enabled) {
            return device_table[i];
        }
    }
    
    // Fallback to Zephyr native device lookup
    return device_get_binding(name);
}

// Get device list by type
int device_factory_get_by_type(device_type_t type, struct device **devices, 
                              uint8_t max_devices) {
    uint8_t found = 0;
    
    for (int i = 0; i < device_count && found < max_devices; i++) {
        if (device_descriptors[i].type == type && 
            device_descriptors[i].enabled) {
            devices[found++] = device_table[i];
        }
    }
    
    return found;
}

// Device configuration update
int device_factory_reconfigure(const char *name, const device_config_t *new_config) {
    for (int i = 0; i < device_count; i++) {
        if (strcmp(device_descriptors[i].name, name) == 0) {
            struct device *dev = device_table[i];
            device_type_t type = device_descriptors[i].type;
            
            // Update configuration
            switch (type) {
                case DEVICE_TYPE_UART: {
                    const struct uart_driver_api *api = 
                        (const struct uart_driver_api *)dev->api;
                    if (api->configure != NULL) {
                        return api->configure(dev, &new_config->uart_cfg);
                    }
                    break;
                }
                case DEVICE_TYPE_I2C:
                    return i2c_configure(dev, new_config->i2c_cfg.speed);
                    
                case DEVICE_TYPE_SPI: {
                    const struct spi_driver_api *api = 
                        (const struct spi_driver_api *)dev->api;
                    if (api->configure != NULL) {
                        return api->configure(dev, &new_config->spi_cfg);
                    }
                    break;
                }
                default:
                    return -ENOTSUP;
            }
            
            // Update saved configuration
            device_descriptors[i].config = *new_config;
            return 0;
        }
    }
    
    return -ENODEV;
}

// Sensor factory implementation
To address the specific needs of sensor devices, we can implement a dedicated sensor factory:
// Sensor driver interface
typedef struct {
    int (*init)(const struct device *dev);
    int (*read)(const struct device *dev, sensor_data_t *data);
    int (*configure)(const struct device *dev, const sensor_config_t *config);
    int (*set_callback)(const struct device *dev, sensor_trigger_handler_t handler);
} sensor_driver_api_t;

// Sensor data format
typedef struct {
    float temperature;
    float humidity;
    float pressure;
    int16_t acceleration[3];
    int16_t gyro[3];
    uint32_t timestamp;
} sensor_data_t;

// Sensor factory
typedef struct {
    const char *sensor_model;
    sensor_type_t type;
    const struct device *(*create)(const char *name, const sensor_config_t *config);
    bool (*validate)(const sensor_data_t *data);
} sensor_factory_t;

// Specific sensor creation function
static const struct device *create_bme280(const char *name, const sensor_config_t *config) {
    const struct device *dev = device_get_binding(name);
    if (dev == NULL) {
        return NULL;
    }
    
    // BME280 specific initialization
    bme280_config_t bme_cfg = {
        .mode = BME280_NORMAL_MODE,
        .filter = BME280_FILTER_OFF,
        .standby_time = BME280_STANDBY_MS_0_5
    };
    
    bme280_set_config(dev, &bme_cfg);
    return dev;
}

static const struct device *create_mpu6050(const char *name, const sensor_config_t *config) {
    const struct device *dev = device_get_binding(name);
    if (dev == NULL) {
        return NULL;
    }
    
    // MPU6050 specific initialization
    mpu6050_config_t mpu_cfg = {
        .accel_range = MPU6050_ACCEL_RANGE_4G,
        .gyro_range = MPU6050_GYRO_RANGE_500DPS,
        .sample_rate = MPU6050_SAMPLE_RATE_100HZ
    };
    
    mpu6050_set_config(dev, &mpu_cfg);
    return dev;
}

// Sensor data validation
static bool validate_bme280(const sensor_data_t *data) {
    return (data->temperature >= -40.0f && data->temperature <= 85.0f) &&
           (data->humidity >= 0.0f && data->humidity <= 100.0f) &&
           (data->pressure >= 30000.0f && data->pressure <= 110000.0f);
}

static bool validate_mpu6050(const sensor_data_t *data) {
    // Validate accelerometer and gyroscope data within reasonable ranges
    for (int i = 0; i < 3; i++) {
        if (abs(data->acceleration[i]) > 16000 || 
            abs(data->gyro[i]) > 2000) {
            return false;
        }
    }
    return true;
}

// Sensor factory registry
static const sensor_factory_t sensor_factories[] = {
    {
        .sensor_model = "BME280",
        .type = SENSOR_ENVIRONMENTAL,
        .create = create_bme280,
        .validate = validate_bme280
    },
    {
        .sensor_model = "MPU6050", 
        .type = SENSOR_MOTION,
        .create = create_mpu6050,
        .validate = validate_mpu6050
    },
    {
        .sensor_model = "LSM6DS3",
        .type = SENSOR_MOTION,
        .create = create_lsm6ds3,
        .validate = validate_lsm6ds3
    }
};

// Sensor creation function
const struct device *sensor_factory_create(const char *model, const char *name, 
                                          const sensor_config_t *config) {
    for (int i = 0; i < ARRAY_SIZE(sensor_factories); i++) {
        if (strcmp(sensor_factories[i].sensor_model, model) == 0) {
            return sensor_factories[i].create(name, config);
        }
    }
    
    printk("Unknown sensor model: %s\n", model);
    return NULL;
}

// Sensor data validation
bool sensor_factory_validate_data(const char *model, const sensor_data_t *data) {
    for (int i = 0; i < ARRAY_SIZE(sensor_factories); i++) {
        if (strcmp(sensor_factories[i].sensor_model, model) == 0) {
            return sensor_factories[i].validate(data);
        }
    }
    
    // Unknown sensor, use generic validation
    return (data->timestamp != 0);
}

// Protocol factory implementation
To address different communication needs, we can implement a protocol factory:
// Protocol interface
typedef struct {
    int (*connect)(void *context);
    int (*send)(void *context, const uint8_t *data, size_t len);
    int (*receive)(void *context, uint8_t *buffer, size_t max_len);
    int (*disconnect)(void *context);
    bool (*is_connected)(void *context);
} protocol_interface_t;

// MQTT protocol implementation
typedef struct {
    protocol_interface_t base;
    struct mqtt_client client;
    const char *broker_url;
    uint16_t port;
    bool connected;
} mqtt_protocol_t;

static int mqtt_connect(void *context) {
    mqtt_protocol_t *mqtt = (mqtt_protocol_t *)context;
    
    struct mqtt_connect_config config = {
        .client_id = "embedded_device",
        .broker_url = mqtt->broker_url,
        .port = mqtt->port
    };
    
    int ret = mqtt_client_connect(&mqtt->client, &config);
    mqtt->connected = (ret == 0);
    
    return ret;
}

static int mqtt_send(void *context, const uint8_t *data, size_t len) {
    mqtt_protocol_t *mqtt = (mqtt_protocol_t *)context;
    
    if (!mqtt->connected) {
        return -ENOTCONN;
    }
    
    return mqtt_client_publish(&mqtt->client, "sensors/data", data, len, 
                              MQTT_QOS_1_AT_LEAST_ONCE);
}

// CoAP protocol implementation
typedef struct {
    protocol_interface_t base;
    struct coap_client client;
    const char *server_addr;
    uint16_t port;
} coap_protocol_t;

static int coap_connect(void *context) {
    coap_protocol_t *coap = (coap_protocol_t *)context;
    return coap_client_init(&coap->client, coap->server_addr, coap->port);
}

static int coap_send(void *context, const uint8_t *data, size_t len) {
    coap_protocol_t *coap = (coap_protocol_t *)context;
    return coap_client_post(&coap->client, "sensors", data, len);
}

// Protocol factory
protocol_interface_t *protocol_factory_create(protocol_type_t type, 
                                             const char *server, 
                                             uint16_t port) {
    switch (type) {
        case PROTOCOL_MQTT: {
            mqtt_protocol_t *mqtt = k_malloc(sizeof(mqtt_protocol_t));
            if (mqtt == NULL) return NULL;
            
            mqtt->base.connect = mqtt_connect;
            mqtt->base.send = mqtt_send;
            mqtt->base.receive = mqtt_receive;
            mqtt->base.disconnect = mqtt_disconnect;
            mqtt->base.is_connected = mqtt_is_connected;
            mqtt->broker_url = server;
            mqtt->port = port;
            mqtt->connected = false;
            
            return &mqtt->base;
        }
            
        case PROTOCOL_COAP: {
            coap_protocol_t *coap = k_malloc(sizeof(coap_protocol_t));
            if (coap == NULL) return NULL;
            
            coap->base.connect = coap_connect;
            coap->base.send = coap_send;
            coap->base.receive = coap_receive;
            coap->base.disconnect = coap_disconnect;
            coap->base.is_connected = coap_is_connected;
            coap->server_addr = server;
            coap->port = port;
            
            return &coap->base;
        }
            
        default:
            return NULL;
    }
}

// Application layer usage example
// System initialization function
void system_init(void) {
    // Initialize UART device
    device_config_t uart_config = {
        .uart_cfg = {
            .baudrate = 115200,
            .data_bits = UART_CFG_DATA_BITS_8,
            .stop_bits = UART_CFG_STOP_BITS_1,
            .parity = UART_CFG_PARITY_NONE,
            .flow_ctrl = UART_CFG_FLOW_CTRL_NONE
        }
    };
    
    device_factory_register("UART_0", DEVICE_TYPE_UART, &uart_config);
    
    // Initialize I2C device
    device_config_t i2c_config = {
        .i2c_cfg = {
            .speed = I2C_SPEED_STANDARD
        }
    };
    
    device_factory_register("I2C_0", DEVICE_TYPE_I2C, &i2c_config);
    
    // Initialize sensor
    sensor_config_t sensor_config = {
        .sample_rate = 10, // 10Hz
        .range = SENSOR_RANGE_NORMAL
    };
    
    device_config_t bme_config = {
        .sensor_cfg = sensor_config
    };
    
    device_factory_register("BME280", DEVICE_TYPE_SENSOR, &bme_config);
}

// Application task example
void application_task(void) {
    // Get UART device
    struct device *uart_dev = device_factory_get("UART_0");
    if (uart_dev == NULL) {
        printk("Failed to get UART device\n");
        return;
    }
    
    // Get sensor device
    struct device *sensor_dev = device_factory_get("BME280");
    if (sensor_dev == NULL) {
        printk("Failed to get sensor device\n");
        return;
    }
    
    // Create communication protocol
    protocol_interface_t *protocol = protocol_factory_create(PROTOCOL_MQTT, 
                                                           "mqtt.broker.com", 1883);
    if (protocol == NULL) {
        printk("Failed to create protocol\n");
        return;
    }
    
    // Connect protocol
    if (protocol->connect(protocol) != 0) {
        printk("Failed to connect protocol\n");
        return;
    }
    
    while (1) {
        // Read sensor data
        sensor_data_t data;
        if (sensor_sample_fetch(sensor_dev) == 0) {
            sensor_channel_get(sensor_dev, SENSOR_CHAN_ALL, &data);
            
            // Validate data
            if (sensor_factory_validate_data("BME280", &data)) {
                // Send data via protocol
                protocol->send(protocol, (uint8_t*)&data, sizeof(data));
                
                // Output debug information via UART
                uart_poll_out(uart_dev, 'D'); // Data valid
            } else {
                // Invalid data
                uart_poll_out(uart_dev, 'E'); // Error
            }
        }
        
        k_sleep(K_MSEC(1000));
    }
}

// Dynamic device configuration update
void update_device_configuration(void) {
    // Update UART configuration at runtime (e.g., switch to a higher baud rate)
    device_config_t new_uart_config = {
        .uart_cfg = {
            .baudrate = 921600,  // Increase baud rate
            .data_bits = UART_CFG_DATA_BITS_8,
            .stop_bits = UART_CFG_STOP_BITS_1,
            .parity = UART_CFG_PARITY_NONE,
            .flow_ctrl = UART_CFG_FLOW_CTRL_NONE
        }
    };
    
    device_factory_reconfigure("UART_0", &new_uart_config);
    printk("UART configuration updated to 921600 baud\n");
}

<strong>Advantages of the Factory Pattern in Embedded Systems</strong>
1. Hardware Abstraction and Portability
Traditional approach:
// Direct calls related to hardware
void init_uart(void) {
    #ifdef STM32F4
        stm32_uart_init(USART1, 115200);
    #elif defined(ESP32)
        esp_uart_init(UART_NUM_0, 115200);
    #elif defined(NRF52)
        nrf_uart_init(UARTE0, 115200);
    #endif
}

Factory Pattern approach:
// Hardware-independent unified interface
void init_uart(void) {
    device_config_t config = {.uart_cfg.baudrate = 115200};
    device_factory_register("UART_0", DEVICE_TYPE_UART, &config);
}

2. Dynamic Device Management
// Runtime device discovery and configuration
void dynamic_device_discovery(void) {
    // Scan I2C bus to discover devices
    for (uint8_t addr = 0x08; addr <= 0x77; addr++) {
        if (i2c_probe(addr)) {
            // Identify device type
            const char *model = identify_i2c_device(addr);
            if (model != NULL) {
                char dev_name[16];
                snprintf(dev_name, sizeof(dev_name), "I2C_0x%02X", addr);
                
                // Dynamically register device
                device_factory_register(dev_name, DEVICE_TYPE_SENSOR, NULL);
                printk("Discovered device: %s at address 0x%02X\n", model, addr);
            }
        }
    }
}

3. Configuration-driven Device Creation
// Create devices from configuration file
int create_devices_from_config(const char *config_file) {
    // Read JSON configuration file
    cJSON *config = read_json_file(config_file);
    if (config == NULL) {
        return -EINVAL;
    }
    
    cJSON *devices = cJSON_GetObjectItem(config, "devices");
    cJSON *device;
    
    cJSON_ArrayForEach(device, devices) {
        const char *name = cJSON_GetObjectItem(device, "name")->valuestring;
        const char *type_str = cJSON_GetObjectItem(device, "type")->valuestring;
        device_type_t type = string_to_device_type(type_str);
        
        device_config_t dev_config;
        parse_device_config(device, &dev_config);
        
        device_factory_register(name, type, &dev_config);
    }
    
    cJSON_Delete(config);
    return 0;
}

<strong>Conclusion</strong>
The Factory Pattern, as demonstrated through the Zephyr RTOS device model, showcases powerful device management capabilities in embedded systems. It not only addresses the coupling issues of device creation and usage but also provides unified device management, flexible configuration mechanisms, and excellent scalability. Whether for peripheral drivers, communication protocols, or sensor devices, the Factory Pattern offers a unified creation and management interface, significantly reducing system complexity.

Leave a Comment