ARTICLE DETAIL

资讯详情

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

FPGA+Linux实战:7寸电容触摸屏驱动移植与调试全解析

FPGA+Linux实战:7寸电容触摸屏驱动移植与调试全解析 1. 黑金云课堂这套教程到底在讲什么做FPGA开发的朋友应该都有体会市面上绝大多数教程都停留在LED灯、按键消抖、数码管扫描这个层面能讲到DDR3读写已经算进阶了。但实际工作里真正卡人的地方往往是那些看起来不起眼、做起来一堆坑的活比如给嵌入式Linux系统接一个触摸屏。黑金云课堂这次出的FPGA技术教程Linux开发系列抓的就是这个痛点。标题里有三个关键词FPGA、Linux、触摸屏驱动。合在一起就是一套很具体的技术栈用FPGA做主控平台跑Linux系统然后把一块7寸的电容触摸屏erect驱动起来让屏幕能正常显示、能精确响应触摸。这个组合在工业控制面板、医疗设备人机界面、电力监控终端、实验室仪器显示单元里都非常常见属于典型的小众但刚需场景。这套教程适合几类人搞FPGA开发但平时只写RTL逻辑没接触过嵌入式Linux软件栈的。做嵌入式Linux驱动开发想了解FPGA平台下驱动和ARM平台有什么不同的。刚想入手高端FPGA开发板准备做显示相关项目需要一套完整参考设计的。先说清楚这篇博文不是教程的复述而是我实际跟着做了一遍之后把整个技术路线、关键原理、遇到的问题都梳理出来。内容会分成四块整体方案设计思路、LCD与触摸屏驱动框架拆解、完整移植流程、常见坑位排查。不论你用的是黑金的开发板还是自己画的其他FPGA板卡这套技术路线都通用。2. 整体方案设计FPGA平台跑Linux驱动7寸屏幕的架构逻辑2.1 为什么用FPGA跑Linux而不是用ARM很多人一看到FPGA跑Linux就皱眉觉得FPGA不是用来做逻辑控制的吗跑操作系统是不是太浪费资源了。这个看法对了一半。确实如果只是点亮一块屏幕用MCU加裸机代码就够了根本不需要Linux。但实际产品一旦涉及到复杂协议栈、多任务调度、文件系统、远程升级这些东西裸机方案的开发成本会迅速上升这时候Linux的价值就出来了。FPGA平台上跑Linux本质上和ARM上跑Linux是一样的都是CPU在跑操作系统FPGA的差别在于逻辑资源是硬件可配置的。你可以把一部分功能用硬逻辑实现比如图像缩放、时序生成、MIPI转发然后通过AXI总线挂到CPU总线上让Linux里看到的只是一个普通外设。这套思路用专业一点的话说叫软硬件协同设计SOC FPGA就是为这个场景生的。黑金这块板子用的具体型号训练是Xilinx Zynq系列也就是ARM Cortex-A9双核加FPGA逻辑的SoC架构。ARM端跑Linux负责应用逻辑、图形界面、触摸事件处理FPGA端负责生成LCD时序信号、处理图像数据、提供灵活的IO映射。屏幕驱动分两头干活这是整个方案的核心思路。2.2 7寸触摸屏驱动拆分显示链路和触摸链路驱动一块完整的7寸触摸屏实际上要干两件独立的事屏幕能显示画面、屏幕能感知触摸位置。这两条链路的技术栈完全不同。显示链路FPGA逻辑里例化一个LCD控制器IP通过AXI接口从DDR3读取帧缓冲数据然后转换成LVDS或RGB接口的时序信号送给7寸屏幕。Linux内核侧的显示框架用DRM/KMS或者简单的framebuffer。触摸链路7寸屏幕一般是电容式触摸屏或者电阻式触摸屏这里黑金选的是电容触摸触摸控制芯片常见的有GT911、FT5x06。触摸控制芯片通过I2C接口把触摸点坐标传给CPU内核里对应一个input设备驱动向用户空间上报触摸事件。我在实际调试中最大的感受是显示链路的问题通常是时序和信号质量问题而触摸链路的问题大多数出在I2C通信和坐标校准上。两个链路不能混为一谈否则排查问题时会绕很多弯路。2.3 驱动移植的三种方案对比针对7寸触摸屏Linux驱动方案主流有三条路内核自带驱动、驱动厂商提供的Linux驱动、自己写驱动。我把它们的优缺点放在一张表里方案开发量稳定性适用场景坑点内核自带驱动如fbtft、ili9341等低高屏幕芯片常见、内核版本较新不一定支持你的芯片型号芯片厂商提供驱动低中快速出demo、厂商维护及时代码质量参差、和内核版本匹配有坑自己写驱动高可控特殊屏幕、特殊时序、深度定制开发周期长、需要熟悉内核框架黑金的教程走的是第二条和第一条结合先用驱动厂商提供的GT911驱动把屏幕点亮再在内核配置里把对应的配置选项打开必要时修改设备树。这样既保证开发效率又能在遇到问题时知道内核框架里怎么去查。3. 核心细节拆解LCD驱动框架、触摸驱动框架、设备树配置3.1 Framebuffer还是DRM/KMS很多从裸机转到Linux的新手会问一个问题我直接在应用层往显存地址写数据不就行了为什么还要管什么显示框架。这话对了一半裸机确实直接写显存但Linux是多任务系统不能让两个程序同时乱搞显存需要一套机制来做权限管理、显存分配、显示参数配置。Linux下的显示驱动框架经历了从Framebuffer到DRM/KMS的演进。Framebuffer比较简单粗暴它把一个显存区域暴露给用户空间应用通过mmap映射后直接读写适合简单的静态画面或极简GUI。DRM/KMS则是目前主流的显示方案支持多图层、VSYNC同步、原子提交、缩放旋转适合跑Qt、Weston这些现代图形环境。黑金的教程基于内核较低版本做演示部分例程直接用Framebuffer接口但实际推荐做法是使用DRM/KMS。我这里也说句实在话如果你的项目只是显示几个仪表盘页面Framebuffer完全够用代码量小、依赖少但如果要跑复杂界面、视频播放、多窗口实时刷新老老实实上DRM不然后期掉帧、撕裂问题会搞得你怀疑人生。3.2 触摸屏驱动框架input子系统的作用触摸屏驱动用到的内核框架叫input子系统。这个框架设计得很优雅它的核心思路是输入设备驱动只负责上报事件不关心事件被谁消费应用程序通过输入设备节点读取事件也不关心设备底层是什么传感器。比如一个电容触摸屏驱动的工作流程大致是触摸芯片检测到手指按下把坐标和压力值写到内部寄存器。驱动通过I2C读取这些寄存器。驱动调用input_report_abs和input_sync向上报事件。内核把事件放入事件队列应用层通过read或select读取。这个框架的好处是QT、GTK这些图形框架只需要统一读取/dev/input/eventX不需要为每一种触摸芯片写适配代码。你的驱动只要按照input子系统的规范上报坐标用户空间的图形应用立即可用。3.3 设备树里触摸屏节点的那些讲究设备树在嵌入式Linux里的作用相当于给内核一份硬件配置清单。内核启动时读取设备树知道了有哪些设备、中断号是多少、I2C总线号是多少、时序参数是什么然后把对应的驱动加载起来。GT911触摸屏的设备树节点一般长这样i2c1 { gt9115d { compatible goodix,gt911; reg 0x5d; interrupt-parent gpio0; interrupts 20 IRQ_TYPE_EDGE_FALLING; reset-gpios gpio0 21 GPIO_ACTIVE_LOW; irq-gpios gpio0 20 GPIO_ACTIVE_HIGH; touchscreen-max-x 1024; touchscreen-max-y 600; touchscreen-inverted-x 0; touchscreen-inverted-y 0; touchscreen-swapped-x-y 0; }; };这里面有几个关键信息reg 0x5dGT911的I2C地址。注意GT911比较特殊它有0x5D和0x14两个地址取决于INT引脚的电平状态。实际调试时我发现不少新手在这里卡住地址不对I2C通信永远不成功。interrupt-parent和interrupts指定触摸的中断引脚。GT911只有检测到手指按下才会拉低中断驱动在中断处理函数里读取坐标。如果没有正确配置触摸反应会非常迟钝或者完全没反应。reset-gpios和irq-gpios复位引脚和中断引脚的GPIO编号。这里要注意GPIO_ACTIVE_LOW和GPIO_ACTIVE_HIGH的含义搞反了复位时序会错乱。touchscreen-max-x这些属性告诉内核触摸屏的分辨率和坐标方向如果没有正确配置坐标会错位、反向。3.4 内核配置裁剪少一个选项驱动就加载不了设备树只是告诉内核有哪些设备内核还得编译了对应的驱动代码才行。内核的触摸屏驱动菜单一般在Device Drivers - Input device support - Touchscreens下面对应配置项是CONFIG_TOUCHSCREEN_GT911。另外要注意GT911驱动依赖I2C子系统、GPIO子系统、中断子系统内核里这些基础支持必须打开否则设备树解析出来之后驱动也注册不上。LCD显示方面如果走DRM/KMS需要打开CONFIG_DRM、CONFIG_DRM_DW_HDMI或对应的LCD控制器驱动如果走Framebuffer需要打开CONFIG_FB。这里我特别提醒Zynq平台DRM驱动一般使用DRM_ZYNQMP_DPSUB或DRM_XILINX选择时要看你具体用的接口是DP还是RGB-LVDS选错了一样黑屏。4. 完整移植流程从拿到开发板到最后触摸滑起来4.1 前期准备硬件连接、工具链、内核源码软件层面的移植工作第一步是准备好交叉编译环境。Zynq平台用的是arm-linux-gnueabihf工具链注意这个hf后缀它代表使用硬件浮点如果工具链没有硬件浮点支持性能会差很多。内核源码建议直接从Xilinx官方GitHub仓库拉取而不是从第三方下载改过的代码因为官方仓库会维护Zynq平台的设备树和驱动补丁。拉取命令git clone https://github.com/Xilinx/linux-xlnx.git cd linux-xlnx git checkout xlnx_rebase_v2019.2这个版本选择有讲究。2019.2版本对Zynq-7000系列支持比较成熟很多教程也是基于这个版本遇到问题时比较容易在网上找到答案。如果你非要追新内核也不是不行但要把Xilinx的补丁移植过去工程量不小。交叉编译内核export ARCHarm export CROSS_COMPILEarm-linux-gnueabihf- make xilinx_zynq_defconfig make menuconfig make -j8如果没有menuconfig这一步直接make默认配置后面触摸屏驱动菜单大概率不会打开所以要进去确认一下。把内核编译出uImage放到SD卡或QSPI Flash上这是最基础的一步。4.2 设备树修改实操记录设备树的修改是整个过程中最需要仔细的一步。我先在厂商提供的设备树源文件基础上找到I2C总线节点然后把GT911节点加进去。我实际用过的设备树片段i2c0 { status okay; clock-frequency 100000; gt9115d { compatible goodix,gt911; reg 0x5d; interrupt-parent gpio0; interrupts 20 IRQ_TYPE_EDGE_FALLING; reset-gpios gpio0 21 GPIO_ACTIVE_LOW; irq-gpios gpio0 20 GPIO_ACTIVE_HIGH; touchscreen-max-x 1024; touchscreen-max-y 600; touchscreen-inverted-x 0; touchscreen-inverted-y 0; touchscreen-swapped-x-y 0; }; };等等这里有一个特别容易犯的错也是我第一次调试时踩了大坑。GPIO编号不是随便写的。Zynq-7000的GPIO控制器有118个引脚bank0管理GPIO0到GPIO31bank1管理GPIO32到GPIO63设备树里GPIO编号是从0开始的如果原理图上标注的是GPIO MIO编号往往要减去对应bank的起始编号。我一开始把原理图上的物理引脚号直接填进去了结果复位之后触摸芯片一直没有回应查了半天才发现是编号偏移的问题。正确的做法是先在Linux系统启动后查看/sys/kernel/debug/gpio确认你用的GPIO在系统里的实际编号。Zynq的GPIO0对应物理MIO的编号还要看厂商封装不同厂商的板卡定义不一样。设备树编译比内核简单得多make dtbs编译出来的dtb文件拷贝到SD卡的boot分区。这里注意某些开发板的启动引导会读取设备树文件覆盖掉内核自带的那份所以设备树更新后一定要确认实际加载的是不是你编译出来的那一份。4.3 触摸驱动加载验证的完整流程驱动移植完成后触摸是否有效需要从底到上一层层确认。我建议按这个顺序排查先确认I2C总线上能不能看到GT911设备。启动后进入系统执行i2cdetect -y 0这里的0是I2C总线号。如果设备树里配置的是i2c0那总线号就是0。如果输出中有一个5d的编号说明I2C通信正常芯片在线。如果输出空说明设备树节点、I2C总线、电源供电中有问题。再确认内核有没有加载GT911驱动lsmod | grep gt911如果驱动是编译进内核而不是编译为模块lsmod看不到输出此时可以查看内核日志dmesg | grep -i gt911正常情况会看到类似“input: Goodix GT911 TouchScreen as /devices/platform/.../input/input0”的日志。这说明驱动已经把触摸设备注册成input设备了。再查看输入设备节点cat /proc/bus/input/devices确认有一个Name包含Goodix的设备Handlers一栏有event0或event1。然后用工具读取原始触摸事件cat /dev/input/event0 | hexdump手指点在屏幕上终端会刷出一串十六进制数据含有坐标信息说明驱动事件上报正常。到这里驱动层面的工作就算完成了。4.4 上层应用联调Qt自动校准坐标驱动正常上报原始事件后剩下的事情是在用户空间做坐标映射。大多数Linux图形框架比如Qt会自己读取触摸屏事件并支持旋转、反向校准。我在Qt里常用的环境变量配置export QT_QPA_PLATFORMlinuxfb export QT_QPA_FB_DRM1 export QT_QPA_GENERIC_PLUGINSevdevtouch:/dev/input/event0如果你的屏幕是竖屏但驱动报的是横屏坐标Qt里可以用环境变量做旋转export QT_QPA_FB_ROTATION90不过这里有个细节要提醒旋转如果放在Qt层做只能改图形绘制方向不能改触摸坐标映射方向。也就是说画面旋转了90度但触摸事件还是原始坐标表现出来就是点了确定按钮却触发的是取消按钮。正确的做法是优先在设备树里配置touchscreen-swapped-x-y和inverted-x/y属性让内核坐标和屏幕方向一致上层应用就不需要做任何映射。5. 常见问题排查与避坑实录5.1 触摸没反应I2C设备枚举不到这是最典型的问题占比大概一半以上。现象是i2cdetect扫不到GT911的地址。可能的原因我排了个优先级复位时序不对。GT911上电后需要经过一段延时再拉高复位引脚然后芯片才能正常工作。如果复位引脚一直拉住低电平芯片永远处于复位状态。用示波器测一下复位引脚的波形确认上电后有没有由低到高的跳变。供电问题。7寸屏触摸部分一般需要3.3V供电如果供电不稳芯片也会枚举不到。I2C引脚地址配置问题。GT911的地址和INT引脚电平相关INT拉高地址是0x5DINT拉低是0x14或者反过来不同版本芯片定义可能不同。可以把I2C地址改成0x14再扫一次。设备树reg值写错。这个很常见检查一下reg值和芯片实际地址是否对应。5.2 屏幕能显示但触摸坐标严重偏移这种问题通常不是驱动问题是校准或者设备树分辨率配置问题。触摸屏的原始坐标范围由触摸芯片内部寄存器决定GT911出厂默认是1024x600或1024x768如果你的屏幕物理分辨率是800x480触摸输出的坐标范围大于屏幕像素范围内核会把触摸坐标除以比例映射到屏幕上这个时候只要touchscreen-max-x和max-y配置对了偏移就能解决。如果只设置了max-x和max-y还不够坐标还是镜像或转置的检查inverted和swapped属性。我提供一个快速排查思路在Hexdump事件模式下点屏幕的左上角、右上角、左下角、右下角四个位置记录上报坐标大小特点就能判断是X反向、Y反向还是XY交换。手动调整设备树属性后重新编译即可。5.3 触摸事件偶发丢失不跟手这种问题和I2C通信速率关系很大。GT911的I2C速率最高可以到400kHz但如果总线走线很长、干扰大跑400kHz可能不稳定。把设备树里clock-frequency改到100kHz虽然慢一点但数据不会丢。我实测100kHz下触摸流畅度几乎感觉不到差异。另一个常见原因是中断触发方式配置错误。GT911的INT引脚在触摸事件到达时拉低应该是边沿触发。如果用IRQ_TYPE_LEVEL_LOW会导致中断一直被触发CPU负载飙升用IRQ_TYPE_EDGE_RISING则可能漏掉触摸事件。设备树里必须用IRQ_TYPE_EDGE_FALLING。实际上我遇到过一种更隐蔽的情况驱动代码里申请中断时用的标志和设备树配置不一致导致中断没有生效。这种情况要看驱动源码里request_irq的第一个参数是否读取了设备树中的interrupts属性还是写死了某个触发方式。5.4 LCD黑屏但背光亮了背光亮了说明屏幕电源正常但画面没有数据。按这个顺序查确认FPGA逻辑里有没有把帧缓冲数据输出到LCD接口。用Vivado连接逻辑分析仪看TFT_VSYNC、TFT_HSYNC波形。确认DDR3内存里有没有正确写入图像数据。可以在应用层往/dev/fb0写一块纯色块看屏幕有没有显示纯色。确认设备树里DRM/KMS节点状态是okay并且对应的时钟频率匹配屏幕规格。7寸屏幕一般需要51MHz的像素时钟如果FPGA输出的是25MHz画面显示会偏慢甚至黑屏。HDMI转LVDS的板卡需要额外的PHY配置确认有没有正确初始化。5.5 内核启动时设备树报错内核启动时打印设备树相关错误大多数是语法问题或者GPIO号超出范围。检查dts文件中是否有重复的节点定义、引用的标签是否存在、GPIO号是否超过平台支持的引脚数。一个容错技巧修改设备树后先用dtc反编译回dts格式检查语法。dtc -I dtb -O dts -o decompiled.dts your.dtb如果反编译失败说明dtb本身就损坏或格式不对。如果反编译成功再检查decompiled.dts关键节点是否正确保留。6. 扩展玩法这块驱动能力还能干更多事情触摸屏驱动解决之后整个平台基本就是一个完整的工业HMI雏形了。扩展方向有很多我列举几个我实践过的一是接MIPI接口的屏幕。Xilinx 7系列FPGA里9系列比如Zynq UltraScale的MIPI DSI IP比较成熟可以用硬核DPHY。UltraScale系列在MIPI DSI驱动上下功夫能够输出桌面级效果。二是接多块屏幕做扩展显示。Zynq平台的DRM/KMS支持多个CRTC也就是多屏显示。触摸功能可以通过对应的设备树节点分别绑定到不同input设备上。这个方案在车载仪表、车载娱乐系统中非常常见。三是把触摸坐标融合进人机交互应用里。比如用Python的PyQt5或者用LVGL这种轻量级图形库做流畅的滑动、翻转页面效果。FPGA平台跑这些现代图形框架完全够用尤其是Zynq系列带硬件浮点Qt界面运行很流畅。四是把触摸交互与FPGA逻辑联动。比如用户触摸屏幕上的某个区域Linux端向FPGA侧写一个寄存器FPGA逻辑检测到寄存器变化后改变某个外设的输出。这种软硬件互动是ARM平台不太好实现的事情也是FPGA做HMI方案最有价值的地方。我在实际项目中就做过一个案例屏幕上有一排按钮按不同的按钮FPGA会切换到不同的信号采集模式同时改变板卡上几个LED指示灯的闪烁状态。整个过程延时控制在几十毫秒内用户感知完全无延迟。7. 一些反向避坑的实在话这套流程整体走下来我觉得有几个平时教程里不会提到的经验值得说说。第一个经验是内核版本别追新。很多人拿到新项目就想用最新版内核实际上对于一些特定平台带厂商补丁的稳定版本经验最佳。Zynq平台2019.2这个版本我用了很久稳定、资料多、踩坑案例丰富。追新版本很可能遇到工具链兼容、设备树语法变化、驱动API调整的问题而这些问题的排查成本和收益完全不成正比。第二个经验是交叉编译工具链尽量用官方提供的预编译版本不要自己从源码编。自己编工具链看着高端但很容易在处理浮点ABI、glibc版本、内核版本之间出现不兼容到头来又得回到老版本。第三个经验是调试时先假数据后真硬件。我的做法是先用devmem命令直接往I2C控制器寄存器里写数据去读触摸芯片的寄存器确认硬件电气链路通再去写驱动调试。很多时候驱动调到怀疑人生其实是芯片都没上电白白浪费好几天。第四个经验是留好备份。设备树修改前好的做法是先把原来能启动的dtb文件备份出来。不要觉得改一个GPIO号不会出问题实际一个节点写错内核可能起不来这时候如果备份还在还能迅速回退不然就得用JTAG重新烧写引导文件折腾。根据我个人反复踩坑的经验FPGA平台跑Linux驱动触摸屏这件事难度不高但琐碎点多。整套环境的稳定性建立在三个检查上设备树配置对不对、内核编译选项全不全、硬件电气信号能不能通。把这三个基础打牢触摸屏这块基本就不会再折腾人了。之后你可以放心地把精力放在更有价值的应用开发上比如界面设计、交互逻辑、软硬件联动。这个方向市场价值不低尤其是工业控制和医疗设备领域一套稳定可靠的人机交互方案含金量远超会点几个外设接口的普通驱动开发。
返回列表