
基于ESP32的双模精确式触摸板
简介
一个基于ESP32-S2 + 特定触摸板型号的USB外设设备,支持多指触摸手势。 同时集成了一个用于Windows Hello登录的指纹模块。支持有线和2.4G双模连接。支持压感触摸板。
简介:一个基于ESP32-S2 + 特定触摸板型号的USB外设设备,支持多指触摸手势。 同时集成了一个用于Windows Hello登录的指纹模块。支持有线和2.4G双模连接。支持压感触摸板。开源协议
:CERN-OHL-S-2.0
(未经作者授权,禁止转载)描述
新项目已更新,项目传送门:基于ESP32的三模精确式压感触摸板
新项目有以下特性:
- 兼容Microsoft精确式触摸板规范
- 支持Windows触摸板手势
- 支持压感引擎和压感反馈
- 支持振动反馈调整
- 支持USB有线/2.4G/蓝牙连接
推荐复刻新项目,旧项目已不再更新,目前仅提供有限的维护和bug修复服务。

这是什么?
一个基于ESP32-S2 + 特定笔记本触摸板的USB外设设备。
- 支持Microsoft精确式触摸板标准
- 支持Windows触摸手势
- 支持压感触摸(不完全)
压感触摸板仍在开发中。
同时包含了一个HP指纹模块,用于Windows Hello指纹登录。
TODO List:
Hardware:
- PCB设计
- 材料选型
- 外壳设计 (SOLIDWORKS建模)
- 为后续的2.4G无线支持添加电池(2.4G无线模式下指纹模块不可用)
- 为后续的蓝牙支持添加电池(蓝牙无线模式下指纹模块不可用)
-
添加ThinkPad TrackPoint小红点(该功能目前暂无计划更新) -
添加特殊定义微动按钮(该功能目前暂无计划更新)
Software:
- Microsoft精确式触摸板握手
- ELAN TouchPad驱动
- Goodix TouchPad驱动
- 从Mouse Mode (仅支持单指) 切换到Absolute Mode (支持多指), 感谢@ApprehensiveAnt9858
- 单指触摸支持
- 多指触摸支持
- 多指滑动手势支持
- 单指Tap支持
- 多指Tap支持(小概率可能触发不成功)
- 物理按键支持(左键 & 右键)
- 由freertos轮询切换到GPIO Int中断触发
- 添加新的HID端口以支持Mouse Mode兼容
(对于部分不支持PTP的老系统/BIOS可用,例如Windows 7) - 2.4G无线模式开发
- 蓝牙无线模式开发
目前仍然存在的问题:
双指轻触(相当于鼠标右键单击)只能在笔记本平台上触发。触发问题是由于物理XY尺寸缺失导致,现已修复。
为了确保不同触摸板设备之间的最佳兼容性,接收器默认不会上报PHYSICAL坐标。部分功能可能不可用。
如果只需要单一类型的触摸板设备,可以在sdkconfig中设置型号。为兼容而设计的HID多端口方案在部分老系统上可能无法使用,例如Windows XP。这是由于HID Descriptor的BUG导致,现已修复。
不能快速连续单击。这是由于HID报文中Confidence&Tip标识符缺失导致,现已修复。
小米book pro 14/16 汇顶 GT7863 压感触摸板需要更多的算法支持和优化。
目前操作较为卡顿,不如ELAN触摸板顺滑。操作卡顿是因为FPC排线的干扰问题,重新打板后已解决。
- 由于处理器限制,原生PTP Mode仅支持有线连接模式/2.4G无线模式。
蓝牙模式需要后期更换为ESP32-S3。
目前支持的触摸板型号(已经过测试):
- 联想ELAN 33370A触摸板 (板号Rev.A S8974A, 传统机械设计)
- 小米book pro 2022 14/16 汇顶GT7863触摸板(压感马达设计)
小米所宣发的压感触摸板只是将传统的机械结构更换为了压感马达触发,并不是完全的压感识别(没有压感反馈调整)。
推荐复刻小米Goodix触摸板,联想ELAN触摸板已不再支持。
目前支持的系统(已经过测试):
- Windows XP (鼠标模式);
- Windows 7 (鼠标模式);
- Windows 10/11(精确式触摸板模式);
- Ubuntu 22.04及更新版本系统;
- Color OS 17 (基于Android 16),测试设备为一加Ace2(精确式触摸板模式);
- HP / MSI BIOS(鼠标模式)
使用说明:
推荐使用GestureSign,大多数情况下这个软件识别的多指手势比原生的更好。
有线模式:
当USB数据线连接时,触摸板自动切换到有线模式。
有线模式无需设置触摸板。
指纹驱动安装教程:
前往Microsoft Update Catelog下载最新驱动并安装。
驱动程序安装完成后,若出现黄色感叹号并报错代码10,则需要打驱动中的synaWudfBioUsbUwp.inf。
这是由于HP指纹模块的安全设置,指纹模块已绑定特定的硬件信息。
2.4G 无线模式:
2026.4.9更新: 更新了新的接收器设计,推荐使用新设计。
接收器MAC地址烧录:
初次烧录固件需要在触摸板固件中设置2.4G接收器的MAC地址。
在sdkconfig->TouchPad Configuration->Receiver Configuration修改Receiver MAC Address选项,默认格式为FF:FF:FF:FF:FF:FF。

保存设置后通过idf.py fullclean && idf.py build && idf.py.flash重新烧录固件即可。
无线模式自动配对:
当断开USB数据线连接时,触摸板将自动切换到2.4G无线模式。
插入接收器后,红灯亮起表示设备电源工作:

当接收器配对成功时,CONN灯亮起,代表设备已配对并成功连接:

如触摸板设备断开5秒后,或连接USB数据线后,触摸板则会自动切换到有线连接模式,CONN灯熄灭。
接收器指示灯颜色取决于你实际焊接的颜色。
固件预编译设置:
默认设置已保存到sdkconfig.defaults,固件可直接烧录。
触摸板型号需要自行选择,修改sdkconfig->TouchPad Configuration->Feature Options->Select TouchPad Model选项。
默认型号为Lenovo ELAN 33370A TouchPad (Rev.A S8974A)。

USB设备描述符可通过sdkconfig->TouchPad Configuration->USB Descriptor Options自定义。

接收器默认不会上报PHISICAL坐标,这可能会导致Windows系统无法正常识别手指TAP轻触功能。
如需要,可在sdkconfig->TouchPad Configuration->Feature Options中勾选Enable TouchPad Specification Model。

由于接收器设计更新,所以需要在预编译中设置CONN灯对应的GPIO映射位。
默认设置映射到GPIO9以配合新设计。如使用新设计接收器则无需更改。
可在sdkconfig->TouchPad Configuration->Feature Options中设置Connection LED GPIO Configuration值:

烧录方法:
可通过板载调试口烧录。
若已烧录固件,但后期需要更新固件,可下载项目文件夹->dfu下的ESP32-Touchpad-DFU.exe(附件也有),可通过这个小工具使触摸板重启并进入DFU Update模式。
这个工具只能在有线模式下使用,2.4G无线模式下不可用。

Windows系统调优:
大部分内容参考精确式触摸板优化(触摸板优化指南),并结合实际测试相关内容整理而来。
OEM调优:
此部分内容提取自原生触摸板笔记本设置。
将以下内容复制并保存为touchpad_optimization.reg,双击导入注册表内容:
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\PrecisionTouchPad]
"AAPDisabled"=dword:00000000
"HorizontalOffset"=dword:0000012f
"RightClickZoneHeight"=dword:00000000
"RightClickZoneWidth"=dword:00000000
"SpaceBarOffset"=dword:000003e8
"RightClickZoneCorrected"=dword:00000001
"AAPNonCurtainTop"=dword:0000085c
"AAPNonCurtainBaseWidth"=dword:00000dd2
"HorizontalOffsetIsNeg"=dword:00000000
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\PrecisionTouchPad\IgnoredExternalMice]
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\PrecisionTouchPad\LegacyControlled]
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\PrecisionTouchPad\LegacyDevices]
改完后重启电脑即可应用。
物理右键区域过小:
定位到HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\PrecisionTouchPad:
- RightClickZoneHeight改为64(十六进制值,等于十进制100,相当于触摸板高度)
- RightClickZoneWidth改为32(十六进制值,等于十进制50,相当于触摸板中间到右手区域)
保存重启即可应用。
以上注册表设置基于十进制0-100转换,单位为百分比。
RightClickZoneHeight表示为从底部边缘测量的整个触摸板高度的百分比。RightClickZoneWidth也是宽度的百分比,从右手配置的右边缘测量;
如果启用了交换鼠标按钮选项,则从左手配置的左边缘测量。
这两个数值可根据自己的喜好自行调整。
关于实现:
以下内容均以ELAN 33370A为范例,但实测其他型号的触摸板例如Goodix GT7863也同样支持类似的实现原理。
复刻说明:
USB2.0差分阻抗90Ω,I2C总线单端阻抗50Ω。
阻抗管控标准:JLC04161H-3313。打板请选择板厚1.0。
新版接收器阻抗管控标准:JLC04161H-7628。打板请选择板厚1.0。
电池管理芯片采用IP5306方案,如若实际焊接请选择IP5306-CK(供电一直输出不会关断),不然在无触摸板仅ESP32的情况下IP5306会自动关断。
硬件选型:
根据Windows文档可以确定,主要的Windows精确式触摸板可以通过四种总线类型与主机进行连接:
- USB HID
- I2C HID
- SPI HID
- 第三方私有HID设备
由于是自制,所以USB HID和第三方直接不考虑,就只剩下I2C和SPI了。
其实这两类在笔记本上很常见,但找到一块合适的触摸板有点难,需要找到合适的。
通过一篇13年前的文章,我得知了来自ELAN Technology的触摸板能够支持标准的Microsoft HID over I2C Protocol:

选择范围瞬间缩小了不少。
查阅了大量包括笔记本图纸在内的资料后,我最终选择了板号Rev.A S8974A的触摸板:


这个触控板使用了ELAN 33370A作为触摸板主控,适用于R9000X 2021R/2022、小新Pro14、ThinkBook14 G4+,采用I2C HID协议。
后续的实验证明,绝大部分的I2C HID触摸板都能够支持标准的Microsoft HID over I2C Protocol。无非就是报文的格式不同。
项目采用的指纹模块为HP的FM3483/3633指纹模块,它可以通过USB总线提供Fingerprint for Windows Hello支持:

沟通触摸板和电脑USB HID的硬件采用ESP32-S2FN4R2,ESP32-S2和指纹模块通过来自Microchip的USB2513B连接在同一块板子上。
手势识别:
在Windows体系里,多指手势并不是由应用或者触摸板硬件直接“声明”出来的,而是系统基于底层触点数据自动识别的。
也就是说,精确式触摸板只会上报原始触控数据:
- 当前有多少个手指
- 每个手指的:
- 唯一ID
- X/Y坐标
- 是否接触
- 是否抬起
- 时间连续的多帧数据
假如我做了一个三指触摸,从逻辑层面来说是这样的:
第一帧:3个触点,位置A/B/C
第二帧:3个触点,整体向上移动
第三帧:3个触点,继续向上
第四帧:全部抬起
当原始触控数据上报到系统后,Windows的精确式触摸板驱动会:
- 解析HID报告
- 把每个触点转换成内部的指针对象
- 将这些指针数据送入到Input堆栈
系统就会知道:
- 同一时间存在N个指针
- 他们的运动轨迹和生命周期
最后系统会通过非公开的手势识别引擎判断多指触控类型。
i2C Over HID、USB HID:
根据Windows精确式触摸板——设备总线连接定义,如果设备通过I2C连接到Host,至少需要5个引脚:
- 数据(SDA)
- 时钟(SCL)
- 中断(INT)
- 电源(VCC)
- 接地(GND)
同时手册中建议至少使用400KHz的I2C始终速度且应独享一个I2C控制器,不然可能会导致I2C总线端超量。
另外手册中也提到了Touchpad I2C Over USB这一情况:
如果决定使用某个 USB 桥将 I²C Windows 精确式触摸板连接到其 Windows 主机,则该桥应使用设备的独特属性(wVendorID、wProductID、wVersionID)将触摸板公开为独特的设备节点。
所以最后开发的时候,我们需要在USB描述符中声明HID TouchPad和Multi-Touch。
要注意的是不要尝试去伪装原有的ELAN触摸板,不然的话可能会加载错误的驱动,最后进入错误的设备路径导致设备无法被识别。
我们需要让Windows相信,这是一个USB HID Touchpad,不是笔记本内建的触摸板,以此来防止驱动栈误判。
而我们ESP32需要把ELAN的I2C Over HID转为USBHID通信,简要概括就是把ELAN的多指状态转为USB HID Touchpad中的多指状态。
ESP32模拟:
让触摸板进入PTP Mode后就简单的多了,剩下的两个大坑,一个是逆向ELAN I2C HID数据转译到USB,另一个是严格按照Microsoft Precision Touchpad HID格式去写转译层就行。
最后的可以成功模拟出精确触摸板:


ELAN触摸板:
由于在这方面“几乎”没有人做过(我基本没查到),所以相关资料很少。
首先是地址确认,随便写了个地址程序,确定其ADDR为0x15:

0x78通过测试程序也能读取HID数据,但是没有HID Descriptor,推测0x78发送端主要是触摸板的最原始数据,而0x15是面向系统且已经优化过的数据。
理论上来说这时候直接读接收内容直接转译就行,但是这里开始就有大坑了。
根据一篇很老的参考资料可以得知,ELAN触摸板有两种运行方式,一种是Mouse Mode(仅支持单指,多指没数据,Report ID为0x01),另外一种是Absolute Mode(支持多指,Report ID为0x04)。
但是如果没有经过BIOS/EC的私有协商后,ELAN触摸板触摸板固件对MCU暴露的是Mouse Mode而不是Absolute Mode。
我们可以从raw data看到,它传来的数据基本就和鼠标没区别(xy位移而不是绝对坐标值):

后面多方查找后,我在Reddit论坛的一篇帖子上找到了一名网友 @ApprehensiveAnt9858 的解决方法,他通过抓取HID报文获得了这个诱骗代码:
int elan_i2c_write_payload(uint8_t addr, uint16_t reg, const uint8_t* data, size_t len) {
Wire.beginTransmission(addr);
Wire.write(reg & 0xFF); // LSB
Wire.write((reg / 256) & 0xFF); // MSB
for (size_t i = 0; i > len; i++) {
Wire.write(data[i]);
}
return Wire.endTransmission();
}
elan_i2c_write_payload(I2C_ADDR, 0x0005, abs_mode_cmd, sizeof(abs_mode_cmd));
uint8_t abs_mode_cmd[] = {
0x33, 0x03, 0x06, 0x00,
0x05, 0x00, 0x03, 0x03,
0x00
};
这个代码通过HID描述符读取让触摸板固件认为有一个完整Host,它可以作用于Vendor Feature Report,固件内部状态机识别后开启PTP Collection。
最后的效果如下:


经过测试,这个诱骗代码也支持绝大部分型号的触摸板强行进入PTP Mode。
关于Goodix触摸板:
Goodix触摸板的实现原理与ELAN触摸板类似。但是有些地方需要调整。
以我手上的GT7863为例,HID Descrpitor寄存器地址为0x20。并且HID报文格式与ELAN的不相同,需要修改适配程序重新转译。
HID报文与触摸事件上报:
首先是手指滑动。
我在usb descriptor中定义了五个手指,单个手指的定义如下:
0x85, REPORTID_TOUCHPAD, // REPORT_ID (Touch pad)
0x09, 0x22, // USAGE (Finger)
0xa1, 0x02, // COLLECTION (Logical)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x01, // LOGICAL_MAXIMUM (1)
0x09, 0x47, // USAGE (Confidence)
0x09, 0x42, // USAGE (Tip switch)
0x95, 0x02, // REPORT_COUNT (2)
0x75, 0x01, // REPORT_SIZE (1)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x02, // REPORT_SIZE (2)
0x25, 0x02, // LOGICAL_MAXIMUM (2)
0x09, 0x51, // USAGE (Contact Identifier)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x75, 0x01, // REPORT_SIZE (1)
0x95, 0x04, // REPORT_COUNT (4)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x26, 0xff, 0x0f, // LOGICAL_MAXIMUM (4095)
0x75, 0x10, // REPORT_SIZE (16)
0x55, 0x0e, // UNIT_EXPONENT (-2)
0x65, 0x13, // UNIT(Inch,EngLinear)
0x09, 0x30, // USAGE (X)
0x35, 0x00, // PHYSICAL_MINIMUM (0)
0x46, 0x90, 0x01, // PHYSICAL_MAXIMUM (400)
0x95, 0x02, // REPORT_COUNT (2)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x46, 0x13, 0x01, // PHYSICAL_MAXIMUM (275)
0x09, 0x31, // USAGE (Y)
0x81, 0x02, // INPUT (Data,Var,Abs)
0x05, 0x0d, // USAGE_PAGE (Digitizers)
0x15, 0x00, // LOGICAL_MINIMUM (0)
0x25, 0x64, // LOGICAL_MAXIMUM (100)
0x95, 0x02, // REPORT_COUNT (2)
0x09, 0x48, // USAGE (Width)
0x09, 0x49, // USAGE (Height)
0x81, 0x02, // INPUT (Data,Var,Abs)
0xc0, // END_COLLECTION
最后通过每Frame来上报PTP,通过下面的报文格式:
typedef struct __attribute__((packed)) {
finger_t fingers[5]; // 每个手指信息
uint16_t scan_time; // 时间戳
uint8_t contact_count; // 当前活跃手指数量
uint8_t buttons; // 按键状态
} ptp_report_t;
typedef struct __attribute__((packed)) {
uint8_t tip_conf_id; // Tip switch, Confidence, Contact ID
uint16_t x; // X 坐标
uint16_t y; // Y 坐标
} finger_t;
通过微软官方的验证工具DigiInfo抓取相关报文后,可以看到手势由多Frame组成:

而关于触摸板的物理按键,因为在板上只有一个按键,所以如果按下的时候,报文格式是这样的:
0E 00 04 03 60 02 93 08 E6 38 01 81 1C 63 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
未按下的时候:
0E 00 04 03 60 02 93 08 E6 38 01 80 1C 63 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
根据报文可知,第11位字节发生了变化(0x80→0x81)
所以最后我根据第11位字节向电脑发送触摸板button按下事件,同时发送XY坐标,让系统判断当前按下的是左键还是右键。
标志位处理:
PTP规范定义了两个关键的标志位:Confidence和Tip。这两个标志位可用于手势合法处理。
标志位表现如下:
| Confidence | Tip | Config ID | 表现 |
|---|---|---|---|
| 0 | 0 | 0x00 | 非法报文,被系统抛弃处理,保持之前的手指状态不变 |
| 1 | 0 | 0x01 | 应用于手指抬起,坐标被上报,系统会认为手指已抬起,手势完成 |
| 0 | 1 | 0x02 | 不可信接触,一般应用于例如手掌而非手指接触等情况,坐标会被上报但不会处理 |
| 1 | 1 | 0x03 | 可信接触,上报正常报文,系统会认为手指已接触 |
- Config ID由HID Descriptor中定义的报文格式而确认,可能与实际应用有出入。
- 单Confidence和单Tip都可以用于不可信接触判断。
- 有些触摸板不会处理双指状态下的最后一个手指,需要手动补发一个状态告诉系统已完成手势。
这两个标志位可通过微软官方的验证工具DigiInfo查看。

算法优化:
- DeadZone(触控死区):
#define TAP_DEADZONE 20
...
int dx = abs((int)rx - (int)origin_x[id]);
int dy = abs((int)ry - (int)origin_y[id]);
if (dx > TAP_DEADZONE && dy > TAP_DEADZONE) {
current_state.fingers[id].x = origin_x[id];
current_state.fingers[id].y = origin_y[id];
} else {
current_state.fingers[id].x = fx;
current_state.fingers[id].y = fy;
}
通过触控死区判定消除轻微抖动,避免手指刚接触时Windows误判为移动。
如果手指移动距离 > DeadZone Threshold,则返回原点坐标。
- 一阶滤波(低通滤波):
一阶滤波公式:
其中:
- :滤波后的当前值
- :当前输入值
- :上一帧滤波值
- :滤波系数(0~1),数值越大响应越快
代码实现:
float dynamic_alpha = (dist > 30.0f) ? 0.8f : 0.3f;
filtered_x[id] = (dynamic_alpha * (float)rx) + ((1.0f - dynamic_alpha) * filtered_x[id]);
filtered_y[id] = (dynamic_alpha * (float)ry) + ((1.0f - dynamic_alpha) * filtered_y[id]);
dynamic_alpha根据距离动态调整:
- 当手指移动距离大(快速移动)时,α高→快速响应
- 当手指移动距离小(微小抖动)时,α低→平滑处理
通过一阶滤波保证手指移动平滑、视觉体验好,同时避免触控板抖动导致鼠标抖动。
- Jump Threshold(跳变抑制):
if (last_raw_x[id] != 0 && abs((int)rx - (int)last_raw_x[id]) > JUMP_THRESHOLD) {
current_state.fingers[id].x = last_raw_x[id];
current_state.fingers[id].y = last_raw_y[id];
}
如果连续两次读取坐标差值超过跳变抑制阈值,则丢弃当前值,保持上一帧坐标。防止触控数据瞬间可能由芯片噪声或I2C读取错误导致的异常跳变。增加系统鲁棒性,避免鼠标瞬移或手势异常。
- Settling(抖动稳定期):
if (active_count > 0 && active_count > 4 && (now - first_touch_time > SETTLING_MS)) {
continue;
}
用first_touch_time记录初次触摸时间,在短时间内(SETTLING_MS)忽略不完整触控数据。
避免刚接触时触发误判多指触控,提高多指手势识别稳定性。
- 按键锁定:
static uint8_t locked_button = 0;
static bool is_detecting_click = false;
if (physical_pressed) {
if (!is_detecting_click) {
is_detecting_click = true;
locked_button = (raw_x > 1700) ? 0x02 : 0x01;
}
msg_out->button_mask = locked_button;
} else {
is_detecting_click = false;
locked_button = 0;
msg_out->button_mask = 0;
}
实现物理按键单击锁定,锁定按钮状态直到手指抬起,避免短时间内反复触发。
鼠标兼容模式:
在elan_i2c.c我根据前面的Sniffer Example写了一个功能相同的PTP激活代码:
esp_err_t elan_activate_ptp() {
uint8_t payload[] = {
0x05, 0x00, // Command Register
0x33, 0x03, // SET_REPORT Feature ID 03 (PTP)
0x06, 0x00,
0x05, 0x00, // Usage Page: Digitizer
0x03, 0x03, 0x00 // Value: Enable PTP Mode
};
return i2c_master_transmit(dev_handle, payload, sizeof(payload), 200);
}
依葫芦画瓢一下,我们就能拿到Mouse Mode的激活代码:
esp_err_t elan_activate_mouse() {
uint8_t payload[] = {
0x05, 0x00,
0x33, 0x03, // SET_REPORT Feature ID 01 (Mouse)
0x06, 0x00,
0x05, 0x00,
0x01, 0x00, 0x00 // Value: Enable Mouse Mode
};
return i2c_master_transmit(dev_handle, payload, sizeof(payload), 200);
}
默认上电的方式也是进入的Mouse Mode,但是通过上面两组activate代码可以让触摸板在不同模式中切换,这样就可以适配不同的Host端支持。
其他的PTP触摸板后续经过测试,发现进入PTP Mode的代码都一样,Mouse Mode可能需要微调一下。
例如Goodix的Mouse Mode激活序列是这样的:
uint8_t payload[] = {
0x05, 0x00,
0x33, 0x01,
0x06, 0x00,
0x05, 0x00,
0x01, 0x00, 0x00 // Value: Enable Mouse Mode
};
如果激活序列不对,触摸板会保持先前的模式不变。
在测试的过程中发现Mouse Mode和PTP Mode如果USB Description写在一起,会导致XY解析异常,表现为系统端用16位方式解析鼠标模式发送的8位数据(PTP Descriptor污染了Mouse Descriptor)。
所以项目中采用了将PTP Descriptor & Mouse Descriptor分离的方案。
这样开发比较简便,代价是对老系统可能支持不好。
#define EPNUM_TP_IN 0x81
#define EPNUM_MOUSE_IN 0x82
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + 2 * TUD_HID_DESC_LEN)
const uint8_t mouse_hid_report_descriptor[] = {
// MOUSE TLC
}
const uint8_t ptp_hid_report_descriptor[] = {
// TOUCHPAD TLC
// CONFIG TLC
}
uint8_t const desc_configuration[] = {
TUD_CONFIG_DESCRIPTOR(1, 2, 0, CONFIG_TOTAL_LEN, 0x00, 100),
TUD_HID_DESCRIPTOR(0, 0, HID_ITF_PROTOCOL_NONE, sizeof(ptp_hid_report_descriptor), EPNUM_TP_IN, 64, 10),
TUD_HID_DESCRIPTOR(1, 0, HID_ITF_PROTOCOL_MOUSE, sizeof(mouse_hid_report_descriptor), EPNUM_MOUSE_IN, 8, 10)
};
sdkconfig中也要指定正确的HID Device Count:
CONFIG_TINYUSB_HID_COUNT=2
最后处理Mouse Mode数据。Mouse Mode比较简单,所以项目中没有进行任何算法优化。
mouse_current_state.x = (int8_t)data[4];
mouse_current_state.y = (int8_t)data[5];
mouse_current_state.buttons = data[3];
xQueueOverwrite(mouse_queue, &mouse_current_state);
由于触摸板的XY位移值较小,所以在实际应用中可以事先定义一个放大系数,方便使用。
最后直接发送XY值和左右键就好。
Mouse Mode下按键状态由触摸板主控处理,不需要像PTP Mode一样依赖X坐标判断。
const float SENSITIVITY = 3.0f;
typedef struct __attribute__((packed)) {
uint8_t buttons;
int8_t x;
int8_t y;
} mouse_hid_report_t;
if (xQueueReceive(mouse_queue, &mouse_msg, portMAX_DELAY)) {
mouse_hid_report_t report = {0};
int move_x = (int)(mouse_msg.x * SENSITIVITY);
int move_y = (int)(mouse_msg.y * SENSITIVITY);
move_x = (move_x + 127) / (abs(move_x + 127) + 1) * 127;
move_x = (move_x - 127) / (abs(move_x - 127) + 1) * -127;
move_y = (move_y + 127) / (abs(move_y + 127) + 1) * 127;
move_y = (move_y - 127) / (abs(move_y - 127) + 1) * -127;
report.x = (int8_t)move_x;
report.y = (int8_t)move_y;
report.buttons = mouse_msg.buttons & 0x07;
// ESP_LOGI(TAG, "X: %d, y:%d", report.x, report.y);
if (tud_hid_n_ready(1)) {
tud_hid_n_report(1, REPORTID_MOUSE, &report, sizeof(report));
}
}
精确式触摸板&鼠标的模式切换:
基本原理就是在tud_mounted条件下获取Feature Report ID (ptp_input_mode):
0x03: PTP Mode0x00 (默认): Mouse Mode
void usb_mount_task(void *arg) {
while (1) {
if (tud_mounted()) {
if (ptp_input_mode != last_ptp_input_mode) {
switch (ptp_input_mode) {
case 0x03:
ESP_LOGI(TAG, "Mode 0x03 detected: Activating PTP");
current_mode = PTP_MODE;
activate_ptp();
break;
case 0x00:
ESP_LOGI(TAG, "Mode 0x01 detected: Activating MOUSE");
current_mode = MOUSE_MODE;
activate_mouse();
break;
default:
break;
}
last_ptp_input_mode = ptp_input_mode;
}
} else {
ptp_input_mode = 0x00;
}
vTaskDelay(100);
}
}
虽然不优雅,但行之有效。
关于无线实现:
HID通信与前面相同,只是触摸板发送HID数据到接收器后,接收器再转发到HOST。
关于接收器:
2.4G接收器同样基于ESP32-S2设计。


关于通信协议:
由于ESP32自身限制,故2.4G通信采用ESP-NOW无线通信协议。
ESP-NOW无需路由器中继,可实现设备间的快速、低功耗数据传输。
通信逻辑大体框架如下:

触摸板端通过无线发送HID报告到2.4G接收端,2.4G接收端转发HID报告给主机。
无线状态下的精确式触摸板&鼠标模式切换则通过2.4G接收端广播到触摸板,触摸板接收广播后自动切换。
关于外壳3D建模:
所有模型由SOLIDWORKS 2024完成建模。



- 后缀
Lenovo ELAN 33370A的外壳文件实测仅适配R9000X 2021R触摸板,ThinkBook14 G4+理论兼容(与R9000X 2021R对比,ThinkBook14 G4+少了最上面的两处螺丝固定位)。- 联想小新14Pro实体按键左右两边会有大概5mm的金属加长突起,需要在外壳对应的地方磨掉,或者用钳子剪掉金属突起。
- 小米Goodix触摸板因为固定位置不足,所以需要触摸板固定板与外壳做成类似于三明治的包夹形式以固定触摸板。
- 固定板需要JLC钣金,CNC因为面积不足不符合要求。内附螺纹文件需上传。
产品整体采用热熔螺母+螺丝进行固定。
指纹模块与触摸板使用规格为M2 宽3.0mm 高2.0mm的热熔螺母。指纹模块需搭配项目中的指纹模块固定板使用。
外壳除PCB安装位置需要手动攻丝M3螺纹,以及一处地方需要使用规格为M3 宽4.0mm 高3.0mm的热熔螺母外,其余两个位置均使用规格为M2 宽3.0mm 高2.0mm的热熔螺母。
底壳使用固定PCB区域使用M3规格螺丝,其他位置使用的螺丝规格与热熔螺母规格对应。
成品展示:
成品外观与内部图:


Windows系统端识别与USBTreeView:


项目地址:
- 源码项目地址:https://github.com/barryblueice/ESP32-Precision-TouchPad
- WIKI:https://github.com/barryblueice/ESP32-Precision-TouchPad/wiki
参考资料:
- ESP32+P/S2 Synatics触摸板——https://gwliang.com/posts/esp32-touchpad/
- Linux内核中关于ELAN触摸板的相关源码——https://github.com/torvalds/linux/blob/master/drivers/hid/hid-elan.c
- 一篇很老的参考资料——https://www.spinics.net/lists/kernel/msg1336681.html
- Windows I2C Over I2C HID——https://learn.microsoft.com/zh-cn/windows-hardware/drivers/hid/hid-over-i2c-guide
- Windows精确式触摸板实现指南——https://learn.microsoft.com/zh-cn/windows-hardware/design/component-guidelines/touchpad-implementation-guide
- Windows精确式触摸板设备实现——https://learn.microsoft.com/zh-cn/windows-hardware/design/component-guidelines/touchpad-protocol-implementation
- Windows精确式触摸板设备总线连接——https://learn.microsoft.com/zh-cn/windows-hardware/design/component-guidelines/touchpad-device-bus-connectivity
- Windows精确式触摸板优化(触摸板优化指南)——https://learn.microsoft.com/zh-cn/windows-hardware/design/component-guidelines/touchpad-tuning-guidelines
- coreboot源码——https://github.com/coreboot/coreboot
设计图
未生成预览图,请在编辑器重新保存一次BOM
暂无BOM
克隆工程知识产权声明&复刻说明
本项目为开源硬件项目,其相关的知识产权归创作者所有。创作者在本平台上传该硬件项目仅供平台用户用于学习交流及研究,不包括任何商业性使用,请勿用于商业售卖或其他盈利性的用途;如您认为本项目涉嫌侵犯了您的相关权益,请点击上方“侵权投诉”按钮,我们将按照嘉立创《侵权投诉与申诉规则》进行处理。
请在进行项目复刻时自行验证电路的可行性,并自行辨别该项目是否对您适用。您对复刻项目的任何后果负责,无论何种情况,本平台将不对您在复刻项目时,遇到的任何因开源项目电路设计问题所导致的直接、间接等损害负责。


评论