12 完整框架:抽象层与硬件解耦

C 语言 OOP 系列导航

本系列面向会 C 和单片机基础、刚进入企业项目的同学,从 struct 封装一路讲到 ops、多态和类 Linux 驱动框架。

  1. C 语言面向对象设计路线
  2. 封装:struct 数据与 me 指针
  3. 信息隐藏:static 与头文件边界
  4. 类的组织:前缀、init/deinit 与生命周期
  5. 数据归位:struct 成员与 static 变量
  6. HAL 映射:工业库的封装结构
  7. 继承:struct 嵌套复用公共字段
  8. 函数指针:把行为作为参数
  9. ops/vtable:行为表与对象绑定
  10. 多态与转型:base 指针与 dispatch
  11. 接口设计:ops 合同与默认实现
  12. 完整框架:抽象层与硬件解耦 ⇐ 当前位置
  13. 终章:自动注册与 Linux 内核 OOP 结构

前面讲的封装、继承、函数指针、ops、多态,单看都像局部技巧。真正放进项目里,它们会拼成一个目标:应用层不直接依赖具体硬件。

如果应用层直接写底层 API:

1
2
3
4
5
6
void app_run(void)
{
gpio_write(5, true);
delay_ms(100);
gpio_write(5, false);
}

LED 从 GPIO 换成 PWM 后,应用层就要改:

1
2
3
4
5
6
void app_run(void)
{
pwm_set_duty(1, 80);
delay_ms(100);
pwm_set_duty(1, 0);
}

再换成 I2C 灯带,又要改一次。

一句话先懂:完整框架的目标,是让应用层只调用抽象接口,硬件差异留在驱动层和 Platform 层。

先看问题:业务代码被硬件绑住

问题 后果
应用直接调 GPIO/PWM 换硬件就改业务
板级初始化散落 移植时找不到边界
驱动接口不统一 上层到处判断类型
平台 API 混进业务 PC 模拟和单片机移植困难

企业项目里,硬件改版、板卡复用、PC 仿真都很常见。抽象层就是为这些变化提前留出口。

框架分层

一个可维护的 LED 框架可以分成四层:

层级 责任
应用层 只表达业务动作
抽象接口层 提供 led_on/off/set_brightness
具体驱动层 GPIO LED、PWM LED、I2C LED
Platform 层 屏蔽芯片或系统差异

每一层只依赖下一层的稳定接口,不越级调用。

抽象接口层:LedBase

抽象层只描述 LED 的能力,不绑定任何硬件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct LedBase LedBase_t;

typedef struct {
int (*on)(LedBase_t *me);
int (*off)(LedBase_t *me);
int (*set_brightness)(LedBase_t *me, uint8_t brightness);
int (*deinit)(LedBase_t *me);
} LedOps_t;

struct LedBase {
const LedOps_t *ops;
uint8_t brightness;
bool is_on;
bool initialized;
};

int led_on(LedBase_t *me);
int led_off(LedBase_t *me);
int led_set_brightness(LedBase_t *me, uint8_t brightness);
int led_deinit(LedBase_t *me);

应用层只认识这些接口。

具体驱动层:GPIO LED

GPIO 驱动实现具体函数,并提供自己的 ops

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
typedef struct {
LedBase_t base;
uint8_t pin;
} GpioLed_t;

static int gpio_led_on(LedBase_t *base)
{
GpioLed_t *me = container_of(base, GpioLed_t, base);

me->base.is_on = true;
platform_gpio_write(me->pin, true);

return 0;
}

static int gpio_led_off(LedBase_t *base)
{
GpioLed_t *me = container_of(base, GpioLed_t, base);

me->base.is_on = false;
platform_gpio_write(me->pin, false);

return 0;
}

static const LedOps_t gpio_led_ops = {
.on = gpio_led_on,
.off = gpio_led_off,
.set_brightness = NULL,
.deinit = NULL,
};

初始化时绑定操作表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int gpio_led_init(GpioLed_t *me, uint8_t pin)
{
if (me == NULL) {
return -1;
}

me->base.ops = &gpio_led_ops;
me->base.brightness = 100;
me->base.is_on = false;
me->base.initialized = true;
me->pin = pin;

platform_gpio_init(pin);
platform_gpio_write(pin, false);

return 0;
}

具体驱动层:PWM LED

PWM LED 使用同一套抽象接口,但实现不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
typedef struct {
LedBase_t base;
uint8_t channel;
} PwmLed_t;

static int pwm_led_on(LedBase_t *base)
{
PwmLed_t *me = container_of(base, PwmLed_t, base);

me->base.is_on = true;
platform_pwm_set_duty(me->channel, me->base.brightness);

return 0;
}

static int pwm_led_set_brightness(LedBase_t *base, uint8_t brightness)
{
PwmLed_t *me = container_of(base, PwmLed_t, base);

me->base.brightness = brightness;

if (me->base.is_on) {
platform_pwm_set_duty(me->channel, brightness);
}

return 0;
}

抽象接口保持不变,硬件差异收敛在驱动内部。

Board 层:集中创建对象

板级代码负责说明“这块板子上有哪些设备”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
typedef struct {
GpioLed_t status_led;
PwmLed_t backlight_led;
} Board_t;

static Board_t g_board;

int board_init(void)
{
platform_init();

gpio_led_init(&g_board.status_led, 5);
pwm_led_init(&g_board.backlight_led, 1);

return 0;
}

LedBase_t *board_get_status_led(void)
{
return &g_board.status_led.base;
}

LedBase_t *board_get_backlight_led(void)
{
return &g_board.backlight_led.base;
}

应用层通过 board_get_xxx() 拿到抽象指针,不需要知道底层是 GPIO 还是 PWM。

App 层:完全脱离硬件

应用层不再出现硬件 API:

1
2
3
4
5
6
7
8
9
10
11
12
13
void app_run(void)
{
LedBase_t *status = board_get_status_led();
LedBase_t *backlight = board_get_backlight_led();

led_on(status);
led_set_brightness(backlight, 80);

delay_ms(100);

led_off(status);
led_set_brightness(backlight, 20);
}

这段代码没有 gpio_write()pwm_set_duty()i2c_write()。换硬件时,应用层不用动。

Platform 层:屏蔽芯片差异

Platform 层为底层硬件操作提供统一签名:

1
2
3
4
5
int platform_gpio_init(uint8_t pin);
int platform_gpio_write(uint8_t pin, bool value);
int platform_pwm_init(uint8_t channel);
int platform_pwm_set_duty(uint8_t channel, uint8_t duty);
void platform_delay_ms(uint32_t ms);

PC 模拟环境可以这样实现:

1
2
3
4
5
int platform_gpio_write(uint8_t pin, bool value)
{
printf("GPIO %u = %s\n", pin, value ? "HIGH" : "LOW");
return 0;
}

单片机环境可以这样实现:

1
2
3
4
5
int platform_gpio_write(uint8_t pin, bool value)
{
/* 调用芯片厂商 HAL 或直接写寄存器 */
return 0;
}

驱动层调用 platform_xxx(),不直接绑定某个芯片厂商 API。

复杂度从 N×M 降到 N+M

假设有 N 个应用场景、M 种硬件实现。应用直连硬件时,复杂度接近:

1
N × M

每个应用都要适配每种硬件。引入抽象层后,复杂度变成:

1
N + M

N 个应用只面向抽象接口,M 种硬件只实现抽象接口。

架构 新增硬件时
应用直连硬件 多个应用都要改
抽象接口 + ops 只新增一个驱动实现

新人常见误区

误区 更稳的理解
分层越多越专业 分层是为了隔离变化,不是为了好看
小项目一开始就上完整框架 小项目先写清楚模块边界即可
Board 层只是初始化 它还负责把具体设备转换成抽象接口
Platform 层可有可无 要做移植、仿真、换芯片时非常关键

C/C++ 对照

C 框架 C++ 框架
LedBase_t 抽象基类
LedOps_t 虚函数表
GpioLed_t/PwmLed_t 派生类
board_get_xxx() 依赖注入 / 工厂
platform_xxx() 硬件抽象层

总结

应用层只面向 LedBase_t,驱动层用 ops 适配硬件,Platform 层屏蔽芯片差异;换硬件不改应用,才叫真正解耦。