ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FPGA+Linux下7寸触摸屏驱动开发全攻略

FPGA+Linux下7寸触摸屏驱动开发全攻略 拿到一块FPGA开发板把Linux跑起来串口能登录网口也能通你可能觉得最难的已经过去了。结果接上一块7寸触摸屏屏亮了触摸却毫无反应——这个瞬间我猜不少人都体会过。我前前后后调过好几块不同方案的FPGA板子有Xilinx Zynq带硬核ARM的有SoC FPGA的也有MicroBlaze软核外挂DDR跑Linux的。坦白说FPGALinux这套组合下触摸屏驱动几乎每一次都会把人拦住。原因很简单纯ARM开发板出厂时厂家已经把设备树、内核配置、驱动全给你配好了你接上一个屏幕就能用而FPGA板卡的硬件逻辑是“你自己搭的”触摸芯片挂在哪条I2C总线上、中断脚接到哪个GPIO、复位时序如何设计Linux完全不知道这全部要靠设备树和驱动告诉它。这篇文章我就把FPGALinux下7寸触摸屏驱动的完整链路讲透从触摸方案选型、硬件接入到设备树配置、驱动代码框架再到实测中我踩过的那些坑和优化手段。不管你是第一次在这个组合上点触摸屏还是已经调通了想优化体验这篇内容应该都能给你省下不少时间。1. FPGALinux这个组合为什么触摸屏驱动反而成了最费神的环节1.1 两个处理器、两套逻辑接口映射是最大的坑FPGALinux和普通ARM Linux最大的区别是系统里有“两个世界”一个是PS处理系统也就是硬核ARM CPU或者运行Linux的软核处理器另一个是PL可编程逻辑也就是FPGA本身。触摸屏这种外设放在纯ARM开发板上硬件连线是固定的厂家的BSP已经把一切写死。但在FPGA开发板上触摸芯片的I2C接口可以挂在PS端原生的I2C控制器上也可以由PL侧的逻辑模拟I2C它的中断脚和复位脚既可以直接接到PS的MIO引脚也可以接到PL的EMIO引脚甚至接进一个AXI GPIO IP核。这意味着Linux内核根本没法自动知道这套硬件拓扑。你得在设备树里把I2C控制器、中断控制器、GPIO编号统统写清楚驱动才可能被正确匹配。打个比方PS是公司里的决策层PL是那个随时可以改流水线的生产车间。现在有一台新设备触摸屏要装进车间你光买回来接上电是不够的你还得告诉管理层这台设备由哪个班组I2C控制器负责供电通信出了状况找谁中断控制器紧急停机按钮接在哪里GPIO。设备树干的就是这件事。1.2 内核里其实有现成驱动但你不配设备树它就“装睡”很多新手以为只要内核带了触摸驱动硬件接好就能用。实际上Linux的驱动模型是“匹配制”驱动先声明自己支持哪些设备通过compatible字符串、I2C设备ID等然后总线上要出现对应的设备节点驱动才会执行probe函数去初始化硬件。比如你用的是GT911这颗电容触摸芯片内核里确实有goodix对应驱动但你如果在设备树里没有给I2C总线下挂一个compatible goodix,gt911的节点这个驱动就永远不会被唤醒。板子上的触摸芯片在I2C总线上扫描到了也没用因为内核默认不会去主动猜哪个地址挂着什么设备。我见过不少人在这一步耗了半天一会怀疑芯片坏了一会怀疑焊接有问题最后发现只是设备树里少了一段描述。所以先记住这句话FPGA上跑Linux外设驱动不是“装内核”而是“写设备树”。1.3 先弄清你的触摸方案是哪一种后面全都不一样7寸屏的触摸方案五花八门动手之前必须先搞清楚你的屏是哪一类因为接口、驱动框架、调试工具和校准方式差异非常大。触摸类型常见接口常见芯片/方案驱动方式是否需要校准电容触摸I2CGT911、FT5x06、GT5688等内核input子系统 中断一般不需要坐标出厂已校准电阻触摸SPI少数I2CXPT2046、ADS7846、TSC2007input子系统 中断/轮询必须校准tslib红外触摸框USB HID串口TouchKit、各类红外框HID驱动或厂家串口协议一般不需要电容主动笔/触控I2CWacom、Goodix笔方案input子系统 MT协议通常不需要从热搜词里的“touchkit 红外触摸屏驱动”来看确实也有不少人在FPGA方案上接红外触摸框。这类触摸框多数走USB HIDLinux内核的hid_multitouch驱动一般能直接认出来反而是最省事的一种。但7寸屏模组最常见的还是电容方案所以下文我会重点讲电容触摸电阻和红外也会穿插提一下。2. 7寸触摸屏硬件接入排线、电平、地址这些容易翻车的点2.1 电容触摸最常见的GT911和FT5x06接口细节先吃透GT911是7寸屏上用得最广泛的电容触摸芯片支持5点触摸内部寄存器地址是16位的I2C从机地址可以通过复位时序来选择常见是0x5D7位地址对应读地址0xBA也可能是0x14读地址0x28。很多人用i2cdetect扫不到设备多半是地址不对或者复位时序没做对。GT911的复位时序其实挺讲究先把INT脚拉低再给RST脚一个从低到高的跳变保持至少10ms然后把INT脚释放。INT电平状态在复位结束那一刻会决定芯片应用哪个I2C地址。驱动里reset-gpios和irq-gpios两个属性通常都要配光有其一很容易出现“设备偶现、时好时坏”的问题。FT5x06系列包括FT5206、FT5306、FT5406等是FocalTech的5点电容方案I2C地址一般是0x38寄存器是8位的比GT911简单但读数据时同样记得芯片字节序是大端坐标是高位在前低位在后。如果你拿到的7寸屏模组丝印上没有芯片型号可以直接看排线上丝印很多国产屏用的是GT911的国产替代寄存器兼容。2.2 电阻触摸的接入XPT2046/ADS7846的坑也不少如果用的是电阻触摸屏XPT2046或者ADS7846是主流方案走SPI接口内部是12位ADC。这类芯片的好处是驱动简单坏处是必须做校准而且触摸精度会随着屏体老化缓慢漂移。电阻触摸有四个引出脚X、X-、Y、Y-通常模组已经集成了ADC芯片接出来的是SPI的SCLK、MOSI、MISO、CS和中断脚。触摸发生时中断脚会拉低告诉CPU“有人按了屏幕”然后通过SPI读取X/Y坐标和压力值。这里特别要注意的是参考电压引脚有些模组的VREF需要外部提供接错或者悬空会导致ADC读数永远是一个固定值。2.3 IRQ和RST引脚到底接哪直接决定设备树怎么写这是FPGA方案里最容易乱的地方。触摸芯片的IRQ脚和RST脚按开发板的连接方式不同在Linux侧会有完全不同的表达方式物理连接方式Linux GPIO编号规律设备树写法直接接PS的MIO引脚编号就是MIO号如MIO20就是GPIO 20interrupt-parent gpio0interrupts 20 IRQ_TYPE_EDGE_FALLING接PL的EMIO引脚编号从54开始EMIO0是GPIO 54同样使用gpio0但引脚号是54接PL侧AXI GPIO IP要看AXI GPIO在系统里的基地址通常是另一个gpio controllerinterrupt-parent可能变成axi_gpio_0需要单独描述中断有次我调试一块板子触摸中断脚接到了PL的EMIO设备树里却按MIO写了引脚号结果驱动probe触发了就是收不到任何中断。后来一查EMIO引脚编号要加54改完设备树重新加载事件就正常上报了。这里有个实用经验先看FPGA工程的约束文件XDC或SDC确认触摸屏的IRQ和RST脚到底连到了哪个物理引脚。是PS侧MIO还是PL侧EMIO有没有经过AXI GPIO这个搞清楚了设备树的引脚号才算有着落。2.4 I2C上拉和电平转换接线时就被很多人埋雷I2C总线必须要有上拉电阻这是基础常识但FPGA开发板上经常有“这个I2C总线还挂了别的设备”的情况。如果上拉电阻用的是1k甚至更小总线负载太重波形会变差如果用10k总线上挂的设备一多信号上升沿又太慢。一般来说触摸屏模组的排线都比较短4.7k是比较稳妥的选择但如果你调了很久通信不稳定可以试着在ACK位挂逻辑分析仪看波形一抓一个准。电平问题也容易踩。很多7寸屏模组的 I/O 电平是1.8V而Zynq PS端I2C控制器默认是3.3V MIO或者1.8V/3.3V可配置的Bank。如果电平不匹配轻则通信不稳定重则烧坏触摸芯片。接板子之前一定要看原理图或者模组手册确认I2C、INT、RST这四条线都在同一个电平域必要时加电平转换芯片。3. 设备树与内核配置让Linux先“看见”这块触摸屏3.1 手写设备树节点每个属性都有它的用途假设你的触摸芯片是GT911挂在PS端的I2C1上I2C地址是0x5DIRQ脚接到MIO21RST脚接到MIO20那么设备树节点大概长这样i2c1 { status okay; clock-frequency 100000; gt911: gt9115d { compatible goodix,gt911; reg 0x5d; interrupt-parent gpio0; interrupts 21 IRQ_TYPE_EDGE_FALLING; irq-gpios gpio0 21 GPIO_ACTIVE_HIGH; reset-gpios gpio0 20 GPIO_ACTIVE_HIGH; touchscreen-max-x 1024; touchscreen-max-y 600; touchscreen-size-x 1024; touchscreen-size-y 600; }; };解释一下各属性的含义compatible驱动匹配用的关键字符串内核里drivers/input/touchscreen/goodix.c就是通过这个字符串来识别硬件的。不同芯片对应的compatible可以在内核源码里查到抄错一个字符驱动就不绑定。regI2C从机地址注意这里填7位地址。GT911如果是0x5D就填0x5d。interrupt-parent和interrupts告诉内核中断信号从哪个控制器来、用哪个GPIO、什么触发方式。Zynq的MIO中断用gpio0引脚号就是MIO号触发方式按芯片手册GT911一般下降沿或者低电平都有实际用下降沿居多。irq-gpios和reset-gpios驱动做复位时序时要用这两个GPIO很多开源驱动里是必须的缺失会导致驱动probe失败。touchscreen-max-x等属性坐标上限。这个值必须和触摸IC固件里的坐标范围一致也要和屏幕分辨率匹配。7寸屏常见的有800x480和1024x600两种如果你的屏是800x480却配了1024x600触摸位置会有明显偏差。3.2 内核配置确保对应驱动被编译进去设备树写好了内核里还得有对应驱动。以GT911为例内核配置项是CONFIG_TOUCHSCREEN_GT9XX在menuconfig里路径一般是Device Drivers - Input device support - -*- Generic input layer [*] Touchscreens - [*] Goodix GT9xxx based I2C touch screensFT5x06类似配置项是CONFIG_TOUCHSCREEN_EDT_FT5X06。如果你用的是USB红外触摸框需要保证CONFIG_HID_MULTITOUCH打开。很多厂家默认的内核配置里并没有把所有这些触摸驱动编上自己重新编译内核时最容易漏的就是这一步。检查内核是否已经打开相关配置可以这样zcat /proc/config.gz | grep -i touchscreen如果内核没有开IKCONFIG那就只能通过menuconfig重新配置再编译了。编译完设备树和内核镜像烧写到板子上先别急着接屏幕用模组自带的测试程序排除硬件问题再说。3.3 验证绑定是否成功别让驱动“装睡”重启系统之后验证顺序应该是这样i2cdetect -y 1把1换成你的I2C总线号能扫到5d或者14说明芯片在线。dmesg | grep -i goodix看驱动有没有打印probe成功的信息。ls /sys/bus/i2c/devices/能看到类似1-005d这样的目录说明设备已经注册到I2C总线。ls /dev/input/能看到event0或event1之类的输入设备节点。cat /proc/interrupts当触摸屏被按下时对应中断号的中断计数应该在增加。我自己调试时最喜欢直接上evtest选到触摸屏对应的event设备手指点一下屏幕看有没有事件流出来。如果上报的X/Y坐标明显不对就是后面要解决的坐标映射问题如果完全没事件回到第1步重新查硬件。3.4 新旧内核设备树属性差异移植时极易翻车内核版本不同触摸屏通用设备树属性的名字也有变化。老内核4.x早期里常见的是touchscreen-size-x和touchscreen-size-y新内核5.x之后推荐的是touchscreen-max-x和touchscreen-max-y还有一些驱动支持touchscreen-inverted-x这种反转属性。如果你是从旧项目的设备树直接拿来改容易忽略这个变化。轻则坐标范围没生效重则驱动解析设备树属性失败直接probe报错。一个比较稳的办法是去内核源码Documentation/devicetree/bindings/input/touchscreen/touchscreen.txt或者对应的YAML文件里查一下你所用内核支持哪些属性名。4. 驱动开发完整流程从I2C驱动框架到触摸事件上报4.1 什么情况下需要自己写驱动如果你用的触摸芯片是GT911、FT5x06这种大路货内核自带驱动通常够用不需要自己写。但如果你用的是国产冷门芯片或者厂家只给了安卓驱动又或者你想在驱动里加入一些特殊处理比如某个区域的触摸屏蔽、双击唤醒、手套模式那就得自己写内核驱动了。我自己写这类驱动框架永远是固定的三块设备匹配与probe、中断处理与数据读取、input子系统事件上报。只要把这三块吃透换任何一个I2C接口的触摸芯片改改寄存器地址和协议解析就能跑起来。4.2 最小可用的I2C触摸驱动代码框架下面是一个精简但完整的驱动骨架假设触摸芯片的I2C协议为复用一个寄存器块每次读取8字节前5字节是触点信息触点数、X高低位、Y高低位#include linux/module.h #include linux/i2c.h #include linux/input.h #include linux/interrupt.h #include linux/of.h #define TOUCH_MAX_X 1024 #define TOUCH_MAX_Y 600 struct my_touch_dev { struct i2c_client *client; struct input_dev *input; }; static irqreturn_t my_touch_threaded_irq(int irq, void *data) { struct my_touch_dev *ts data; u8 buf[8]; int ret; int x, y; ret i2c_master_recv(ts-client, buf, sizeof(buf)); if (ret 0) return IRQ_HANDLED; if (!buf[0]) goto release; /* 没有触点 */ x (buf[1] 8) | buf[2]; y (buf[3] 8) | buf[4]; input_report_key(ts-input, BTN_TOUCH, 1); input_report_abs(ts-input, ABS_X, x); input_report_abs(ts-input, ABS_Y, y); input_sync(ts-input); return IRQ_HANDLED; release: input_report_key(ts-input, BTN_TOUCH, 0); input_sync(ts-input); return IRQ_HANDLED; } static int my_touch_probe(struct i2c_client *client) { struct my_touch_dev *ts; int ret; ts devm_kzalloc(client-dev, sizeof(*ts), GFP_KERNEL); if (!ts) return -ENOMEM; ts-client client; i2c_set_clientdata(client, ts); ts-input devm_input_allocate_device(client-dev); if (!ts-input) return -ENOMEM; ts-input-name my-touchscreen; ts-input-id.bustype BUS_I2C; __set_bit(EV_KEY, ts-input-evbit); __set_bit(BTN_TOUCH, ts-input-keybit); __set_bit(EV_ABS, ts-input-evbit); input_set_abs_params(ts-input, ABS_X, 0, TOUCH_MAX_X, 0, 0); input_set_abs_params(ts-input, ABS_Y, 0, TOUCH_MAX_Y, 0, 0); ret input_register_device(ts-input); if (ret) return ret; ret request_threaded_irq(client-irq, NULL, my_touch_threaded_irq, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, my_touch, ts); if (ret) return ret; return 0; } static const struct of_device_id my_touch_of_match[] { { .compatible my,touch }, { } }; MODULE_DEVICE_TABLE(of, my_touch_of_match); static struct i2c_driver my_touch_driver { .driver { .name my_touch, .of_match_table my_touch_of_match, }, .probe my_touch_probe, }; module_i2c_driver(my_touch_driver); MODULE_LICENSE(GPL);注意我特意用了request_threaded_irq而不是普通的request_irq第一个handler参数传了NULL第二个参数是线程化处理函数。原因很直接I2C读取是可能睡眠的操作不能在中断上下文直接执行。线程化中断把处理函数放到内核线程上下文就可以安全地调用I2C读函数不用担心原子上下文报错。4.3 input子系统上报事件的核心逻辑驱动里报坐标用的是Linux input子系统核心API就几个input_allocate_device分配一个input设备结构体。input_register_device把input设备注册到内核注册之后用户态/dev/input/eventX节点才会出现。input_report_abs上报绝对坐标。触摸屏是绝对定位设备用ABS_X/ABS_Y如果是触摸板之类相对定位设备才用REL_X/REL_Y。input_report_key上报按键状态触摸事件里的BTN_TOUCH就是这个角色。input_sync同步上报告诉用户态“这一批事件结束了”。写驱动时最容易漏的就是input_sync。漏一次用户态收到的就是残缺事件evtest里会看到坐标一直在变但点的状态不对。4.4 多点触摸用MT Protocol B管理触点上面示例只支持单点触摸对7寸屏的日常点击操作够用。但如果你想支持双指缩放、多指手势就得用多点触摸协议。多点触摸有两种常见实现方式Type A每个触点用独立的一对ABS_MT_POSITION_X/Y上报加上ABS_MT_TRACKING_ID来区分触点。适合不支持硬件触点跟踪的老芯片。Type B用slot机制管理触点触摸芯片如果自己能报告“触点ID”用Type B最高效。Type B的典型代码片段input_set_abs_params(ts-input, ABS_MT_POSITION_X, 0, TOUCH_MAX_X, 0, 0); input_set_abs_params(ts-input, ABS_MT_POSITION_Y, 0, TOUCH_MAX_Y, 0, 0); input_mt_init_slots(ts-input, 5, 0); /* 最多支持5点 */ /* 中断里 */ input_mt_slot(ts-input, slot_id); input_mt_report_slot_state(ts-input, MT_TOOL_FINGER, true); input_report_abs(ts-input, ABS_MT_POSITION_X, x); input_report_abs(ts-input, ABS_MT_POSITION_Y, y);Type B协议的好处是同一个slot的触点状态可以增量更新不用每次把所有触点全报一遍性能更好也更省电。4.5 中断、轮询、线程化中断三种方式怎么选触摸芯片上报数据的方式大致有三种硬件中断芯片检测到触摸时拉低IRQ脚驱动在中断处理里读数据。这是电容屏的主流方式功耗低、响应快。软件轮询没有IRQ脚或者IRQ没接时只能用内核定时器周期性读寄存器。缺点是浪费CPU触摸响应有延迟而且容易出现坐标拖尾。线程化中断threaded irq上面的代码示例用的就是这种方式。适合需要处理I2C/SPI读数据、且不想在原子上下文里做太多工作的驱动。如果条件允许优先用硬件中断线程化处理。如果板子上的IRQ脚确实没接轮询也不是不能用但建议轮询周期控制在10ms到20ms之间再低CPU占用就不划算了。5. 实测踩坑与排查无响应、坐标错乱、抖动漂移5.1 问题一i2cdetect扫不到触摸芯片这个现象最常见可能原因也最多。我按概率排一下I2C总线上没有上拉电阻或者上拉电阻虚焊。用万用表量一下SCL/SDA对地电压正常空闲状态应该是高电平如果是0V就说明上拉有问题。触摸芯片处于复位状态或休眠状态。GT911这种芯片必须按正确时序复位后才进入正常工作状态如果你的RST脚一直拉低芯片就一直是复位态。I2C地址不对。同一个芯片可能因复位时序不同或地址脚配置不同有多个地址扫描的时候要i2cdetect -y 1 0x10 0x7f扫全范围。I2C总线时钟频率太高。有些国产触摸芯片对I2C时序要求不高但时钟频率超过400kHz之后就容易出问题建议先按100kHz试。芯片供电电压不对或者电源纹波太大。这种情况比较隐蔽用示波器看VDD引脚如果触摸发生时电压明显跌落多半是供电能力不足。排查时最好的工具是逻辑分析仪。把SCL和SDA两根线挂上去启动时看有没有I2C通信波形。如果芯片被主机访问后没有ACK说明地址不对或芯片没正常工作如果主机根本没发起通信问题在设备树和I2C控制器配置上。5.2 问题二能收到事件但坐标方向反了、偏了触摸屏能上报坐标了但点左上角光标跑到右上角这属于坐标映射问题。原因一般是三种屏幕的安装方向和触摸IC的坐标轴方向不一致。比如屏幕旋转了180度安装但触摸IC固件里的坐标原点还是按排线方向定义的。这时候最简单的做法是在驱动里做坐标变换x max_x - xy max_y - y。设备树里的坐标范围和IC固件不匹配。比如IC固件实际是1024x600你设备树写的是800x480坐标会被按比例扭曲触摸位置越往边缘偏得越厉害。屏体是竖屏分辨率600x1024但你是横屏使用1024x600X/Y没交换。我之前调过一块7寸屏触摸方向和显示方向互为90度驱动里直接把X/Y交换一下就好了。这种问题不是硬件故障别急着换屏幕。如果用了tslib或者libinput还可以通过校准矩阵来纠正坐标映射不必改驱动代码。libinput的校准配置在weston或X11里都能设置tslib则用ts_calibrate生成校准文件。5.3 问题三触摸静置时坐标漂移、轻微抖动电容屏在带电环境下偶尔有坐标噪声但如果漂移严重比如手没碰屏幕坐标却在小范围乱跳就要排查了。我的排查顺序是先看电源纹波。触摸芯片的VDD如果是直接从DC-DC输出取的纹波大的时候ADC参考电压不稳定坐标就会漂。给VDD加一个10uF钽电容和100nF陶瓷电容通常能缓解。检查触摸屏和LCD模组之间的接地。有些屏模组触摸部分和显示部分共地不良会导致触摸信号受显示刷新干扰坐标周期性跳变。看驱动是否做了软件滤波。工业级触摸驱动里普遍会做一个简单的滑动平均或者中值滤波比如连续采5个点取中值就能有效抑制单点噪声。注意滤波不能太激进否则触摸拖动时会明显“发黏”影响手感。红外触摸框如果出现漂移多半是环境光干扰或者红外管上有灰尘遮挡。驱动层面能做的有限首先保证安装遮光条避免阳光或者强灯光直射。5.4 问题四中断风暴小半天CPU占用拉满触摸屏没坏但板子CPU占用率很高top一看全是软中断或者内核线程在忙。这类问题的典型原因是中断处理方式不对。最常见的坑触摸芯片的IRQ脚是低电平有效也就是“有数据时一直拉低”但你在设备树里配的是下降沿触发IRQ_TYPE_EDGE_FALLING。芯片中断后IRQ一直保持低电平不会再有新的下降沿于是中断丢了不说驱动里如果还有轮询或者定时重读就会造成反复尝试读取CPU被白白耗掉。解决办法是改用低电平触发IRQ_TYPE_LEVEL_LOW并且在线程化中断里把数据完全读完、等芯片释放IRQ后再返回。IRQF_ONESHOT标志在线程化中断里会自动处理电平的屏蔽和恢复实测下来能解决大部分中断风暴问题。5.5 校准电阻屏必须做的操作电阻屏不像电容屏出厂时坐标就调好了它必须做校准。Linux下最常用的是tslib库。基本流程export TSLIB_TSDEVICE/dev/input/event1 export TSLIB_CALIBFILE/etc/pointercal ts_calibrate屏幕上会出现几个十字交叉点依次点完tslib会生成校准文件。如果你的系统用的是Qt或GTK应用需要在环境变量里指定TSLIB_CONSOLEDEVICE和TSLIB_FBDEVICE指向你的终端设备和framebuffer设备校准界面才能正常显示。要注意的是校准文件里的坐标变换矩阵和屏幕分辨率强相关。如果你换了不同分辨率的屏幕校准必须重新做不能直接把旧的pointercal文件拷过去。6. 性能优化与进阶玩法触摸体验从“能用”到“好用”6.1 读数据优化减少I2C通信次数是关键I2C本身速率不高每次通信还有地址、寄存器、应答的开销。触摸屏的数据读取频率一般是60Hz到120Hz如果每次都在中断里发起好几次I2C传输总线占用会明显偏高还可能影响到同一总线上其他设备。优化的方向用i2c_master_recv或者i2c_smbus_read_i2c_block_data一次读完所有需要的数据而不是分多次读取。如果芯片支持批量读尽量读一次完整状态帧包含所有触点、压力、触摸ID信息。适当降低采样率。对7寸屏来说报点频率60Hz已经完全够用不是所有场景都要120Hz。把触摸IC的采样率寄存器调低一级能省不少CPU。6.2 数据帧校验别让一个错误字节毁掉整个触摸体验很多触摸芯片的协议里都有校验位或者固定的帧头。比如GT911的坐标寄存器区每帧数据里会有触点状态字你要检查这个字的合理性再上报。我见过一个案例触摸屏偶尔出现“鬼触点”屏幕突然乱跳一下然后恢复。后来抓I2C数据发现是触摸芯片返回了异常字节驱动没有校验就直接上报input子系统就把它当作一个真实触点处理了。加上校验之后问题彻底消失。校验规则一般这么写检查触点数是否在合理范围0到触点上限之间。检查坐标是否在设备树配置的范围之内。如果协议有checksum校验一下整体数据的和。6.3 触摸与显示协同游戏级流畅度怎么来的如果你只是做个控制面板触摸屏60Hz上报够用了。但如果你要做游戏或者图形界面实时交互触摸和显示的时序协同就很重要。Linux display栈里触摸事件从内核到用户态要经过input子系统、libinput/evdev、Wayland或X11每一层都有缓冲和延迟。实测中比较有效的优化有启用libinput的“触摸屏手势”功能把轻扫滚动、双指缩放这类常见操作交给libinput处理应用层就不用自己解析原始坐标。在驱动的input_sync之后立刻唤醒等待线程而不是依赖用户态的EventLoop轮询。如果FPGA侧有自己的显示刷新机制可以尝试把触摸中断和显示VSync对齐减少触摸到画面的视觉延迟。这在纯软件方案里较难做到但一些高端FPGA方案里会用PL侧逻辑直接同步。6.4 更进阶在PL侧做触摸数据预处理既然项目叫“FPGA技术教程”只讲Linux侧驱动多少有点浪费。我见过不少成熟产品会把触摸的脏活累活在PL侧做掉PS侧只负责接结果。思路大概是这样PL侧实现一个I2C控制器或者直接调用Xilinx的I2C IP定时或者由硬件中断触发读取触摸IC数据然后在PL逻辑里做坐标滤波、区域判定、手势初步识别最后通过AXI-Lite接口把归一化后的坐标放到几个寄存器里。Linux驱动变得极其简单probe时只映射一组寄存器地址中断里读4个字节就能拿到当前有效触摸点。这样做的好处很明显触摸扫描不占用PS侧CPU和I2C总线带宽。滤波逻辑完全硬件化延迟恒定。底层的冷门触摸IC协议变化时只改PLLinux驱动纹丝不动。缺点是PL侧开发量增加而且需要熟悉AXI总线的寄存器接口设计。如果你的项目里触摸只是辅助功能不建议一上来就搞这么重但如果触摸是产品核心交互我强烈推荐这个方案长期维护成本反而更低。调试FPGALinux触摸屏驱动我个人的体会是先硬件后软件先寄存器后框架。设备树和驱动代码写了一大堆不如先用i2cget把触摸芯片的版本寄存器读出来硬件通没通一目了然。硬件确认正常之后设备树和驱动只是照着芯片手册把协议翻译成代码的事。最后分享一个调试技巧如果手头没有示波器或逻辑分析仪可以在驱动probe成功之后把触摸IC的IRQ脚临时复用成一个普通的输入GPIO在用户态写个小脚本轮询这个引脚的电平变化配合evtest看事件流。这样能快速判断触摸事件是卡在硬件中断链路还是卡在内核input子系统报点的环节比一上来就翻驱动日志高效得多。
返回列表