Skip to content

Latest commit

 

History

History
151 lines (112 loc) · 5.82 KB

File metadata and controls

151 lines (112 loc) · 5.82 KB

Actuator Driver - Architecture Rationale

Overview

The actuator_driver.c file exists in the DRIVER layer to provide abstraction for logical actuators. This document explains why this design choice maintains proper 4-layer architecture principles.

The Question

"Why does actuator_driver.c exist in DRIVER layer when GPIO is an MCU peripheral?"

This is a valid architectural question. At first glance, GPIO operations seem like they should belong entirely in the MCU-PERIPHERALS layer since GPIO is a microcontroller peripheral.

Three Possible Solutions

Option 1: Remove actuator_driver.c (Move logic to CONTROL)

  • Pros: Simpler, fewer files
  • Cons: CONTROL layer directly manages hardware mapping (PORT_A/PIN_0), mixing concerns
  • Verdict: Violates separation - CONTROL shouldn't know physical hardware details

Option 2: Move actuator_driver.c to MCU-PERIPHERALS

  • Pros: GPIO is indeed an MCU peripheral
  • Cons: MCU-PERIPHERALS would contain application-specific logic (LED_STATUS, FAN, HEATER)
  • Verdict: Violates base layer principle - MCU-PERIPHERALS should be generic and reusable

Option 3: Keep as actuator_driver.c in DRIVER (CHOSEN)

  • Pros: Clear separation between logical actuators and physical hardware
  • Cons: Adds an extra abstraction layer
  • Verdict: Best architectural choice - maintains proper layering and reusability

Why Option 3 is Correct

1. Logical vs Physical Abstraction

MCU-PERIPHERALS Layer (Physical):

// Generic GPIO operations - no application knowledge
hal_gpio_set_pin(HAL_GPIO_PORT_A, HAL_GPIO_PIN_0);  // Just sets a pin

DRIVER Layer (Logical):

// Application-specific actuator abstraction
actuator_driver_set(ACTUATOR_LED_STATUS, true);  // Turns on status LED
actuator_driver_set(ACTUATOR_FAN, true);         // Turns on cooling fan

The DRIVER layer translates between what (status LED) and where (PORT_A, PIN_0).

2. Comparison with Other Drivers

Consider these other drivers in the same layer:

Driver Device Type Why in DRIVER Layer?
temperature_sensor_driver.c External I2C sensor Abstracts LM75 sensor protocol over I2C peripheral
humidity_sensor_driver.c External I2C sensor Abstracts SHT31 sensor protocol over I2C peripheral
actuator_driver.c Logical actuators Abstracts application actuators over GPIO peripheral

All these drivers share the same purpose:

  • They translate application-level concepts to hardware operations
  • They use MCU-PERIPHERALS (I2C, GPIO, ADC) to communicate with devices
  • They hide hardware details from upper layers

3. Pin Mapping Table

The actuator driver contains a pin mapping table:

typedef struct {
    hal_gpio_port_t port;
    hal_gpio_pin_t pin;
} pin_mapping_t;

static const pin_mapping_t pin_map[5] = {
    {HAL_GPIO_PORT_A, HAL_GPIO_PIN_0},  // ACTUATOR_LED_STATUS
    {HAL_GPIO_PORT_A, HAL_GPIO_PIN_1},  // ACTUATOR_LED_ERROR
    {HAL_GPIO_PORT_B, HAL_GPIO_PIN_0},  // ACTUATOR_FAN
    {HAL_GPIO_PORT_B, HAL_GPIO_PIN_1},  // ACTUATOR_HEATER
    {HAL_GPIO_PORT_C, HAL_GPIO_PIN_0}   // ACTUATOR_ALARM
};

This mapping is board-specific configuration, not generic GPIO functionality. It belongs in DRIVER because:

  • Different boards may have different pin assignments
  • MCU-PERIPHERALS should not know about "FAN" or "LED_STATUS" concepts
  • This keeps MCU-PERIPHERALS reusable across projects

4. Interface Pattern Benefits

The actuator driver uses an interface pattern like other drivers:

typedef struct {
    bool (*init)(actuator_id_t actuator, actuator_mode_t mode);
    void (*set)(actuator_id_t actuator, bool state);
    bool (*get)(actuator_id_t actuator);
    void (*toggle)(actuator_id_t actuator);
    bool (*is_initialized)(actuator_id_t actuator);
} actuator_interface_t;

Benefits:

  • Testability: Can mock actuators in unit tests
  • Consistency: Same pattern as I2C, UART drivers
  • Flexibility: Easy to add new actuator types without changing CONTROL layer

Real-World Example: Multiple Board Support

Imagine supporting two different boards:

Board A (Current implementation):

ACTUATOR_LED_STATUSPORT_A, PIN_0
ACTUATOR_FANPORT_B, PIN_0

Board B (Different hardware):

ACTUATOR_LED_STATUSPORT_C, PIN_5
ACTUATOR_FANPORT_D, PIN_2

With actuator_driver.c in DRIVER layer:

  • MCU-PERIPHERALS: No changes needed (generic GPIO functions)
  • DRIVER: Only change pin mapping table in actuator_driver.c
  • CONTROL: No changes needed (uses ACTUATOR_LED_STATUS enum)
  • APP: No changes needed (calls CONTROL layer functions)

Without actuator_driver.c:

  • CONTROL layer would need board-specific #ifdefs everywhere
  • Hard to maintain multiple board configurations
  • Mixing hardware details with control logic

Summary

Layer Responsibility Example
APP Business logic "Check temperature and decide if cooling needed"
CONTROL Control algorithms "If temp > threshold, turn on fan"
DRIVER Device abstraction "Map FAN to PORT_B PIN_0 and set it"
MCU-PERIPHERALS Hardware operations "Set PORT_B PIN_0 to HIGH"

The actuator_driver.c exists in DRIVER layer because it provides device-level abstraction of logical actuators, translating application concepts to hardware operations. This is exactly what the DRIVER layer is designed for.

Naming Clarification

The file was renamed from gpio_driver.c to actuator_driver.c to better reflect its purpose:

  • Old name (gpio_driver): Suggests generic GPIO operations
  • New name (actuator_driver): Clearly indicates it manages logical actuators

This naming makes the architectural intent clearer: it's not just a GPIO wrapper, it's an actuator abstraction layer.