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.
"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.
- 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
- 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
- Pros: Clear separation between logical actuators and physical hardware
- Cons: Adds an extra abstraction layer
- Verdict: Best architectural choice - maintains proper layering and reusability
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 pinDRIVER 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 fanThe DRIVER layer translates between what (status LED) and where (PORT_A, PIN_0).
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
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
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
Imagine supporting two different boards:
Board A (Current implementation):
ACTUATOR_LED_STATUS → PORT_A, PIN_0
ACTUATOR_FAN → PORT_B, PIN_0Board B (Different hardware):
ACTUATOR_LED_STATUS → PORT_C, PIN_5
ACTUATOR_FAN → PORT_D, PIN_2With 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_STATUSenum) - 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
| 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.
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.