第8篇:从零写一个 OpenHarmony 触摸驱动:TW3106 移植实录

1 阅读6分钟

本文是《从零到一:把一块 RK3568 开发板移植到 OpenHarmony 6.1 全记录》系列的第 8 篇。完整记录一次"从零写驱动"的过程:找协议 → 套模板 → 写探测 → 写读点 → 接入构建。

上篇讲了 HDF 框架和"改驱动"。这篇是从零写:这块板的触摸芯片是 TW3106,OpenHarmony 官方没有它的驱动,只有 Android 源码里有一份。

我的做法:从 Android 驱动里"翻译"出芯片协议,套进 HDF 驱动模板

一、写驱动的第一步:不是写代码,是读协议

芯片不会说话,它和主芯片之间用固定格式的数据交流。这个格式就是"协议",写驱动之前必须先搞清三件事:

  1. 怎么确认芯片存在? —— 读芯片 ID 寄存器
  2. 怎么读坐标数据? —— 读数据寄存器的格式
  3. 读到的数据怎么解析? —— 每字节代表什么

答案都在 Android 源码的 TW3106 驱动里(原厂把协议写进了代码)。我做的就是把它"翻译"出来:

1. 芯片 ID:读寄存器 0x10FC

static int32_t ChipDetect(ChipDevice *device)
{
    uint8_t reg[2] = { 0x10, 0xFC };   /* ID 寄存器地址 */
    uint8_t buf[2] = {0};
    InputI2cRead(i2cClient, reg, 2, buf, 2);  /* I2C 读 2 字节 */

    deviceId = (buf[0] << 8 | buf[1]) & 0xFF | 0x3100;
    if (deviceId == 0x3106) {           /* TW3106 的 ID */
        return HDF_SUCCESS;             /* 芯片在,我上岗 */
    }
    return HDF_FAILURE;                 /* 芯片不在,干净退出 */
}

这个"探测"函数是 HDF 触摸驱动的标配,也是下一篇"多芯片共存"的关键:芯片在板上,读 ID 就有应答;没焊,就没应答,驱动自动跳过。

2. 读点数据:两段式读取 + CRC 校验

TW3106 的数据读取是两段式:

第一段:读事件寄存器 0x0000,16 字节"头部"
  头部结构: { event事件类型; address数据地址; length数据长度; ...; headCrc16 }
  头部自带 CRC16 校验,算不对说明读错了

第二段:如果 event==5(触摸事件),按 head.address/head.length 读点数据
  点数据: { scrInfo手指数; point[10]坐标数组; crc16 }

CRC16 校验是协议里的"防错码"——数据在总线上传输可能出错,接收方用同样的算法算一遍,对不上就丢弃重读。算法是标准的 CRC-16/CCITT(多项式 0x1021,初值 0xFFFF),Android 源码里就有实现,照搬即可:

static uint16_t Tw3106GetCrc16(const uint8_t *ptr, int32_t len)
{
    uint8_t crch = 0xFF, crcl = 0xFF;
    while (len--) {
        index = crch ^ *ptr++;
        crch = crcl ^ g_crcTabH[index];
        crcl = g_crcTabL[index];
    }
    return (crch << 8) | crcl;
}

3. 解析坐标:4 字节一个点,位域打包

最有意思的是坐标格式——为了省空间,一个触摸点用 4 字节位域打包:

struct Tw3106Point {
    uint32_t y : 13;   /* y 坐标,13 位 */
    uint32_t x : 13;   /* x 坐标,13 位 */
    uint32_t id : 5;   /* 手指编号,5 位(区分多点触控的第几根手指) */
    uint32_t stat : 1; /* 状态:按下/抬起 */
};

13+13+5+1 = 32 位,正好 4 字节,一点不浪费。解析时直接按位取:

frame->fingers[i].x = (int32_t)data.point[i].x;
frame->fingers[i].y = (int32_t)data.point[i].y;
frame->fingers[i].trackId = (int32_t)data.point[i].id;
frame->fingers[i].valid = true;

二、第二步:套 HDF 模板

HDF 触摸驱动的骨架是固定的,每个芯片驱动要实现 7 个函数:

static struct TouchChipOps g_tw3106ChipOps = {
    .Init = ChipInit,            /* 初始化 */
    .Detect = ChipDetect,        /* 探测:读芯片 ID */
    .Resume = ChipResume,        /* 休眠唤醒 */
    .Suspend = ChipSuspend,      /* 进入休眠 */
    .DataHandle = ChipDataHandle,/* 读数据:中断来了读坐标 */
    .UpdateFirmware = NULL,      /* 固件升级(暂无) */
    .SetAbility = SetAbility,    /* 声明能力:支持哪些事件类型 */
};

其中 SetAbility 声明"我这个输入设备支持什么"——绝对坐标、多点触控:

static void SetAbility(ChipDevice *device)
{
    inputDev->abilitySet.eventType[0] = SET_BIT(EV_SYN) | SET_BIT(EV_KEY) | SET_BIT(EV_ABS);
    inputDev->abilitySet.absCode[1] = SET_BIT(ABS_MT_POSITION_X) |
        SET_BIT(ABS_MT_POSITION_Y) | SET_BIT(ABS_MT_TRACKING_ID);
    inputDev->attrSet.axisInfo[ABS_X].max = device->boardCfg->attr.resolutionX - 1;
    ...
}

新手提示:SetAbility 里的分辨率要和屏幕一致(1024x600),这来自 HCS 配置文件,和 C 代码分离。

三、第三步:接入构建与配置(4 处)

驱动写完了,还要在 4 个地方"登记",漏一处就不生效:

  1. Kconfig + Makefile(内核编译开关):
config DRIVERS_HDF_TP_CHESHIT10_TW3106
    bool "Enable HDF tp TW3106 driver for CHISHI CHESHIT10 board"
  1. defconfig:CONFIG_DRIVERS_HDF_TP_CHESHIT10_TW3106=y
  2. HCS 配置(芯片地址/中断方式/上电时序):
chip1 :: touchChip {
    match_attr = "chishi_tw3106_cheshit10";
    chipName = "tw3106";
    deviceAddr = 0x0C;      /* I2C 地址 */
    irqFlag = 1;            /* 上升沿中断 */
    ...
}
  1. HDF 设备表(声明驱动模块):
device1 :: deviceNode {
    moduleName = "HDF_TOUCH_TW3106";
    deviceMatchAttr = "chishi_tw3106_cheshit10";
}

四、从零写驱动的完整方法论

复盘这次经历,方法论四步:

  1. 读协议,别急着写代码:协议在同类芯片的既有驱动里(Android 源码/内核源码/芯片手册),先翻译清楚"ID 怎么读、数据怎么来、字节怎么解"
  2. 套模板:HDF 驱动骨架固定,找官方类似芯片(如 gt911)的驱动当模板,只改芯片相关的三个函数(Detect/DataHandle/SetAbility)
  3. 四处登记:Kconfig、defconfig、HCS、设备表,缺一不可
  4. 真机验证预期:编译通过 ≠ 能用,CRC 校验、坐标映射这类细节必须真机验证——所以驱动里我保留了详细日志,方便上板后对照

写在最后

写驱动最难的从来不是 C 语言,而是理解芯片的通信协议。协议吃透了,剩下的都是体力活。

下一篇是本系列我最想聊的一篇:如何设计一个框架,让一块板子兼容 4 种触摸芯片和 3 种 4G 模组。


📚 本系列文章导航

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