当前位置: 首页 > news >正文

硬件抽象层 (HAL, Hardware Abstraction Layer)的简单使用示例

需求一

现在需要向一个地址写值,但是有两种硬件设备,他们的写入的地址都是不一样,写的值都是一样的。

分析

  1. 最简单的就是一个大的if else 用于判断不同的设备类型
  2. 扩展性,如果后续设备种类增加,写入的地址容易改变,写入的值也容易变,在if else中就不是很好修改。在switch case会清晰些,但是代码的行数会非常大。

考虑到以后的扩展,对硬件操作进行一个简单的封装。

目标:

业务层用统一 API 读写“逻辑寄存器”,不接触物理地址;HAL 负责把 (设备, 寄存器ID) → 真实地址。

要点

  1. 用二维表 device_regs[dev][reg] 做映射。

  2. 统一的 RegId 集合;两台设备的寄存器数量一致。

  3. 编译期静态断言 + 运行时边界检查,避免越界。

代码

#include <stdint.h>
#include <stdio.h>typedef enum { DEV_A, DEV_B, DEV_MAX } DeviceType;
typedef enum { REG_A, REG_B, REG_C, REG_COUNT } RegId;/* --- 模拟寄存器空间(真实环境换成 MMIO 地址) --- */
static uint8_t devA_space[REG_COUNT] = {0};
static uint8_t devB_space[REG_COUNT] = {0};typedef uint8_t *reg_ptr_t;/* 设备 → 逻辑寄存器 → 地址 */
static const reg_ptr_t device_regs[DEV_MAX][REG_COUNT] = {[DEV_A] = {&devA_space[0], &devA_space[1], &devA_space[2]},[DEV_B] = {&devB_space[0], &devB_space[1], &devB_space[2]},
};_Static_assert(DEV_MAX == 2, "update table when adding devices");
_Static_assert(REG_COUNT == 3, "update table when adding regs");/* HAL API */
int hal_write(DeviceType dev, RegId reg, uint8_t val) {if (dev >= DEV_MAX || reg >= REG_COUNT) return -1;volatile uint8_t *addr = device_regs[dev][reg];*addr                  = val;return 0;
}
int hal_read(DeviceType dev, RegId reg, uint8_t *out) {if (dev >= DEV_MAX || reg >= REG_COUNT || !out) return -1;volatile uint8_t *addr = device_regs[dev][reg];*out                   = *addr;return 0;
}/* demo */
static void show(DeviceType d) {for (int i = 0; i < REG_COUNT; i++)printf("D%d R%d=0x%02x  ", d, i, *device_regs[d][i]);puts("");
}
int main(void) {puts("Scene1:");show(DEV_A);show(DEV_B);hal_write(DEV_A, REG_B, 0x11);hal_write(DEV_B, REG_C, 0x22);show(DEV_A);show(DEV_B);return 0;
}

运行结果

➜  week-two ./hal_demoV2                                              
Scene1:
D0 R0=0x00  D0 R1=0x00  D0 R2=0x00  
D1 R0=0x00  D1 R1=0x00  D1 R2=0x00  
D0 R0=0x00  D0 R1=0x11  D0 R2=0x00  
D1 R0=0x00  D1 R1=0x00  D1 R2=0x22 

这里是对寄存器地址进行了抽象,不同设备写的寄存器地址不一样。

需求二

还是一样的写地址,但是另外一种设备,他只有三个地址供读写(初始的是5个),他们的写入的地址都是不一样,写的值都是一样的。

分析

继续保持统一 API,但不同设备的寄存器个数不一样,访问不存在的寄存器要安全失败。

要点

  1. 每个设备独立一张表 + reg_count。

  2. HAL 在访问前检查 reg < reg_count。

  3. 保留统一的 RegId 超集,但不是所有设备都支持所有 ID。

代码

#include <stdint.h>
#include <stdio.h>typedef enum { DEV_A2, DEV_B2, DEV2_MAX } DeviceType2;
/* 统一超集:A 有 A..E(5个),B 只有 A..C(3个) */
typedef enum { REG2_A, REG2_B, REG2_C, REG2_D, REG2_E, REG2_MAX } RegId2;/* 模拟寄存器空间 */
static uint8_t devA2_space[5] = {0};
static uint8_t devB2_space[3] = {0};typedef uint8_t *reg_ptr_t;static const reg_ptr_t devA2_regs[] = {&devA2_space[0], &devA2_space[1], &devA2_space[2],&devA2_space[3], &devA2_space[4]};
static const reg_ptr_t devB2_regs[] = {&devB2_space[0], &devB2_space[1], &devB2_space[2]};typedef struct {const reg_ptr_t *regs;size_t reg_count;
} DeviceMap2;
static const DeviceMap2 devmap2[DEV2_MAX] = {[DEV_A2] = {devA2_regs, sizeof(devA2_regs) / sizeof(devA2_regs[0])}, // 5[DEV_B2] = {devB2_regs, sizeof(devB2_regs) / sizeof(devB2_regs[0])}, // 3
};int hal2_write(DeviceType2 dev, RegId2 reg, uint8_t val) {if (dev >= DEV2_MAX) return -1;if (reg >= devmap2[dev].reg_count) return -2; // 关键:越界/不支持volatile uint8_t *addr = devmap2[dev].regs[reg];*addr                  = val;return 0;
}
int hal2_read(DeviceType2 dev, RegId2 reg, uint8_t *out) {if (dev >= DEV2_MAX || !out) return -1;if (reg >= devmap2[dev].reg_count) return -2;volatile uint8_t *addr = devmap2[dev].regs[reg];*out                   = *addr;return 0;
}/* demo */
static void show2(DeviceType2 d) {for (size_t i = 0; i < devmap2[d].reg_count; i++)printf("D2-%d R%zu=0x%02x  ", d, i, *devmap2[d].regs[i]);puts("");
}
int main(void) {puts("Scene2:");show2(DEV_A2);show2(DEV_B2);hal2_write(DEV_A2, REG2_D, 0x55);           // A 支持 Dhal2_write(DEV_B2, REG2_C, 0x66);           // B 支持 Cif (hal2_write(DEV_B2, REG2_D, 0x77) < 0) { // B 不支持 Dputs("B2 does not support REG_D (safe fail).");}show2(DEV_A2);show2(DEV_B2);return 0;
}

结果

Scene2:
D2-0 R0=0x00  D2-0 R1=0x00  D2-0 R2=0x00  D2-0 R3=0x00  D2-0 R4=0x00  
D2-1 R0=0x00  D2-1 R1=0x00  D2-1 R2=0x00  
B2 does not support REG_D (safe fail).
D2-0 R0=0x00  D2-0 R1=0x00  D2-0 R2=0x00  D2-0 R3=0x55  D2-0 R4=0x00  
D2-1 R0=0x00  D2-1 R1=0x00  D2-1 R2=0x66  
[1] + Done                       "/usr/bin/gdb" --interpreter=mi --tty=${DbgTerm} 0<"/tmp/Microsoft-MIEngine-In-m45bp0uc.udc" 1>"/tmp/Microsoft-MIEngine-Out-jvqrijcw.44u"

需求三

两种设备,寄存器数量不同;同一个“逻辑操作”(开启/关闭/复位)对应不同寄存器与不同值(甚至写多步)。比如说,对于开启设备的操作,设备A只需要向0x11地址写入aa,设备B需要先向0xa2地址写入a0,再向0x15写入c1。

目标

业务层只说“开启/关闭”,HAL 负责把逻辑操作翻译成设备特定的寄存器写序列与取值。

代码

#include <stdint.h>
#include <stdio.h>typedef enum { DEV_A3, DEV_B3, DEV3_MAX } DeviceType3;
/* A 有 A..E;B 只有 A..C */
typedef enum { R3_A, R3_B, R3_C, R3_D, R3_E, R3_MAX } RegId3;
/* 逻辑操作 */
typedef enum { OP_ENABLE, OP_DISABLE, OP_RESET, OP_MAX } OpId;/* 模拟寄存器 */
static uint8_t A3_space[5] = {0};
static uint8_t B3_space[3] = {0};typedef uint8_t *reg_ptr_t;
/* 地址表 */
static const reg_ptr_t A3_regs[] = {&A3_space[0], &A3_space[1], &A3_space[2], &A3_space[3],&A3_space[4]};
static const reg_ptr_t B3_regs[] = {&B3_space[0], &B3_space[1], &B3_space[2]};typedef struct {const reg_ptr_t *regs;size_t reg_count;
} DevMap3;
static const DevMap3 devmap3[DEV3_MAX] = {[DEV_A3] = {A3_regs, sizeof(A3_regs) / sizeof(A3_regs[0])}, // 5[DEV_B3] = {B3_regs, sizeof(B3_regs) / sizeof(B3_regs[0])}, // 3
};/* 基础 HAL */
static int hal3_write(DeviceType3 d, RegId3 r, uint8_t v) {if (d >= DEV3_MAX) return -1;if (r >= devmap3[d].reg_count) return -2;volatile uint8_t *addr = devmap3[d].regs[r];*addr                  = v;return 0;
}
static int hal3_read(DeviceType3 d, RegId3 r, uint8_t *out) {if (d >= DEV3_MAX || !out) return -1;if (r >= devmap3[d].reg_count) return -2;volatile uint8_t *addr = devmap3[d].regs[r];*out                   = *addr;return 0;
}/* 逻辑操作 → 设备特定写序列 */
typedef struct {RegId3 reg;uint8_t val;
} Step;
typedef struct {const Step *steps;size_t n;
} OpPlan;/* 设备 A:开启需要先写 D=0x44 再写 A=0x01;关闭写 A=0x00;复位写 E=0x5A */
static const Step A_ENABLE[]  = {{R3_D, 0x44}, {R3_A, 0x01}};
static const Step A_DISABLE[] = {{R3_A, 0x00}};
static const Step A_RESET[]   = {{R3_E, 0x5A}};/* 设备 B:开启只写 B=0x80;关闭写 B=0x00;复位写 C=0xA5 再 A=0x01 */
static const Step B_ENABLE[]  = {{R3_B, 0x80}};
static const Step B_DISABLE[] = {{R3_B, 0x00}};
static const Step B_RESET[]   = {{R3_C, 0xA5}, {R3_A, 0x01}};static const OpPlan plans[DEV3_MAX][OP_MAX] ={[DEV_A3] ={[OP_ENABLE]  = {A_ENABLE, sizeof(A_ENABLE) / sizeof(A_ENABLE[0])},[OP_DISABLE] = {A_DISABLE, sizeof(A_DISABLE) / sizeof(A_DISABLE[0])},[OP_RESET]   = {A_RESET, sizeof(A_RESET) / sizeof(A_RESET[0])},},[DEV_B3] = {[OP_ENABLE]  = {B_ENABLE, sizeof(B_ENABLE) / sizeof(B_ENABLE[0])},[OP_DISABLE] = {B_DISABLE, sizeof(B_DISABLE) / sizeof(B_DISABLE[0])},[OP_RESET]   = {B_RESET, sizeof(B_RESET) / sizeof(B_RESET[0])},}};/* 高层 HAL:执行逻辑操作 */
int hal3_op(DeviceType3 d, OpId op) {if (d >= DEV3_MAX || op >= OP_MAX) return -1;const OpPlan *p = &plans[d][op];if (!p->steps || p->n == 0) return -2; /* 不支持该操作 */for (size_t i = 0; i < p->n; i++) {/* 安全:步骤中列出的寄存器也要再检查一次是否被该设备支持 */if (p->steps[i].reg >= devmap3[d].reg_count) return -3; /* 计划配置错误 */int rc = hal3_write(d, p->steps[i].reg, p->steps[i].val);if (rc) return rc;}return 0;
}/* demo */
static void show3(DeviceType3 d) {for (size_t i = 0; i < devmap3[d].reg_count; i++)printf("D3-%d R%zu=0x%02x  ", d, i, *devmap3[d].regs[i]);puts("");
}
int main(void) {puts("Scene3:");show3(DEV_A3);show3(DEV_B3);/* 业务层:完全不知道写哪些寄存器/值,只说“开启/关闭/复位” */hal3_op(DEV_A3, OP_ENABLE);hal3_op(DEV_B3, OP_ENABLE);show3(DEV_A3);show3(DEV_B3);hal3_op(DEV_A3, OP_DISABLE);hal3_op(DEV_B3, OP_RESET);show3(DEV_A3);show3(DEV_B3);return 0;
}

代码执行结果

Scene3:
D3-0 R0=0x00  D3-0 R1=0x00  D3-0 R2=0x00  D3-0 R3=0x00  D3-0 R4=0x00  
D3-1 R0=0x00  D3-1 R1=0x00  D3-1 R2=0x00  
D3-0 R0=0x01  D3-0 R1=0x00  D3-0 R2=0x00  D3-0 R3=0x44  D3-0 R4=0x00  
D3-1 R0=0x00  D3-1 R1=0x80  D3-1 R2=0x00  
D3-0 R0=0x00  D3-0 R1=0x00  D3-0 R2=0x00  D3-0 R3=0x44  D3-0 R4=0x00  
D3-1 R0=0x01  D3-1 R1=0x80  D3-1 R2=0xa5  
[1] + Done                       "/usr/bin/gdb" --interpreter=mi --tty=${DbgTerm} 0<"/tmp/Microsoft-MIEngine-In-ulin2vku.3zm" 1>"/tmp/Microsoft-MIEngine-Out-eystlo0h.ob5"

最后叠甲

  1. 不要做过度的设计,没必要为了一个简单的需求写一个复杂的系统,如果这个需求是后两个版本做的,就把重构工作给后两个版本吧,谁也不知道这个代码中间会不会有改动。
  2. 代码能跑就不要动他,除非你觉得这个代码很复杂繁琐,或者增加代码以及非常的麻烦了,再去重构。
  3. 这里HAL层最好的一个理念就是抽象,业务层不需要了解底层干了啥,只需要了解HAL提供的抽象的接口“打开” “关闭”如何使用。
http://www.dtcms.com/a/348185.html

相关文章:

  • 邮箱创建时间打标与自动删除功能设计思路
  • UML时序图中opt,alt,switch-case的问题
  • 用户和组笔记
  • pion/webrtc v4.1.4 版本发布:关键特性与性能优化全面解析
  • 网络协议UDP、TCP
  • maven私服架构
  • Axure RP 9 交互原型设计(Mac 中文)
  • 【实习总结】快速上手Git:关键命令整理
  • 目标检测数据集 第007期-基于yolo标注格式的茶叶病害检测数据集(含免费分享)
  • 深度剖析Spring AI源码(一):蓝图初探,项目结构与设计哲学
  • 【嵌入式开发 Linux 常用命令系列 8 -- git checkout 解冲突详细介绍】
  • 【从零开始学习Redis】如何设计一个秒杀业务
  • [身份验证脚手架] 认证路由 | 认证后端控制器与请求
  • Zabbix 7.0中文乱码矫正
  • 网络协议---TCP
  • 论文阅读:VACE: All-in-One Video Creation and Editing
  • 机器学习算法-朴素贝叶斯
  • k8sday16调度器
  • Java全栈工程师面试实战:从基础到微服务的深度解析
  • 【运维进阶】高可用和负载均衡技术
  • 港口集装箱编号识别误识率↓79%!陌讯多模态融合算法落地优化
  • 静电服漏检率↓79%!陌讯多模态识别算法在智慧安检的实战解析
  • 下料口堵塞误报率↓79%!陌讯多模态融合算法在工业物料输送的实战解析
  • 电子厂静电释放检测误报率↓81%!陌讯多模态融合算法在安全生产监控的落地实践
  • 【Linux】Java线上问题,一分钟日志定位
  • 【C语言强化训练16天】--从基础到进阶的蜕变之旅:Day12
  • lanczos算法的核心——Ritz向量的计算(主要思想为反向映射)
  • 《一次高并发场景下疑难Bug的深度排查与复盘》
  • 基于Langchain框架的DeepSeek-v3+Faiss实现RAG知识问答系统(含完整代码)
  • 【股票数据API接口12】如何获取股票近年分红数据之Python、Java等多种主流语言实例代码演示通过股票数据接口获取数据