ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

嵌入式Linux串口触摸屏实战:从内核配置到坐标解析

嵌入式Linux串口触摸屏实战:从内核配置到坐标解析 在嵌入式Linux项目里做HMI串口屏是绕不开的选项。很多人觉得串口屏不就是“单片机通过UART发指令屏幕显示界面”嘛Linux上应该更简单结果一上手就发现触摸点没反应、坐标乱飘、串口数据收到一半就丢问题一个接一个。我刚完成一个基于Linux串口触摸屏的项目把整个链路从内核配置到应用层解析梳理了一遍这里做个设计总结内容覆盖“Linux内核怎么支持串口屏”和“串口触摸屏方案怎么落地”两条主线适合正在调串口屏的嵌入式工程师、做HMI相关开发的读者参考。串口屏这个方案在嵌入式Linux里的定位一直很微妙它不像RGB屏、LVDS屏那样完全依赖主控的显示控制器和framebuffer而是通过串口和屏内部的“组态逻辑/协处理器”通信。也就是说你的主控要做的不是“显示”这件事而是“解析坐标、渲染交给屏”。但也正因为如此很多习惯了framebufferinput子系统的Linux工程师会在“如何把串口屏的触摸数据变成标准输入事件”这个环节卡住。下面这篇文章就直接按项目实战的顺序来写为什么选串口屏、内核和设备树怎么配、触摸数据怎么解析、如何把坐标送进input子系统以及常见坑怎么排查。1. 为什么选串口屏方案对比与应用场景1.1 串口屏和RGB/LVDS屏的本质区别串口屏本质上是一个“带触摸、自带固件的显示终端”。主控MCU/SoC只需要通过UART发出事先约定好的指令帧屏端固件就会负责画界面、切换页面、显示文本和图形并把用户的触摸动作编码成串口数据回传给主控。整个过程完全不占用主控的LCD控制器、不占采内存带宽也不需要写复杂的显示驱动。RGB屏和LVDS/MIPI屏就不一样了。这一类屏幕只是单纯的“显示单元”主控必须配好显示控制器、分配framebuffer内存、配置像素时钟/H-sync/V-sync等时序参数然后每一次界面刷新都要由主控端实时渲染。触摸部分还要单独接I2C或SPI触控芯片需要编写对应的input驱动。做产品时这意味着工作量会分散到“显示驱动、LCD时序调试、触摸IC驱动、GUI框架适配”等多个环节任何一个环节出问题都会拖慢进度。从设计角度讲串口屏把显示和触摸两大麻烦一起带走主控侧只需要关心一个干净可靠的串口。这个思路特别适合Linux项目里“不想碰DRM/KMS、不想调fbdev、触摸屏型号又不固定”的场景。1.2 串口屏和RGB屏的快速对比维度串口屏UART屏RGB/LVDS/MIPI屏主控硬件要求一个UART外设即可需要LCD控制器、甚至DSI/LVDS PHY驱动开发量只需要串口驱动数据解析需要显示驱动、触摸IC驱动、背光调节等帧率与复杂动画一般30~60fps以内复杂动画受限主控端有足够性能时可做到流畅视频级触摸方案屏内部集成串口回报坐标需要外接触摸IC独立输入设备开发周期短协议熟悉后几天能跑通长需要显示时序和驱动调试适用场景工业HMI、电桩、医疗设备、控制面板手机、平板、多媒体交互设备、高端仪表串口屏的“短板”也非常明确它的图形渲染能力局限在屏端芯片做不了复杂视频、3D界面如果主控本身性能很强比如四核A53用串口屏会显得有点浪费反而不如直接上RGB屏加Qt跑双缓冲流畅。所以在方案选型时我一般建议按照产品形态来定显示内容以固定控件、文本、仪表盘为主交互只涉及触摸点击和滑动串口屏是性价比极高的选择如果界面里要放视频流、地图、动态矢量渲染那还是老老实实选RGB/LVDS屏。1.3 串口触摸屏的常见协议与坐标模型市面上的串口屏厂商很多比如淘晶驰、大彩、迪文等底层协议各家有各家的风格但共同点都是“二进制帧或文本命令”。触摸坐标这一块绝大多数屏的回报格式可以抽象为按下事件坐标值X/Y或者抬起事件。有的屏还会把“触摸事件编号/触摸ID”一起打包方便做多点触控。对Linux这侧来说触摸数据进来后最理想的目标是转化成标准Linux input事件。也就是把“X坐标、Y坐标、按下/抬起状态”上报给内核的input子系统这样上层应用Qt、GTK、EVDEV等无需关心你接的是串口屏还是I2C电容屏统一走/dev/input/eventX就能读到触摸。驱动设计和上层框架的边界也在这里划分内核支持串口屏并不是说Linux内核里要内置某个“串口屏驱动”而是你提供一条将串口数据接入input子系统的通道。内核做通道应用做渲染串口屏做最终显示。2. 内核支持串口屏的前期准备串口配置与设备树2.1 内核里需要打开的配置项Linux内核默认不会为某个具体品牌串口屏提供驱动除非厂商做了一套很完善的但内核需要提供“串口、输入子系统、设备树”这些基础能力。以我常用的4.9/5.10内核为例建议确认以下配置已经打开CONFIG_SERIAL_8250 / CONFIG_SERIAL_8250_CONSOLE标准串口驱动很多嵌入式SoC的UART都挂在8250框架下。CONFIG_SERIAL_COREserial核心层一般会默认选上。CONFIG_INPUT_EVDEV用户空间访问input事件的接口层打开后才有/dev/input/eventX。CONFIG_INPUT_MISC一些杂项输入设备驱动可选的父选项。CONFIG_INPUT_UINPUT如果你想用应用层方式模拟触摸设备UINPUT是必不可少的。CONFIG_OF和CONFIG_OF_GPIO / CONFIG_OF_SERIAL设备树相关配置现代ARM平台基本强制开启。CONFIG_PINCTRL引脚复用控制串口TX/RX引脚都需要通过pinctrl配置。如果内核里串口驱动没开设备树里写了串口节点也不会生成对应的/dev/tty**节点。我在项目里就遇到过板子默认配置里只开了调试串口应用串口全被裁剪掉的情况。检查方法很简单启动后在/dev下看看ttyS0/ttymxc0/ttyAMA0这些节点数量和设备树里配置的串口个数是否匹配。2.2 设备树中串口节点的正确写法拿典型的NXP i.MX6ULL来举例板上有一个串口屏挂在UART2上设备树需要做两件事一是把UART2的pinctrl引出来二是使能节点并设置合适参数。uart2 { pinctrl-names default; pinctrl-0 pinctrl_uart2; status okay; }; iomuxc { pinctrl_uart2: uart2grp { fsl,pins MX6UL_PAD_UART2_TX_DATA__UART2_DCE_TX 0x1b0b1 MX6UL_PAD_UART2_RX_DATA__UART2_DCE_RX 0x1b0b1 ; }; };很多新手在这里会出现两个典型问题。第一个是pinctrl配置写成了DCE模式结果TX/RX对不上第二个是设备树里写了status disabled或者被uboot通过bootargs屏蔽了某个串口导致内核启动后根本没有节点。调试时可以先用一个简单方法验证启动完成后cat /proc/tty/driver/ttymxc不同平台名字不同通用是看/proc/tty/drivers如果能列出对应的tty设备说明节点已经注册成功。2.3 确认串口是否被调试console占用这是串口屏项目里非常容易翻车的一个地方。很多开发板的默认bootargs里写了consolettymxc0,115200如果串口屏恰好接在ttymxc0上那你在屏幕上看到的任何数据都混着登录提示和内核打印触摸消息根本没办法干净解析。正确做法是给串口屏单独分配一个未用作console的串口比如ttymxc1或ttymxc2。如果有必要保留调试串口可以在设备树里把调试口和串口屏口分开bootargs也做相应修改保证串口屏挂载的串口完全交给业务使用。验证串口是否干净可以这样测试在板卡上执行echo hello /dev/ttymxc1再用UART调试器或USB转串口连接到对端看是否能收到干净的ASCII数据。如果收到的是夹杂打印log的乱码或者根本收不到先排查console占用、引脚复用以及串口屏是否已经给主控回数据。3. 触摸数据获取与解析从串口帧到标准输入事件3.1 串口屏触摸协议的常见帧结构串口屏的触摸协议可以自定义也可以按厂商固件内置协议来。我这里用一个常见的通用帧结构来举例项目里实际也踩过这类格式字节说明0xAA 0x55帧头0x01命令触摸/坐标事件0x00 0x02数据长度x_high x_lowX坐标y_high y_lowY坐标press_state0x01按下0x00抬起checksum异或和或累加和校验注意不同厂家的协议差异很大。有的屏会把“按下”和“抬起”分成两条不同命令有的屏只在上报一次“按下”后就不再重复发送直到下一次按下才发新坐标这就需要上层自行维护之前的坐标状态有的屏则允许连续上报“滑动轨迹”开发时千万别假设屏幕一定会像鼠标一样持续滑动。解析时最关键的一步是“帧同步”。串口是流式通道你可能一次read调用里收到半包、一包半甚至多包。如果你直接按照read(buf, len)返回的数据长度去解析大概率会错位。正确做法是维护一个环形缓冲或状态机先从字节流里找到帧头再按长度字段取完整数据帧最后做校验。我的习惯是每次读到数据先塞进缓冲区再循环“找帧头、判长度、取整帧、校验”处理不完的下次再进来继续清。3.2 内核态上报input事件的方法把串口数据变成Linux标准输入事件最稳定的是写一个内核驱动在串口接收中断/接收回调里解析触摸帧然后通过input_report_abs和input_sync上报。驱动框架大致是注册一个serdev客户端或者更简单的“平台驱动misc设备”组合申请输入设备节点初始化input_set_abs_params设置坐标范围然后在收到串口数据时解析并上报。static struct input_dev *tp_input_dev; static void report_touch(int x, int y, int press) { input_report_key(tp_input_dev, BTN_TOUCH, press); input_report_abs(tp_input_dev, ABS_X, x); input_report_abs(tp_input_dev, ABS_Y, y); input_sync(tp_input_dev); }坐标范围要和屏幕分辨率对上比如屏是1024x600那么input_set_abs_params(tp_input_dev, ABS_X, 0, 1023, 0, 0)不然应用层拿到的坐标可能超出屏幕边界Qt窗口里会表现为“触摸点跑到窗口外面”。内核态方案的优点是延迟低、不依赖用户进程、系统重启后驱动自动加载适合产品化缺点是需要写内核代码调试稍微麻烦。如果项目周期紧、不想编译内核驱动也可以先在应用层做机制上使用uinput。3.3 应用层使用uinput虚拟触摸设备uinput是内核提供的一个虚拟输入设备接口用户进程可以在应用层创建一个“假触摸屏”然后把解析到的坐标写进去上层应用程序看到的效果和真实触摸设备一致。打开/dev/uinput后通过ioctl注册设备int fd open(/dev/uinput, O_WRONLY | O_NONBLOCK); struct uinput_setup setup { 0 }; setup.id.bustype BUS_USB; setup.id.vendor 0x1234; setup.id.product 0x5678; strcpy(setup.name, serial-touch); ioctl(fd, UI_DEV_SETUP, setup); ioctl(fd, UI_DEV_CREATE);然后在收到坐标的线程里上报struct input_event ev; memset(ev, 0, sizeof(ev)); ev.type EV_ABS; ev.code ABS_X; ev.value x; write(fd, ev, sizeof(ev)); // 同理上报ABS_Y、BTN_TOUCH、EV_SYNuinput方式很适合快速原型验证因为不需要编译内核改协议也方便。但缺点也很明显如果进程异常退出虚拟触摸设备会消失上层应用拿不到事件排查起来没有内核态驱动那么“干净”。所以我个人建议原型验证阶段用uinput产品化阶段尽量把解析放到内核态或做成一个常驻保活的服务进程。3.4 坐标方向与屏幕旋转的处理串口屏最常见的问题之一就是坐标方向不一致。有的屏出厂零点在左上角有的在右下角如果产品中屏幕默认旋转90度安装那Linux上报的坐标就要跟着转换。最简单的方案是在驱动或应用里做一次坐标变换旋转90度时X_new Y, Y_new MAX_X - X旋转180度时X_new MAX_X - X, Y_new MAX_Y - Y。这些映射关系不要写死最好做成设备树属性或配置文件方便现场工程师根据屏幕安装方向调整。如果你用tslib做校准它内部会做“线性校准矩阵”计算效果比手工变换好很多尤其对电阻屏的非线性漂移很友好。但电容串口屏一般出厂已经做好线性校准通常只需要调整方向不需要再做五点/三点校准强行做tslib校准反而可能破坏出厂线性度。4. 实操记录串口触摸屏联调的完整流程4.1 硬件连接与电平检查串口屏和主控之间的硬件连接看起来简单实际上很容易出问题。大多数串口屏是TTL电平引脚定义一般是VCC、GND、TX、RX、背光/复位等。主控端串口的IO电压必须匹配比如主控是3.3V IO串口屏也要求3.3V那就直连如果串口屏是5V电平主控是3.3V就必须加电平转换芯片不然运气不好会烧IO口。TX/RX一定要交叉连接主控的TX接屏的RX主控的RX接屏的TX。很多开发板丝印上标注的是“对外端口”还是“主控端口”没有仔细看说明书就照丝印接结果板子对外TX接屏对外TX数据全飞了。另外串口屏的电源要单独考虑。屏工作时的背光电流不小尤其7寸及以上尺寸瞬时电流可能上到500mA甚至更高。如果直接从主控板的3.3V或5V LDO拉电主控MCU复位时电压波动可能导致串口屏也一起重启表现就是系统起来后串口屏白屏或反复重启。最好给串口屏单独一路电源或者至少保证电源余量充足。4.2 串口打通使用常用工具验证硬件接好以后先不要急着写Linux驱动直接在系统里打通串口链路。推荐用stty配置波特率再用cat或写个小Python脚本接收数据。stty -F /dev/ttymxc1 115200 raw -echo cat /dev/ttymxc1 | xxd这时候触摸一下串口屏屏幕上应该能看到十六进制数据流。如果xxd没有任何输出先用万用表量一下屏的TX引脚是否有电平活动如果TX有活动但主控收不到大概率是RX引脚复用配置错误或者UART没有真正打开。反过来也要测试主控发数据给屏是否能收到。可以给屏发一条厂商规定的查询指令如果屏有回应就说明双向链路都通了。这个阶段测试很关键因为后续所有问题如果都堆到应用层去查很难定位到是UART不通还是协议解析有bug。4.3 解析透传触摸数据并映射为input事件串口数据验证正常后就开始写解析程序。这里用C写一个最简单的读取循环作为demo实际上项目中我建议把这个逻辑做成独立线程或小服务。int fd open(/dev/ttymxc1, O_RDWR | O_NOCTTY); struct termios tio; tcgetattr(fd, tio); cfsetispeed(tio, B115200); cfsetospeed(tio, B115200); cfmakeraw(tio); tio.c_cc[VMIN] 0; tio.c_cc[VTIME] 0; tcsetattr(fd, TCSANOW, tio); unsigned char buf[256]; while (1) { int n read(fd, buf, sizeof(buf)); for (int i 0; i n; i) { // 喂给帧解析状态机 parse_byte(buf[i]); } }解析状态机里要做两件事识别完整数据包然后把坐标换算后上报。如果采用uinput方案解析线程和上报线程可以合并如果采用内核方案那这个应用进程就不需要了直接把串口接收回调里的数据透传给内核解析模块即可。实际开发中我建议先用evtest验证上报结果运行evtest /dev/input/eventX手指在串口屏上滑动时能看到ABS_X/Y数据持续变化说明链路已经完整。这时候再用Qt或GTK应用打开evdevtouch插件触摸功能基本就通了。4.4 校准工具链与坐标变换电容式串口屏出厂前一般会做自身的坐标校准主控直接按分辨率映射即可。电阻式串口屏受机械误差影响可能要做线性校准。如果Linux侧直接把原始坐标上报应用层会明显感觉“触摸点偏移”比如点一个按钮却触发旁边按钮。tslib是Linux传统触摸屏校准库用法上需要编译并移植到开发板然后设置环境变量export TSLIB_TSDEVICE/dev/input/eventX export TSLIB_CALIBFILE/etc/pointercal export TSLIB_CONFFILE/etc/ts.conf ts_calibrate校准过程会生成校准矩阵文件/etc/pointercal。之后应用层通过tslib获取坐标就不再是原始的ABS_X/ABS_Y而是经过线性变换后的坐标。Qt 5内置了tslib插件QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS配合libinput或tslib可以完成适配。如果不想引入tslib也可以在代码里手动实现“两点/三点校准”。两点校准只能解决线性放大和偏移问题三点校准还能解决一定程度的旋转误差。计算公式本质上是一个仿射变换X_screen a * X_raw b * Y_raw c Y_screen d * X_raw e * Y_raw f采集三个已知点的原始坐标和屏幕坐标解六元一次方程组求出 a~f 六个系数。这个方案不依赖额外库在资源紧张的板子上很实用。4.5 与Qt/显示层对接的注意点串口屏方案里Linux主控端可能并不需要Qt去渲染屏幕内容因为画面已经由串口屏自己绘制了。但很多场景下Linux端还是要有界面逻辑比如动态数据下发、参数配置。此时触摸事件进入Qt后需要确保Qt的事件循环能捕获到。如果Qt用的插件是linuxfb触摸事件默认走evdevtouch需要指定-plugin evdevtouch或者通过环境变量启用。如果你创建的是QWidget应用测试时发现点击无反应先看/dev/input/eventX是否存在再看Qt日志里是否加载了evdevtouch插件最后用evtest确认事件在持续上报。一个容易忽视的问题串口屏协议如果只上报“按下”时的坐标、不上报“抬起”时的坐标那么Qt的鼠标事件里release事件可能丢失导致按钮一直处于按下状态。解决思路是在解析层收到按下事件后内部维护一个“当前触摸点”状态如果超过一定时间没有收到新坐标或抬起事件就主动补发一次抬起事件保证上层交互状态一致。5. 常见问题与排查技巧实录5.1 串口屏没数据先别改代码用硬件思维排查触摸屏完全没响应时90%的问题不在Linux内核也不在解析程序而在链路层。先用USB转串口工具直接监听屏的TX引脚看有没有数据输出如果屏自身没有输出检查屏是否进入休眠、触摸FPC是否插好、屏的配置界面是否把触摸回报功能关掉了。确认屏有数据但主控read不到再检查内核侧。查看/dev下是否存在对应tty节点用stty -F /dev/ttymxc1查看波特率是否和屏一致用示波器观察主控RX引脚是否有波形。如果RX引脚已经被其他外设复用数据会直接消失。在这类问题里我踩过最深的坑是“主控的串口接收DMA被误配置成不可用状态”。有些SoC的串口接收FIFO深度很小没有DMA时高波特率比如1.5Mbps下会丢字节。解决办法是降低波特率到115200/921600或者在内核DMA驱动里把接收DMA打开具体名字通常是CONFIG_SERIAL_8250_DMA或CONFIG_CPUFREQ_DT无关需要看datasheet。5.2 坐标方向反了或中心偏移方向反了是最好解决的检查上报时是不是把ABS_X和ABS_Y赋值反了或者设备树里配置了touchscreen-inverted-x等属性。如果做了tslib校准确认校准矩阵里的系数正负号是否正常比如对角线系数为负就说明某一轴被镜像了。中心偏移但方向正确一般是因为屏幕分辨率映射不对。比如屏实际是800x480但驱动里input_set_abs_params设置成了1024x600上层Qt识别设备时会按绝对值范围读取坐标超出屏幕的部分应用层可能会忽略或映射错误。建议直接把上报范围设置为与屏分辨率严格一致。如果是电阻屏并且只在屏幕某个区域出现偏移大概率是触摸屏本身线性度变差需要重新校准。我遇到过一次屏内部模拟前端使用了“非线性校正表”而主控又做了一次线性校正导致局部扭曲。这种情况下要查看屏厂家协议里是否支持“关闭屏内自身校准”的指令避免双重校准冲突。5.3 数据丢包、CRC错误频繁串口屏数据丢包是最磨人的一个问题。原因分三类波特率过高、串口接收缓冲区不够、电磁干扰。波特率越高每个字节时间越短如果主控串口FIFO较浅或者中断响应不及时很容易丢。先用逻辑分析仪统计收到的字节数和屏发出的字节数是否相等如果丢包率在高速率下明显增加降速是立竿见影的办法。缓冲区不足多发生在Linux应用层。如果read不及时或者线程调度延迟大串口驱动层的缓冲区被写满后面字节就被丢弃。解决方法是把termios的VMIN/VTIME设置成阻塞读取或把串口FIFO触发中断调深也可以用O_NONBLOCKpoll模型持续读取。电磁干扰导致误码属于现场环境问题表现是固定干扰源靠近时CRC错误增多。可以在串口线上加磁环、缩短线缆长度、使用屏蔽双绞线或者提高帧校验强度——从单个字节异或校验改成CRC16误检率下降一个数量级。很多工业串口屏协议都支持CRC不要怕麻烦坚持在解析层校验不然产品现场“时不时乱跳一下”会让人崩溃。5.4 休眠唤醒后触摸失效Linux系统休眠后串口屏往往还保持上电状态但主控端的串口时钟和引脚状态可能已经被切断。唤醒后常见现象是应用层程序还在运行但read不到任何数据或者屏端因为主控掉电重启导致协议状态错乱。排查思路唤醒后先用cat /dev/ttymxc1看是否还有数据如果没有检查串口所在电源域是否正常恢复有的平台需要在休眠回调里重新配置pinctrl或复位串口控制器。如果是驱动持有设备树里的时钟休眠时被关闭唤醒后必须执行clk_prepare_enable。另外串口屏如果和主控共用电源主控进入低功耗时可能会把整板电压拉低导致屏自动复位。唤醒后主控虽然起来了但屏还在重新初始化此时要等一段时间再发指令。我在项目里的做法是唤醒后主动休眠100~200ms然后向屏发送一条查询指令收到正确回包再恢复业务数据读取确保两端状态重新同步。5.5 常见问题速查表现象可能原因排查/解决方案串口屏完全无响应TX/RX接反、电平不匹配、电源不足交叉接线、加电平转换、独立供电主控收不到触摸数据串口未打开、pinmux错误、波特率不匹配检查tty节点示波器看RX波形stty设置波特率触摸坐标方向不对上报坐标轴映射错误、屏幕安装旋转交换ABS_X/ABS_Y按旋转角度做变换坐标漂移未校准或双重校准三点校准或关闭屏内校准只保留一侧校准丢包/误码高波特率过高、缓冲区溢出、干扰降速、调整FIFO、CRC校验、屏蔽线缆唤醒后触摸失效串口时钟未恢复、屏复位不同步休眠回调恢复时钟唤醒后重发握手/查询指令应用层收到点击却没有release协议只上报按下事件解析层维护状态超时自动补发抬起事件6. 内核移植裁剪与串口屏方案的扩展思考如果你在内核移植阶段就计划支持串口屏那么在裁剪内核时不要把串口相关模块全部按“最小系统”来裁。很多工程师做内核裁剪时为了体积拼命去掉各种驱动结果串口屏需要的SERIAL_8250、SERIAL_OF_PLATFORM、INPUT_EVDEV被一并砍掉。建议在做内核裁剪时单独保留一套“HMI用串口屏配置清单”逐项确认后再做整体裁剪。另外一个值得思考的点是串口屏方案里Linux主控的角色其实可以很轻。它不需要完整的桌面环境甚至不需要fbdev只需要一个精简根文件系统、一个串口驱动、一个输入子系统再加一个业务进程即可。因此在系统设计时要克制住“什么库都往上装”的冲动串口屏的哲学就是“主控做减法屏幕做加法”。如果产品后期需要从单点触控升级到多点触控串口屏协议要能提供MTP_SLOT、ABS_MT_POSITION_X/Y等事件类型。此时Linux侧的input上报要改用Type B多点协议即在input_mt_slot中指定触摸点ID再分别上报对应坐标。如果你的屏协议不支持多个触摸点ID想esp32这类虽然能“声称支持多点”但回报数据只是一堆互不关联的坐标点那在Linux侧强行模拟多点触控效果会非常差这也是选型时需要提前考察的。实操中我个人的体会是串口屏调试的难点不在“有没有触摸数据”而在“如何把数据变成稳定的坐标流”。只要你的串口链路稳定、协议解析状态机正确、input事件上报路径清晰这个方案做起来会异常顺手。更多时候真正让项目拖延的反而是“内核里没配好某个串口节点”这种看起来很低级的问题。因此动手之前先把硬件链路、内核配置、设备树、数据格式四件事查清楚后面就会顺畅很多。
返回列表