1、驱动分层思想:为什么需要驱动层?硬件抽象层(HAL)与驱动层(Driver)的区别

做嵌入式开发这些年,我见过太多工程师直接把寄存器操作写在业务逻辑里。嗯,刚开始确实爽,代码跑得飞快。但等到要换芯片、要移植、要调试的时候——那叫一个欲哭无泪。

说白了,驱动分层就是给代码留条后路。你想想看,产品不可能永远不换MCU,也不可能永远不改硬件。没有分层,每次改动都得翻遍整个工程。

为什么需要驱动层?

先看一个反面教材。这是我早期项目里真实出现过的代码:

// 直接操作寄存器点亮LED
void set_led_on(void) {
    GPIOA->BSRR = (1 << 5);  // PA5输出高
}

void set_led_off(void) {
    GPIOA->BRR = (1 << 5);   // PA5输出低
}

这段代码有什么问题?

  • 硬件耦合太紧:换到STM32F4系列,GPIO寄存器结构体都不一样
  • 无法复用:另一个项目用PB0控制LED,得重写
  • 调试困难:想加个逻辑分析仪抓波形?得改业务代码

我在一个车载项目里吃过这个亏。当时产品要升级MCU,从STM32F103换到GD32F303。你以为只是改个启动文件?错了。所有外设驱动全部重写,整整折腾了两周。从那以后,我强制团队所有代码必须分层。

核心原则:业务逻辑永远不要直接操作寄存器。驱动层就是业务和硬件之间的防火墙。

硬件抽象层(HAL) vs 驱动层(Driver)

这两个概念经常被混用,但它们的职责完全不同。我习惯这样区分:

维度 硬件抽象层 (HAL) 驱动层 (Driver)
定位 芯片厂商提供的外设封装 项目自定义的设备操作接口
抽象粒度 寄存器级 → API级 API级 → 功能级
可移植性 同系列芯片可移植 跨平台、跨芯片
典型内容 GPIO_SetBits, USART_SendData Led_On, Sensor_ReadTemp
谁维护 芯片厂商或MCU部门 应用开发团队

举个例子你就明白了。HAL层封装了寄存器的读写,比如:

// HAL层:STM32标准库风格
void HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState) {
    if (PinState != GPIO_PIN_RESET) {
        GPIOx->BSRR = GPIO_Pin;
    } else {
        GPIOx->BRR = GPIO_Pin;
    }
}

而驱动层在此基础上再做一层封装:

// 驱动层:面向应用
void Led_On(void) {
    HAL_GPIO_WritePin(LED_GPIO_PORT, LED_PIN, GPIO_PIN_SET);
}

void Led_Off(void) {
    HAL_GPIO_WritePin(LED_GPIO_PORT, LED_PIN, GPIO_PIN_RESET);
}

看到区别了吗?HAL层关心「怎么操作寄存器」,驱动层关心「这个设备怎么用」。

我的习惯:HAL层尽量用厂商提供的库,别自己造轮子。驱动层必须自己写,因为只有你知道设备在项目中怎么用。

分层架构的核心逻辑

下面这张图是我做项目时一直贴在白板上的分层结构:

嵌入式软件分层架构 应用层 (Application) 业务逻辑、任务调度、状态机 驱动层 (Driver Layer) Led_Driver、Sensor_Driver、Flash_Driver 硬件抽象层 (HAL) GPIO、UART、SPI、I2C、TIM 硬件层 (Hardware) MCU、外设芯片、传感器 依赖方向 ↓ 向下依赖 应用层 → 调用驱动层 驱动层 → 调用HAL层 HAL层 → 操作寄存器 硬件层 被操作 抽象程度递增

这个结构里有个关键点:依赖方向是单向的。应用层只能调用驱动层,驱动层只能调用HAL层。绝对不能出现应用层跳过驱动层直接调HAL的情况。

我曾经踩过的坑:有个同事图省事,在应用层直接调了HAL_GPIO_WritePin来控制电机。后来电机驱动芯片换了,HAL接口变了,结果改了20多处调用点。如果当时走驱动层,只需要改Driver文件夹里的一个文件。

什么时候该分层?

不是所有项目都需要严格分层。我一般按这个标准判断:

  • 原型验证(1-2周):不分层,快速出活
  • 小批量产品(1000台以内):简单分层,至少把外设驱动独立出来
  • 量产产品(万级以上):严格分层,驱动层、HAL层、应用层完全解耦
  • 平台化产品(多项目共用):必须分层,而且要定义好接口规范

说白了,分层是有成本的。多一层封装就多一层代码,多一层调试时间。但你要算总账——维护成本、移植成本、团队协作成本。我见过太多项目死在「先跑起来再说」这句话上。

分层的实际收益

拿我最近做的一个传感器项目举例。项目用了3种温度传感器:DS18B20、SHT30、NTC热敏电阻。如果没有驱动层,应用层得写3套读取逻辑。有了驱动层之后:

// 驱动层统一接口
typedef struct {
    int16_t (*init)(void);
    int16_t (*read_temp)(float *temp);
    int16_t (*deinit)(void);
} temp_driver_t;

// 应用层只需要调用
temp_driver_t *drv = get_temp_driver(TEMP_SENSOR_TYPE);
drv->read_temp(&temperature);

应用层根本不需要知道底层用的是哪种传感器。换传感器?改一行配置就行。这就是分层的威力。

一句话总结:HAL层解决「怎么操作硬件」的问题,驱动层解决「怎么使用设备」的问题。把这两层分开,你的代码才能活得更久。