1. 驱动层设计哲学:为什么需要驱动层?
做嵌入式开发这些年,我见过太多人上来就对着寄存器一顿操作。代码能跑,但换个芯片、换个平台,全部重写。说白了,这就是没有驱动层的后果。
今天咱们聊聊驱动层设计哲学。不扯虚的,就讲清楚三个问题:为什么需要驱动层?分层架构到底在分什么?设计原则有哪些?
1.1 没有驱动层的代码长什么样?
先看一段我早期项目里的代码。那时候刚入行,觉得“封装”就是多此一举。
// 直接操作寄存器点亮LED
void led_on(void) {
// 假设LED接在PA0
GPIOA->BSRR = (1 << 0); // 置位
}
void led_off(void) {
GPIOA->BRR = (1 << 0); // 复位
}
// 延时函数
void delay_ms(uint32_t ms) {
for(uint32_t i = 0; i < ms * 1000; i++) {
__NOP();
}
}
这段代码有什么问题?
- 硬件耦合太紧:GPIOA、BSRR、BRR 全是 STM32 特有的寄存器
- 不可移植:换到 GD32、AT32,甚至换个引脚,全得改
- 不可测试:没有硬件,这段代码根本跑不了
- 业务逻辑和硬件混在一起:哪天想改成 PWM 控制亮度,得动整个函数
我曾经在一个量产项目里吃过这个亏。产品卖得好,客户说要换国产芯片降成本。结果一算,光驱动层重写就要两个月。老板脸都绿了。从那以后,我再也不敢把硬件操作和业务逻辑写在一起。
1.2 分层架构到底在分什么?
分层架构的核心,说白了就是把“做什么”和“怎么做”分开。
我习惯把嵌入式软件分成三层:
| 层次 | 职责 | 依赖关系 | 典型内容 |
|---|---|---|---|
| 应用层 | 业务逻辑、状态机、协议解析 | 只调用驱动层接口 | 按键处理、数据显示、通信协议 |
| 驱动层 | 硬件抽象、外设操作、资源管理 | 调用HAL/LL层,向上提供统一接口 | GPIO、UART、SPI、I2C、定时器 |
| 硬件抽象层(HAL) | 寄存器操作、芯片相关初始化 | 直接操作硬件 | 寄存器定义、中断向量、时钟配置 |
你想想看,应用层需要知道LED是接在PA0还是PB5吗?不需要。它只关心“调用led_on()就能亮”。这就是分层的目的——隔离变化。
1.3 驱动层设计原则
做了十几个项目后,我总结出几条铁律。嗯,每条都是用加班换来的。
原则一:接口稳定,实现可变
驱动层对外暴露的接口,一旦确定就不要轻易改。内部实现随便折腾,换芯片、换引脚、换外设库,都不影响上层代码。
// 好的驱动接口 —— 稳定
void led_init(void);
void led_on(void);
void led_off(void);
void led_toggle(void);
// 内部实现可以随便换
// 方案A:直接操作寄存器
// 方案B:使用HAL库
// 方案C:换成PWM控制亮度
原则二:单一职责
一个驱动文件只负责一种外设。GPIO驱动不要掺和定时器的事,UART驱动不要管SPI。
我个人习惯:每个外设一个.c/.h文件。比如led.c/led.h、uart.c/uart.h、spi.c/spi.h。文件多了不怕,结构清晰比什么都重要。
原则三:可配置性
驱动层应该支持通过配置来适配不同硬件。不要硬编码引脚号、波特率这些参数。
// led_cfg.h —— 配置头文件
#ifndef LED_CFG_H
#define LED_CFG_H
typedef struct {
uint8_t port; // GPIO端口号
uint8_t pin; // GPIO引脚号
uint8_t active_level; // 有效电平:0-低电平有效,1-高电平有效
} led_cfg_t;
// 板级配置
#define LED_RED_CFG {GPIOA, 0, 1}
#define LED_GREEN_CFG {GPIOA, 1, 1}
#define LED_BLUE_CFG {GPIOB, 5, 0}
#endif
原则四:错误处理不吞没
驱动层遇到错误,不能自己吃掉。要返回错误码,或者通过回调通知上层。
// 好的做法:返回错误码
int uart_send(uint8_t *data, uint16_t len);
// 坏的做法:死等或者直接忽略
void uart_send(uint8_t *data, uint16_t len) {
while(!(USART1->SR & USART_SR_TXE)); // 死等
// 如果硬件挂了,这里就永远卡死
}
1.4 驱动层架构图
下面这张图,是我每次做新项目都会贴在墙上的。它清楚地展示了各层之间的关系。
1.5 什么时候该做驱动层封装?
有人问我:“做个流水灯也要封装驱动层吗?是不是小题大做?”
我的回答是:看项目规模。
- 学习验证、一次性Demo:直接操作寄存器,怎么快怎么来
- 产品级项目、可能迭代:必须做驱动层封装,这是投资
- 团队协作项目:不做封装,后期维护成本指数级上升
我的判断标准:如果这个代码三个月后还要看,或者要给别人看,那就值得做驱动层封装。你想想看,三个月后的你,其实就是另一个人。别让未来的自己骂现在的你。
1.6 小结
驱动层设计哲学,说到底就一句话:把不变的接口留给上层,把变化的实现藏在下层。
这一章我们讲了:
- 没有驱动层的代码有多坑
- 三层架构的职责划分
- 四条核心设计原则
- 什么时候该做封装