ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

FPGA工程师学Linux驱动:7寸触摸屏移植与设备树配置实战

2026/9/18 11:02:28 拓冰建站 浏览量
FPGA工程师学Linux驱动:7寸触摸屏移植与设备树配置实战 1. 为什么FPGA工程师要跨出舒适区去学Linux驱动开发关注黑金云课堂的朋友应该都有印象这套FPGA教程系列从Verilog基础一路讲到SoC异构开发而7寸触摸屏驱动这一节恰恰卡在了一个很有意思的位置上前半段是纯FPGA逻辑后半段突然转向了Linux内核与设备树。很多人在这里掉头就走或者硬着头皮把驱动搞“通”了却完全不懂底层发生了什么。以我自己的项目经历来说这一课如果真正吃透价值远不止点亮一块屏幕那么简单。先说说为什么会用到Linux驱动。现在主流的FPGA芯片已经很少是单纯的可编程逻辑阵列了像Xilinx Zynq、Intel Cyclone SoC这类芯片内部直接集成了ARM硬核处理器PS端和可编程逻辑PL端。PL端负责高速并行数据处理比如图像采集、视频输出、MIPI收发PS端跑Linux系统负责调度、网络、文件系统、人机交互。你的7寸触摸屏接在开发板上屏幕显示时序可以用FPGA逻辑去做但触摸坐标的读取、上报、校准这些工作放在Linux内核里处理显然要高效得多。这就逼着FPGA工程师必须去接触内核驱动、设备树、交叉编译这些纯软件的东西。这一课解决的现实问题非常具体黑金系列开发板通常配套一块7寸LCD电容触摸屏出厂时Linux系统往往能显示画面但触摸功能需要自己移植驱动或者你换了一款第三方触摸屏原来的驱动对不上也得自己改。你需要的不是把厂商给的内核镜像烧进去就能用而是理解触摸屏从硬件连接到内核驱动的完整链路以后换板子、换屏幕、调整参数都能自己搞定。对于读者群体我的判断是已经能把LED流水灯、数码管玩得比较熟了知道FPGA基本开发流程但还没怎么碰过Linux驱动的人这一篇非常适合作为切入点。如果你已经会写简单的字符设备驱动那触摸屏驱动里涉及的I2C、中断、输入子系统刚好可以帮你把知识体系补完整。不管你是学生还是刚转行做嵌入式开发的工程师这一课都能帮你少走不少弯路。2. 7寸触摸屏方案选型先搞清楚你的屏幕属于哪一类2.1 触摸屏的两种主流技术路线做驱动开发之前第一件事不是写代码而是去查屏幕硬件。7寸屏在工控、医疗、教育终端里非常常见但触摸方案并不统一大致分两类。第一类是电阻式触摸屏典型控制芯片有ADS7846、TSC2046、XPT2046。工作方式是通过ADC采样电压分压判断触摸点的坐标。这类屏幕成本低、能用笔和手套操作但支持单点、需要校准、透光率一般。在Linux里对应的是input子系统下的ads7846驱动设备树里配一个SPI节点就能跑起来开发和调试都比较简单。第二类是电容式触摸屏也是现在的主流。控制芯片常见的有汇顶科技的GT911、GT9147、GT9271敦泰的FT5x06、FT6x06以及Goodix的后续型号。这类屏幕通过检测电容变化来判断触摸位置支持多点触控、支持手势响应速度快。通信接口大多是I2C中断引脚负责通知主机有触摸事件发生。黑金7寸屏的触摸部分用的正是这一类方案所以本节的驱动移植工作主要围绕I2C总线展开。2.2 解析触摸控制芯片以GT911为例如果你手头是黑金7寸屏大概率碰到的是GT911或者GT9147它们之间比较像但寄存器地址和配置流程有些差异。GT911算是这个尺寸屏幕上用得最多的芯片之一它的关键参数和时序驱动开发的时候必须心中有数I2C从机地址根据INT引脚的电平和引脚配置芯片上电后地址可能是0x28/0x29也可能是0xBA/0xB88位地址模式。Linux内核驱动里常用7位地址0x28、0x29、0x5D来匹配具体用哪个得看硬件原理图和数据手册。上电时序GT911对复位和中断引脚的时序要求比较严格一般是先拉低复位再拉高延时一段时间后把INT引脚设置成输入模式然后芯片才会以正确地址出现在I2C总线上。寄存器布局芯片内部有配置区末尾有校验和读取坐标要从0x814E区域读取缓冲区状态再按点数逐一解析。FT5x06的逻辑类似区别是地址通常是0x38寄存器偏移量和数据格式不同。做驱动移植时最关键的就是确认芯片具体的型号和I2C地址很多问题都出在“芯片型号没看清地址对不上”这种低级错误上。2.3 硬件连接与原理图核对清单拿到板子和屏幕后不要急着编译内核先把硬件连接捋清楚。你需要核对以下几个信号SCL和SDA触摸芯片的I2C时钟和数据线是否接到了PS端的I2C控制器上。有些开发板会把触摸的I2C和HDMI、摄像头等外设的I2C复用在同一个控制器上这时要注意总线上地址是否冲突。INT中断引脚触摸芯片检测到触摸后会拉高或拉低INT引脚通知处理器读数据。这个引脚建议接到专用的GPIO上并且确认是上升沿触发、下降沿触发还是低电平触发。RST复位引脚由处理器控制用于上电复位触摸芯片。复位操作的时序必须满足芯片规格书要求否则芯片可能初始化失败。电源和电平触摸芯片的供电电压一般是3.3V或2.8VI2C总线电平可能也是3.3V。如果你用的SoC的I2C引脚是1.8V电平中间需要加电平转换芯片否则通信不稳定。不要嫌这个环节枯燥我见过太多人在这一步偷懒结果后面排查了三天最后发现是I2C地址因为复位引脚没拉对而“找不到设备”。原理图先看明白后面省下的时间远比你投入的多。3. 开发环境搭建交叉编译内核和设备树的前置工作3.1 交叉编译工具链的选取与配置Xilinx Zynq平台跑的Linux一般是ARM 32位arm-linux-gnueabihf或者ARM 64位aarch64-linux-gnu取决于你用的内核和文件系统。黑金开发板很多是基于Zynq-7000系列ARM Cortex-A9双核属于32位ARMv7架构因此大部分资料用的是arm-linux-gnueabihf-工具链。在Ubuntu 22.04 LTS上最简单的安装方式是sudo apt update sudo apt install gcc-arm-linux-gnueabihf libc6-dev-armhf-cross device-tree-compiler装完之后验证工具链是否可用arm-linux-gnueabihf-gcc -v如果输出里能看到gcc version信息说明工具链已经就绪。需要特别提醒的是触摸屏驱动编译出来的.ko内核模块其格式和宿主机上跑的Ubuntu内核模块是完全不同的所以编译时一定要确保CROSS_COMPILE指向arm工具链而不是本机的gcc。内核模块一旦用错编译器insmod的时候大概率会报“invalid module format”的错误。3.2 内核源码准备与编译流程内核源码建议从开发板厂商提供的BSP里拿或者从Xilinx官方Git仓库拉取对应的Linux内核版本。以Xilinx Linux内核为例常用的是xlnx-rebase-v2019.2或者更新的分支。拿到源码后先设置环境变量export ARCHarm export CROSS_COMPILEarm-linux-gnueabihf- export LOADADDR0x8000然后选择默认配置。以Zynq为例厂商通常会提供带有板级配置的defconfig文件如果没有可以用xilinx_zynq_defconfig作为起点make xilinx_zynq_defconfig make menuconfig菜单配置界面里你需要检查触摸屏驱动对应的配置项是否打开。这里建议模块和内核都编一下模块方便调试时单独替换内核内置则适合最终产品化。编译整个内核的时间取决于机器性能通常在几分钟到十几分钟。编译完成后会生成uImage或zImage以及一系列.ko模块文件。同时还需要单独编译设备树二进制命令类似make zynq-zed.dtb3.3 为什么设备树比内核配置更关键触摸屏驱动虽然要内核支持但具体到“哪一路I2C上有触摸芯片”“中断接在哪个GPIO”“屏幕分辨率是多少”这些信息内核并不知道必须通过设备树告诉它。设备树就是描述硬件拓扑结构的数据文件Linux内核启动时会解析它然后根据compatible字符串匹配对应的驱动再通过reg、interrupts、gpios这些属性把硬件资源传递给驱动。过去ARM Linux每次改硬件都要重新编译内核设备树就是用来解决这个问题的。所以你在做触摸屏移植时绝大部分工作是修改设备树而不是修改内核C代码。这一节的技术路线非常简单清晰硬件原理图 → 设备树描述 → 内核驱动识别 → 输入子系统上报事件。4. 设备树配置合理编写一步步把硬件信息告诉内核4.1 在I2C节点下挂载触摸屏的子节点打开你要使用的设备树文件找到对应的I2C控制器节点。以Zynq上的I2C1为例原始节点可能长这样i2c1 { status okay; clock-frequency 100000; };触摸芯片是一个挂在I2C总线上的从设备所以需要在I2C1节点下增加子节点。参考GT911的常见配置i2c1 { status okay; clock-frequency 100000; gt9115d { compatible goodix,gt911; reg 0x5d; interrupt-parent intc; interrupts 0 29 IRQ_TYPE_EDGE_FALLING; irq-gpios gpio0 20 GPIO_ACTIVE_HIGH; reset-gpios gpio0 21 GPIO_ACTIVE_HIGH; touchscreen-max-x 800; touchscreen-max-y 480; touchscreen-size-x 800; touchscreen-size-y 480; }; };这里逐个字段解释一下很多人就是栽在不理解这些属性含义上compatible这是驱动和硬件匹配的关键字段。内核里的goodix驱动会声明compatible goodix,gt911设备树里写一致才能成功匹配。regI2C从机地址。这里用7位地址0x5D对应GT911第三地址配置。如果屏幕没反应可以试0x28或者0x29。interrupt-parent和interrupts中断控制器和中断号。xilinx intc的第二个参数表示中断类型IRQ_TYPE_EDGE_FALLING表示下降沿触发具体要看你硬件上INT引脚是常态高还是常态低。irq-gpios和reset-gpios驱动会通过GPIO子系统控制这两个引脚。注意这里绑定的GPIO编号是SoC内部GPIO控制器的编号不能随便写必须和原理图对应。GPIO_ACTIVE_HIGH表示高电平有效至于复位引脚是低有效还是高有效不同芯片不一样驱动里一般会结合设备树的标志位处理。touchscreen-max-x/y和touchscreen-size-x/y报告坐标范围。关于I2C时钟频率GT911支持最高400kHz但调试初期建议先把clock-frequency设为100000不要一上来就跑高速。I2C总线布线如果不够好或者线太长高速模式下很容易出现数据错乱表现为触摸偶尔没反应、坐标乱跳。4.2 中断配置的实践体会触摸屏中断的配置质量直接决定系统CPU占用率和触摸响应速度。好的做法是让驱动在空闲时休眠有触摸事件时通过中断唤醒然后通过I2C读取坐标。如果中断配置错误可能会出现两种情况一是中断不触发驱动永远收不到触摸事件二是中断风暴CPU持续被中断打断系统卡成幻灯片。我在实际项目里遇到过中断引脚电平配置反了的情况。GT911的中断输出极性是可以通过寄存器配置的设备树里写IRQ_TYPE_EDGE_FALLING还是IRQ_TYPE_EDGE_RISING取决于芯片固件配置。如果方向不对最直接的表现是一旦触摸系统立刻死机或者dmesg里刷出一堆中断信息。这时不要怀疑芯片坏了先把设备树里的触发方式换一下试试大概率就好。复位引脚GPIO的配置同样不能大意。有些开发板的复位引脚没有接到SoC的GPIO上而是直接接到了固定电平这种情况下设备树里reset-gpios就要删掉驱动会跳过复位操作。如果强行配置一个不存在的GPIO驱动加载时就会报GPIO申请失败的错误反而把事情变复杂。4.3 编译并使用新的设备树修改完设备树后重新编译dtb文件。注意有的内核版本里设备树源文件是.dts有的是.dtsi还有的是通过include层层组合起来的改的时候要确认最终编译进去的确实是你的改动。编译完成后把新的dtb拷贝到开发板的FAT分区或者uEnv.txt指定的位置重启开发板。如果内核启动正常在终端输入ls /sys/bus/i2c/devices/如果设备树配置正确你应该能看到类似1-005d这样的目录项。数字1是I2C控制器编号0x5d就是设备地址。这一步看到设备节点出现硬件链路基本就通了一半。5. 触摸屏内核驱动编译、配置与加载从模块到输入设备5.1 选择内核自带的驱动别一上来就写驱动代码初次接触触摸屏驱动的人容易陷入“我要从零写一个驱动”的思维误区。实际上GT911、FT5x06这些主流触摸芯片Linux内核主线已经自带驱动你要做的是选对驱动、配好设备树最多修改极少量的适配代码。这就像你买了个新显示器你不会去写显卡驱动而是把显示器接到电脑上操作系统自动识别。触摸屏的情况类似芯片厂商和内核社区已经把80%的活干完了。以GT911来说内核里对应的驱动是drivers/input/touchscreen/goodix.c它支持GT911、GT9147、GT9271、GT928等多款芯片。FT5x06对应的是focaltech_touch.c部分内核版本或edt-ft5x06.c基于EDT的驱动。如果你用的芯片不在这两个里面就需要去找芯片厂商的BSP驱动或者自己写。在内核配置界面里确认驱动是否被编译成模块make menuconfig进入路径Device Drivers - Input device support - Touchscreens找到Goodix touchscreen controller这一项按M或者Y选中。M表示编译成.ko模块方便单独拷贝Y表示编进内核直接随内核启动。调试阶段建议选M因为改驱动后只需重新编译模块不用整包烧内核效率高很多。5.2 驱动加载与设备匹配的底层逻辑把编译生成的goodix.ko拷贝到开发板的文件系统执行insmod goodix.ko然后查看内核日志dmesg | tail -20如果一切正常日志里会出现类似这样的信息goodix 1-005d: Goodix GT911 detected input: Goodix GT911 as /devices/soc0/amba0/e0004000.i2c/i2c-1/1-005d/input/input0看到input0出现说明驱动已经成功注册了一个输入设备触摸事件将上报到Linux输入子系统。input设备在用户空间表现为/dev/input/eventX。你可以用工具验证触摸是否真的产生事件hexdump /dev/input/event0手指点在屏幕上如果能看到类似0000000 04d2 0000 0000 0000 0001 0000 0000 0000这样的数据流说明触摸事件确实上报了。这一段是16进制原始数据分别记录时间戳、事件类型、事件code、事件value有点难直接看懂。建议安装evtest工具它会解析出更人性化的信息evtest /dev/input/event0输出会显示Input device ID: bus 0x18 vendor 0x0416 product 0x0911 version 0x0100 Input device name: Goodix GT911按下屏幕时能看到ABS_MT_POSITION_X和ABS_MT_POSITION_Y等事件值这些就是触摸点的坐标。5.3 内核输入子系统的作用驱动只是上报者这里插一句原理上的东西。触摸屏驱动并不直接负责把坐标显示到屏幕上它只负责把触摸到的点通过input_report_abs等API上报给Linux内核输入子系统。输入子系统再把这个事件转发给上层应用比如Qt、GTK、Weston或者轻量级的tslib。这套架构的好处是驱动和GUI解耦换屏幕只需要换驱动不用改上层程序。很多初学者在上层界面里点了没反应查了一圈发现驱动也在跑、事件也在报问题其实出在GUI的input后端配置上比如环境变量没有设置QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS或者没有指定用/dev/input/eventX。排查到这一层时建议先用evtest验证驱动链路再回头查上层配置别一上来就在Qt或GTK里排查会把问题带偏。6. 触摸屏坐标校准、旋转与多点触控调试6.1 坐标系统与屏幕分辨率的对应关系新接上一块触摸屏后最常见的问题就是“能触摸但点A却响应成B”也就是坐标漂移或坐标不匹配。原因是触摸控制芯片上报的原始坐标范围和LCD显示分辨率不一致或者触摸的坐标原点和屏幕的原点不重合。7寸屏常见的分辨率有800x480和1024x600。设备树里touchscreen-max-x和touchscreen-max-y配置的就是触摸芯片的最大坐标范围而LCD输出的分辨率由FPGA或者显示控制器决定。当二者比例不一致时内核输入子系统的坐标会被直接透传给上层最终导致触摸点和显示点不重合。如果你的设备树里没有设置这两个属性可以试着在内核驱动源码里看它默认值。GT911驱动默认可能按最大支持范围上报比如4095而屏幕实际只有800x480你不做缩放自然就会偏。解决方法是在设备树里明确写出屏幕尺寸大部分情况下驱动会自动做坐标缩放处理。如果还是偏可能需要在应用层做坐标变换。使用tslib的话可以通过ts_calibrate工具进行校准生成校准参数文件。对于Qt程序可以通过设置环境变量export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS/dev/input/event0:rotate0rotate0表示不旋转rotate90、rotate180、rotate270分别对应四个旋转方向。6.2 屏幕旋转方向的处理方法嵌入式设备上屏幕的安装方向不一定和PCB一致经常出现触摸坐标旋转90度或者镜像的情况。这类问题纯靠校准不一定能解决最好在驱动或设备树层面处理。以GT911驱动为例它支持通过设备树属性或者模块参数来配置坐标交换和翻转。常见做法是修改设备树touchscreen-inverted-x 0; touchscreen-inverted-y 0; touchscreen-swapped-x-y 0;这些属性是给内核驱动用的。要注意的是不同内核版本对这几个属性的支持程度可能不一样。如果你发现改了没用就回到驱动源码里去查看它有没有读取这些属性没有的话就自己补一段逻辑。以我经验来说坐标交换和翻转这类小功能直接在驱动里改代码往往比硬找参数快。6.3 多点触控的验证方法GT911支持五点触控内核输入子系统也支持多点触控协议MT Protocol B。验证多点触控是否正常可以用下面的命令查看watch -n 0.1 cat /proc/bus/input/devices或者用getevent工具getevent -lt /dev/input/event0真实的双指缩放、滑动等手势是上层应用通过多点触摸协议解析出来的驱动层只负责把所有的触点都上报。如果你的驱动只上报第一个触点后面的触点不上报多半是驱动里对ABS_MT_SLOT的处理不完整。标准内核驱动一般不会犯这种错但如果是芯片厂商提供的老驱动就要特别小心。7. 常见问题速查这五个坑我基本每次都碰到在反复实验黑金FPGA开发板和7寸触摸屏驱动过程中我积累了不少典型问题的排查经验。下面这张表基本能覆盖90%的常见情况。现象可能原因排查与解决办法I2C总线上找不到触摸设备I2C地址错误、复位引脚未拉对、芯片供电异常用i2cdetect -y 1扫描总线逐一测试0x28、0x29、0x5D地址核对复位时序是否满足数据手册要求驱动提示Goodix GT911 detected但触摸无事件中断触发类型不对、中断引脚没连好按屏幕时用示波器看INT引脚是否有电平变化设备树里把EDGE_FALLING换成EDGE_RISING测试触摸事件上报但坐标不对配置的max-x/max-y和实际屏幕不符将touchscreen-max-x/y改成LCD实际分辨率例如800x480触摸方向不对屏幕安装角度与坐标系不同在设备树里配置inverted-x/inverted-y/swapped-x-y或用应用层rotate参数触摸偶尔失灵或乱跳I2C时钟频率过高、电源纹波大、触摸屏排线接触不良把clock-frequency降到100kHz检查供电电容重新插拔FPC排线同时按下多个点只识别一个点驱动不支持多点上报或触摸IC固件配置问题确认内核驱动支持MT协议B必要时重新刷触摸IC固件配置区7.1 用i2cdetect排查设备探测不到的问题排查I2C设备有一个非常实用的命令就是i2cdetect。先进到开发板终端安装i2c-tools如果没有的话然后扫描I2C总线i2cdetect -y 1输出会以表格形式显示总线上探测到的设备地址。如果触摸芯片正常上电且复位时序正确你会在对应地址位置看到一个数字比如28、29或者5d如果那一格是--说明总线上没有设备应答如果是UU说明该地址被某个驱动占用了。我遇到最多的情况是i2cdetect能看到GT911地址但驱动加载时报I2C读失败。这种问题通常是驱动初始化时对芯片的复位时机太早或太晚。可以尝试在驱动里增加一个延时或者手动执行一次复位命令再加载驱动。另外GT911的I2C地址会随INT引脚的电平状态变化如果复位时INT引脚被拉高地址可能变成0x28如果拉低则是0x29。你扫描到的地址和你设备树里填的地址必须一致这一点要反复确认。7.2 中断风暴导致的系统卡顿这是一种比较隐蔽的问题现象是驱动一加载系统变得极卡top命令能看到CPU占用率飙升到90%以上。原因往往是中断配置成电平触发但中断引脚的电平在触摸结束后又恢复到触发电平导致中断反复触发。GT911的INT引脚默认在发生触摸时输出一定宽度的低电平脉冲。驱动通过读取触摸寄存器后芯片才会停止拉低INT。如果驱动读取失败、或者读取的不够及时中断就会反复触发形成风暴。遇到这个问题优先检查I2C通信是否正常再检查设备树里的中断触发方式是边沿触发还是电平触发。调成边沿触发之后系统占用率会明显降下来。7.3 屏参和坐标范围不匹配导致的上层界面错位这个问题经常出现在用Qt的嵌入式应用里。驱动已经上报了正确的原始坐标但Qt界面还是错位比如你点屏幕下方按钮响应却在屏幕上方。这种问题的本质是坐标原点和旋转方向没有对齐。排查方法很简单手动在终端里运行evtest /dev/input/event0触摸屏幕的左上角看报告的X和Y坐标是否接近最小值触摸右下角看是否接近最大值。如果发现X和Y反了或者最大值最小值错位就在设备树层或者应用层做修正。这个排查思路同样适用于其他带触摸屏的嵌入式Linux设备不只是FPGA平台。8. 从触摸屏驱动延伸出去FPGALinux开发的完整技能树做完7寸触摸屏驱动之后我个人建议你趁热打铁把相关的几个技能点补齐因为它们具有天然的关联性。首先是I2C协议本身的深挖触摸屏只是I2C设备的一个典型例子掌握了I2C驱动框架之后摄像头传感器、温湿度传感器、RTC时钟芯片这些设备的驱动移植都会变得很轻松。其次是GPIO子系统你会逐渐习惯通过设备树描述引脚、用GPIO子系统API控制引脚而不需要去操作寄存器。再往后是中断子系统理解request_irq、devm_request_threaded_irq这些接口的使用场景你会对整个Linux内核的事件驱动机制有更深的理解。如果你还想往FPGA方向继续深入可以尝试在PL端实现一个自定义的I2C控制器用它连接触摸芯片然后在Linux里通过一个平台驱动来控制这个PL端外设。这一步一旦跨过去你就真正打通了FPGA逻辑和Linux驱动之间的桥梁理解什么是“软硬件协同设计”。黑金云课堂后续如果继续出类似项目应该也会沿着这个方向走因为这才是SoC FPGA的真正价值所在。最后再分享一个小技巧调试触摸屏驱动的时候建议在驱动里加一个简单的调试开关用printk打印每个触摸事件的上报坐标并且带一个时间戳。早期排查时这些日志能非常直观地告诉你触摸事件的时间间隔、坐标变化趋势。等产品功能稳定之后再把这些调试日志去掉避免影响实时性。这个习惯我在做FPGA图像处理和Linux驱动混合项目时一直坚持确实能省下不少联调时间。