1. HAL概述:什么是硬件抽象层、HAL在嵌入式系统中的位置、为什么需要HAL
1.1 什么是硬件抽象层
硬件抽象层,英文叫 Hardware Abstraction Layer,简称 HAL。说白了,它就是在你写的应用代码和底层硬件寄存器之间,加了一层“翻译官”。
我刚开始做嵌入式那会儿,代码都是直接操作寄存器的。比如点亮一个 LED,我得翻数据手册找到 GPIO 的基地址,算偏移量,然后对着寄存器位操作。代码写出来大概是这样的:
// 直接操作寄存器
#define GPIOB_BASE 0x40010C00
#define GPIOB_CRL (*(volatile uint32_t *)(GPIOB_BASE + 0x00))
#define GPIOB_BSRR (*(volatile uint32_t *)(GPIOB_BASE + 0x10))
void led_on(void) {
GPIOB_CRL |= (1 << 20); // 配置 PB5 为推挽输出
GPIOB_BSRR = (1 << 5); // 拉高 PB5
}
这段代码在 STM32F103 上跑得挺好。但换到 GD32 或者 AT32 上呢?寄存器地址不一样了,位定义也可能变了。你得全部重写。这就是没有 HAL 的痛点。
加了 HAL 之后,代码变成这样:
// 使用 HAL 抽象层
void led_on(void) {
hal_gpio_set_pin(GPIO_PORT_B, GPIO_PIN_5);
hal_gpio_write_pin(GPIO_PORT_B, GPIO_PIN_5, GPIO_LEVEL_HIGH);
}
你看,hal_gpio_set_pin 这个函数,不管底层是 STM32 还是 GD32,调用方式完全一样。换芯片时,只需要重新实现这个函数内部的寄存器操作就行。应用层代码,一行都不用改。
1.2 HAL在嵌入式系统中的位置
嵌入式系统的软件架构,从下往上大概分这么几层:
- 硬件层:芯片、外设、引脚、中断控制器……这些物理实体。
- HAL 层:直接操作寄存器的代码,封装成标准接口。
- 驱动层:基于 HAL 接口,实现具体设备的功能逻辑。
- 中间件/OS 层:RTOS、文件系统、协议栈等。
- 应用层:业务逻辑,用户功能。
我画了一张图,帮你直观理解 HAL 的位置:
从这张图可以清楚看到,HAL 层紧贴着硬件,是所有上层代码的基石。它把硬件细节“包起来”,上层只管调用接口,不用关心底层怎么实现。
hal_gpio.h、hal_uart.h。实现文件则按芯片平台分目录存放,比如 stm32/、gd32/。这样换平台时,只需要换实现文件,接口头文件完全不动。
1.3 为什么需要HAL
这个问题,我当年也问过自己。直接操作寄存器多快啊,加一层抽象不是多此一举吗?
后来踩了几个坑,才明白 HAL 的价值。我总结了几点:
| 痛点 | 没有HAL的情况 | 有HAL的情况 |
|---|---|---|
| 芯片换型 | 所有寄存器操作代码重写,工作量巨大 | 只需重写 HAL 实现,应用层代码零改动 |
| 代码复用 | 每个项目从零开始,重复造轮子 | HAL 接口可跨项目复用,积累自己的库 |
| 团队协作 | 每个人写自己的寄存器操作,风格混乱 | 统一接口规范,多人并行开发互不干扰 |
| 测试验证 | 必须依赖真实硬件才能测试 | 可以 mock HAL 接口,在 PC 上做单元测试 |
| 文档维护 | 寄存器操作散落在代码各处,难以维护 | HAL 接口本身就是一份活文档 |
举个例子。我曾经接手过一个项目,用的是某国产 MCU,代码里全是直接操作寄存器。后来客户要求换成另一家芯片,因为供货问题。我一看代码,好家伙,光 GPIO 操作就散落在 20 多个文件里,每个文件里都是 *(volatile uint32_t *)0xXXXX 这种写法。改了一个星期,还改出了好几个 bug。
如果当时有 HAL 层,我只需要重新实现 hal_gpio_init()、hal_gpio_write() 这几个函数,一天就能搞定。
1.4 HAL的设计原则
好的 HAL 设计,应该遵循几个基本原则:
- 接口稳定:一旦发布,接口尽量不变。变的话要做好版本管理。
- 功能完整:覆盖常用外设的基本操作,比如 GPIO、UART、SPI、I2C、定时器等。
- 性能可接受:抽象层不能太厚,关键路径上允许“透传”到寄存器。
- 易于移植:实现文件与接口文件分离,换平台时只改实现。
- 可测试:接口设计要方便 mock,支持在 PC 环境做单元测试。
我个人习惯,在设计 HAL 接口时,会先写一个 hal_template.h,里面只放函数声明和注释,不写实现。然后针对每个平台,创建对应的 hal_stm32.c、hal_gd32.c 等文件。这样架构清晰,后期维护也方便。