设计模式组合应用:传感器数据采集与处理系统

作为嵌入式C语言开发者,你一定在传感器数据采集与处理项目中踩过这些坑:新增一种传感器,就要修改大量核心代码;想给数据加个滤波、校准功能,就得改动数据处理的核心逻辑;不同场景下切换传感器类型,代码冗余且容易出错,后期维护起来更是头疼不已。其实,这些问题的核心是------我们习惯了"平铺式"编码,没有用设计模式来规范代码结构,导致代码耦合度高、可扩展性差。

今天,我们就聚焦传感器数据采集与处理系统,手把手教你组合使用组合模式、装饰器模式、简单工厂模式,彻底解决嵌入式开发中传感器扩展、功能叠加、场景切换的痛点。全程基于C语言实操,不搞空洞的理论推导,贴合DSP C开发者、STM32开发者的编码习惯,从原理拆解到工程实现,再到实战验证、问题排查,让你快速学会用设计模式写出高可扩展、高可维护、低耦合的嵌入式代码,提升开发效率,摆脱"改一处崩全量"的困境。

一、原理拆解:三种设计模式核心逻辑(嵌入式C语言视角)

在讲解系统方案前,我们先拆解三种设计模式的核心逻辑------重点讲"嵌入式场景怎么用",而非晦涩的标准定义,避免大家被复杂术语劝退。三种模式各司其职、相互配合,组合起来就能完美解决传感器系统的扩展与维护问题:简单工厂负责"统一创建传感器",组合模式负责"管理多个传感器集群",装饰器模式负责"动态叠加数据处理功能"。

1.1 简单工厂模式:传感器的"统一创建车间"

简单工厂模式的核心作用是统一创建对象,将"创建传感器"与"使用传感器"的逻辑彻底分离------嵌入式开发中,我们常会用到多种传感器(如温度、湿度、加速度传感器),每种传感器的初始化、数据读取逻辑差异较大,若每次使用都手动创建、初始化,代码会极度冗余,且新增传感器时,需修改所有使用该传感器的代码,极易出错。

对应到传感器系统,简单工厂的核心逻辑的是:定义一个"传感器工厂函数",根据传入的传感器类型(如温度=1、湿度=2),自动创建对应的传感器实例,并返回统一的操作接口(初始化、读数据)。后续使用传感器时,无需关心具体的传感器型号和初始化细节,只需调用工厂函数、传入类型即可;新增传感器时,只需修改工厂函数,无需改动其他业务代码,大幅降低维护成本。

通俗来讲,简单工厂就像传感器的"采购专员",你告诉它要哪种传感器(传入类型),它就给你做好初始化、调试好,你拿到手直接调用接口使用即可,不用管它是怎么准备的,省去了重复初始化的繁琐操作。

1.2 组合模式:传感器集群的"统一管理管家"

组合模式的核心作用是统一管理单个对象和对象集合,将多个传感器(单个对象)组合成一个"传感器集群"(对象集合),并提供统一的操作接口------嵌入式系统中,我们常常需要同时使用多个传感器(如工业环境监测系统,需同时采集温度、湿度、气压数据),若分别管理每个传感器的初始化、数据采集,代码会非常繁琐,且难以统一调度、同步采集。

对应到传感器系统,组合模式的核心逻辑的是:定义一个统一的"传感器组件接口",单个传感器(叶子节点)和传感器集群(组合节点)都实现这个接口。这样一来,无论是操作单个传感器,还是操作整个传感器集群,都可以调用相同的接口(如统一初始化所有传感器、统一采集所有传感器数据),无需区分是单个还是多个,大幅简化代码逻辑,也便于后续扩展更多传感器。

简单说,组合模式就像传感器的"项目经理",它不管你有多少个传感器、是什么类型,都能统一管理、统一调度;你只需给项目经理下一个指令(如"采集所有传感器数据"),它就会安排所有传感器同步完成工作,不用你逐个指挥、逐个调用接口。

1.3 装饰器模式:数据处理功能的"动态叠加插件"

装饰器模式的核心作用是动态给对象添加额外功能,不改变对象本身的核心逻辑,且功能可以灵活叠加、按需组合------传感器采集到的原始数据,往往需要经过滤波、校准、缩放等处理才能使用,若将这些处理逻辑写死在传感器的读数据函数中,后续想新增、删除某类处理功能,就必须修改传感器的核心代码,违背"开闭原则",也容易引入新的bug。

对应到传感器系统,装饰器模式的核心逻辑的是:定义一个"装饰器接口",与传感器接口保持一致,装饰器内部包含一个传感器实例(被装饰的对象);在调用传感器读数据接口时,装饰器会先获取原始数据,再动态添加额外的处理功能(如先读原始数据,再进行滤波,再进行校准)。不同的处理功能对应不同的装饰器,可根据需求灵活组合叠加(如有的场景需要"滤波+校准",有的场景只需要"缩放"),无需修改传感器本身的代码。

通俗来讲,装饰器模式就像传感器数据的"加工流水线",原始数据从传感器出来后,可根据项目需求,依次经过不同的"加工环节"(装饰器),每个环节只做一件事(如滤波去噪、校准误差),且可以随时增加、删除加工环节,不影响传感器本身的采集逻辑,灵活性极高。

1.4 三种模式组合逻辑(核心重点)

三种模式并非单独使用,而是相互配合、形成闭环,完美适配传感器数据采集与处理的全流程,组合逻辑如下(贴合嵌入式实操流程,记熟可直接落地):

  1. 简单工厂模式,根据项目需求,统一创建所需的单个传感器实例(如温度、湿度传感器),无需手动初始化,降低代码冗余;

  2. 组合模式,将创建好的单个传感器实例,组合成传感器集群,实现统一初始化、统一采集数据,简化多传感器调度逻辑;

  3. 装饰器模式,给传感器集群或单个传感器,动态叠加数据处理功能(滤波、校准等),得到可用的最终数据,灵活适配不同场景需求;

整个流程中,新增传感器只需修改工厂函数,新增数据处理功能只需新增装饰器,修改传感器集群只需调整组合逻辑,三者互不干扰,大幅提升代码的可扩展性和可维护性,彻底解决嵌入式传感器项目的维护痛点。

二、工程化分析:传感器系统需求与模式适配

结合嵌入式实际项目(如工业环境监测、设备状态采集),我们先明确传感器数据采集与处理系统的核心需求,再分析三种设计模式的工程化适配方案------避免设计模式与实际需求脱节,确保方案可落地、可复用,贴合STM32、DSP等嵌入式设备的资源特点。

2.1 系统核心需求(嵌入式实操场景)

以工业环境监测系统为例,核心需求如下(贴合多数嵌入式传感器项目,可直接参考适配自身项目):

  • 支持多种传感器:温度(DS18B20)、湿度(DHT11)、加速度(ADXL345),后续可快速扩展气压(BMP280)、光照传感器,无需大规模修改代码;

  • 支持传感器集群管理:可同时初始化、采集所有传感器数据,也可单独操作某一个传感器,适配不同采集场景;

  • 数据处理可灵活配置:原始数据需支持滤波(去除高频噪声)、校准(修正系统误差)、缩放(转换为实际物理量),不同场景下可组合不同处理功能;

  • 代码可维护、可扩展:新增传感器、新增数据处理功能时,尽量少改或不改核心代码,降低维护成本,减少bug引入;

  • 适配嵌入式资源:代码简洁、占用内存少,可稳定运行于STM32、DSP等嵌入式设备,避免过度封装导致资源浪费、CPU占用过高。

2.2 三种模式工程化适配方案

针对上述需求,三种设计模式的适配方案如下(重点讲C语言实现的核心思路,避免抽象,嵌入式开发者可直接参考落地):

(1)简单工厂模式适配:传感器创建标准化

适配需求:支持多种传感器,新增传感器无需修改使用处代码,降低冗余和出错概率。

工程化思路:定义一个传感器结构体(包含初始化、读数据的函数指针),作为所有传感器的统一接口;针对每种传感器(DS18B20、DHT11等),单独实现对应的初始化和读数据函数;创建一个工厂函数,根据传入的传感器类型,自动绑定对应的函数指针,返回统一的传感器结构体指针。

关键注意点(嵌入式实操重点):嵌入式场景中,避免频繁使用malloc动态分配内存(易造成内存碎片、稳定性差),可提前定义静态传感器实例,工厂函数直接返回对应实例的指针,节省内存、提升代码运行效率。

(2)组合模式适配:传感器集群管理统一化

适配需求:传感器集群统一管理,支持单个/多个传感器操作,简化多传感器调度逻辑。

工程化思路:复用传感器结构体接口,新增一个"组合传感器结构体",包含传感器数组(存储单个传感器指针)、传感器数量,以及统一的初始化、读数据函数(内部调用所有单个传感器的对应函数);组合传感器和单个传感器,都实现相同的接口,外部调用时无需区分,大幅简化代码。

关键注意点(嵌入式实操重点):传感器数组的数量可通过宏定义配置(如#define MAX_SENSOR 8),适配不同项目的传感器数量需求,避免固定数组导致的资源浪费或扩展受限。

(3)装饰器模式适配:数据处理功能插件化

适配需求:数据处理功能灵活叠加,新增功能无需修改传感器核心代码,适配不同场景的数据处理需求。

工程化思路:定义装饰器结构体,与传感器结构体接口完全一致(包含初始化、读数据函数指针),内部包含一个传感器指针(被装饰的对象,可为单个传感器或传感器集群);针对每种数据处理功能(滤波、校准、缩放),单独实现对应的装饰器,在装饰器的读数据函数中,先调用被装饰对象的读数据函数获取原始数据,再执行对应的处理逻辑,返回处理后的数据;多个装饰器可嵌套使用,实现功能叠加。

关键注意点(嵌入式实操重点):装饰器仅负责数据处理,不改变传感器的采集逻辑,确保代码低耦合;数据处理函数尽量简洁(如滤波用简单滑动平均,避免高阶复杂算法),避免占用过多CPU资源,适配嵌入式实时性需求。

三、C语言实现:嵌入式场景完整落地(STM32/DSP通用)

结合上述工程化适配方案,我们用C语言实现传感器数据采集与处理系统,代码简洁、实操性强,无多余冗余,可直接复制到Keil、STM32CubeIDE中使用,适配STM32、DSP等嵌入式设备。全程遵循嵌入式C语言编码习惯,避免C++特性,仅使用结构体、函数指针实现设计模式,新手也能轻松看懂、快速落地。

3.1 核心头文件(sensor_system.h)

定义统一的接口(传感器、装饰器),以及相关结构体、宏定义,集中管理,便于后续维护和扩展,减少头文件冗余。

c 复制代码
#ifndef SENSOR_SYSTEM_H
#define SENSOR_SYSTEM_H

#include "stm32f1xx_hal.h"  // 适配STM32,DSP可替换为对应头文件(如ti_dsp.h)
#include <stdint.h>

// 传感器类型定义(新增传感器,只需在此处添加类型,无需修改其他代码)
typedef enum {
    SENSOR_TEMP = 1,    // 温度传感器(DS18B20)
    SENSOR_HUMI = 2,    // 湿度传感器(DHT11)
    SENSOR_ACC  = 3     // 加速度传感器(ADXL345)
} SensorType;

// 数据处理类型定义(新增处理功能,只需在此处添加类型)
typedef enum {
    DECORATOR_FILTER = 1,  // 滤波装饰器(去噪)
    DECORATOR_CALIB  = 2,  // 校准装饰器(修正误差)
    DECORATOR_SCALE  = 3   // 缩放装饰器(转换为物理量)
} DecoratorType;

// 传感器数据结构体(存储采集到的原始/处理后数据,按需扩展字段)
typedef struct {
    float temp;   // 温度值(℃)
    float humi;   // 湿度值(%RH)
    float acc_x;  // 加速度X轴(g)
    float acc_y;  // 加速度Y轴(g)
    float acc_z;  // 加速度Z轴(g)
} SensorData;

// 传感器接口结构体(统一接口,单个传感器、组合传感器、装饰器均实现此接口)
typedef struct Sensor {
    // 初始化函数(返回0:成功,非0:失败,便于排查初始化问题)
    int8_t (*init)(struct Sensor* sensor);
    // 读数据函数(返回采集到的数据,统一接口规范)
    SensorData (*read)(struct Sensor* sensor);
    // 私有数据(存储传感器专属信息,如引脚、地址等,隔离传感器差异)
    void* private_data;
} Sensor;

// 组合传感器结构体(组合模式核心,管理多个单个传感器)
typedef struct {
    Sensor sensor;          // 继承传感器接口,实现统一调用
    Sensor* sensors[8];     // 存储单个传感器指针(最多8个,可通过宏定义修改)
    uint8_t sensor_count;   // 实际传感器数量,动态管理
} CompositeSensor;

// 装饰器结构体(装饰器模式核心,动态添加数据处理功能)
typedef struct {
    Sensor sensor;          // 继承传感器接口,实现统一调用
    Sensor* decorated_sensor;  // 被装饰的传感器(单个/组合传感器)
    // 数据处理函数(根据装饰器类型,实现不同的处理逻辑)
    SensorData (*process)(struct Sensor* decorator, SensorData data);
} Decorator;

// -------------------------- 简单工厂函数声明 --------------------------
// 创建单个传感器(根据类型,返回统一的传感器接口指针,无需手动初始化)
Sensor* sensor_factory_create(SensorType type);

// -------------------------- 组合模式函数声明 --------------------------
// 初始化组合传感器(清空数组、初始化参数)
int8_t composite_sensor_init(CompositeSensor* composite);
// 向组合传感器中添加单个传感器(自动初始化新增传感器)
int8_t composite_sensor_add(CompositeSensor* composite, Sensor* sensor);
// 组合传感器读数据(读取所有单个传感器的数据,合并返回)
SensorData composite_sensor_read(CompositeSensor* composite);

// -------------------------- 装饰器模式函数声明 --------------------------
// 创建装饰器(根据类型,返回统一的传感器接口指针,传入被装饰的传感器)
Sensor* decorator_factory_create(DecoratorType type, Sensor* decorated_sensor);

#endif  // SENSOR_SYSTEM_H

3.2 核心实现文件(sensor_system.c)

实现各个传感器的初始化、读数据函数,以及三种设计模式的核心逻辑,代码注释详细(贴合嵌入式开发者阅读习惯),便于理解、修改和移植,模拟逻辑可直接替换为实际驱动代码。

c 复制代码
#include "sensor_system.h"
#include <string.h>

// -------------------------- 单个传感器实现(DS18B20、DHT11、ADXL345) --------------------------
// 1. 温度传感器(DS18B20)实现(模拟逻辑,可直接替换为实际驱动代码)
static int8_t temp_sensor_init(Sensor* sensor) {
    // 实际项目中,替换为DS18B20的初始化代码(如GPIO配置、复位、ROM匹配等)
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);  // 模拟GPIO初始化
    HAL_Delay(10);  // 模拟复位等待时间
    return 0;  // 初始化成功,返回0(便于排查问题)
}

static SensorData temp_sensor_read(Sensor* sensor) {
    SensorData data = {0};
    // 实际项目中,替换为DS18B20的读数据代码(时序读取、数据解析)
    data.temp = 25.3f;  // 模拟采集到的温度值(实际需替换为真实解析值)
    return data;
}

// 静态温度传感器实例(避免动态分配内存,适配嵌入式,提升稳定性)
static Sensor temp_sensor = {
    .init = temp_sensor_init,
    .read = temp_sensor_read,
    .private_data = NULL
};

// 2. 湿度传感器(DHT11)实现(模拟逻辑,可直接替换为实际驱动代码)
static int8_t humi_sensor_init(Sensor* sensor) {
    // 实际项目中,替换为DHT11的初始化代码(GPIO配置、复位时序)
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_SET);  // 模拟GPIO初始化
    HAL_Delay(10);  // 模拟复位等待时间
    return 0;
}

static SensorData humi_sensor_read(Sensor* sensor) {
    SensorData data = {0};
    // 实际项目中,替换为DHT11的读数据代码(时序读取、校验、解析)
    data.humi = 60.5f;  // 模拟采集到的湿度值(实际需替换为真实解析值)
    return data;
}

static Sensor humi_sensor = {
    .init = humi_sensor_init,
    .read = humi_sensor_read,
    .private_data = NULL
};

// 3. 加速度传感器(ADXL345)实现(模拟逻辑,可直接替换为实际驱动代码)
static int8_t acc_sensor_init(Sensor* sensor) {
    // 实际项目中,替换为ADXL345的初始化代码(I2C配置、寄存器初始化、量程设置)
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);  // 模拟I2C引脚初始化
    HAL_Delay(10);  // 模拟初始化等待时间
    return 0;
}

static SensorData acc_sensor_read(Sensor* sensor) {
    SensorData data = {0};
    // 实际项目中,替换为ADXL345的读数据代码(I2C读取、数据转换)
    data.acc_x = 0.12f;
    data.acc_y = 0.08f;
    data.acc_z = 9.81f;  // 模拟采集到的加速度值(实际需替换为真实转换值)
    return data;
}

static Sensor acc_sensor = {
    .init = acc_sensor_init,
    .read = acc_sensor_read,
    .private_data = NULL
};

// -------------------------- 简单工厂模式实现(核心:统一创建传感器) --------------------------
Sensor* sensor_factory_create(SensorType type) {
    // 根据传感器类型,返回对应的静态实例指针(避免malloc,适配嵌入式)
    switch(type) {
        case SENSOR_TEMP:
            return &temp_sensor;
        case SENSOR_HUMI:
            return &humi_sensor;
        case SENSOR_ACC:
            return &acc_sensor;
        default:
            return NULL;  // 类型错误,返回空指针(便于排查错误)
    }
}

// -------------------------- 组合模式实现(核心:统一管理传感器集群) --------------------------
// 组合传感器初始化
int8_t composite_sensor_init(CompositeSensor* composite) {
    if (composite == NULL) return -1;  // 空指针判断,避免崩溃
    // 初始化传感器接口,绑定组合传感器的读数据函数
    composite->sensor.read = (SensorData (*)(Sensor*))composite_sensor_read;
    composite->sensor.init = (int8_t (*)(Sensor*))composite_sensor_init;
    composite->sensor_count = 0;  // 初始传感器数量为0
    memset(composite->sensors, 0, sizeof(composite->sensors));  // 清空传感器数组
    return 0;
}

// 向组合传感器添加单个传感器
int8_t composite_sensor_add(CompositeSensor* composite, Sensor* sensor) {
    if (composite == NULL || sensor == NULL) return -1;  // 空指针判断
    // 传感器数量不超过上限(8个)
    if (composite->sensor_count < 8) {
        composite->sensors[composite->sensor_count++] = sensor;
        sensor->init(sensor);  // 新增传感器时,自动初始化(简化操作)
        return 0;
    }
    return -2;  // 传感器数量已满,返回错误码(便于排查)
}

// 组合传感器读数据(读取所有单个传感器的数据,合并返回)
SensorData composite_sensor_read(CompositeSensor* composite) {
    SensorData total_data = {0};
    if (composite == NULL) return total_data;  // 空指针判断
    
    // 循环读取每个传感器的数据,合并到total_data中(统一返回,简化调用)
    for (uint8_t i = 0; i < composite->sensor_count; i++) {
        SensorData data = composite->sensors[i]->read(composite->sensors[i]);
        // 合并数据(根据传感器类型,赋值对应字段,避免数据错乱)
        if (composite->sensors[i] == &temp_sensor) {
            total_data.temp = data.temp;
        } else if (composite->sensors[i] == &humi_sensor) {
            total_data.humi = data.humi;
        } else if (composite->sensors[i] == &acc_sensor) {
            total_data.acc_x = data.acc_x;
            total_data.acc_y = data.acc_y;
            total_data.acc_z = data.acc_z;
        }
    }
    return total_data;
}

// -------------------------- 装饰器模式实现(核心:动态叠加数据处理功能) --------------------------
// 1. 滤波装饰器(简单滑动平均滤波,去噪效果好、占用资源少,适配嵌入式)
static SensorData filter_process(Sensor* decorator, SensorData data) {
    // 模拟滑动平均滤波(实际项目中,可替换为FIR、IIR滤波代码,直接复用)
    static float temp_buf[5] = {0};  // 滤波缓冲区(5个采样点,可调整)
    static uint8_t buf_idx = 0;
    float sum = 0;
    
    // 温度滤波(其他字段可按需添加滤波逻辑)
    temp_buf[buf_idx++] = data.temp;
    if (buf_idx >= 5) buf_idx = 0;  // 缓冲区循环使用
    for (uint8_t i = 0; i < 5; i++) sum += temp_buf[i];
    data.temp = sum / 5;  // 取平均值,实现去噪
    
    return data;
}

// 2. 校准装饰器(修正系统误差,模拟校准逻辑,可替换为实际校准参数)
static SensorData calib_process(Sensor* decorator, SensorData data) {
    // 模拟校准:根据实际传感器误差,调整参数(实际需通过校准实验获取)
    data.temp += 0.2f;    // 温度校准(修正系统偏差)
    data.humi -= 1.0f;    // 湿度校准
    data.acc_x += 0.05f;  // 加速度X轴校准
    data.acc_y -= 0.05f;  // 加速度Y轴校准
    data.acc_z += 0.05f;  // 加速度Z轴校准
    return data;
}

// 3. 缩放装饰器(将原始数据转换为实际物理量,模拟逻辑,可替换为实际量程参数)
static SensorData scale_process(Sensor* decorator, SensorData data) {
    // 模拟缩放:根据传感器量程,转换为实际物理量(实际需结合传感器 datasheet 修改)
    data.temp *= 1.0f;    // 温度无需缩放(模拟)
    data.humi *= 1.0f;    // 湿度无需缩放(模拟)
    data.acc_x *= 1.0f;   // 加速度无需缩放(模拟,实际需根据量程调整)
    data.acc_y *= 1.0f;
    data.acc_z *= 1.0f;
    return data;
}

// 装饰器读数据函数(核心:先读原始数据,再执行处理逻辑)
static SensorData decorator_read(Sensor* decorator) {
    Decorator* dec = (Decorator*)decorator;
    if (dec == NULL || dec->decorated_sensor == NULL) {
        SensorData data = {0};
        return data;  // 空指针判断,避免崩溃
    }
    // 第一步:读取被装饰传感器的原始数据
    SensorData data = dec->decorated_sensor->read(dec->decorated_sensor);
    // 第二步:执行装饰器的处理逻辑(动态添加功能)
    return dec->process(decorator, data);
}

// 装饰器初始化函数(空实现,可根据需求扩展,如初始化处理缓冲区)
static int8_t decorator_init(Sensor* decorator) {
    return 0;
}

// 装饰器工厂(创建不同类型的装饰器,统一返回接口,便于调用)
Sensor* decorator_factory_create(DecoratorType type, Sensor* decorated_sensor) {
    static Decorator decorators[3] = {0};  // 静态装饰器实例,避免动态分配内存
    Decorator* dec = NULL;
    
    // 根据装饰器类型,绑定对应的处理函数
    switch(type) {
        case DECORATOR_FILTER:
            dec = &decorators[0];
            dec->process = filter_process;
            break;
        case DECORATOR_CALIB:
            dec = &decorators[1];
            dec->process = calib_process;
            break;
        case DECORATOR_SCALE:
            dec = &decorators[2];
            dec->process = scale_process;
            break;
        default:
            return NULL;  // 类型错误,返回空指针
    }
    
    // 初始化装饰器接口,绑定被装饰的传感器
    dec->sensor.init = decorator_init;
    dec->sensor.read = decorator_read;
    dec->sensor.private_data = NULL;
    dec->decorated_sensor = decorated_sensor;
    
    return (Sensor*)dec;
}

// -------------------------- 系统封装(对外提供统一接口,简化调用) --------------------------
// 初始化传感器系统(创建传感器、组合传感器、装饰器,一站式初始化)
int8_t sensor_system_init(CompositeSensor* composite) {
    if (composite == NULL) return -1;  // 空指针判断
    
    // 1. 初始化组合传感器
    composite_sensor_init(composite);
    
    // 2. 用简单工厂创建单个传感器,并添加到组合传感器中(自动初始化)
    Sensor* temp_sensor = sensor_factory_create(SENSOR_TEMP);
    Sensor* humi_sensor = sensor_factory_create(SENSOR_HUMI);
    Sensor* acc_sensor = sensor_factory_create(SENSOR_ACC);
    
    composite_sensor_add(composite, temp_sensor);
    composite_sensor_add(composite, humi_sensor);
    composite_sensor_add(composite, acc_sensor);
    
    return 0;
}

3.3 应用示例(main.c片段)

展示如何使用上述代码,实现传感器数据采集与处理,贴合嵌入式实际项目的调用逻辑,可直接复制到main.c中使用,只需根据自身硬件修改GPIO、串口相关配置。

c 复制代码
#include "stm32f1xx_hal.h"
#include "sensor_system.h"
#include <stdio.h>

// 定义组合传感器(核心对象,统一管理所有传感器)
CompositeSensor sensor_composite;

int main(void) {
    // 1. STM32系统初始化(HAL库初始化、时钟初始化、GPIO/串口初始化,按自身硬件修改)
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_USART1_UART_Init();
    
    // 2. 初始化传感器系统(组合+简单工厂,一站式初始化,无需手动操作单个传感器)
    if (sensor_system_init(&sensor_composite) != 0) {
        printf("传感器系统初始化失败!\r\n");
        while(1);  // 初始化失败,死循环,便于排查问题
    }
    
    // 3. 用装饰器给组合传感器添加数据处理功能(滤波+校准,可灵活组合、按需调整)
    Sensor* filtered_sensor = decorator_factory_create(DECORATOR_FILTER, (Sensor*)&sensor_composite);
    Sensor* calib_sensor = decorator_factory_create(DECORATOR_CALIB, filtered_sensor);
    
    // 4. 循环采集、处理传感器数据(嵌入式典型业务逻辑)
    while (1) {
        // 读取处理后的数据(经过滤波+校准,可直接用于后续显示、控制)
        SensorData data = calib_sensor->read(calib_sensor);
        
        // 打印数据(实际项目中,可替换为OLED显示、串口上传、存储等逻辑)
        printf("温度:%.1f℃  湿度:%.1f%%RH\r\n", data.temp, data.humi);
        printf("加速度:X=%.2fg, Y=%.2fg, Z=%.2fg\r\n", data.acc_x, data.acc_y, data.acc_z);
        printf("----------------------------------------\r\n");
        
        HAL_Delay(1000);  // 1秒采集一次,可根据项目需求修改采集周期
    }
}

3.4 代码说明(重点解读,嵌入式开发者必看)

  1. 接口统一:所有传感器、组合传感器、装饰器,都实现了Sensor结构体接口,外部调用时无需区分类型,大幅简化代码,降低调用难度;

  2. 避免动态分配:全程使用静态实例替代malloc,适配嵌入式资源有限的场景,提升代码运行效率和稳定性,避免内存碎片问题;

  3. 可扩展性强:新增传感器,只需3步(添加类型、实现init/read、修改工厂函数);新增数据处理功能,只需2步(添加类型、实现process),无需改动核心逻辑;

  4. 实操性强:代码中模拟的传感器采集、数据处理逻辑,可直接替换为实际驱动代码(如DS18B20时序、ADXL345 I2C读写),无需修改整体框架,快速落地项目。

四、实战验证:嵌入式场景测试与问题解决

将上述代码下载到嵌入式设备(以STM32F103为例),结合实际传感器进行实战测试,验证系统功能和稳定性;同时,针对嵌入式开发中可能出现的高频问题,给出具体的解决方案,避免大家踩坑,提升调试效率。

4.1 实战测试步骤(STM32为例,可直接落地)

  1. 硬件连接:将DS18B20(温度)、DHT11(湿度)、ADXL345(加速度)传感器,分别连接到STM32的对应GPIO引脚(与sensor_system.c中init函数的引脚一致),确保供电正常、接触良好;

  2. 代码修改:将sensor_system.c中,各个传感器的init和read函数,替换为实际的传感器驱动代码(如DS18B20的复位、读时序,ADXL345的I2C读写、寄存器配置等);

  3. 编译下载:将代码编译生成hex文件,下载到STM32单片机中,打开串口助手(波特率115200、8N1),连接单片机串口;

  4. 功能验证:观察串口输出的传感器数据,确认数据采集正常、滤波校准功能有效(如温度数据波动减小、数值接近实际环境值);

  5. 扩展测试:新增一种传感器(如气压传感器BMP280),按照3步新增流程操作,验证新增传感器时,是否只需修改工厂函数,无需改动其他代码,测试扩展可行性。

4.2 常见问题与解决方案(嵌入式高频痛点,快速排查)

问题1:传感器初始化失败,串口输出"初始化失败"

原因:① 传感器硬件连接错误(引脚接反、接触不良、供电不足);② 传感器驱动代码错误(如GPIO引脚配置错误、时序不正确、寄存器配置错误);③ 组合传感器添加传感器时,工厂函数返回空指针(传感器类型错误)。

解决方案:① 检查硬件连接,确认引脚与代码中一致,传感器供电正常(如DS18B20需寄生电源或外部3.3V供电);② 单独测试单个传感器的驱动代码,确保init函数返回0,可通过串口打印调试信息,排查时序或配置错误;③ 检查sensor_factory_create函数,确认传入的传感器类型正确,新增传感器时已添加对应的case分支。

问题2:采集到的数据异常(如温度为0、湿度为100%、加速度数据错乱)

原因:① 传感器读数据函数逻辑错误(时序不正确、数据解析错误、校验失败);② 装饰器处理逻辑错误(如滤波缓冲区未初始化、校准参数错误、缩放比例错误);③ 传感器未正常初始化,read函数未获取到有效数据。

解决方案:① 注释掉装饰器相关代码,直接读取组合传感器的数据,确认原始数据是否正常,排查传感器读数据函数的问题;② 检查传感器读数据函数的时序和解析逻辑,修正数据校验、转换错误;③ 检查装饰器的process函数,确认滤波缓冲区初始化正常、校准参数合理,缩放比例符合传感器量程。

问题3:新增传感器时,代码修改量过大,不符合扩展需求

原因:未严格遵循简单工厂模式的设计逻辑,新增传感器时,修改了除工厂函数外的其他代码(如组合传感器的读数据函数、接口结构体等),破坏了代码的低耦合性。

解决方案:严格遵循3步新增流程:① 在SensorType中添加传感器类型;② 实现该传感器的init和read函数,定义静态实例;③ 在sensor_factory_create函数中,添加对应的case分支,返回该传感器实例;无需修改组合传感器、装饰器的任何代码,确保扩展便捷。

问题4:代码占用内存过大,单片机运行卡顿、串口输出卡顿

原因:① 装饰器嵌套过多(如同时使用3个以上装饰器),导致每次采集都需执行多次处理逻辑,CPU占用率过高;② 组合传感器的传感器数量过多,循环读取数据耗时过长;③ 滤波等处理逻辑过于复杂(如高阶FIR滤波),占用过多CPU资源。

解决方案:① 减少不必要的装饰器嵌套,只保留核心的数据处理功能(如仅保留滤波+校准);② 合理控制组合传感器的传感器数量,避免过多传感器同时采集,可分批次采集;③ 简化数据处理逻辑(如用简单的滑动平均滤波替代高阶FIR滤波),提升执行效率,降低CPU占用。

五、总结与互动引导

到这里,我们已经完成了"组合+装饰器+简单工厂"三种设计模式在传感器数据采集与处理系统中的组合应用,从原理拆解、工程化分析,到C语言完整实现、实战验证、问题排查,全程贴合嵌入式C语言开发者(DSP C开发者、STM32开发者)的编码习惯,不搞空洞理论,重点突出实操性和落地性。

总结一下核心要点(记熟可直接用于项目开发):三种设计模式的组合,完美解决了嵌入式传感器系统的三大痛点------简单工厂模式实现传感器创建标准化,新增传感器无需大量修改代码,降低冗余;组合模式实现传感器集群管理统一化,简化多传感器调度逻辑,提升开发效率;装饰器模式实现数据处理功能插件化,灵活叠加处理功能,不改变核心逻辑,适配不同场景需求。三者组合使用,能让你的代码变得高可扩展、高可维护、低耦合,彻底摆脱"改一处崩全量"的困境,提升代码质量和开发效率。

对于嵌入式开发者来说,设计模式不是"花里胡哨"的技巧,而是解决实际项目痛点的实用工具------学会这三种模式的组合应用,不仅能应对传感器系统的开发需求,还能迁移到其他嵌入式项目中(如串口通信、LED控制、设备控制等),大幅提升你的编码能力和项目交付效率。

如果这篇博客对你的学习、项目开发有帮助,麻烦点赞+收藏+关注哦!关注我,后续会持续更新嵌入式C语言、设计模式、STM32/DSP实战、传感器驱动相关的实操教程,从原理到代码,手把手带你搞定嵌入式开发中的各类技术难点,少走弯路、提升效率。

最后,欢迎在评论区留言交流:你在嵌入式项目中,还用过哪些设计模式?在使用设计模式时,遇到过哪些棘手的问题?有哪些实用的编码技巧?我们一起探讨、共同进步,把嵌入式C语言编码的效率和质量拉满!

相关推荐
日拱一卒——功不唐捐2 小时前
01背包(C语言)
c语言
Hello World . .2 小时前
数据结构:二叉树(Binary tree)
c语言·开发语言·数据结构·vim
范纹杉想快点毕业2 小时前
嵌入式实时系统架构设计:基于STM32与Zynq的中断、状态机与FIFO架构工程实战指南,基于Kimi设计
c语言·c++·单片机·嵌入式硬件·算法·架构·mfc
茶本无香3 小时前
设计模式之十一—桥接模式:解耦抽象与实现的艺术
设计模式·桥接模式
短剑重铸之日3 小时前
《设计模式》第四篇:观察者模式
java·后端·观察者模式·设计模式
划破黑暗的第一缕曙光3 小时前
[数据结构]:6.二叉树链式结构的实现2
c语言·数据结构·二叉树
水饺编程3 小时前
第4章,[标签 Win32] :文本尺寸的度量
c语言·c++·windows·visual studio
浅念-3 小时前
C语言——自定义类型:结构体、联合体、枚举
c语言·开发语言·数据结构·c++·笔记·学习·html
mirror_zAI3 小时前
C语言中的sscanf用法详解
c语言·开发语言