本文是《从零到一:把一块 RK3568 开发板移植到 OpenHarmony 6.1 全记录》系列的第 9 篇,也是整个系列最有工程含量的一篇:如何设计一个框架,让一块硬件兼容多副面孔。
前几篇讲的是"让系统认识一块板子"。这篇解决一个更现实的问题:
这块板子以后要卖不同配置——屏幕尺寸会变、触摸供应商会变、4G 模组会换。如果每个组合一套固件,3 种屏 × 4 种触摸 × 3 种模组 = 36 套固件,维护噩梦。
怎么办?答案是一套变体框架。这篇讲完整的设计思路。
一、核心设计原则(全文的纲)
分辨率不同 → 换编译配置;同分辨率的一切差异 → 一个固件内自适应;4G 永远运行时全兼容。
为什么这么分?看每个差异能不能"运行时自动识别":
| 差异 | 能运行时识别吗? | 为什么 | 结论 |
|---|---|---|---|
| 屏幕分辨率/时序 | ❌ | 屏不会"报参数",换分辨率连界面布局都要变 | 编译期换配置 |
| 触摸芯片(同尺寸) | ✅ | I2C 总线上探测芯片 ID,谁在谁应答 | 运行时自适应 |
| 屏供应商(同尺寸) | ⚠️ 通常能 | 同分辨率时序一致,init 序列多数兼容 | 默认零改动 |
| 4G 模组 | ✅ | USB 设备有 VID/PID 身份证 | 运行时自适应 |
工程哲学:能在运行时解决的,就不要编译期解决;必须编译期解决的,用参数化(一个参数切换),而不是 36 套代码。
二、编译期变体:一个参数切换屏幕档位
分辨率必须在编译期决定,但"编译期"不等于"每套屏写一套代码"。设计是一条 gn 参数贯穿整条构建链:
./build.sh --gn-args 'cheshit10_display_size="7inch"'
│
▼
device.gni 的 declare_args 接收(带默认值)
│
▼
kernel/BUILD.gn 把参数传给内核构建脚本
│
▼
build_kernel.sh 里 case 映射: 7inch → 板名 "CHESHIT10-7INCH"
│
▼
make-ohos.sh 的"板名→DTS 对照表"查到对应设备树
│
▼
编译 rk3568-cheshit10-7inch.dts → 屏幕说明书就是 7 寸屏的
真实代码片段(build_kernel.sh 里的映射):
CHESHIT10_DISPLAY_SIZE=${@: -2:1} # 倒数第 2 个参数
BOARD_NAME="TB-RK3568X0"
case "${CHESHIT10_DISPLAY_SIZE}" in
7inch) BOARD_NAME="CHESHIT10-7INCH" ;;
*) echo "unknown display size, fallback" ;;
esac
eval $MAKE_OHOS_ENV ./make-ohos.sh $BOARD_NAME ...
新增一个尺寸档位 = 写一份新的屏幕说明书 + 在两处对照表各加一行。不用碰任何其他代码。
顺带还有两个出厂参数走同一链路:屏幕密度(cheshit10_density=160)和方向(cheshit10_rotation=270,这块屏物理旋转 270° 安装)。
三、运行时自适应 1:触摸芯片自动"敲门"
一个固件里编入全部候选触摸驱动,开机时每颗芯片驱动都去 I2C 地址上敲门:
开机
├─ 去 I2C3 地址 0x0c 敲门 → 有人应! → TW3106 上岗 ✓
├─ 去 I2C3 地址 0x5d 敲门 → 没人应 → GT911 跳过
├─ 去 I2C3 地址 0x41 敲门 → 没人应 → ILI2511 跳过
└─ 去 I2C3 地址 0x2e 敲门 → 没人应 → CHSC5448 跳过
结果:这块板用的是 TW3106,全自动搞定
实现机制就是上一篇讲的 ChipDetect(读芯片 ID):芯片在,有应答;不在,驱动返回失败,HDF 框架干净跳过,不注册输入设备。
前提条件:候选芯片必须共用同一组接线(同一条 I2C 总线、同样的中断/复位引脚)——这是硬件设计阶段就约定好的"模组接口规范"。软件的自适应,永远建立在硬件接口统一的基础上。
四、运行时自适应 2:4G 模组自动识别
4G 模组通过 USB 插在板上,而每个 USB 设备都有"身份证":VID(厂商 ID)+ PID(产品 ID)。利用它:
插入 4G 模组
├─ 内核 USB 驱动按 VID/PID 认设备(全编入)
├─ HDF USB 即插即用表按 VID/PID 接管上网通道
└─ RIL 自动加载表按 VID/PID 选"翻译官"(RIL 库)
→ 插哪个模组,加载哪个 RIL,全自动
落地是一张表(modem_adapter.h):
UsbDeviceInfo g_usbModemVendorInfo[] = {
{.idVendor = 0x2c7c, .idProduct = 0x6005, .libPath = "libril_vendor.z.so"}, /* EC200T 真机 */
{.idVendor = 0x2c7c, .idProduct = 0x0125, .libPath = "libril_vendor.z.so"}, /* EC25 */
{.idVendor = 0x2DEB, .idProduct = 0x4D20, .libPath = "libril_vendor.z.so"}, /* ML307R */
...
};
新增模组 = 表里加一行(先用通用 AT 指令库兜底,特殊模组再写专用库)。
五、同尺寸"屏供应商"差异的兜底
同分辨率不同供应商的屏:时序一致(分辨率相同),init 序列可能不同:
- 序列兼容(常见)→ 零改动,一个固件直接点亮
- 序列不兼容 →
cheshit10_panel_vendor参数切一份序列变体(编译期微调,兜底用)
六、这套设计的本质:三个通用模式
回头看,这个框架是三个通用设计模式的组合,完全可以迁移到其他项目:
- 参数化配置:变体差异收敛成"数据"(DTS 说明书/HCS 配置/映射表),逻辑代码共享——新增变体只加数据
- 运行时探测:能"敲门问"的硬件(总线有应答、USB 有身份证),全部运行时自适应,一套固件通吃
- 编译期查表:必须编译期决定的(分辨率),用"参数→查表→产物"一步到位
写在最后
这个框架最值得分享的不是代码,而是把"变化"分层管理的思维:
- 变化可以探测 → 运行时解决
- 变化必须编译期决定 → 参数化 + 查表
- 变化偶尔发生 → 留一个兜底开关
硬件千变万化,但"变化管理"的方法论是相通的。
最后一篇:完整复盘这次移植,从 0 到可烧写镜像的全过程。
📚 本系列文章导航
《从零到一:把一块 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 到可烧写镜像,我做了什么