我是兰瓶Coding,一枚刚踏入鸿蒙领域的转型小白,原是移动开发中级,如下是我学习笔记《零基础学鸿蒙》,若对你所有帮助,还请不吝啬的给个大大的赞~

前言

先把心里话摆在前面:驱动不是“能编过就算赢”,而是“被内核/框架信任并稳定接管硬件”。在 HarmonyOS/OpenHarmony 生态里,这件事的关键字是 HDF(Huawei Driver Foundation)HCS(Harmony Config System):前者是驱动框架与 API,后者是驱动与设备的“身份证”。本文严格按照你的大纲,带你走一遍可落地的三步:驱动模板 → 设备描述文件 → 编译与加载;并围绕你关心的技术点 hcs 配置HDF API 给出完整样例和踩坑清单。

说明:不同发行分支/芯片平台在目录与 API 名上可能有细微差异,下文以通用、主干思路常见路径示例,遇到你本仓差异时按实际工程替换路径/宏即可。

一、驱动模板:从“空壳”到“可绑定可初始化”

1.1 HDF 快速心智图

  • Host(设备宿主进程/上下文):同一 Host 中可挂多个驱动实例。常见 host 有 platformi2c_hostspi_host 等。
  • Driver(驱动模块):实现 Bind/Init/Release 三件套,通过 HdfDriverEntry 注册到 DevMgr。
  • Device(设备实例):由 HCS 描述,匹配到具体 Driver 后由框架创建 HdfDeviceObject 传给你。
  • Service(对外服务句柄,可选):你可以把方法表挂到 deviceObject->service,供用户态/其他驱动通过 HdfIoService 调用。

一句话:DevMgr 读 HCS → 找到 moduleName 匹配的 DriverEntry → 调用 Bind/Init → 设备上线

1.2 目录建议

/drivers/peripheral/my_led/
  ├── src/my_led_driver.c
  ├── include/my_led_driver.h
  ├── config/my_led.hcs
  ├── BUILD.gn
  └── README.md

1.3 最小可用 Driver 模板(Platform 设备为例)

这个示例实现一个“GPIO 灯”驱动:启动时解析 HCS 里的 gpioNum/activeLow,导出一个简单服务 MY_LED_TURNMY_LED_STATUS

// src/my_led_driver.c
#include "hdf_device_desc.h"
#include "hdf_log.h"
#include "device_resource_if.h"
#include "hdf_base.h"
#include "osal_mem.h"
#include "hdf_sbuf.h"
// 如接入真实 GPIO,还需包含对应 BUS/Pin 控制头文件,略

#define HDF_LOG_TAG my_led

enum {
    IO_CMD_LED_TURN   = 1,  // data: uint8_t on/off
    IO_CMD_LED_STATUS = 2,  // resp: uint8_t current
};

struct MyLedDev {
    struct HdfDeviceObject *hdfDev;
    uint32_t gpioNum;
    bool activeLow;
    uint8_t state; // 0/1
};

static int32_t MyLedParseConfig(struct MyLedDev *dev, struct HdfDeviceObject *obj)
{
    if (dev == NULL || obj == NULL) return HDF_ERR_INVALID_PARAM;
    const struct DeviceResourceNode *node = obj->property;
    const struct DeviceResourceIface *iface = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
    if (iface == NULL || iface->GetUint32 == NULL) {
        HDF_LOGE("no resource iface");
        return HDF_FAILURE;
    }
    (void)iface->GetUint32(node, "gpioNum", &dev->gpioNum, 0);
    uint32_t al = 0;
    (void)iface->GetUint32(node, "activeLow", &al, 0);
    dev->activeLow = (al != 0);
    HDF_LOGI("parsed gpio=%u activeLow=%d", dev->gpioNum, dev->activeLow);
    return HDF_SUCCESS;
}

// 真实项目这里操作 GPIO,这里用占位函数模拟
static void MyLedApplyPin(struct MyLedDev *dev, uint8_t on)
{
    dev->state = on ? 1 : 0;
    // 若 activeLow,则 on=0 拉低;此处略
    HDF_LOGI("LED %s (gpio=%u)", on ? "ON":"OFF", dev->gpioNum);
}

/* ========== 可选:对外服务 ========== */
static int32_t MyLedDispatch(struct HdfDeviceIoClient *client, int cmd,
                             struct HdfSBuf *data, struct HdfSBuf *reply)
{
    if (client == NULL || client->device == NULL) return HDF_ERR_INVALID_PARAM;
    struct MyLedDev *dev = (struct MyLedDev *)client->device->priv;
    if (dev == NULL) return HDF_FAILURE;

    switch (cmd) {
    case IO_CMD_LED_TURN: {
        if (data == NULL) return HDF_ERR_INVALID_PARAM;
        uint8_t on = 0;
        if (!HdfSbufReadUint8(data, &on)) return HDF_ERR_IO;
        MyLedApplyPin(dev, on);
        return HDF_SUCCESS;
    }
    case IO_CMD_LED_STATUS: {
        if (reply == NULL) return HDF_ERR_INVALID_PARAM;
        if (!HdfSbufWriteUint8(reply, dev->state)) return HDF_ERR_IO;
        return HDF_SUCCESS;
    }
    default:
        return HDF_ERR_NOT_SUPPORT;
    }
}

static struct HdfDeviceIoService g_ledService = {
    .Dispatch = MyLedDispatch,
    .Open = NULL,
    .Release = NULL,
};

/* ========== HDF 生命周期 ========== */
static int32_t MyLedBind(struct HdfDeviceObject *obj)
{
    if (obj == NULL) return HDF_FAILURE;
    struct MyLedDev *dev = (struct MyLedDev *)OsalMemCalloc(sizeof(*dev));
    if (dev == NULL) return HDF_ERR_MALLOC_FAIL;
    dev->hdfDev = obj;
    obj->priv = dev;

    // 对外发布服务(可选)
    obj->service = &g_ledService;
    HDF_LOGI("bind ok");
    return HDF_SUCCESS;
}

static int32_t MyLedInit(struct HdfDeviceObject *obj)
{
    if (obj == NULL || obj->priv == NULL) return HDF_FAILURE;
    struct MyLedDev *dev = (struct MyLedDev *)obj->priv;
    int32_t ret = MyLedParseConfig(dev, obj);
    if (ret != HDF_SUCCESS) return ret;

    // 这里做 GPIO 请求/复用/默认状态设定,略
    MyLedApplyPin(dev, 0);
    HDF_LOGI("init ok");
    return HDF_SUCCESS;
}

static void MyLedRelease(struct HdfDeviceObject *obj)
{
    if (obj == NULL || obj->priv == NULL) return;
    struct MyLedDev *dev = (struct MyLedDev *)obj->priv;
    // 释放 GPIO/中断等资源,略
    OsalMemFree(dev);
    obj->priv = NULL;
    HDF_LOGI("release ok");
}

/* ========== DriverEntry ========== */
struct HdfDriverEntry g_myLedEntry = {
    .moduleVersion = 1,
    .moduleName    = "HDF_MY_LED",   // **与 HCS moduleName 对应**
    .Bind          = MyLedBind,
    .Init          = MyLedInit,
    .Release       = MyLedRelease,
};

HDF_INIT(g_myLedEntry);

要点复盘

  • HdfDriverEntry.moduleName 必须与 HCS 中的 moduleName 完全一致
  • Bind 里挂 obj->service = &你的 service 才能被用户态/其他模块通过 HdfIoServiceBind 访问。
  • 设备属性从 obj->property 读,使用 DeviceResourceIfaceGetUint32/GetString/...

二、设备描述文件(HCS):把“驱动能力”绑定到“具体那块硬件”

2.1 HCS 是什么?

HCS(Harmony Config System)用类 JSON 的 DSL描述系统组件与设备树。对 HDF 来说,HCS 决定了 DevMgr 在启动时要创建哪些设备实例、它们属于哪个 host,用哪个 driver(moduleName),并把自定义属性传给驱动。

2.2 常见文件与位置

  • device_info.hcs:总入口,包含 host 与设备节点。
  • 也可拆分模块独立 xxx.hcs 并在 device_info.hcs#include

工程常见路径(示意):

/vendor/<your_vendor>/<board>/hdf_config/device_info.hcs
/drivers/peripheral/my_led/config/my_led.hcs   # 你自己的模块

2.3 样例 HCS(platform host 上挂一个 LED 设备)

// config/my_led.hcs
root {
  my_led :: host {
    hostName = "platform";                 // 宿主:平台 Host(也可以是 i2c_host/spi_host 等)
    priority = 50;

    device my_led_dev {                     // 设备实例
      deviceId   = 0x1001;                  // 业务自定义,驱动里一般用不到
      policy     = 2;                        // 0/1/2:按需加载策略(不同分支略有差异)
      preload    = 0;                        // 1=开机预载
      moduleName = "HDF_MY_LED";            // **与 g_myLedEntry.moduleName 一致**
      serviceName = "my_led_service";       // 对外服务名(HdfIoServiceBind 使用)

      // 设备自定义属性 —— 驱动里通过 DeviceResourceIface 读取
      gpioNum = 12;
      activeLow = 1;
    }
  }
}

如果你的平台遵循“集中入口”方式,在 device_info.hcs 里包含它:

#include "my_led.hcs"

2.4 驱动如何拿到这些属性?

见前面 MyLedParseConfig(),关键点:

const struct DeviceResourceNode *node = obj->property;
const struct DeviceResourceIface *iface = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
iface->GetUint32(node, "gpioNum", &dev->gpioNum, 0);

支持的常见读取:GetStringGetUint8/16/32GetBoolGetArray* 等(视分支)。


三、编译与加载:把 .c 变成可执行的“设备能力”

3.1 BUILD.gn(示意)

不同分支组件名不完全一致,以下给常见写法,把你的源编成 HDF 可加载的 共享库驱动(user-mode):

# drivers/peripheral/my_led/BUILD.gn
import("//build/ohos.gni")

ohos_shared_library("my_led_driver") {
  sources = [
    "src/my_led_driver.c",
  ]
  include_dirs = [
    "include",
    "//drivers/hdf/frameworks/include",               # HDF 头
    "//drivers/hdf/frameworks/core/common/include",
  ]
  deps = [
    "//drivers/hdf/frameworks/core/manager:devmgr",   # DevMgr 能力
    "//drivers/hdf/frameworks/core/shared:hdf_core",  # 基础 HDF 能力
  ]
  cflags = [ "-DHDF_LOG_TAG=my_led" ]
  part_name = "drivers_peripheral"    # 归属部件(示意,按你项目部件填)
  subsystem_name = "drivers"          # 子系统
}

内核态(kernel-mode)驱动则会用到 kernel_module/ohos_kernel_module,并与内核 Kconfig/Makefile 相关;本文以用户态 HDF 驱动为例,更易上手、易调试。

3.2 把你的部件纳入系统构建

在对应部件的 bundle.json / 组件清单里加入 target,或在板级 productdefine/common/component 打开你的组件开关。不同仓/发行略有差异,核心就是:hb build 能找到并编进镜像

3.3 HCS 的打包路径

构建系统会把 HCS 部署到 /etc/hdf/(或同等目录)下,DevMgr 启动时读取。你只需保证:

  • 你的 my_led.hcsdevice_info.hcs include;
  • 最终产物里能看到 .../etc/hdf/my_led.hcs(或合并后的大 HCS)。

3.4 构建与烧录

hb set           # 选好 product/board
hb build -f      # 全量构建
# 烧录或以仿真环境运行,具体依你的板卡方式

3.5 加载与日志验证

  • 开机自动加载:如果 preload=1 或策略允许,DevMgr 会在解析到你的节点后调用 Bind/Init

  • 日志:串口或 hilog 里 grep 你的 TAG:

    hilog -x | grep my_led
    

    你应该能看到:

    I/my_led: bind ok
    I/my_led: parsed gpio=12 activeLow=1
    I/my_led: init ok
    

3.6 运行时访问(用户态示例)

用户态进程可通过 HdfIoService 与你的驱动通信(前提是你在 Bind 挂了 obj->service 且 HCS 里设置了 serviceName)。

// user/test_my_led.c  —— 简易验证程序
#include "hdf_io_service_if.h"
#include "hdf_sbuf.h"
#include <stdio.h>

int main(void)
{
    struct HdfIoService *svc = HdfIoServiceBind("my_led_service");
    if (svc == NULL) { printf("bind service fail\n"); return -1; }

    struct HdfSBuf *data = HdfSbufObtainDefaultSize();
    struct HdfSBuf *reply = HdfSbufObtainDefaultSize();
    // 开灯
    HdfSbufWriteUint8(data, 1);
    int ret = svc->dispatcher->Dispatch(&svc->object, 1 /*IO_CMD_LED_TURN*/, data, reply);
    printf("turn ret=%d\n", ret);
    HdfSbufRecycle(data); HdfSbufRecycle(reply);

    // 读状态
    data = HdfSbufObtainDefaultSize(); reply = HdfSbufObtainDefaultSize();
    ret = svc->dispatcher->Dispatch(&svc->object, 2 /*STATUS*/, data, reply);
    uint8_t st = 0; HdfSbufReadUint8(reply, &st);
    printf("status=%u\n", st);

    HdfSbufRecycle(data); HdfSbufRecycle(reply);
    HdfIoServiceRecycle(svc);
    return 0;
}

如果你要提供 HDI(接口定义语言) 给 ArkTS/上层调用,建议再加一层 IDL + 代码生成,让接口类型安全、升级更平滑。


四、常见总线场景的差异点(I²C/SPI/UART)

  • Platform 设备:最简单,常用于虚拟设备/聚合控制器;无额外总线绑定。
  • I²C/SPI:在 HCS 里除了 moduleName 外,还需要标注总线号/片选/地址等属性(如 bus=1, addr=0x3C)。驱动里通过对应总线的 HDF 接口获取 I2cDev/SpiDev 句柄再读写。
  • UART:注意波特率/数据位/校验位等串口参数也放 HCS;打开串口后用异步回调或轮询方式收发。

核心变化只是HCS 的属性字段驱动里拿句柄的 API变了,Bind/Init/Release + 解析属性的范式不变。


五、排障与“玄学去除剂”

  1. Bind/Init 没被调用

    • 检查 moduleName 与 HCS 是否完全一致(大小写也要一致)。
    • device_info.hcs 是否包含了你的 my_led.hcs
    • hostName 是否存在,并且该 host 在你的镜像里启用了。
  2. 属性读取总是默认值

    • 确认 HCS 文法、数字是否无引号(gpioNum = 12 而不是 "12")。
    • DeviceResourceIface 是否从 HDF_CONFIG_SOURCE 取,字段名拼写是否一致。
  3. 用户态绑定服务失败 (HdfIoServiceBind 返回 NULL)

    • HCS 里的 serviceName 是否设置;
    • Bind 是否把 obj->service 指向了你的 HdfDeviceIoService
    • policy/preload 导致设备未加载,尝试把 preload=1 验证。
  4. 多实例设备撞车

    • 给每个设备实例不同的 serviceNamedeviceId,HCS 里拆成多个 device {} 节点。
  5. 升级后 ABI 报错/符号缺失

    • 检查分支的 HDF 头/库变化,优先使用公共 API(HdfDeviceObjectHdfSBufDeviceResourceIfaceHdfIoService)。
    • 不要依赖未公开的内部符号。

六、上线前的工程化清单

  • 日志 TAG:每个关键路径(Bind/Init/属性解析/总线打开/错误码)都有 HDF_LOGI/E
  • 资源释放Release 里关闭中断、释放总线、free 内存。
  • 并发安全:服务 Dispatch 中可能多线程进入,必要时加 OsalMutex
  • 边界检查:SBuf 读写返回值逐个判断,避免越界。
  • HCS 版本化:大改动时为不同板卡/产品准备独立 HCS,避免“通用一份把谁都带崩”。
  • 自测工具:提供一个 user/test_xxx 小工具做回归。

七、把样板改造成你的驱动:一页速用指南

  1. 复制模板:把本文 my_led_driver.c 改名为你的设备名,保留 Bind/Init/ReleaseDispatch 框架。
  2. 定接口:把 IO_CMD_*HdfDeviceIoService 方法根据业务改掉。
  3. 写 HCS:在 config/your_device.hcs 中设好 hostName/moduleName/serviceName 与你的属性。
  4. BUILD.gn 加入构建,确保被部件/产品引用。
  5. hb build + 烧录,hilog 验证
  6. 用户态小工具ArkTS HDI 调通后再逐步接入业务应用。

结语

驱动这活儿,最怕“糊里糊涂能跑”,一换板/一升级就挂。把 HDF 生命周期HCS 绑定对外服务这三件事按上面的模板走一遍,你会发现:驱动是可重复生产的工程品,不是玄学

(未完待续)

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐