第9篇:一块板子如何兼容 4 种触摸芯片和 3 种 4G 模组?多模组变体设计

2 阅读6分钟

本文是《从零到一:把一块 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 参数切一份序列变体(编译期微调,兜底用)

六、这套设计的本质:三个通用模式

回头看,这个框架是三个通用设计模式的组合,完全可以迁移到其他项目:

  1. 参数化配置:变体差异收敛成"数据"(DTS 说明书/HCS 配置/映射表),逻辑代码共享——新增变体只加数据
  2. 运行时探测:能"敲门问"的硬件(总线有应答、USB 有身份证),全部运行时自适应,一套固件通吃
  3. 编译期查表:必须编译期决定的(分辨率),用"参数→查表→产物"一步到位

写在最后

这个框架最值得分享的不是代码,而是把"变化"分层管理的思维:

  • 变化可以探测 → 运行时解决
  • 变化必须编译期决定 → 参数化 + 查表
  • 变化偶尔发生 → 留一个兜底开关

硬件千变万化,但"变化管理"的方法论是相通的。

最后一篇:完整复盘这次移植,从 0 到可烧写镜像的全过程。


📚 本系列文章导航

《从零到一:把一块 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 到可烧写镜像,我做了什么