HAL 映射:工业库的封装结构
06 HAL 映射:工业库的封装结构
C 语言 OOP 系列导航
本系列面向会 C 和单片机基础、刚进入企业项目的同学,从
struct封装一路讲到ops、多态和类 Linux 驱动框架。
学完 struct + me + static + init/deinit 后,再看 STM32 HAL,会发现它并不是一堆随便命名的函数。它只是把同样的设计放到了更大的硬件库里。
HAL GPIO 常见元素有这些:
1 | HAL_GPIO_Init(); |
以及这些类型和宏:
1 | GPIO_TypeDef |
一句话先懂:HAL 的 GPIO 模块,本质也是 struct 表示对象、指针指定对象、前缀组织接口、Init/DeInit 管生命周期。
先看映射关系
| HAL 元素 | 本系列里的对应物 |
|---|---|
GPIO_TypeDef |
用 struct 封装一组寄存器 |
GPIOA/GPIOB |
同一类型的不同实例 |
GPIO_TypeDef *GPIOx |
C 版 me 指针 |
HAL_GPIO_ |
模块前缀 / 类名 |
HAL_GPIO_Init() |
初始化 / 构造 |
.c 里的 helper |
static 私有实现 |
看懂这张表后,HAL 源码会从“一堆库函数”变成“一个个模块对象”。
GPIO_TypeDef:硬件寄存器对象
教学版可以这样理解:
1 | typedef struct { |
它和 LED 对象的形式很像:
1 | typedef struct { |
区别在于封装的数据不同:
| 对象 | 封装的数据 |
|---|---|
Led_t |
LED 的软件状态 |
GPIO_TypeDef |
GPIO 端口的一组寄存器 |
GPIOA/GPIOB:同一类型的多个实例
教学模型可以写成:
1 | GPIO_TypeDef gpioa_regs; |
真实芯片里,GPIOA 通常映射到固定外设地址。设计模型仍然一样:GPIOA、GPIOB、GPIOC 是同一类型的不同实例。
这和下面的 LED 多实例没有本质差别:
1 | Led_t red_led; |
GPIOx 就是 me 指针
HAL 函数签名通常把对象指针放在第一个参数:
1 | void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t pin, bool value) |
这里的 GPIOx 决定本次操作哪个 GPIO 端口。它和 Led_t *me 是同一类东西:
1 | int led_on(Led_t *me) |
| 教学 LED | HAL GPIO |
|---|---|
Led_t *me |
GPIO_TypeDef *GPIOx |
me->pin |
GPIOx->ODR |
led_on(&red) |
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, true) |
| 对象指针决定操作谁 | 端口指针决定操作哪个 GPIO |
HAL_GPIO_Init 是工业版构造函数
GPIO 初始化需要配置结构体:
1 | typedef struct { |
初始化函数会根据配置写寄存器:
1 | void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *init) |
它与 led_init() 是同一类函数:
| 初始化步骤 | LED 模块 | HAL GPIO |
|---|---|---|
| 检查参数 | me == NULL |
`GPIOx == NULL |
| 配置硬件 | gpio_init(pin) |
写 MODER/OTYPER 等寄存器 |
| 设置默认状态 | is_on = false |
配置模式、速度、上下拉 |
| 指定对象 | Led_t *me |
GPIO_TypeDef *GPIOx |
.h 与 .c 的分工也一样
HAL 头文件暴露公共接口:
1 | void HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *init); |
源文件内部保留私有辅助函数:
1 | static void set_2bit_field(uint32_t *reg, int pin, uint32_t value); |
公共接口与私有实现分离,这一点和我们自己写 LED 模块没有区别。
新人常见误区
| 误区 | 更准确的理解 |
|---|---|
| HAL 是纯过程式库,和 OOP 没关系 | 它大量使用对象指针和模块前缀 |
GPIOA 是普通变量 |
它通常是映射到固定寄存器地址的对象指针 |
GPIOx 只是随便起的名字 |
它就是这次操作的 GPIO 对象 |
| 只会调 HAL 就够了 | 看懂结构后,才能更稳地封装自己的驱动 |
C/C++ 对照
| C / HAL 写法 | OOP 理解 |
|---|---|
GPIO_TypeDef |
GPIO 类的数据结构 |
GPIOA |
GPIO A 实例 |
GPIO_TypeDef *GPIOx |
this 指针 |
HAL_GPIO_ 前缀 |
类名 / 命名空间 |
HAL_GPIO_Init() |
构造 / 初始化 |
HAL_GPIO_DeInit() |
析构 / 释放 |
static helper |
private 方法 |
嵌入式项目意义
看工业库源码时,可以按这些问题拆:
| 看源码时的问题 | 应该找什么 |
|---|---|
| 这个模块的数据在哪里? | 找核心 struct |
| 这次操作哪个对象? | 看第一个指针参数 |
| 公共接口有哪些? | 看头文件函数声明 |
| 内部细节在哪里? | 看 .c 的 static 函数 |
| 初始化做了什么? | 看 Init 函数 |
| 释放做了什么? | 看 DeInit 函数 |
这就是从“会调用库”到“看懂库设计”的分界线。
总结
HAL 库几千个函数看起来复杂,本质仍然是
struct封装数据、指针指定对象、前缀组织接口、Init/DeInit管生命周期。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Rvosy的小破站!










