通过分层架构和依赖倒置,实现:

高内聚低耦合:每层只关心自己的职责 ;

可测试性:服务层可脱离硬件进行单元测试(mock DAL) ;

可移植性:更换 MCU 或外设只需重写硬件驱动层 ;

清晰的数据流向:从传感器/按键输入,经服务层处理,再输出到 LCD/加热器/串口 ;

以上方案完整覆盖功能梳理、层次划分、接口定义、数据流图、文件组织及模块实现。

嵌入式分层架构实战:从“面条代码”到高内聚低耦合

很多嵌入式初学者把全部代码塞进 main.c,用 switch-case 和 if-else 堆砌出几千行的“意大利面条”。换一个传感器要改几十处,换MCU等于重写项目。
本文通过对比的方式,展示传统“不分层”代码的痛点,再引出分层架构 + 依赖倒置的优雅解法。以智能温控器为例,手把手实现高内聚低耦合、可测试、可移植的嵌入式软件。

目录

  1. 传统写法 vs 分层架构:对比示例
  2. 分层架构核心原则
  3. 智能温控器实例:功能与模块划分
  4. UML建模:组件图、依赖关系图、数据流图
  5. 项目文件结构
  6. 详细模块实现(驱动抽象层 → 硬件驱动层 → 服务层 → 应用层)
    • 6.1 驱动抽象层(DAL)—— 定义硬件无关接口
    • 6.2 硬件驱动层 —— 实现具体外设操作
    • 6.3 服务层 —— 业务逻辑与算法
    • 6.4 应用层 —— 任务调度与初始化
  7. 可测试性实战:如何Mock DAL进行单元测试
  8. 可移植性实战:更换MCU/传感器只需改驱动层
  9. 总结

1. 传统写法 vs 分层架构:对比示例

1.1 不推荐:传统“不分层”写法(伪代码)

// main.c - 所有东西搅在一起
void main() {
    // 初始化各种硬件寄存器
    GPIO_Init();    // 温度传感器引脚
    ADC_Init();     // 如果用模拟传感器
    I2C_Init();     // 如果用数字传感器
    LCD_Init();
    UART_Init();
    Timer_Init();

    while(1) {
        // 直接操作寄存器读取温度
        uint8_t raw[2];
        OneWire_Reset();
        OneWire_Write(0xCC);
        OneWire_Write(0x44);
        // ... 几十行时序代码
        
        // 计算温度
        float temp = (raw[0] | (raw[1]<<8)) * 0.0625;
        
        // 控制逻辑
        if (temp < setpoint - 1.0) {
            GPIO_WritePin(HEATER_PIN, 1);   // 直接操作GPIO
        } else if (temp > setpoint + 1.0) {
            GPIO_WritePin(HEATER_PIN, 0);
        }
        
        // LCD显示
        LCD_Clear();
        sprintf(buf, "T:%.1f", temp);
        LCD_Print(buf);
        
        // 串口上报
        UART_Send(buf);
        
        // 按键扫描
        if (GPIO_ReadPin(KEY_PIN)) {
            setpoint += 0.5;
        }
        
        delay(200);
    }
}

痛点:

  • 换一个温度传感器(DS18B20 → LM35)需要在主循环里修改读取代码,甚至改写ADC逻辑。
  • 想增加滤波算法?直接塞进主循环,代码膨胀。
  • 想加一个Wi-Fi上报?继续往主循环里加,耦合度爆炸。
  • 无法单独测试控制算法,必须连接真实硬件。
  • 换MCU(比如从STM32换到GD32),所有寄存器操作都要重写。

1.2 推荐:分层架构 + 依赖倒置

核心思想:上层只依赖抽象接口,不依赖具体硬件。硬件通过“适配”方式注入。

// ========== 驱动抽象层(接口) ==========
// temp_sensor_if.h
typedef struct {
    int (*read_celsius)(float *temp);
} TempSensorOps;
extern const TempSensorOps *g_temp_sensor;

// ========== 服务层 ==========
float get_filtered_temperature(void) {
    float raw;
    g_temp_sensor->read_celsius(&raw);   // 通过接口调用,不依赖具体硬件
    return apply_filter(raw);
}

// ========== 应用层 ==========
void main() {
    // 初始化:注册具体硬件驱动(适配器)
    g_temp_sensor = &ds18b20_ops;   // 或 &lm35_ops
    
    while(1) {
        float temp = get_filtered_temperature();
        control_heater(temp);
        display_temp(temp);
        send_uart(temp);
        delay(200);
    }
}

优势:

  • 高内聚低耦合:每层只关心自己职责,服务层不知道硬件细节。
  • 可测试:测试控制算法时,用 mock_temp_sensor 返回固定温度即可。
  • 可移植:换MCU只需重写驱动层,服务层和应用层零修改。
  • 数据流向清晰:输入(传感器/按键)→ 服务层(滤波/控制)→ 输出(LCD/加热器/串口)。

2. 分层架构核心原则

2.1 分层模型(自顶向下)

层级职责依赖方向
应用层任务调度、系统状态机、初始化顺序依赖服务层接口
服务层数据处理(滤波、算法)、业务逻辑(温度控制、参数管理)依赖驱动抽象层接口
驱动抽象层定义硬件操作的标准接口(读温度、写GPIO、发UART)不依赖任何层(纯接口)
硬件驱动层实现具体外设的寄存器操作、时序实现驱动抽象层接口
硬件层物理设备(MCU、传感器、执行器)-

2.2 依赖倒置原则(DIP)

高层模块不应依赖低层模块,二者都应依赖抽象。
抽象不应依赖细节,细节应依赖抽象。

  • 服务层(高层)不直接调用 DS18B20_Read(),而是调用 g_temp_sensor->read_celsius()。
  • 硬件驱动层(低层)实现 TempSensorOps 接口,并注册到全局指针。
  • 这样,服务层和驱动层都依赖于 TempSensorOps 这个抽象,实现了“倒置”。

2.3 数据流向图(清晰可见)

输出

服务层

驱动抽象层

输入

温度传感器

temp_driver

按键

key_driver

temp_if

key_if

温度采集服务

按键处理服务

温度控制服务

参数管理服务

heater_if

显示服务

lcd_if

通信服务

uart_if

加热器驱动

LCD驱动

UART驱动

加热器

LCD屏幕

串口上位机

3. 智能温控器实例:功能与模块划分

3.1 功能需求

  • 周期性采集温度(DS18B20 或 LM35)
  • 根据设定温度控制加热器(继电器)
  • 三个按键:增加设定温度、减少设定温度、手动/自动模式切换
  • LCD 1602 显示:当前温度、设定温度、加热状态、模式
  • 串口每2秒上报一次温度、设定值、加热状态
  • 参数掉电保存(EEPROM)

3.2 模块识别

层级模块说明
应用层main_task主循环调度,周期性调用各服务
服务层temp_svc温度采集、滑动滤波
control_svc开关控制算法(带回差)
param_svc参数管理(目标温度、模式、EEPROM读写)
display_svcLCD刷新(根据当前状态更新显示)
comm_svc串口上报
key_svc按键扫描、去抖、触发参数修改
驱动抽象层temp_sensor_ifread_celsius()
heater_ifset_state(), get_state()
key_ifscan() 获取按键码
lcd_ifinit(), clear(), print()
uart_ifinit(), send(), receive()
storage_ifread(), write() (EEPROM)
硬件驱动层ds18b20_drv / lm35_drv实现 temp_sensor_if
gpio_heater_drv实现 heater_if
key_matrix_drv实现 key_if
lcd1602_drv实现 lcd_if
stm32_uart_drv实现 uart_if
eeprom_drv实现 storage_if

4. UML建模:组件图、依赖关系图、数据流图

4.1 分层组件图(依赖关系)

硬件驱动层

驱动抽象层DAL

服务层

应用层

任务调度

温度服务

控制服务

参数服务

显示服务

按键服务

通信服务

temp_if

heater_if

key_if

lcd_if

uart_if

storage_if

温度驱动

加热器驱动

按键驱动

LCD驱动

UART驱动

EEPROM驱动

4.2 数据流时序图(一次温度采集与控制周期)

UIf LIf PSvc 通信服务 显示服务 heater_if 控制服务 温度驱动 temp_if 温度服务 应用层调度 UIf LIf PSvc 通信服务 显示服务 heater_if 控制服务 温度驱动 temp_if 温度服务 应用层调度 每200ms调用update() read_celsius(&temp) 实际读取硬件 原始数据 温度值 滑动滤波 返回滤波后温度 执行控制 获取当前温度 获取设定温度/模式 开关算法 set_state(ON/OFF) 刷新显示 获取温度 获取设定值 获取加热状态 print(...) 周期上报 send(...)

5. 项目文件结构

smart_thermostat/
├── docs/                      # 文档
├── drivers/                   # 硬件驱动层
│   ├── inc/
│   │   ├── ds18b20.h
│   │   ├── lm35.h
│   │   ├── gpio_heater.h
│   │   ├── key_matrix.h
│   │   ├── lcd1602.h
│   │   ├── stm32_uart.h
│   │   └── eeprom_24cxx.h
│   └── src/
│       ├── ds18b20.c
│       ├── lm35.c
│       ├── gpio_heater.c
│       ├── key_matrix.c
│       ├── lcd1602.c
│       ├── stm32_uart.c
│       └── eeprom_24cxx.c
├── dal/                       # 驱动抽象层(接口定义)
│   ├── inc/
│   │   ├── temp_sensor_if.h
│   │   ├── heater_if.h
│   │   ├── key_if.h
│   │   ├── lcd_if.h
│   │   ├── uart_if.h
│   │   └── storage_if.h
│   └── src/
│       └── dal_common.c       # 全局接口指针定义
├── service/                   # 服务层
│   ├── inc/
│   │   ├── temp_svc.h
│   │   ├── control_svc.h
│   │   ├── param_svc.h
│   │   ├── display_svc.h
│   │   ├── comm_svc.h
│   │   └── key_svc.h
│   └── src/
│       ├── temp_svc.c
│       ├── control_svc.c
│       ├── param_svc.c
│       ├── display_svc.c
│       ├── comm_svc.c
│       └── key_svc.c
├── app/                       # 应用层
│   ├── inc/
│   │   └── main_task.h
│   └── src/
│       └── main.c
├── platform/                  # MCU启动文件、链接脚本
│   └── stm32f1xx/
├── test/                      # 单元测试(mock DAL)
│   ├── mock/
│   │   └── mock_temp_sensor.c
│   └── test_control_svc.c
└── build/

6. 详细模块实现(驱动抽象层 → 硬件驱动层 → 服务层 → 应用层)

6.1 驱动抽象层(DAL)—— 定义硬件无关接口

dal/inc/temp_sensor_if.h
#ifndef TEMP_SENSOR_IF_H
#define TEMP_SENSOR_IF_H

#include <stdint.h>

typedef struct {
    int (*init)(void);
    int (*read_celsius)(float *temp);
} TempSensorOps;

extern const TempSensorOps *g_temp_sensor;   // 全局指针,由驱动层赋值

#endif
dal/inc/heater_if.h
#ifndef HEATER_IF_H
#define HEATER_IF_H

typedef enum { HEATER_OFF = 0, HEATER_ON = 1 } HeaterState;

typedef struct {
    void (*init)(void);
    void (*set_state)(HeaterState state);
    HeaterState (*get_state)(void);
} HeaterOps;

extern const HeaterOps *g_heater;

#endif
dal/inc/key_if.h
#ifndef KEY_IF_H
#define KEY_IF_H

#include <stdint.h>

typedef struct {
    int (*init)(void);
    int (*scan)(uint8_t *key_code, uint8_t *pressed);  // 返回按键值及是否按下
} KeyOps;

extern const KeyOps *g_key;

#endif
dal/inc/lcd_if.h
#ifndef LCD_IF_H
#define LCD_IF_H

#include <stdint.h>

typedef struct {
    int (*init)(void);
    void (*clear)(void);
    void (*print)(uint8_t row, uint8_t col, const char *str);
} LcdOps;

extern const LcdOps *g_lcd;

#endif
dal/inc/uart_if.h
#ifndef UART_IF_H
#define UART_IF_H

#include <stdint.h>

typedef struct {
    int (*init)(uint32_t baud);
    int (*send)(const uint8_t *data, uint16_t len);
    int (*receive)(uint8_t *buf, uint16_t max_len, uint32_t timeout_ms);
} UartOps;

extern const UartOps *g_uart;

#endif
dal/inc/storage_if.h
#ifndef STORAGE_IF_H
#define STORAGE_IF_H

#include <stdint.h>

typedef struct {
    int (*init)(void);
    int (*write)(uint16_t addr, const uint8_t *data, uint16_t len);
    int (*read)(uint16_t addr, uint8_t *data, uint16_t len);
} StorageOps;

extern const StorageOps *g_storage;

#endif

6.2 硬件驱动层 —— 实现具体外设操作

drivers/src/ds18b20.c(实现温度传感器接口)
#include "temp_sensor_if.h"
#include "stm32f1xx_hal.h"

static int ds18b20_init(void) {
    // 初始化单总线GPIO
    // ...
    return 0;
}

static int ds18b20_read_celsius(float *temp) {
    uint8_t rom[8];
    uint8_t data[2];
    // 单总线通信时序(省略具体实现)
    // 假设读取成功,data[0]为低字节,data[1]为高字节
    int16_t raw = (data[1] << 8) | data[0];
    *temp = raw * 0.0625f;
    return 0;
}

static const TempSensorOps ds18b20_ops = {
    .init = ds18b20_init,
    .read_celsius = ds18b20_read_celsius
};

const TempSensorOps *g_temp_sensor = &ds18b20_ops;   // 注册
drivers/src/gpio_heater.c(实现加热器接口)
#include "heater_if.h"
#include "stm32f1xx_hal.h"

#define HEATER_PIN  GPIO_PIN_13
#define HEATER_PORT GPIOB

static void heater_init(void) {
    GPIO_InitTypeDef gpio = { .Pin = HEATER_PIN, .Mode = GPIO_MODE_OUTPUT_PP };
    HAL_GPIO_Init(HEATER_PORT, &gpio);
    HAL_GPIO_WritePin(HEATER_PORT, HEATER_PIN, GPIO_PIN_RESET);
}

static void heater_set_state(HeaterState state) {
    HAL_GPIO_WritePin(HEATER_PORT, HEATER_PIN, state == HEATER_ON ? GPIO_PIN_SET : GPIO_PIN_RESET);
}

static HeaterState heater_get_state(void) {
    return HAL_GPIO_ReadPin(HEATER_PORT, HEATER_PIN) == GPIO_PIN_SET ? HEATER_ON : HEATER_OFF;
}

static const HeaterOps heater_ops = {
    .init = heater_init,
    .set_state = heater_set_state,
    .get_state = heater_get_state
};

const HeaterOps *g_heater = &heater_ops;
drivers/src/lcd1602.c(实现LCD接口)
#include "lcd_if.h"
#include "stm32f1xx_hal.h"

static void lcd_init(void) { /* 初始化1602时序 */ }
static void lcd_clear(void) { /* 发送清屏命令 */ }
static void lcd_print(uint8_t row, uint8_t col, const char *str) {
    // 设置光标位置,逐字符输出
}
static const LcdOps lcd_ops = { .init = lcd_init, .clear = lcd_clear, .print = lcd_print };
const LcdOps *g_lcd = &lcd_ops;

其他驱动(按键、UART、EEPROM)类似实现,此处省略。

6.3 服务层 —— 业务逻辑与算法(可脱离硬件测试)

service/inc/temp_svc.h 与 src/temp_svc.c
// temp_svc.h
#ifndef TEMP_SVC_H
#define TEMP_SVC_H

void temp_svc_init(void);
void temp_svc_update(void);        // 每200ms调用
float temp_svc_get_current(void);  // 获取滤波后温度

#endif
// temp_svc.c
#include "temp_svc.h"
#include "temp_sensor_if.h"

#define FILTER_WIN 5
static float buf[FILTER_WIN];
static uint8_t idx = 0, count = 0;
static float filtered_temp = 0.0f;

void temp_svc_init(void) {
    g_temp_sensor->init();
}

void temp_svc_update(void) {
    float raw;
    if (g_temp_sensor->read_celsius(&raw) == 0) {
        buf[idx] = raw;
        idx = (idx + 1) % FILTER_WIN;
        if (count < FILTER_WIN) count++;
        float sum = 0;
        for (int i = 0; i < count; i++) sum += buf[i];
        filtered_temp = sum / count;
    }
}

float temp_svc_get_current(void) {
    return filtered_temp;
}
service/inc/param_svc.h 与 src/param_svc.c
// param_svc.h
#ifndef PARAM_SVC_H
#define PARAM_SVC_H

#include <stdint.h>

typedef struct {
    float target_temp;
    uint8_t mode;       // 0: manual, 1: auto
    uint8_t eeprom_valid;
} SystemParams;

extern SystemParams g_params;

void param_svc_init(void);
void param_svc_save(void);
void param_svc_set_target(float temp);
float param_svc_get_target(void);
void param_svc_set_mode(uint8_t mode);
uint8_t param_svc_get_mode(void);

#endif
// param_svc.c
#include "param_svc.h"
#include "storage_if.h"

#define PARAM_ADDR 0x00

SystemParams g_params;

void param_svc_init(void) {
    g_storage->init();
    if (g_storage->read(PARAM_ADDR, (uint8_t*)&g_params, sizeof(g_params)) != 0 ||
        g_params.eeprom_valid != 0xAA) {
        // 默认参数
        g_params.target_temp = 25.0f;
        g_params.mode = 1;  // auto
        g_params.eeprom_valid = 0xAA;
        param_svc_save();
    }
}

void param_svc_save(void) {
    g_storage->write(PARAM_ADDR, (const uint8_t*)&g_params, sizeof(g_params));
}

void param_svc_set_target(float temp) {
    g_params.target_temp = temp;
    param_svc_save();
}
float param_svc_get_target(void) { return g_params.target_temp; }

void param_svc_set_mode(uint8_t mode) {
    g_params.mode = mode;
    param_svc_save();
}
uint8_t param_svc_get_mode(void) { return g_params.mode; }
service/inc/control_svc.h 与 src/control_svc.c
// control_svc.h
#ifndef CONTROL_SVC_H
#define CONTROL_SVC_H

void control_svc_init(void);
void control_svc_update(void);   // 根据当前温度控制加热器

#endif
// control_svc.c
#include "control_svc.h"
#include "temp_svc.h"
#include "param_svc.h"
#include "heater_if.h"

#define HYSTERESIS 1.0f

static HeaterState last_state = HEATER_OFF;

void control_svc_init(void) {
    g_heater->init();
}

void control_svc_update(void) {
    if (param_svc_get_mode() == 0) return;  // 手动模式,不自动控制
    
    float current = temp_svc_get_current();
    float target = param_svc_get_target();
    
    if (last_state == HEATER_OFF && current <= target - HYSTERESIS) {
        last_state = HEATER_ON;
        g_heater->set_state(HEATER_ON);
    } else if (last_state == HEATER_ON && current >= target + HYSTERESIS) {
        last_state = HEATER_OFF;
        g_heater->set_state(HEATER_OFF);
    }
}
service/inc/display_svc.h 与 src/display_svc.c
// display_svc.h
void display_svc_init(void);
void display_svc_refresh(void);
// display_svc.c
#include "display_svc.h"
#include "lcd_if.h"
#include "temp_svc.h"
#include "param_svc.h"
#include "heater_if.h"
#include <stdio.h>

void display_svc_init(void) {
    g_lcd->init();
}

void display_svc_refresh(void) {
    char line1[17], line2[17];
    float temp = temp_svc_get_current();
    float target = param_svc_get_target();
    HeaterState heat = g_heater->get_state();
    uint8_t mode = param_svc_get_mode();
    
    snprintf(line1, sizeof(line1), "T:%.1f Set:%.1f", temp, target);
    snprintf(line2, sizeof(line2), "Heater:%s Mode:%s", 
             heat == HEATER_ON ? "ON" : "OFF",
             mode ? "Auto" : "Man");
    g_lcd->clear();
    g_lcd->print(0, 0, line1);
    g_lcd->print(1, 0, line2);
}
service/src/comm_svc.c(串口上报)
#include "comm_svc.h"
#include "uart_if.h"
#include "temp_svc.h"
#include "param_svc.h"
#include "heater_if.h"
#include <stdio.h>

void comm_svc_init(void) {
    g_uart->init(115200);
}

void comm_svc_report(void) {
    char buf[64];
    float temp = temp_svc_get_current();
    float target = param_svc_get_target();
    HeaterState heat = g_heater->get_state();
    int len = snprintf(buf, sizeof(buf), "TEMP:%.2f,TARGET:%.2f,HEATER:%d\r\n",
                       temp, target, heat == HEATER_ON ? 1 : 0);
    g_uart->send((const uint8_t*)buf, len);
}
service/src/key_svc.c(按键处理)
#include "key_svc.h"
#include "key_if.h"
#include "param_svc.h"
#include "display_svc.h"

#define KEY_UP      1
#define KEY_DOWN    2
#define KEY_MODE    3

void key_svc_init(void) {
    g_key->init();
}

void key_svc_task(void) {
    uint8_t key, pressed;
    if (g_key->scan(&key, &pressed) && pressed) {
        switch (key) {
            case KEY_UP:
                param_svc_set_target(param_svc_get_target() + 0.5f);
                display_svc_refresh();
                break;
            case KEY_DOWN:
                param_svc_set_target(param_svc_get_target() - 0.5f);
                display_svc_refresh();
                break;
            case KEY_MODE:
                param_svc_set_mode(!param_svc_get_mode());
                display_svc_refresh();
                break;
        }
    }
}

6.4 应用层 —— 任务调度与初始化

app/src/main.c
#include "main_task.h"
#include "temp_svc.h"
#include "control_svc.h"
#include "param_svc.h"
#include "display_svc.h"
#include "comm_svc.h"
#include "key_svc.h"
#include "stm32f1xx_hal.h"

int main(void) {
    HAL_Init();
    
    // 初始化各层(驱动抽象层接口已在驱动层注册,此处只需调用服务层初始化)
    param_svc_init();
    temp_svc_init();
    control_svc_init();
    display_svc_init();
    comm_svc_init();
    key_svc_init();
    
    uint32_t last_temp_time = 0;
    uint32_t last_report_time = 0;
    uint32_t last_display_time = 0;
    const uint32_t TEMP_INTERVAL_MS = 200;
    const uint32_t REPORT_INTERVAL_MS = 2000;
    const uint32_t DISPLAY_INTERVAL_MS = 500;
    
    while (1) {
        uint32_t now = HAL_GetTick();
        
        if (now - last_temp_time >= TEMP_INTERVAL_MS) {
            last_temp_time = now;
            temp_svc_update();          // 读取温度并滤波
            control_svc_update();       // 控制加热器
        }
        
        if (now - last_display_time >= DISPLAY_INTERVAL_MS) {
            last_display_time = now;
            display_svc_refresh();      // 刷新LCD
        }
        
        if (now - last_report_time >= REPORT_INTERVAL_MS) {
            last_report_time = now;
            comm_svc_report();          // 串口上报
        }
        
        key_svc_task();                 // 按键扫描(可20ms一次,这里简单放主循环)
        
        HAL_Delay(10);
    }
}

7. 可测试性实战:如何Mock DAL进行单元测试

由于服务层只依赖 g_temp_sensor 等抽象接口,我们可以编写模拟驱动,在PC上测试控制逻辑。

7.1 创建 Mock 驱动(用于测试)

// test/mock/mock_temp_sensor.c
#include "temp_sensor_if.h"

static float mock_temperature = 25.0f;

static int mock_read_celsius(float *temp) {
    *temp = mock_temperature;
    return 0;
}

static const TempSensorOps mock_ops = {
    .read_celsius = mock_read_celsius
};

const TempSensorOps *g_temp_sensor = &mock_ops;

// 测试辅助函数
void mock_temp_sensor_set(float temp) { mock_temperature = temp; }

7.2 编写控制服务的单元测试(使用Unity框架示例)

// test/test_control_svc.c
#include "unity.h"
#include "control_svc.h"
#include "mock_temp_sensor.h"
#include "mock_heater.h"
#include "param_svc.h"

void setUp(void) {
    // 初始化参数服务,设置目标温度为25.0,模式为自动
    param_svc_init();   // 需提供mock storage
    g_params.target_temp = 25.0f;
    g_params.mode = 1;
}

void test_control_should_turn_on_heater_when_temp_below_threshold(void) {
    mock_temp_sensor_set(23.5f);   // 低于25-1=24
    control_svc_update();
    TEST_ASSERT_EQUAL(HEATER_ON, g_heater->get_state());
}

void test_control_should_turn_off_heater_when_temp_above_threshold(void) {
    // 先让加热器开启
    g_heater->set_state(HEATER_ON);
    mock_temp_sensor_set(26.5f);   // 高于25+1=26
    control_svc_update();
    TEST_ASSERT_EQUAL(HEATER_OFF, g_heater->get_state());
}

int main(void) {
    UNITY_BEGIN();
    RUN_TEST(test_control_should_turn_on_heater_when_temp_below_threshold);
    RUN_TEST(test_control_should_turn_off_heater_when_temp_above_threshold);
    return UNITY_END();
}

效果:无需任何真实硬件,在PC上即可完成控制算法的单元测试,极大提升代码质量。

8. 可移植性实战:更换MCU/传感器只需改驱动层

8.1 更换MCU(从STM32换到GD32)

只需要重写硬件驱动层的文件,例如:

  • drivers/src/gpio_heater.c 中修改GPIO寄存器操作为GD32的库函数。
  • drivers/src/stm32_uart.c 换为 gd32_uart.c,实现相同的 uart_if 接口。
  • 服务层和应用层 一行代码都不需要改。

8.2 更换温度传感器(从DS18B20换到LM35)

  1. 编写 drivers/src/lm35.c,实现 TempSensorOps 接口:
static int lm35_read_celsius(float *temp) {
    uint16_t adc_val = HAL_ADC_GetValue();  // 读取ADC
    *temp = adc_val * 3.3f / 4096.0f * 100.0f;  // 假设LM35输出10mV/℃
    return 0;
}
static const TempSensorOps lm35_ops = { .read_celsius = lm35_read_celsius };
const TempSensorOps *g_temp_sensor = &lm35_ops;   // 只需改变这个指针
  1. 在 main.c 之前,确保 g_temp_sensor 指向新的驱动(通过链接或条件编译)。
  2. 服务层 temp_svc.c 中调用 g_temp_sensor->read_celsius() 保持不变。

移植完成,上层逻辑完全不受影响。

9. 总结

通过分层架构和依赖倒置,我们实现了:

目标实现方式
高内聚低耦合每层只负责自己的职责:驱动层管寄存器,服务层管算法,应用层管调度。依赖方向单向。
可测试性服务层依赖抽象接口,可替换为Mock驱动,在PC上完成单元测试。
可移植性更换MCU或外设只需要重写硬件驱动层,服务层和应用层零修改。
清晰数据流向输入(传感器/按键)→ 驱动抽象层 → 服务层处理 → 驱动抽象层 → 输出(LCD/加热器/串口)。

对比传统写法(如第一节所示),分层架构让代码:

  • 更易于阅读理解(每个文件有明确职责)
  • 更易于团队协作(接口稳定,并行开发)
  • 更易于维护和扩展(新增功能只需添加新模块,不影响旧模块)

这就是嵌入式工程师从“码农”走向“架构师”的必经之路。

更多推荐