第7篇:OpenHarmony HDF 驱动框架入门:屏幕驱动"参数化"改造

2 阅读6分钟

本文是《从零到一:把一块 RK3568 开发板移植到 OpenHarmony 6.1 全记录》系列的第 7 篇。讲 HDF 驱动框架的入门概念,以及一个真实的驱动改造:把"写死的屏幕参数"改成"从设备树读取"。

设备树(DTS)告诉内核"有这个硬件、接在哪里",驱动告诉内核"怎么操作这个硬件"。OpenHarmony 操作硬件有自己的一套框架,叫 HDF

这篇先讲 HDF 是什么,再讲我在移植中做的第一件驱动改造——屏幕驱动参数化

一、HDF 是什么

HDF(Hardware Driver Foundation,硬件驱动基金会) 是鸿蒙统一的驱动框架:规定驱动怎么写、怎么注册、配置放哪(HCS 文件)、用户态怎么调用。

对比 Android 就好懂了:

AndroidOpenHarmony
内核Linux 内核,驱动在 drivers/同款 Linux 内核
上层框架HAL(各厂商各写各的,五花八门)HDF(统一标准)
屏幕/触摸/音频厂商私有 HAL + 内核驱动优先 HDF 驱动,复杂硬件仍用内核驱动

好处:不同厂商的驱动写法统一,可移植性好,配置与代码分离。

本项目里两类驱动都有:

  • 屏幕:HDF 面板驱动(这次参数化改造的对象)
  • 触摸:HDF 输入驱动(gt911 官方自带,TW3106 下篇新写)
  • 4G 模组:内核 USB 驱动 + HDF USB 即插即用 + RIL 适配层
  • 电源 RK809:内核驱动(瑞芯微自带,移植没动它)

二、改造前的问题:屏幕参数写死在代码里

官方 HDF 屏幕驱动(ili9881_st_5p5.c)长这样(节选):

static struct PanelInfo g_panelInfo = {
    .width = 720,          /* 分辨率写死 */
    .height = 1280,
    .hfp = 40,             /* 时序写死 */
    .hsw = 10,
    .clockFreq = 75000000, /* 时钟写死 */
    ...
};

屏幕分辨率、时序、初始化序列全部硬编码。后果:换一块屏幕,就要改 C 代码、重新编译内核——而我这块板子以后要换不同尺寸的屏

这就是"数据与逻辑没有分离"的典型问题:硬件参数(数据)应该放说明书(DTS),而不是焊死在逻辑(代码)里。

三、改造方案:启动时读设备树

改造思路很朴素:

改造前:驱动启动 → 用写死的参数
改造后:驱动启动 → 去 DTS 里读参数 → 读到了用 DTS 的
                    └─ 没读到 → 回退写死的默认值(兼容旧板)

核心是新增一个解析函数,在驱动初始化时调用:

static int32_t PanelParseDtsInfo(struct panel_ili9881_dev *panel_dev,
                                 struct device_node *panelNode)
{
    /* 1. 读时序:display-timings/native-mode → timing0 节点 */
    of_property_read_u32(timingNode, "clock-frequency", &value);
    panel_dev->panelInfo.clockFreq = value;
    of_property_read_u32(timingNode, "hactive", &value);
    panel_dev->panelInfo.width = value;
    /* ...hfront-porch/hsync-len/vactive 等一整套... */

    /* 2. 读初始化序列:panel-init-sequence 属性 */
    PanelParseInitSeq(panel_dev, "panel-init-sequence", &code, &len);

    /* 3. 读上下电延时:reset-delay-ms 等(可选) */
    /* 4. 读 MIPI 参数:dsi,lanes / dsi,format / dsi,flags(可选) */

    return ret;
}

初始化序列的解析值得一提。DTS 里的序列格式是 [类型 延时 长度 命令字节 数据...],驱动里的数据结构正好一一对应:

/* DTS 里的一个命令: 05 78 01 11
 * = 类型0x05, 延时120ms, 长度1, 数据{0x11}(屏幕启动命令) */
code[n].dataType = prop[i];
code[n].delay = prop[i + 1];
code[n].dataLen = prop[i + 2];
code[n].payload = &prop[i + 3];

改造的完整清单:

改造前改造后
分辨率/时序全局变量写死从 DTS display-timings
初始化/退出序列全局数组写死从 DTS panel-init-sequence
上下电延时常量从 DTS *-delay-ms 读,缺省用默认
MIPI 参数写死 4 lane RGB888从 DTS dsi,* 读,缺省用默认

关键设计:任何一项 DTS 里没有,就回退到内置默认值——这样官方旧板(没写这些属性的)行为完全不变,向后兼容。

四、改造的收益

改完之后,支持一块新屏幕的流程从:

改 C 代码 → 重编内核(几小时) → 烧写 → 验证

变成:

写 DTS 说明书(几分钟) → 重编内核(说明书变了也要编) → 烧写 → 验证

看起来还是要编译?对,但差别巨大:逻辑代码一次写对后永不再动,以后所有屏幕差异都收敛到"数据文件"(DTS)里。改数据比改代码安全得多——代码有逻辑会写错,数据写错了影响面也一目了然。

这正是工程上"数据与逻辑分离"思想的落地:变化的东西(硬件参数)放数据,不变的东西(驱动逻辑)放代码。

五、给 HDF 新手的三个建议

  1. 先抄官方驱动,再改:HDF 驱动的模板结构(Init/Detect/DataHandle 等)是固定的,先找一颗官方支持的类似芯片的驱动当模板,比从零写快十倍
  2. 配置和代码分开找:HDF 驱动的"芯片地址、引脚、上电时序"在 HCS 配置文件里,不在 C 代码里——排查问题时要两个地方一起看
  3. 探测是 HDF 触摸驱动的标配:HDF 触摸驱动启动时会读芯片 ID 确认芯片存在,读不到就干净退出。理解这一点,下一篇"多芯片共存"就顺理成章

写在最后

这篇的改造不复杂,但它是我整个移植里最满意的一个设计:一行逻辑代码都没白写,以后换屏的边际成本趋近于零。

下一篇是真正的从零写驱动:把 Android 的 TW3106 触摸驱动,翻译成 OpenHarmony 的 HDF 驱动。


📚 本系列文章导航

《从零到一:把一块 RK3568 开发板移植到 OpenHarmony 6.1 全记录》

  1. Ubuntu 24.04 编译 OpenHarmony 6.1:我踩过的 11 个坑
  2. 我把一块跑 Android 11 的开发板,换成了 OpenHarmony
  3. 零基础拆解:一块 RK3568 开发板里有什么
  4. 按下电源后发生了什么?嵌入式开机全流程
  5. 给 Linux 内核写一份"硬件说明书":设备树 DTS 入门
  6. 设备树翻译实战:把 Android 4.19 的 DTS 搬到 OpenHarmony 6.6
  7. OpenHarmony HDF 驱动框架入门:屏幕驱动"参数化"改造
  8. 从零写一个 OpenHarmony 触摸驱动:TW3106 移植实录
  9. 一块板子如何兼容 4 种触摸芯片和 3 种 4G 模组?
  10. 移植全景复盘:从 0 到可烧写镜像,我做了什么