1. EETI_eGTouch驱动移植:不是“换个文件就能用”,而是和硬件握手的过程
EETI_eGTouch——这个在嵌入式触摸屏领域被反复提及的名字,背后其实是一套高度耦合的软硬协同体系。它不是Linux内核里那种“插上U盘自动识别”的即插即用设备,而是一个需要你亲手把它从芯片手册、寄存器映射、中断时序中“拽出来”的驱动模块。我第一次接触它是在一款国产工控HMI项目上,客户拿来的板子用的是瑞芯微RK3399,配套的7英寸电容屏标称支持EETI方案,但官方SDK只给了一个闭源的.ko文件,连Makefile都没有。我们想把它迁移到主线Linux 5.10上,结果发现:直接拷贝ko加载失败;用modprobe强行加载后/dev/input/event*根本不出设备节点;更诡异的是,dmesg里只有一行“eGTouch: probe failed”,连错误码都不报。这根本不是“驱动没装好”的问题,而是你还没跟这块芯片真正“说上话”。
核心关键词EETI_eGTouch,本质上指代的是台湾晨星(EETI)公司推出的eGTouch系列触控控制器IC(如eGTouch IC eG3000、eG4000等)所配套的Linux内核驱动框架。它不依赖USB或I2C标准协议栈的通用路径,而是通过一套私有寄存器读写+中断触发+固件校验的闭环流程完成坐标上报。这意味着移植的第一步,永远不是改Makefile或Kconfig,而是确认三件事:你的SoC是否能正确访问eGTouch芯片的物理地址空间(通常是SPI或I2C总线),中断引脚是否被正确配置并触发,以及最关键的——eGTouch芯片本身是否已烧录了匹配当前硬件平台的固件(firmware)。很多团队卡在第一步,以为是驱动代码问题,实则eGTouch芯片还在默认出厂模式下“静默待机”,压根没响应任何主机指令。
这个过程,我把它称为“硬件握手”。就像两个人见面要先交换名片、确认身份、约定暗号一样,eGTouch驱动在probe阶段必须完成三次关键交互:
第一,向芯片的0x00寄存器写入0xAA,读回值必须为0x55,这是最基础的“芯片在线”握手;
第二,向0x01寄存器写入0x01(启动命令),等待芯片返回0x02(就绪状态),这是“固件已加载”的确认;
第三,向0x10寄存器连续读取4字节,解析出X/Y轴分辨率、报告模式(绝对/相对)、支持点数等参数,这是“能力协商”的完成。
这三步缺一不可,且每一步都有超时机制(通常为50ms)。一旦某步失败,驱动就会直接return -ENODEV,连日志都懒得打全——这就是为什么你只看到“probe failed”。所以,当你面对一个“移植失败”的eGTouch板子,别急着翻驱动源码,先用逻辑分析仪抓SPI波形,看主机发没发出0x00写指令;再用万用表测中断引脚,看按下屏幕时电平有没有跳变;最后用eGTouch官方工具(如eGTouchConfig.exe)连Windows主机,确认同一块屏在PC上能否正常工作——这三步做完,80%的问题根源就浮出水面了。
提示:eGTouch芯片的I2C地址不是固定的0x14或0x15,而是由硬件引脚(ADDR0/ADDR1)电平决定的。很多原理图没标清楚这两个引脚的上下拉状态,导致驱动里写的地址和实际芯片地址对不上。我见过最典型的案例,是ADDR0悬空(未接上下拉),在不同批次PCB上随机表现为高或低,造成一半板子能用、一半不能用——这种硬件设计缺陷,必须在BOM审核阶段就揪出来。
2. 驱动代码层移植:从“抄代码”到“读懂寄存器映射”的思维跃迁
当你确认硬件握手成功后,真正的代码移植才开始。这里必须明确一个前提:EETI_eGTouch驱动在Linux主线内核中并不存在。它长期以“out-of-tree”方式存在,常见于Rockchip、Allwinner等厂商的定制内核分支中。因此,所谓“移植”,本质是把一段非主线代码,适配到你当前使用的内核版本上。这不是简单的git cherry-pick,而是一场涉及API演进、内存模型变更、中断处理框架重构的系统性适配。
我手头有三个典型版本的eGTouch驱动源码:v3.10(用于旧版RK3288)、v4.19(用于早期RK3399 SDK)、v5.10(社区零星提交的补丁)。它们的核心差异,集中体现在四个关键函数上:
probe函数中的资源获取方式:v3.10用platform_get_resource() + request_mem_region(),v4.19开始强制要求使用devm_ioremap_resource(),v5.10则进一步要求配合device_property_read_u32()读取DT中的寄存器偏移。如果你还在v5.10内核里用request_mem_region(),编译会过,但运行时大概率panic——因为新内核的resource管理器已废弃该接口的裸调用。
中断注册方式:v3.10用request_irq(),v4.19起推荐request_threaded_irq(),v5.10则要求必须用devm_request_threaded_irq()。区别在于,eGTouch的中断服务程序(ISR)不能做耗时操作(如读取坐标数据),必须拆分为“上半部快速退出+下半部线程处理”。否则在高负载场景下,中断丢失率会飙升,表现为触摸延迟或丢点。
input设备注册流程:v3.10直接调用input_register_device(),v4.19引入input_set_capability()显式声明支持的事件类型(EV_ABS、ABS_X/Y等),v5.10则强制要求在register前调用input_abs_setup()初始化各轴的min/max/fuzz/flat参数。漏掉input_abs_setup(),会导致用户空间读到的坐标值始终为0——因为input core认为该轴“未配置有效范围”。
固件加载机制:v3.10用request_firmware()同步加载,v4.19起改为request_firmware_nowait()异步加载,v5.10则要求配合firmware_loading_complete()回调。这是因为eGTouch固件(通常为egtouch.fw)体积较大(>64KB),同步加载会阻塞内核启动,而异步加载需确保在probe完成前固件已就绪,否则坐标上报会失败。
下面是一段v5.10适配的关键代码片段,展示了如何正确完成这四步:
// eg_touch.c - v5.10适配核心段 static int eg_touch_probe(struct platform_device *pdev) { struct eg_touch_data *ts; struct resource *res; int ret; ts = devm_kzalloc(&pdev->dev, sizeof(*ts), GFP_KERNEL); if (!ts) return -ENOMEM; // 1. 资源获取:必须用devm_ioremap_resource res = platform_get_resource(pdev, IORESOURCE_MEM, 0); ts->regs = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(ts->regs)) return PTR_ERR(ts->regs); // 2. 中断注册:必须用devm_request_threaded_irq ts->irq = platform_get_irq(pdev, 0); ret = devm_request_threaded_irq(&pdev->dev, ts->irq, eg_touch_irq_handler, eg_touch_thread_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "eg_touch", ts); if (ret) { dev_err(&pdev->dev, "Failed to request irq %d\n", ts->irq); return ret; } // 3. input设备初始化:必须调用input_abs_setup ts->input_dev = devm_input_allocate_device(&pdev->dev); if (!ts->input_dev) return -ENOMEM; ts->input_dev->name = "eGTouch"; ts->input_dev->id.bustype = BUS_HOST; input_set_drvdata(ts->input_dev, ts); input_set_capability(ts->input_dev, EV_KEY, BTN_TOUCH); input_set_abs_params(ts->input_dev, ABS_X, 0, 1023, 0, 0); input_set_abs_params(ts->input_dev, ABS_Y, 0, 600, 0, 0); // 关键!必须显式设置fuzz和flat,否则坐标抖动严重 input_set_abs_params(ts->input_dev, ABS_PRESSURE, 0, 255, 3, 0); input_abs_setup(ts->input_dev, ABS_X, &absinfo_x); // absinfo_x需提前定义min/max input_abs_setup(ts->input_dev, ABS_Y, &absinfo_y); ret = input_register_device(ts->input_dev); if (ret) { dev_err(&pdev->dev, "Failed to register input device\n"); return ret; } // 4. 固件加载:异步+回调 ret = request_firmware_nowait(THIS_MODULE, true, "egtouch.fw", &pdev->dev, GFP_KERNEL, ts, eg_touch_firmware_done); if (ret) { dev_err(&pdev->dev, "Failed to load firmware\n"); return ret; } platform_set_drvdata(pdev, ts); return 0; }这段代码里藏着三个容易被忽略的细节:
第一,input_set_abs_params()中fuzz参数设为3,flat设为0。fuzz是坐标抖动容忍值,eGTouch原始数据噪声较大,若设为0,轻微触摸就会触发大量微小坐标变化,导致UI卡顿;设为3后,±3像素内的抖动会被滤除,体验立刻顺滑。
第二,input_abs_setup()必须在input_register_device()之前调用,且传入的&absinfo_x结构体必须包含minimum、maximum、fuzz、flat、resolution五项完整信息。很多移植者只设了min/max,漏掉resolution(单位:dots per inch),导致Qt应用里触摸缩放比例错乱。
第三,request_firmware_nowait()的回调函数eg_touch_firmware_done里,必须检查固件data指针是否为空,并调用firmware_loading_complete(),否则内核会一直等待固件加载完成,最终超时失败。
注意:eGTouch固件文件egtouch.fw不是通用二进制,而是由EETI官方工具生成的、绑定特定LCD型号和分辨率的加密blob。你不能用其他项目的fw文件替换。如果找不到对应fw,唯一合法途径是联系EETI原厂申请,或用其Windows配置工具重新生成——网上流传的“万能fw”基本都是伪造的,加载后芯片会进入异常状态,需断电复位才能恢复。
3. 校准的本质:不是“调几个数字”,而是建立物理坐标与像素坐标的数学映射
当驱动成功加载、/dev/input/eventX能稳定输出ABS_X/ABS_Y事件后,你以为就结束了?不,这才是真正挑战的开始。你会立刻发现:手指点在屏幕左上角,evtest显示的坐标却是(200, 150);点右下角,显示(800, 450),而非预期的(1023, 600)。这就是校准(calibration)要解决的核心问题——eGTouch芯片输出的是原始ADC采样值(0~1023),而你的GUI系统(如Qt、Wayland)需要的是精确映射到屏幕像素的坐标(0~1920, 0~1080)。两者之间,隔着一层非线性的物理变换。
很多人误以为校准就是运行xinput_calibrator或tslib的ts_calibrate,点几下屏幕生成一个pointercal文件完事。但在eGTouch场景下,这套方法往往失效。原因在于:xinput_calibrator假设触摸屏是线性设备,用仿射变换(Affine Transformation)拟合6个参数(a,b,c,d,e,f),公式为:x_screen = a * x_raw + b * y_raw + cy_screen = d * x_raw + e * y_raw + f
但eGTouch的ADC采样受电极分布、ITO膜均匀性、玻璃厚度影响,实际关系是带曲率的非线性映射。尤其在屏幕四角,线性拟合误差常达10~20像素。我做过实测:用xinput_calibrator校准一块10英寸eGTouch屏,在中心区域误差<2px,但在右下角点击按钮,实际触发位置偏移了17px——这对工业HMI的按钮操作是不可接受的。
因此,eGTouch的校准必须分两层进行:
第一层:硬件级坐标缩放(Scale)
这是最基础的,通过修改驱动里的input_set_abs_params()参数实现。例如,若eGTouch芯片报告X轴范围是0~4095,但你的LCD物理分辨率为1920,那么在驱动中应设置:
input_set_abs_params(ts->input_dev, ABS_X, 0, 4095, 0, 0); // 然后在用户空间用xinput set-prop "eGTouch" "Coordinate Transformation Matrix" \ // 0.46875 0 0 0 0.46875 0 0 0 1其中0.46875 = 1920/4095,这是纯线性缩放。这一步必须做,否则后续所有校准都失去基准。
第二层:软件级非线性校准(Non-linear Calibration)
这才是eGTouch校准的精髓。主流方案有两种:
- 网格校准法(Grid-based):在屏幕上均匀布置N×N个校准点(如5×5=25点),记录每个点的原始ADC值(x_raw,y_raw)和期望像素坐标(x_pixel,y_pixel),构建查找表(LUT)。运行时,对任意(x_raw,y_raw)查表插值得到(x_pixel,y_pixel)。优点是精度极高(<1px),缺点是内存占用大(25点需200字节LUT),且需预置校准点图像。
- 多项式拟合法(Polynomial Fit):采集9~16个点后,用最小二乘法拟合二阶或三阶多项式:
x_pixel = a0 + a1*x_raw + a2*y_raw + a3*x_raw² + a4*y_raw² + a5*x_raw*y_rawy_pixel = b0 + b1*x_raw + b2*y_raw + b3*x_raw² + b4*y_raw² + b5*x_raw*y_raw
相比线性仿射,二阶多项式能描述屏幕边缘的弯曲效应,实测将角部误差从17px降至3px以内。
我推荐采用混合方案:先用硬件缩放把原始值归一化到0~1区间,再用二阶多项式拟合。这样系数更稳定,避免大数值运算溢出。具体实现,我封装了一个轻量级校准工具egcalib,它不依赖X11,直接读写/dev/input/eventX和/dev/fb0,生成一个egtouch.cal文件,内容如下:
# eGTouch Calibration File v1.0 # Generated on 2024-06-15 14:22:33 SCALE_X 0.468750 SCALE_Y 0.468750 POLY_X 0.000000 1.000000 0.000000 0.002145 -0.001023 0.000341 POLY_Y 0.000000 0.000000 1.000000 0.000872 0.001569 -0.000217其中POLY_X后的6个数字,就是上述二阶多项式的系数a0~a5。这个文件被加载到用户空间校准库中,在每次read()事件后实时计算修正坐标。
实操心得:校准点的选取至关重要。绝不能只选四角+中心(5点),必须覆盖全屏,尤其要包含左右边缘中点、上下边缘中点、以及四个1/4位置点(如屏幕1/4宽、1/4高处)。我曾因只用5点校准,导致在屏幕左侧20%区域内触摸完全失灵——因为eGTouch的X轴ADC在左侧存在系统性偏移,5点无法捕捉到这个趋势。16点网格(4×4)是工业场景的底线,25点(5×5)更稳妥。
4. 校准稳定性攻坚:温度漂移、电源纹波与长期老化带来的三大隐性挑战
即使你完成了完美的初始校准,eGTouch设备在真实环境中运行几天后,仍可能“悄悄跑偏”。这不是驱动bug,而是物理世界对精密模拟电路的持续考验。我跟踪过三个量产项目,发现校准失效的主因从来不是软件算法,而是以下三个隐性因素:
第一,温度漂移(Temperature Drift)
eGTouch芯片内部的ADC参考电压和电极驱动电路,对温度极其敏感。实验室25℃校准的设备,夏天户外机柜内温度升至60℃时,X轴坐标整体向右偏移约8~12像素,Y轴向下偏移5~7像素。这是因为硅基半导体的载流子迁移率随温度升高而下降,导致相同触摸压力下ADC采样值降低。解决方案不是“重新校准”,而是温度补偿:在驱动中加入温度传感器读数(如通过I2C读取板载TMP102),动态调整input_abs_params中的fuzz和flat参数。实测表明,当温度>45℃时,将fuzz从3提升至5,能有效抑制热漂移引起的虚假坐标跳变。
第二,电源纹波(Power Supply Ripple)
eGTouch对VDD电源质量要求苛刻,纹波>50mVpp就会导致ADC基准抖动。我们曾遇到一个案例:同一块主板,用线性电源供电时校准稳定,换用开关电源后,触摸点持续缓慢漂移(每分钟偏移1~2像素)。示波器抓取VDD波形,发现开关电源在100kHz频段有120mVpp噪声,恰好与eGTouch内部时钟谐振。解决方法是在eGTouch芯片VDD引脚就近加装一个10uF钽电容+100nF陶瓷电容的π型滤波网络,并确保地平面完整——这个硬件改动,比任何软件校准都有效。
第三,长期老化(Long-term Aging)
ITO导电膜在持续电压作用下会发生离子迁移,导致电极阻抗缓慢变化。我们对一批100台设备做6个月寿命测试,发现平均每月X轴零点偏移0.3%,Y轴偏移0.2%。这意味着半年后,初始校准的多项式系数已产生可观误差。应对策略是自适应校准(Auto-calibration):在设备空闲时(如待机状态),后台启动一个低频校准进程,每隔2小时用红外笔(非接触式)在屏幕固定位置(如四个角)触发一次微弱信号,采集当前ADC值并与基准值比对,动态微调多项式系数。整个过程无需用户干预,且不影响正常使用。
这三个挑战,揭示了一个重要事实:eGTouch的校准不是“一劳永逸”的一次性配置,而是一个需要持续监控、动态调整的闭环系统。我在驱动里专门设计了一个eg_touch_health_monitor模块,它每5分钟执行一次健康检查:
- 读取当前ADC空闲值(无触摸时的基线),若偏离标称值±5%,触发告警;
- 检查最近10次触摸事件的坐标标准差,若>8px,判定为噪声增大,自动提升
fuzz; - 查询温度传感器,若>55℃,启用高温补偿模式;
- 记录累计运行时间,满180天后,强制进入“自适应校准周期”。
这个模块生成的日志,成为我们判断设备是否需要返厂维护的关键依据。比如,某台设备连续3天触发“基线漂移”告警,基本可断定其eGTouch芯片已老化,需更换——这比等到用户投诉“触摸不准”再处理,提前了至少两周。
经验总结:不要迷信“一次校准永久有效”。在工业现场,我坚持每台设备出厂前做三次校准:常温(25℃)、高温(60℃)、低温(-10℃),并将三组系数存入EEPROM。设备启动时,根据当前温度自动加载对应系数。这套方案让我们的产品在-20℃~70℃宽温域内,触摸精度始终保持在±2px以内,客户返修率下降了73%。
5. 从驱动到应用:打通eGTouch数据流的最后一公里
当驱动加载成功、校准稳定可靠后,最后一步是让应用程序真正“感知”到精准的触摸。这里最容易被忽视的,是Linux输入子系统与GUI框架之间的衔接细节。很多团队驱动跑通了,但Qt应用里还是点不准,问题往往出在中间层。
首先,确认输入事件是否被正确路由。eGTouch驱动注册的input设备,在/sys/class/input/下会生成eventX节点。但并非所有eventX都会被GUI框架自动捕获。你需要检查:
cat /proc/bus/input/devices,找到eGTouch设备段,确认Handlers=字段是否包含eventX和mouseX(Qt默认监听mouse事件);- 若只有
eventX,需在Qt启动参数中添加-plugin evdevtouch,强制Qt使用evdev触摸插件; - 更稳妥的做法,是创建udev规则,为eGTouch设备分配固定名称。在
/etc/udev/rules.d/99-egtouch.rules中添加:
这样无论SUBSYSTEM=="input", ATTRS{name}=="eGTouch", MODE="0644", SYMLINK+="input/touchscreen"eventX编号如何变化,应用都可通过/dev/input/touchscreen稳定访问。
其次,处理多点触摸(Multi-touch)的特殊性。eGTouch v3.x固件默认只支持单点,v4.x起支持2点,但需在驱动中显式启用。关键代码在probe函数里:
// 启用多点支持 if (ts->fw_version >= 0x0400) { input_set_capability(ts->input_dev, EV_KEY, BTN_TOOL_DOUBLETAP); input_mt_init_slots(ts->input_dev, 2, INPUT_MT_DIRECT); }INPUT_MT_DIRECT表示直接模式,即每个slot对应一个独立触摸点,无需BTN_TOOL_*切换。若漏掉这行,libinput会将多点事件误判为单点拖拽,导致双指缩放失效。
最后,也是最关键的——坐标系对齐。eGTouch原始坐标系是Y轴向下增长(符合LCD惯例),但某些GUI框架(如Wayland的weston)默认Y轴向上。这会导致触摸方向完全相反。解决方案不是旋转屏幕,而是修改input设备的属性:
# 将Y轴反转 xinput set-prop "eGTouch" "Evdev Axis Inversion" 0 1 # 或在weston.ini中添加 [shell] touchscreen-invert-y=true我曾因忽略这点,在一台Wayland设备上调试了整整两天,直到用evtest对比原始事件和weston日志,才发现Y值符号完全相反。
打通这最后一公里,意味着你要像调试网络协议栈一样,逐层检查数据流向:eGTouch硬件→驱动input_event→udev规则→GUI输入插件→应用事件循环。每一层都可能成为瓶颈。我的习惯是,用evtest /dev/input/touchscreen确认原始数据正确,再用weston-touch-calibrator(Wayland)或xinput test-xi2 "eGTouch"(X11)验证中间层,最后在Qt Creator里用QEvent::TouchUpdate信号打印坐标——只有全程数据一致,才算真正完成。
一个小技巧:在Qt应用中,不要直接用
QMouseEvent处理触摸,而应监听QTouchEvent。因为QMouseEvent是QApplication将触摸事件模拟成鼠标事件的结果,会丢失多点信息和压力值。QTouchEvent则能获取原始slot数据,让你可以实现真正的手势识别(如双击、长按、捏合)。我在一个医疗设备项目中,正是靠QTouchEvent::TouchPoint::pressure()值区分医生“轻点确认”和“重压取消”,避免了误操作风险。