嵌入式C语言中的”类“思想

在嵌入式单片机项目开发时,自己面对一块从未使用过的新芯片时,从0搭建与调节各种外设是一件非常繁琐的事情,有没有什么方法可以减轻这种繁琐的痛苦呢?我们知道在C++中,一个类通常包含成员变量(数据)和成员函数(方法),正式这种块状的结构可以使得该方法套用起来非常方便,那么本篇就这种块状套用的思想来研究下该方法思想在嵌入式C单片机开发的应用。
对于一个嵌C项目,要块状化的前提是将代码APP层与芯片底层解耦,并将联系APP和底层芯片中间夹杂的硬件抽象层切块化,例如IIC设备,当有挂载多IIC设备可以使用结构体存储用于存储数据(类似于C++的成员变量),函数指针作为结构体的成员,指向具体的操作函数(类似于C++的成员函数)
例如:假设我们有一个STM32项目,需要管理三种不同的I2C设备:
温湿度传感器 (SHT30)
加速度计 (ADXL345)
RTC时钟芯片 (DS3231)

1.定义通用接口结构体

通用设备接口结构体(模拟基类)

typedef struct {
    uint8_t device_addr;          // I2C设备地址
    const char* name;             // 设备名称
    int (*init)(void* self);      // 初始化方法
    int (*read_data)(void* self, uint8_t* buffer, size_t len);  // 读取数据方法
    int (*write_data)(void* self, const uint8_t* buffer, size_t len); // 写入数据方法
    int (*update)(void* self);    // 更新/刷新方法
} I2C_Device_t;

2.定义各设备特有的数据结构
SHT30 温湿度传感器特有数据

typedef struct {
    I2C_Device_t base;            // 继承基类接口
    float temperature;            // 当前温度
    float humidity;               // 当前湿度
    uint8_t measurement_mode;     // 测量模式
} SHT30_Device_t;

ADXL345 加速度计特有数据

typedef struct {
    I2C_Device_t base;            // 继承基类接口
    int16_t x_axis;               // X轴加速度
    int16_t y_axis;               // Y轴加速度
    int16_t z_axis;               // Z轴加速度
    uint8_t power_mode;           // 电源模式
} ADXL345_Device_t;

DS3231 RTC时钟芯片特有数据

typedef struct {
    I2C_Device_t base;            // 继承基类接口
    uint8_t seconds;
    uint8_t minutes;
    uint8_t hours;
    uint8_t day;
    uint8_t date;
    uint8_t month;
    uint8_t year;
    float temperature_compensation; // 温度补偿值
} DS3231_Device_t;

3.实现各设备的具体方法并实例化

实现int sht30_init(void* self) {...};等
然后创建具体的设备实例:

SHT30_Device_t sht30_dev = {
    .base = {
        .device_addr = 0x44,
        .name = "SHT30 Temperature/Humidity Sensor",
        .init = sht30_init,
        .read_data = sht30_read_data,
        .write_data = sht30_write_data,
        .update = sht30_update,
    },
    .temperature = 0.0f,
    .humidity = 0.0f,
    .measurement_mode = 0,
};

ADXL345_Device_t adxl345_dev = {
    .base = {
        .device_addr = 0x53,
        .name = "ADXL345 Accelerometer",
        .init = adxl345_init,
        .read_data = adxl345_read_data,
        .write_data = adxl345_write_data,
        .update = adxl345_update,
    },
    .x_axis = 0,
    .y_axis = 0,
    .z_axis = 0,
    .power_mode = 0,
};

DS3231_Device_t ds3231_dev = {
    .base = {
        .device_addr = 0x68,
        .name = "DS3231 RTC",
        .init = ds3231_init,
        .read_data = ds3231_read_data,
        .write_data = ds3231_write_data,
        .update = ds3231_update,
    },
    .seconds = 0,
    .minutes = 0,
    .hours = 0,
    .day = 0,
    .date = 0,
    .month = 0,
    .year = 0,
    .temperature_compensation = 0.0f,
};

设备列表,便于统一管理

I2C_Device_t* i2c_devices[] = {
    (I2C_Device_t*)&sht30_dev,
    (I2C_Device_t*)&adxl345_dev,
    (I2C_Device_t*)&ds3231_dev,
};

4.应用层调用示例
初始化所有I2C设备

#define DEVICE_COUNT  3
    for (int i = 0; i < DEVICE_COUNT; i++) {
        printf("Initializing device: %s\n", i2c_devices[i]->name);
        if (i2c_devices[i]->init(i2c_devices[i]) != 0) {
            printf("Failed to initialize %s\n", i2c_devices[i]->name);
        } else {
            printf("Successfully initialized %s\n", i2c_devices[i]->name);
        }
    }

在main或者timer中定时读取所有设备的数据:

        for (int i = 0; i < DEVICE_COUNT; i++) {
            printf("\nUpdating %s:\n", i2c_devices[i]->name);
            if (i2c_devices[i]->update(i2c_devices[i]) != 0) {
                printf("Failed to update %s\n", i2c_devices[i]->name);
            }
        }

或者单独读取操作等。

5.总结

封装性: 每个设备的数据和方法都被封装在各自的结构体中
多态性: 通过统一的接口可以操作不同类型的设备
可扩展性: 添加新设备只需定义新的结构体和方法,不影响现有代码
代码复用: 即便换芯片平台也只需要更改底层实现,而APP层逻辑可直接套用

posted on 2026-05-13 16:33  酸辣香菜  阅读(123)  评论(1)    收藏  举报

导航