12 完整框架:换硬件不改应用

C 语言 OOP 系列导航

  1. C语言你真的会封装吗?
  2. 三个LED写了三份代码?
  3. 同事改一行,LED全乱了
  4. 手搓class:前缀、init/deinit和生命周期
  5. 你的全局变量,该死了
  6. 工业库也是同一套封装套路
  7. struct嵌套消灭复制粘贴
  8. 写死的函数怎么换
  9. 把一组函数指针装进对象
  10. 一个指针管所有LED
  11. 虚函数不实现会怎样
  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,应用层就要改;再换成 I2C 灯带,又要改。这暴露了一个事实:业务代码被硬件绑住了。

框架分层

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

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

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

抽象接口层

抽象层只描述 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

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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,
};

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 层:完全脱离硬件

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 模拟时可以 printf("GPIO %u HIGH\n", pin),单片机上可以调用芯片厂商 HAL。驱动层只调 platform_xxx(),不直接绑定某个芯片。

变化从 N×M 降到 N+M

假设有 N 个应用场景、M 种硬件实现。应用直连硬件时,复杂度接近 N×M — 每个应用都要适配每种硬件。引入抽象层后变成 N+M — N 个应用只面向抽象接口,M 种硬件只实现抽象接口。

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

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

新人常见误区

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

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