本文是《从零到一:把一块 RK3568 开发板移植到 OpenHarmony 6.1 全记录》系列的第 7 篇。讲 HDF 驱动框架的入门概念,以及一个真实的驱动改造:把"写死的屏幕参数"改成"从设备树读取"。
设备树(DTS)告诉内核"有这个硬件、接在哪里",驱动告诉内核"怎么操作这个硬件"。OpenHarmony 操作硬件有自己的一套框架,叫 HDF。
这篇先讲 HDF 是什么,再讲我在移植中做的第一件驱动改造——屏幕驱动参数化。
一、HDF 是什么
HDF(Hardware Driver Foundation,硬件驱动基金会) 是鸿蒙统一的驱动框架:规定驱动怎么写、怎么注册、配置放哪(HCS 文件)、用户态怎么调用。
对比 Android 就好懂了:
| Android | OpenHarmony | |
|---|---|---|
| 内核 | 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 新手的三个建议
- 先抄官方驱动,再改:HDF 驱动的模板结构(Init/Detect/DataHandle 等)是固定的,先找一颗官方支持的类似芯片的驱动当模板,比从零写快十倍
- 配置和代码分开找:HDF 驱动的"芯片地址、引脚、上电时序"在 HCS 配置文件里,不在 C 代码里——排查问题时要两个地方一起看
- 探测是 HDF 触摸驱动的标配:HDF 触摸驱动启动时会读芯片 ID 确认芯片存在,读不到就干净退出。理解这一点,下一篇"多芯片共存"就顺理成章
写在最后
这篇的改造不复杂,但它是我整个移植里最满意的一个设计:一行逻辑代码都没白写,以后换屏的边际成本趋近于零。
下一篇是真正的从零写驱动:把 Android 的 TW3106 触摸驱动,翻译成 OpenHarmony 的 HDF 驱动。
📚 本系列文章导航
《从零到一:把一块 RK3568 开发板移植到 OpenHarmony 6.1 全记录》
- Ubuntu 24.04 编译 OpenHarmony 6.1:我踩过的 11 个坑
- 我把一块跑 Android 11 的开发板,换成了 OpenHarmony
- 零基础拆解:一块 RK3568 开发板里有什么
- 按下电源后发生了什么?嵌入式开机全流程
- 给 Linux 内核写一份"硬件说明书":设备树 DTS 入门
- 设备树翻译实战:把 Android 4.19 的 DTS 搬到 OpenHarmony 6.6
- OpenHarmony HDF 驱动框架入门:屏幕驱动"参数化"改造
- 从零写一个 OpenHarmony 触摸驱动:TW3106 移植实录
- 一块板子如何兼容 4 种触摸芯片和 3 种 4G 模组?
- 移植全景复盘:从 0 到可烧写镜像,我做了什么