ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

触摸屏坐标采集全解析:从物理触摸到PLC二进制码的精准链路

触摸屏坐标采集全解析:从物理触摸到PLC二进制码的精准链路 做触摸屏相关的项目最常遇见的故障不是黑屏、不是不亮而是它“找不到感觉”你明明按的是启动按钮光标却停在旁边同一个位置点三次三次数值都不一样好不容易校准一次过两天温度一变坐标又开始飘。更麻烦的是就算触摸数据勉强传到了PLC也因为波动太大、精度不足根本不敢让它参与核心逻辑计算。很多工程师习惯把这类问题归因于“触摸屏质量差”或者直接在上位机里加死区、加延迟靠“撞大运”强行消抖。但触摸不准这件事本质上不是玄学而是一条数据链路里的误差没有被控制住。从手指按下屏幕到CPU拿到一个可以参与计算的二进制数值中间要经过传感器、模数转换、触摸控制器、通信接口、寄存器映射等一长串环节每个环节都会引入偏差。只要有一个环节出了问题最终参与计算的二进制码就不可能精准。这篇文章要讲清楚的核心问题是触摸屏究竟怎样把一次物理触摸变成一组精准的、可参与逻辑计算的二进制码。我会从电阻屏和电容屏的坐标采集原理讲起深入到GT911、FT5336这类常见触摸控制器再分析触摸屏与PLC通信时的地址映射和数据类型问题最后给出一套从校正、滤波到工程排错的完整思路。无论你是做单片机驱动、HMI组态还是工业设备联调读完都能知道问题出在哪一环。1. 触摸屏“找不到感觉”的本质是什么先说结论触摸屏“不准”本质上不是感觉问题而是“物理坐标 → 原始数值 → 逻辑坐标 → 通信数值”这条链路上出了偏差。如果我们把一次触摸拆开看会发现它其实经历了四次转换手指按下触摸屏的物理层产生一个模拟信号模拟信号经过ADC采样变成一组原始的数字量触摸控制器或主控固件对原始数字量做校正、滤波、坐标变换得到屏幕逻辑坐标逻辑坐标再通过串口、网口或内部寄存器交给HMI/PLC参与区域判断、数值设定、手势识别等计算。所谓“找不到感觉”通常就发生在第2步到第4步之间。比如ADC参考电压不稳定原始数字量就会上下跳触摸屏没做校准逻辑坐标就会整体偏移通信协议里的寄存器地址或数据类型映射错了即便坐标本身是准的PLC最终拿到的也是一个错得离谱的数。这里有一个新手最容易误解的地方很多人觉得触摸屏只是一个“高级鼠标”点击之后只要把坐标发给屏幕控件就行了。但在工业现场触摸屏并不只是用来“点按钮”的它还要承担数据录入、参数设定、量程换算、趋势显示这类任务。用户通过触摸屏输入一个温度值这个值会先写到HMI的内部寄存器再通过通信协议映射到PLC的保持寄存器PLC拿到的是一串二进制码。只要这串二进制码和用户输入的数值对不上整个控制逻辑就全乱了。所以“触摸屏找不到感觉”的准确技术表达是触摸屏输出的二进制码无法稳定、准确地还原用户的真实触摸位置和输入意图。要解决这个问题必须先看懂这段数据链路。2. 触摸数据链路全景从物理触摸到二进制码一张完整的数据链路图如下物理触摸 ↓ 模拟信号电阻屏分压 / 电容屏电荷变化 ↓ ADC采样12位 / 16位原始数值 ↓ 触摸控制器处理校准、滤波、边缘抑制 ↓ 坐标寄存区I2C/SPI中断通知主控读取 ↓ HMI/上位机读取串口、网口、内部总线 ↓ PLC寄存器 / 逻辑计算二进制码参与运算这条链路里每一步都有固定的误差来源。我们需要做的不是“消除所有误差”而是把每一步的误差控制到可接受范围内。电阻屏和电容屏在链路的前半段差异极大必须分开看对比项电阻触摸屏电容触摸屏信号来源两层导电膜受压接触形成分压电压人体与触摸屏形成耦合电容改变电荷量ADC采样对象X/Y轴分压电压电极电容变化量典型控制芯片XPT2046、ADS7846GT911、FT5336、FT6206接口方式SPI主控主动查询I2C/SPI中断通知后读取是否支持多点通常单点支持5点/10点校准需求强制造公差和贴合误差明显出厂校准一次长期使用也可能漂移主要失效模式老化、压力不均、磨损静电、温度湿度、电源噪声从这张表能看出电阻屏必须做校准因为ADC读到的电压值和屏幕像素坐标之间不是天然相等的电容屏虽然出厂时做过映射但装配到不同设备、不同盖板厚度后同样可能出现坐标偏移。对于参与PLC计算这个目标来说链路后半段往往比前半段更容易出问题。很多项目里触摸控制器输出的坐标本身是准的但HMI和PLC之间的寄存器地址、数据类型、字节序没有统一导致“屏幕上显示是50PLC读出来却是负数”这类奇怪现象。这也是为什么文章后面会花一整章讲地址映射。3. 电阻触摸屏坐标采集原理与实现电阻触摸屏的核心是一个可变电阻分压器。四线电阻屏的上下两层导电膜在无按压时是分开的按压后会在触点位置接触把其中一层作为分压电阻另一层作为测量点。通过轮流给X轴和Y轴施加电压就能在测量点得到与触点位置成正比的电压再用ADC把这个电压变成数字量。以常见的XPT2046触摸控制器为例它内部集成了12位ADC通过SPI接口和主控通信。读取X坐标时发送一个控制字节选择X通道和差分测量模式然后读回两个字节的ADC结果。实际项目中代码一般长这样// 文件路径bsp_touch.c #include bsp_touch.h #define TOUCH_CS_LOW() HAL_GPIO_WritePin(TOUCH_CS_GPIO_Port, TOUCH_CS_Pin, GPIO_PIN_RESET) #define TOUCH_CS_HIGH() HAL_GPIO_WritePin(TOUCH_CS_GPIO_Port, TOUCH_CS_Pin, GPIO_PIN_SET) // XPT2046 控制字节 #define CMD_READ_X 0x90 // 差分模式读取X坐标12位精度 #define CMD_READ_Y 0xD0 // 差分模式读取Y坐标12位精度 static uint16_t XPT2046_ReadADC(uint8_t cmd) { uint8_t send_buf cmd; uint8_t recv_buf[2] {0}; uint16_t value 0; TOUCH_CS_LOW(); HAL_SPI_Transmit(hspi1, send_buf, 1, HAL_MAX_DELAY); HAL_SPI_Receive(hspi1, recv_buf, 2, HAL_MAX_DELAY); TOUCH_CS_HIGH(); value ((recv_buf[0] 8) | recv_buf[1]) 4; return value; } uint16_t XPT2046_ReadX(void) { return XPT2046_ReadADC(CMD_READ_X); } uint16_t XPT2046_ReadY(void) { return XPT2046_ReadADC(CMD_READ_Y); }这段代码的思路是CS拉低选中芯片发送一个命令字节再连续读取两个字节把结果右移4位得到12位有效数据。不同参考代码在具体时序上略有差异有的会读3个字节有的会在命令字节末尾补时钟但核心逻辑都是“发送命令 → 读ADC结果”。实际移植时一定要以芯片手册的时序图为准。这里要特别提醒一个问题XPT2046的原始ADC值并不能直接拿来当屏幕坐标用。原因有三层ADC本身有零点漂移和增益误差同一个触点在不同的电压、温度下读到的值会不一样触摸屏的导电膜、贴合工艺存在非线性屏幕中间和边缘的“单位距离对应的电压变化量”并不完全一致ADC坐标原点和LCD显示坐标原点往往不在同一个位置X轴、Y轴方向也可能相反。所以读回来的原始二进制码只是“触摸屏的物理感觉”不是“屏幕的像素位置”。要让这个数值参与计算必须先经过校准。4. 电容触摸屏与GT911、FT5336驱动电容触摸屏的坐标采集方式和电阻屏完全不同。它利用的是人体手指与触摸屏电极之间的电容耦合效应手指接近时对应位置的电极电容发生变化。触摸控制器扫描整块面板的电极矩阵通过检测电容变化量计算出触摸点的位置最后把计算好的坐标写入内部寄存器等待主控读取。GT911是工业设备里非常常见的电容触摸控制器用I2C接口和主控通信。它的优势是集成了温度补偿、基线跟踪、边缘抑制等算法主控不需要自己处理太多模拟信号问题。GT911的上电流程一般是主控通过I2C写配置寄存器设置分辨率、I2C地址、扫描模式然后等待触摸中断一旦INT引脚拉低就说明有触摸事件主控去寄存器区读取坐标数据。简化的读取逻辑如下// 文件路径gt911_driver.c // 说明以下寄存器地址为常见资料整理实际移植请以GT911规格书为准 #define GT911_I2C_ADDR 0x5D #define GT911_COORD_ADDR 0x814E // 读取触摸状态低4位表示有效触点数量 uint8_t gt911_read_status(void) { uint8_t status 0; i2c_read_byte(GT911_COORD_ADDR, status); return status; } // 读取所有触点坐标 uint8_t gt911_read_points(touch_point_t *points) { uint8_t status gt911_read_status(); uint8_t point_count status 0x0F; uint8_t buf[64]; if (point_count 0 || point_count 5) { return 0; } // 从坐标缓冲区的第二个字节开始读取每个触摸点数据 i2c_read_bytes(GT911_COORD_ADDR 1, buf, point_count * 8); for (uint8_t i 0; i point_count; i) { uint8_t *p buf i * 8; points[i].x (p[1] 8) | p[0]; points[i].y (p[3] 8) | p[2]; points[i].size p[5]; } return point_count; }FT5336也是类似的思路同样走I2C从状态寄存器和坐标寄存器读取数据。它常见于安卓手机、平板和中控面板上在STM32上移植时要注意I2C地址可能因硬件引脚配置不同而改变。电容屏的驱动层其实已经帮主控解决了很多“感觉”问题。如果GT911读出来的坐标仍然不准多半不是坐标计算逻辑的问题而是以下几个方面I2C通信不稳定读到的数据不完整触摸屏的盖板玻璃太厚或者贴合工艺不好导致信号强度偏弱电源纹波过大触摸控制器内部的模拟电路被干扰触摸控制器的配置分辨率与实际LCD分辨率不一致导致坐标被错误缩放。尤其最后一点在HMI项目里特别常见。LCD是1024x600但GT911配置成了1280x800读出来的坐标自然偏得离谱。遇到这种情况不要急着改滤波算法先检查控制器的分辨率配置和坐标范围。5. 触摸屏校正从“感觉”到“坐标”的最后一公里不管电阻屏还是电容屏要保证二进制码精准校正都是绕不开的一步。校正的本质是建立“ADC原始坐标”和“屏幕逻辑坐标”之间的映射关系。从数学上看电阻屏的ADC坐标(Xa, Ya)和屏幕坐标(x, y)之间存在仿射变换关系Xa a * x b * y c Ya d * x e * y f其中a、b、c、d、e、f是6个未知数。理论上只要知道3个不共线点的“ADC坐标”和“屏幕坐标”就能解出全部6个参数。这就是三点校准法的数学基础。工程上校准流程是在屏幕上显示一个十字光标让用户依次点击3个已知位置系统记录每个位置的ADC原始值然后代入方程组求解参数。等参数计算完成后每次读取到ADC值就能反推出对应的屏幕坐标。用C语言实现参数求解核心代码如下// 文件路径touch_calib.c typedef struct { float a, b, c; // Xa a*x b*y c float d, e, f; // Ya d*x e*y f } touch_calib_t; touch_calib_t calib; // 三个已知屏幕坐标像素 static const float screen_x[3] { 50.0f, 270.0f, 50.0f }; static const float screen_y[3] { 50.0f, 50.0f, 190.0f }; // 三个对应的ADC采样原始坐标由校准时采集得到 static const float adc_x[3] { 520.0f, 3900.0f, 540.0f }; static const float adc_y[3] { 480.0f, 510.0f, 3700.0f }; static float det_screen 0.0f; void touch_calib_init(void) { det_screen screen_x[0] * (screen_y[1] - screen_y[2]) screen_x[1] * (screen_y[2] - screen_y[0]) screen_x[2] * (screen_y[0] - screen_y[1]); calib.a (adc_x[0] * (screen_y[1] - screen_y[2]) adc_x[1] * (screen_y[2] - screen_y[0]) adc_x[2] * (screen_y[0] - screen_y[1])) / det_screen; calib.b (adc_x[0] * (screen_x[2] - screen_x[1]) adc_x[1] * (screen_x[0] - screen_x[2]) adc_x[2] * (screen_x[1] - screen_x[0])) / det_screen; calib.c (adc_x[0] * (screen_x[1] * screen_y[2] - screen_x[2] * screen_y[1]) adc_x[1] * (screen_x[2] * screen_y[0] - screen_x[0] * screen_y[2]) adc_x[2] * (screen_x[0] * screen_y[1] - screen_x[1] * screen_y[0])) / det_screen; // d、e、f 的计算同理只需要把 adc_x 替换成 adc_y calib.d (adc_y[0] * (screen_y[1] - screen_y[2]) adc_y[1] * (screen_y[2] - screen_y[0]) adc_y[2] * (screen_y[0] - screen_y[1])) / det_screen; // ... } // 由ADC原始值反求屏幕坐标 void touch_convert(uint16_t raw_x, uint16_t raw_y, float *out_x, float *out_y) { float det calib.a * calib.e - calib.b * calib.d; *out_x (calib.e * (raw_x - calib.c) - calib.b * (raw_y - calib.f)) / det; *out_y (calib.a * (raw_y - calib.f) - calib.d * (raw_x - calib.c)) / det; }这段代码的关键在于校准参数计算出来后日常使用时不直接拿ADC原始值做判断而是先做仿射逆变换得到屏幕坐标再进行区域判断或数值映射。在Windows设备上研华一体机这类工业触摸电脑一般自带校正工具系统设置里也能找到“平板电脑设置”进行校准。Linux系统下则可以通过libinput或xinput完成校准和禁用操作。比如临时禁用触摸屏可以先用xinput list找到设备ID再用xinput disable 设备ID。这些工具的本质都是一样的修正从物理触摸到逻辑坐标的映射关系。6. 触摸屏与PLC通信地址、寄存器与二进制码映射触摸屏坐标校好了二进制码也精准了但还没到“参与计算”这一步。在工业控制里触摸屏通常不是最终的计算单元它只是一个人机接口真正的逻辑判断在PLC里。触摸屏必须把用户的输入转换成PLC能识别的寄存器值这个过程如果映射错了前面所有努力都白费。这里就必须理解HMI地址类型。以威纶通触摸屏为例它支持的PLC地址类型里3x、4x、5x是最常被问到的三个地址类型名称位/字读写属性典型用途0x内部继电器位读写HMI内部逻辑、按钮状态1x输入点位只读读取外部开关信号3x输入寄存器字只读读取外部设备数值量4x保持寄存器字读写读写设备设定值、参数5x输出线圈位读写控制外部设备启停6x内部寄存器字读写HMI内部数值存储对照Modbus协议就更好理解了3x对应Modbus功能码04读输入寄存器只能读适合读取传感器、编码器这类外部设备产生的数值4x对应功能码03/06/16读写保持寄存器适合保存参数设定值和运算结果是PLC通信里最常用的一类地址5x对应功能码01/05/15读写线圈适合做启停控制、状态输出。实际项目中常见错误是把触摸屏的“数值输入元件”关联到了3x地址。3x是只读的用户输入的值自然写不进去屏幕上可能表现为“输入后值不变化”或者直接提示通信错误。正确做法是需要用户输入的参数关联到4x保持寄存器只显示不可修改的测量值关联到3x输入寄存器。还有一个容易踩坑的地方是数据类型长度。PLC的4x寄存器通常是16位但用户输入的温度可能是浮点数或者是需要32位表示的数值。这种场景下HMI侧要选择32位或浮点格式并且和PLC侧的地址连续占用方式保持一致。比如西门子S7-1200通过Modbus TCP通信时一个Real类型占用两个字如果HMI只映射了一个字读出来的数值就会错乱出现“显示不信任PLC”之类的奇怪现场。时间值、累计量这类长数值尤其要注意长度和字节序。昆仑通态触摸屏的思路类似只是它把变量分成了开关型、数值型、字符串型等数在组态环境里通过设备通道映射到PLC寄存器。做三菱Q系列PLC连接时要么使用MCGS自带的Q系列驱动要么走Modbus协议关键还是确认寄存器地址、数据类型和通信参数三要素一致。通信链路里还有一类隐蔽问题字节序。有些设备高字节在前有些低字节在前HMI和PLC如果不匹配读出来的16位数值可能就是“倒过来”的。排查时建议先用一个固定值做测试比如在PLC里写一个已知的16进制数0x1234看HMI读出来是不是4660如果不是就要检查字节序设置。7. 坐标滤波与数据稳定性处理即便做了校正触摸屏输出的坐标还是可能在某些场景下抖动。常见的抖动来源包括ADC采样噪声、电源纹波、手指或触摸笔接触面积变化、电容屏受到电磁干扰等。以电阻屏为例12位ADC在满量程4096范围内如果噪声导致几个LSB的跳动反映到屏幕上可能就是2到3个像素的抖动。对于“点击按钮”这种应用几个像素的抖动无所谓但对于画线、拖动滑块、模拟量设置这类需要持续跟踪坐标的应用抖动就会让数据写入变得很不稳定。解决办法是加软件滤波。工程上最常用的是滑动平均法// 文件路径touch_filter.c #define TOUCH_FILTER_N 8 static uint32_t filter_buf[TOUCH_FILTER_N]; static uint8_t filter_idx 0; static uint32_t filter_sum 0; uint32_t touch_filter(uint32_t raw_value) { filter_sum - filter_buf[filter_idx]; filter_sum raw_value; filter_buf[filter_idx] raw_value; filter_idx (filter_idx 1) % TOUCH_FILTER_N; return filter_sum / TOUCH_FILTER_N; }滑动平均能有效抑制高频噪声但代价是带来延迟。滤波窗口越大数据越平滑响应越迟钝。在工业HMI里如果滤波窗口太大用户会感觉“触摸跟不上手”反而影响操作体验。一般建议滤波窗口取4到8次再配合限幅滤波当两次采样差值超过一定阈值时直接丢弃本次值而不是把它混进平均值里这样既能抑制毛刺又不会明显增加延迟。另一种工程经验是“区域内判定”。比如检测用户是否按下了某个启动按钮不要只判断“坐标是否落在按钮矩形范围内”而是判断“连续N次坐标是否都落在范围内”只有达到数量阈值才认为是一次有效触摸。这种方法对付偶然的抖动点非常有效而且比单纯改滤波参数更稳健。还有一点容易被忽略触摸数据从触摸控制器到PLC中间可能经过多个环节每个环节都可能产生延迟和抖动。HMI上的数值输入控件在用户点击确认之前应该使用内部缓冲变量等用户编辑完成并确认后再一次性写入PLC。千万不要让PLC寄存器在每次触摸坐标变化时都被实时写入否则不仅数据抖动还会把PLC的通信负载打满。8. 常见问题与排查思路问题现象可能原因排查方式解决方案触摸点整体偏移未做触摸屏校正或校正参数被误删用系统校准工具重新校准或执行三点校准重新校正并把校准参数保存到非易失存储区同一位置读数反复跳ADC噪声、电源纹波、触摸控制器信号弱示波器观察电源纹波打印原始ADC值增加滤波算法改善电源检查接地边缘区域坐标“拉不住”触摸屏贴合工艺差或者分辨率配置不匹配检查触摸控制器的分辨率设置和坐标范围修正分辨率配置必要时换屏电容屏突然漂移温度变化、手指带水、静电积累重新上电观察是否恢复让触摸控制器重新做基线校准检查使用环境HMI数值输入后PLC值不变地址类型用了只读3x或寄存器地址映射错误检查HMI地址类型和PLC端变量表把输入控件绑定到4x或6x可写地址PLC读到负数或乱码数据类型长度不匹配字节序不一致用固定值0x1234做读回测试统一16位/32位/浮点格式调整字节序触摸屏通信时好时坏串口参数不一致、线路接触不良、网线质量问题查看HMI通信状态寄存器用串口助手抓包统一波特率、校验位更换通信线缆台达触摸屏程序下载失败驱动未安装、USB口占用、固件版本过低检查上位机驱动和设备管理器重装驱动换USB口升级固件触摸坐标准但响应慢滤波窗口过大或HMI刷新周期过长调整滤波参数观察坐标上报周期减少滤波长度缩短HMI采样周期排查时有一个原则先看原始数据再做软件调整。也就是说第一步永远是把触摸控制器读取出来的原始坐标打印出来判断问题出在物理层、驱动层还是通信层。直接从PLC侧改程序通常只能掩盖问题不能根治。9. 最佳实践与工程建议做触摸屏相关项目如果不想在后期被“找不到感觉”这类问题反复折磨建议从一开始就把下面几件事做到位。第一硬件设计时留出校准接口。无论是电阻屏还是电容屏设备出厂前都应当完成一次校准并把校准参数保存在EEPROM或Flash里。设备在现场使用一段时间后由于温差、老化坐标可能重新漂移因此校准功能不能只能出厂用还要留一个让现场工程师重新触发的入口。第二触摸控制器的分辨率和LCD分辨率必须严格匹配。GT911这类控制器一般通过寄存器配置分辨率配置错了后面的坐标变换全都白做。建议在上电初始化后读回配置寄存器确认并在系统日志里记录当前坐标范围。第三HMI与PLC通信地址规划要集中管理。把“触摸输入值”和“PLC运行值”分开命名不要直接在触摸屏控件里写PLC硬地址。比如在威纶通里先用LW内部寄存器接收用户输入用户确认后再通过宏指令或按钮将LW写入PLC的4x寄存器。这样即使后期要更换PLC型号也不需要改每个触摸控件的地址。第四数据类型和字节序在项目启动时就定好。PLC侧用16位有符号还是16位无符号浮点用IEEE754单精度还是双精度HMI侧和PLC侧必须形成书面约定。联调时第一件事用固定值测试每个寄存器地址确认数据通路没问题再开始写真正的业务逻辑。第五重视电源和接地。很多触摸屏“现场返修、回来就好”的诡异问题最后都指向电源纹波和静电。工业环境里开关电源输出端的纹波可能高达几十毫伏这足以让ADC产生明显的坐标抖动。触摸屏电源建议单独走线和电机、变频器等高干扰负载保持距离。第六留足调试手段。建议在HMI工程里保留一个“触摸调试画面”把原始坐标、校准后坐标、通信寄存器值全部显示出来。现场设备出问题时让操作员拍一张照片就能定位问题比
返回列表