12 完整框架:抽象层与硬件解耦
C 语言 OOP 系列导航
本系列面向会 C 和单片机基础、刚进入企业项目的同学,从 struct 封装一路讲到 ops、多态和类 Linux 驱动框架。
C 语言面向对象设计路线
封装:struct 数据与 me 指针
信息隐藏:static 与头文件边界
类的组织:前缀、init/deinit 与生命周期
数据归位:struct 成员与 static 变量
HAL 映射:工业库的封装结构
继承:struct 嵌套复用公共字段
函数指针:把行为作为参数
ops/vtable:行为表与对象绑定
多态与转型:base 指针与 dispatch
接口设计:ops 合同与默认实现
完整框架:抽象层与硬件解耦 ⇐ 当前位置
终章:自动注册与 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 层为底层硬件操作提供统一签名:
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) { return 0 ; }
驱动层调用 platform_xxx(),不直接绑定某个芯片厂商 API。
复杂度从 N×M 降到 N+M 假设有 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 层屏蔽芯片差异;换硬件不改应用,才叫真正解耦。