
去年年底拿到一块黑金开发板板载 FPGA 芯片原本想跑个 Linux 系统练手结果在 7 寸触摸屏驱动上卡了整整一周。当时网上资料零零散散要么只讲 ARM 板子怎么配触摸屏要么只讲 FPGA 逻辑怎么写 I2C 控制器很少有把“FPGA Linux 7寸触摸屏”这条完整链路讲清楚的。这篇文章就把我实际调试这套组合的过程、原理和踩过的坑都写出来希望能帮到同样在 Linux 下驱动触摸屏的朋友尤其是手里刚好有 FPGA 开发板、想跑嵌入式 Linux 的。1. 项目拆解7寸触摸屏在FPGALinux系统里到底卡在哪很多人一开始以为给触摸屏写驱动就是装个内核模块、改一下设备树其实这只是最后一步。在“FPGA Linux”这套组合里触摸屏驱动涉及的面比普通 ARM 板卡宽得多因为 FPGA 侧的软核 CPU比如 MicroBlaze、NIOS II或者直接上 Zynq 里的硬核 ARM和片内逻辑、外设 IP 之间需要协同工作。1.1 先弄清楚触摸屏本身接口、控制器与模组选型7 寸触摸屏通常有两种触摸方式电阻屏和电容屏。现在主流是电容屏触摸控制器一般集成在模组上通过 I2C 接口输出触摸坐标。常见的触摸控制 IC 包括 GT911、FT5x06、GT9147 等这些芯片在 Linux 内核里大多有原厂驱动或社区驱动问题在于内核版本、I2C 时序、中断引脚、复位时序这些细节需要匹配。以 GT911 为例这颗芯片的 I2C 地址可能是 0x5D 或 0x14取决于 INT 引脚的上拉电阻配置。如果你在驱动里写死地址 0x5D但硬件上配置成了 0x14那触摸屏就完全没反应。我第一次调试时没仔细看原理图地址写错启动日志里 I2C 探测一直失败走了不少弯路。1.2 Linux 驱动在整个链路里的位置从物理触摸到应用层事件的完整路径我们可以把触摸屏工作链路拆成四段第一段手指触摸屏幕触摸控制 IC 检测到电容变化通过 I2C 总线把坐标数据写入内部寄存器。第二段FPGA 片内的 I2C 控制器或软核 CPU 的 I2C 外设读取触摸 IC 的寄存器数据。第三段Linux 内核里的触摸屏驱动input subsystem 下的驱动定时读取或中断触发读取坐标然后上报给 input 子系统。第四段应用程序通过 /dev/input/eventX 读取触摸事件完成手势识别、坐标映射等处理。驱动开发者主要工作在第三段但前面两个环节如果没打通驱动写得再完美也白搭。我在调试时先确保 I2C 总线能枚举到设备再用 i2c-tools 直接读寄存器确认触摸 IC 工作正常后才开始做内核驱动。1.3 方案选型为什么最终走“软核CPU Linux 现成触摸IC驱动”路线FPGA 上跑 Linux一般有三种方案一是用 Zynq 这类带硬核 ARM 的 SoC FPGA直接在 ARM 上跑 LinuxFPGA 逻辑作为外设二是用纯 FPGA 软核 CPUMicroBlaze/NIOS II软核跑 Linux三是 FPGA 只做逻辑外接 ARM 主控。我调试的平台属于第二种软核 CPU 跑 Linux。这种方案的好处是灵活可以在 FPGA 里自定义 I2C 控制器、中断控制器、DMA 等外设坏处是软核性能弱外设 IP 的寄存器时序需要自己核对接好。触摸屏驱动本身用内核自带的 GT911 驱动改动量不大难点在于让软核 CPU 的中断系统和设备树描述匹配这部分需要仔细对寄存器基地址和中断号。2. 触摸屏驱动开发前的准备工作正式写代码之前有几项准备工作不能跳过否则后面调试会很痛苦。这些工作包括硬件连接确认、内核配置、设备树编写、驱动源码准备。2.1 硬件连接与原理图确认I2C地址、复位引脚、中断引脚从硬件上要确认的最重要信息就是触摸控制 IC 的型号、I2C 地址、复位引脚 GPIO 编号、中断引脚 GPIO 编号。以 7 寸 GT911 模组为例常见连接是SCL、SDA 接到 FPGA 软核的 I2C 外设引脚INT 引脚接到 FPGA 的一个 GPIO可配置为中断输入RST 引脚接到另一个 GPIO用于上电复位I2C 地址由 INT 引脚电平决定。我在实际调板时发现INT 引脚在复位期间有特殊时序要求。GT911 要求在上电后先拉低 RST保持一段时间再拉高 RST然后根据 INT 引脚电平决定地址。如果只用简单的 GPIO 拉高拉低不关注时序可能触摸 IC 会一直处于异常状态。2.2 内核配置与设备树让触摸IC在Linux里“现形”在 Linux 里I2C 设备驱动通过设备树节点与硬件绑定。如果内核没有开启相应的 driver就算设备树写得再对也没用。首先要确保内核开启了 I2C 子系统、GPIO 中断支持、以及对应触摸 IC 的驱动。内核配置项一般在 Device Drivers - Input device support - Touchscreens 下GT911 对应的配置项是 CONFIG_TOUCHSCREEN_GT9XXFT5x06 是 CONFIG_TOUCHSCREEN_EDT_FT5X06。编译内核时可以用 menuconfig 搜索看看有没有使能。设备树节点的核心信息包括 compatible 字符串、regI2C 地址、interrupt-parent 和 interrupts、复位 GPIO、触摸屏分辨率等。compatible 字符串必须和驱动源码里的 of_match_table 一致否则内核不会绑定驱动。2.3 选择驱动源码内核自带、厂商SDK还是手写根据触摸 IC 的不同驱动的获取路径有差异GT911、FT5x06 这类主流触控 IC内核主线自带驱动通常直接可用只需要修改设备树节点配置。一些国产触摸 IC内核主线可能没有驱动需要去芯片原厂官网下载 Linux 驱动或者参考同系列驱动移植。如果触摸 IC 非常冷门可能要自己根据数据手册写一个 input 子系统驱动工作量会大不少。我的建议是先查内核版本对应的源码目录比如 drivers/input/touchscreen/ 下有没有 gt9xx.c 或 ft5x06.c。如果有优先用主线驱动因为社区维护者已经踩过大量坑稳定性远好于来源不明的厂商驱动。我实际用 GT911 时就是直接改设备树节点没有动驱动源码。3. 驱动移植与调试的核心实操这一章是文章最核心的部分。我会以 GT911 为例给出设备树节点、内核配置、编译加载、事件验证的完整流程。这套流程同样适用于 FT5x06 等 I2C 触摸 IC。3.1 设备树节点编写实例GT911 典型配置假设 FPGA 软核 CPU 的 I2C 外设节点名为 i2c0基地址 0x40000000中断控制器 handle 为 intc。设备树里新增一个子节点典型内容如下i2c0 { status okay; clock-frequency 100000; gt911: gt9115d { compatible goodix,gt911; reg 0x5d; interrupt-parent intc; interrupts 3 2; pinctrl-names default; reset-gpios gpio0 10 GPIO_ACTIVE_LOW; irq-gpios gpio0 11 GPIO_ACTIVE_HIGH; goodix,cfg-group-0 [ 00 20 03 E0 02 0A 05 01 01 2D 0F 8A 32 05 05 00 ... ]; touchscreen-size-x 1024; touchscreen-size-y 600; }; };注意 compatible 有两种常见写法goodix,gt911 或 goodix,gt9xx具体要看内核驱动里 of_device_id 表支持哪个。reset-gpios 是复位引脚irq-gpios 是中断引脚。如果你的板子是 GPIO 子系统没有接好可以先用轮询方式调试把 poll-interval 加上。这里给出参数时要注意中断号要查软核 CPU 的中断控制器分配表不是随便填的。我调试时误用了别的外设中断号导致驱动申请中断失败。比较稳妥的做法是先不加 interrupts用 poll-interval 10 轮询模式验证触摸数据是否正常确认没问题后再切换成中断模式。3.2 内核编译与模块加载从零构建可用的触摸屏驱动环境如果你的系统是直接烧录整个内核镜像需要重新编译内核并生成新的 boot 镜像如果能加载内核模块也可以把触摸驱动编成 .ko 模块。我习惯先把驱动编成模块调试这样改参数不用反复烧系统。进到内核源码目录设置好 ARCH 和 CROSS_COMPILE 环境变量执行make menuconfig在 Device Drivers - Input device support - Touchscreens 里找到 Goodix touchscreen按 M 编译成模块。然后编译模块make ARCHarm CROSS_COMPILEarm-linux-gnueabihf- modules编译完成后把 drivers/input/touchscreen/goodix.ko 拷贝到目标系统的 /lib/modules/$(uname -r)/ 下执行 depmod -a然后 modprobe goodix。如果设备树节点匹配成功启动日志会打印类似 “input: Goodix GT911 TouchScreen as /devices/.../input/input0” 的信息。模块加载后用 dmesg 查看驱动初始化信息重点确认是否成功探测到设备、中断申请是否成功、有没有校验和错误。我遇到最多的问题就是 I2C 设备地址不匹配导致 probe 失败日志里显示 “goodix: failed to read config data”。3.3 触摸事件验证从 evtest 到坐标上报驱动加载成功只是第一步关键要确认触摸事件能正常上报。用 evtest 工具可以看到 input 设备列表选择触摸屏设备后手指点击屏幕终端会实时打印 ABS_MT_POSITION_X、ABS_MT_POSITION_Y 等事件。evtest /dev/input/event0如果事件打印正常说明驱动和硬件链路已经打通。接着可以做一个简单测试用手指在屏幕左上角和右下角分别点击记录上报坐标用这些坐标做后续校准。如果 evtest 没有打印任何事件优先检查 /dev/input/ 下是否有 eventX 节点以及 dmesg 里驱动 probe 是否成功。如果 probe 成功但没中断多半是中断引脚配置有问题可以转轮询模式验证。4. 触摸不准怎么办校准、坐标变换与稳定性问题很多时候驱动能让触摸屏“动起来”但坐标不对要么点击位置偏了要么横竖屏响应方向不对要么触摸飘移、偶发误触。这些问题不全是驱动 bug很多是坐标映射和校准环节的问题。4.1 触摸校准原理为什么需要校准以及tslib怎么用触摸屏的原始坐标和 LCD 显示坐标之间存在比例、偏移、旋转等映射关系。理想情况下两者是线性变换可以用一个 3x3 矩阵描述校准的目的就是把这个矩阵求出来。嵌入式 Linux 里常用 tslib 做校准。tslib 提供 five-point calibration 和 linear transform 等插件会生成 /etc/pointercal 校准文件。使用前需要设置环境变量export TSLIB_TSDEVICE/dev/input/event0 export TSLIB_CALIBFILE/etc/pointercal ts_calibrate按屏幕提示点击五个点tslib 会计算出校准参数写入 /etc/pointercal。之后所有坐标读取都会经过 tslib 插件变换触摸位置就准了。4.2 横竖屏切换与坐标旋转的处理如果你的 7 寸屏是竖屏安装但 Linux 显示是横屏坐标就需要旋转。tslib 有 rotate 插件可以在配置文件中设置旋转角度。不过更推荐在应用层处理坐标变换因为 tslib 的旋转只影响通过 tslib 读坐标的程序而 direct input 方式读 /dev/input/eventX 的程序不受影响。坐标旋转本质是矩阵变换。90 度旋转的公式比较简单假设触摸原始坐标是 (x, y)显示宽高是 W、H顺时针旋转 90 度后显示坐标是 (y, W - x)。这个变换可以直接写进应用的读取逻辑里避免依赖 tslib。我在实际项目里最终没有用 tslib而是把坐标变换逻辑写在 Qt 应用的 QScreen 事件处理里。因为触摸屏的驱动节点固定应用可以直接读原始坐标自定义映射更灵活。4.3 触摸漂移、误触与干扰的处理经验电容屏在低温、潮湿、电磁干扰环境下容易产生坐标漂移或误触。处理这类问题有几个经验检查电源纹波。触摸 IC 对供电敏感如果供电纹波大触摸坐标会跳动厉害。我测量过在开关电源输出加一个 10uF 陶瓷电容后漂移明显减少。检查 I2C 总线时序。I2C 速率过高可能导致通信出错触摸数据偶尔读回 0xFFFF表现为触摸点偶尔跳到角落。把 clock-frequency 从 400000 降到 100000 往往能改善。检查中断处理函数是否耗时过长。如果中断里读取多个坐标寄存器而在读取期间 GPIO 抖动会产生误触。可以在中断里加简单的消抖逻辑。5. FPGA侧逻辑如何与Linux驱动协同工作前面讲的驱动移植更多是通用 Linux 知识。但既然标题是 FPGA 技术教程必须把 FPGA 侧的逻辑设计也讲透。软核 CPU 跑 Linux外部设备通过 AXI 总线或自定义 Wishbone 总线挂在 CPU 上FPGA 内部逻辑要提供正确的寄存器接口和中断信号Linux 驱动才有东西可操作。5.1 FPGA在触摸链路中的角色I2C控制器、中断控制器与寄存器桥在纯 FPGA 软核方案中I2C 控制器有两种实现路径一是直接用软核 CPU 自带的外设 IP比如 MicroBlaze 的 AXI IIC IP、NIOS II 的 I2C IP二是自己在 FPGA 里写一个 I2C 控制器通过 AXI-Lite 接口挂到总线上。我用的平台有现成的 AXI IIC IP省了不少事。但要注意AXI IIC IP 的驱动对应内核里的 drivers/i2c/busses/i2c-xiic.c设备树里 compatible 要写对否则内核识别不了这个 I2C 控制器。除了 I2C 控制器FPGA 侧还有一个重要模块是 GPIO 中断控制器。触摸 IC 的 INT 引脚接到 FPGA 的 GPIOGPIO 检测到边沿后通过中断控制器上报给 CPU。在 FPGA 代码里中断边沿检测逻辑要设置好上升沿还是下降沿触发并且要有足够宽的中断脉冲否则 CPU 可能收不到。5.2 AXI-Lite寄存器接口设计让CPU“看得见”触摸数据如果你不想用现成 I2C IP而是自己写 I2C 控制器那么 AXI-Lite 寄存器接口设计就很关键。一个典型的寄存器接口需要包括控制寄存器启动、停止、读、写、中断使能状态寄存器忙标志、错误标志、FIFO 空满数据寄存器发送数据、接收数据地址寄存器从设备地址和寄存器地址。我自己的经验是寄存器位定义越简单越好不要搞太多组合状态。一个寄存器一个功能驱动侧用 writel/readl 直接访问省去很多调试时间。中断状态寄存器要支持“写1清0”的模式这样驱动处理完中断后能及时清除标志避免重复进入中断。5.3 裸机驱动到Linux驱动的复用与适配在做 Linux 驱动之前我一般会先在裸机环境里用 FPGA 软核的 SDK 把 I2C 控制器调通读一下触摸 IC 的 ID 寄存器确认通信正常。裸机程序调试简单出问题可以直接看寄存器值定位快速。裸机程序转到 Linux 驱动时有几个注意点裸机用的 baseaddr 是物理地址Linux 驱动要 ioremap 之后才能访问裸机的中断处理函数是直接绑定到 Cortex-M 或 MicroBlaze 的中断向量表Linux 里要注册中断处理函数且处理函数不能太耗时耗时操作要放到 workqueue 或 threaded irq 里裸机的 GPIO 操作很简单Linux 里要用 GPIO 子系统的 API而且设备树里的 GPIO 编号要对应 FPGA 内部逻辑的引脚。6. 踩坑记录与效率工具让触摸屏驱动开发少走弯路最后这部分我把调试过程中遇到的高频问题整理成了速查表再分享一些能提升效率的命令和技巧。这些问题很多是硬件平台相关的但在其他嵌入式 Linux 触摸屏项目里也同样适用。6.1 常见问题速查表现象可能原因排查方法I2C 探测不到触摸 ICI2C 地址错误、触摸 IC 复位时序不对、I2C 控制器时钟未使能i2cdetect 扫描地址检查复位 GPIO 时序dmesg 查看 I2C 控制器状态驱动 probe 失败设备树 compatible 不匹配、中断号错误核对 of_match_table确认中断号在软核中断表里有效触摸事件不打印驱动未加载、中断未触发、I2C 读坐标超时先用轮询模式验证再查中断 GPIO 电路坐标位置偏移大未校准、坐标分辨率不匹配运行 ts_calibrate检查设备树 touchscreen-size-x/y 是否与屏幕一致触摸偶尔跳点I2C 速率过高、电源纹波、静电干扰降低 I2C 速率检查电源滤波检查触摸屏排线接地横竖屏触摸方向不对坐标未旋转应用层做坐标矩阵变换不要依赖 tslib6.2 实用调试命令与效率技巧调试 I2C 设备时i2c-tools 是我必装的一套工具。安装后在终端执行 i2cdetect -y 0可以列出 I2C 总线 0 上探测到的设备地址。这个命令能快速确认触摸 IC 是否在总线上以及地址是 0x14 还是 0x5D。确认设备地址后用 i2cget 读取触摸 IC 的 ID 寄存器也是一种快速验证方式。GT911 的寄存器 0x8140 保存了芯片 ID读出 0x39、0x31、0x31 就说明通信正常。这一步能帮你把问题范围缩小到“硬件通信”还是“驱动软件”。还有一个技巧就是利用 Linux 的 input 子系统设备节点数量变化判断驱动加载情况。加载 goodix 模块前/dev/input/ 下可能只有 event0加载成功后会多出 event1 或 event2。用 udevadm monitor 也能捕捉到内核动态创建 input 设备的事件这对确认 probe 是否成功很有帮助。调试触摸屏驱动时我习惯先在应用层写一个简单的 Qt 或 GTK 程序通过 QTouchEvent 或直接读 /dev/input/eventX 打印坐标。应用层能实时反映驱动改动效果比反复编译内核模块再查日志直接得多。我个人在实际操作中的体会是软件层面的驱动移植只要设备树和中断号不出错半天就能完成真正耗时的往往是硬件信号完整性、复位时序、供电纹波这些偏硬件的问题。建议大家在 LCD 点亮、触摸 IC 通信正常之前不要着急写驱动先把基础链路打通后面会顺很多。最后再分享一个提升效率的小技巧FPGA 开发板调试触摸屏时尽量在 FPGA 逻辑里预留一个调试 GPIO用于观察触摸 IC 中断信号的时序。用逻辑分析仪抓 INT 引脚波形确认触摸事件确实产生了中断脉冲再谈 Linux 端的事情。这个习惯帮我省了大量排查时间强烈推荐你也试试。