c++ 硬件抽象层hal c++嵌入式系统如何设计hal

夜丽酱_3315

夜丽酱_3315

2026-03-24

272人浏览

原创

hal接口必须用纯虚类定义,所有函数声明为virtual void func() = 0;禁止基类实现、硬件头文件包含及非pod成员;bsp需隔离封装,hal不管理资源与生命周期;编译期配置优先用constexpr模板,运行时行为保留虚函数。

c++ 硬件抽象层hal c++嵌入式系统如何设计hal

HAL 接口必须用纯虚类定义,不能带实现

嵌入式 C++ 的 HAL 本质是解耦驱动与业务逻辑的契约,不是工具库。一旦在 HAL 基类里写具体实现(比如默认空函数体、std::cout 日志、或调用底层寄存器操作),就等于把硬件细节泄漏进抽象层,后续换芯片或仿真时必然崩。

常见错误现象:HAL_SPI::transfer() 默认实现里直接调了 HAL_SPI_TransmitReceive()(STM32 HAL 库函数),导致上层代码编译通过但无法跨平台;或者加了 virtual ~HAL_SPI() = default; 却忘了加 virtual 到所有接口函数,子类重写后多态失效。

  • 所有接口函数必须声明为 virtual void init() = 0; 形式,强制派生类实现
  • 析构函数必须是 virtual 且纯虚或带空实现(virtual ~HAL_SPI() = default; 可接受)
  • 禁止在基类中包含硬件头文件(如 "stm32f4xx_hal.h" 或 "regmap.h"),只允许前向声明
  • 成员变量只能是 POD 类型(bool, uint8_t, void*),禁用 std::string、std::vector 等动态分配类型

HAL 实现类要隔离 BSP,避免 #include 硬件 SDK

很多人把 HAL 实现直接塞进 hal_stm32f4.cpp,里面大段 #include "stm32f4xx_hal.h" 和 __HAL_RCC_GPIOA_CLK_ENABLE(),结果导致整个应用层被绑死在某颗芯片上,连单元测试都写不了。

正确做法是让 HAL 实现只依赖「最小 BSP 接口」——比如一个独立的 bsp_gpio.hpp,它只暴露 bsp_gpio_init()、bsp_gpio_write() 这种无 SDK 依赖的 C 风格函数,由 BSP 层自己去对接 HAL 库或寄存器操作。

  • BSP 层提供 C 函数(非类),不暴露任何芯片头文件给 HAL 实现
  • HAL 实现文件(如 hal_spi_stm32.cpp)只 include 自己的 hal_spi.hpp 和 bsp_spi.hpp
  • 如果要用 CMSIS 或厂商 HAL,必须封装在 BSP 层内部,HAL 实现里看不到 HAL_SPI_StateTypeDef 这类类型
  • 仿真时只需替换 BSP 实现(比如用 bsp_spi_mock.cpp 返回固定数据),HAL 和业务层完全不动

别在 HAL 里做资源管理,交给上层或 RAII 封装

HAL_UART::open() 或 HAL_I2C::acquire_bus() 这类函数看似方便,实则埋雷:谁负责关?什么时候关?并发访问怎么同步?HAL 不该承担设备生命周期职责,它只管“怎么发”“怎么读”,不管“谁来用”“用多久”。

C++14
C++14

C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。

下载

典型踩坑:多个线程同时调 hal_i2c->transfer(),HAL 内部用静态 mutex 锁总线,结果性能卡死;或者 HAL_ADC::start_conversion() 启动 DMA 后没提供 cancel 接口,超时处理只能硬复位。

  • HAL 接口应设计为无状态、无副作用的“操作函数”,如 read(uint8_t* buf, size_t len),不隐式初始化/释放硬件
  • 资源获取(如 GPIO 引脚占用、I2C 总线仲裁)放在单独的 Driver 或 Device 类里,用 RAII 管理(构造即 acquire,析构即 release)
  • 中断回调必须是纯函数指针或 std::function 参数,HAL 基类不持有回调对象,避免循环引用或生命周期错配
  • 对 DMA、中断等异步能力,HAL 只提供注册接口(set_rx_callback(void (*cb)(const uint8_t*, size_t))),不参与调度逻辑

constexpr + 模板参数可替代部分运行时 HAL 多态

不是所有抽象都需要虚函数。像 SPI 模式(CPOL/CPHA)、UART 波特率、GPIO 上拉配置这些编译期已知的参数,硬用虚函数+运行时分支,既增加 ROM 占用,又削弱内联机会。

例如 template<uint32_t br> struct UART_HAL : HAL_UART</uint32_t>,波特率作为模板参数,生成的 write() 可直接展开寄存器配置指令,比 virtual write(const uint8_t*, size_t, uint32_t br) 快且确定。

  • 适合固定配置的外设(如板载温感 I2C 地址固定为 0x40),用模板特化实现 TempSensorHAL
  • 运行时可变参数(如用户通过串口输入改变 ADC 采样率)仍走虚函数路径,二者混用不冲突
  • 注意模板实例膨胀:别把整个 HAL 接口全模板化,只对真正影响代码生成的参数模板化
  • Clang/GCC 下 -flto 能帮优化掉未使用的模板实例,但裸机环境慎用,链接脚本可能不兼容

HAL 的难点从来不在“怎么写虚函数”,而在于划清三道线:HAL 和 BSP 的头文件边界、HAL 和 Driver 的责任边界、编译期配置和运行时行为的表达边界。越早用静态分析(比如 cppcheck --suppress=unusedFunction 扫虚函数实现)和链接脚本检查符号依赖,越不容易在量产前夜发现 HAL_I2C 间接拖进了 libstdc++.a。

C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!

相关文章

c++速学教程(入门到精通)
c++速学教程(入门到精通)

c++怎么学习?c++怎么入门?c++在哪学?c++怎么学才快?不用担心,这里为大家提供了c++速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

c++

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.02

6159

3

java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

2025.11.27

335

14

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

2023.09.20

2158

7

javascriptvoid(o)怎么解决
javascriptvoid(o)怎么解决

javascriptvoid(o)的解决办法:1、检查语法错误;2、确保正确的执行环境;3、检查其他代码的冲突;4、使用事件委托;5、使用其他绑定方式;6、检查外部资源等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2023.11.23

636

5

java中void的含义
java中void的含义

本专题整合了Java中void的相关内容,阅读专题下面的文章了解更多详细内容。

2025.11.27

351

13

C++ 智能指针与现代内存管理
C++ 智能指针与现代内存管理

深入讲解 C++ 现代内存管理的核心工具——智能指针,涵盖 unique_ptr 独占所有权语义、shared_ptr 引用计数机制与循环引用问题、weak_ptr 弱引用的应用场景、make_unique/make_shared 工厂函数的性能优势、自定义删除器的编写、RAII 资源管理思想的实践,以及从裸指针迁移到智能指针的重构策略,帮助开发者编写安全无泄漏的现代 C++ 代码。

2026.04.23

359

31

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

2023.10.19

3128

3

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

2025.10.17

4809

12

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2025.12.29

3769

9

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C++ Core Guidelines
C++ Core Guidelines

共0课时 | 0人学习

C++ Reference
C++ Reference

共0课时 | 0人学习

C++ 官方标准说明
C++ 官方标准说明

共0课时 | 0人学习