解码Linux文件IO之触摸屏原理及应用

news/2025/10/21 17:36:08/文章来源:https://www.cnblogs.com/YouEmbedded/p/19156002

触摸屏基本概念

核心定义与作用

触摸屏是一种人机交互输入设备,通过检测手指(或触控笔)的按压、滑动等动作,将物理位置转换为数字坐标,实现 “点击屏幕操作界面” 的功能。常见应用场景:智能设备(手机、平板)、工业控制屏、车载中控、开发板人机界面(如 LCD 配套触摸屏)。

image

Linux 下的输入设备管理逻辑

Linux 系统中,输入设备(键盘、鼠标、触摸屏、摇杆等)种类繁多,为统一管理,设计了输入子系统(Input Subsystem)。核心思路:用 “中间层” 屏蔽不同硬件的细节,让应用程序无需关注硬件差异,只需通过统一接口读取输入事件(如触摸坐标、按键按下)。

image

Linux 输入子系统架构

输入子系统从下到上分为 3 层,每层职责明确,协同完成 “硬件事件→应用可识别数据” 的转换:

设备驱动层(最下层)

  • 核心职责:直接操作触摸屏硬件,完成两件事:
    • 读写硬件寄存器:初始化触摸屏、检测触摸动作(如手指按压时硬件产生中断);
    • 转换硬件数据:将硬件输出的原始坐标(如电压值)转换为标准 “输入事件”(如 X 轴坐标 = 512),并提交给上层核心层。
  • 示例:触摸屏驱动程序会监听硬件中断,当手指滑动时,实时读取触摸点的原始坐标,转换为input_event结构体格式。

核心层(中间层)

  • 核心职责:“承上启下” 的桥梁,提供标准化接口:
    • 对下:给设备驱动层提供 “提交事件” 的接口(如input_report_abs()用于上报绝对坐标);
    • 对上:给事件层提供 “接收事件” 的接口,同时规整驱动层提交的事件(确保格式统一)。
  • 作用:让驱动层无需关心 “如何将事件传给应用”,事件层无需关心 “硬件如何产生事件”,降低耦合。

事件层(最上层)

  • 核心职责:给应用程序提供统一的访问接口:
    • 生成设备文件:驱动加载后,在/dev/input/目录下生成设备文件(如event0);
    • 分发事件:将核心层传来的事件,通过设备文件分发给正在读取该文件的应用程序。
  • 应用视角:只需打开/dev/input/event0,读取文件中的input_event结构体,就能获取触摸屏的所有动作(按压、滑动、松开)。

核心结构体:input_event(事件封装)

所有输入设备的事件(触摸、按键、鼠标移动)都被统一封装为input_event结构体,定义在/usr/include/linux/input.h头文件中。应用程序读取到的触摸屏数据,本质就是一个个input_event结构体。

结构体定义与字段说明

#include <linux/input.h>
/*** @brief 输入事件结构体(所有输入设备的事件都用此格式封装)* @note 大小固定,不同设备仅填充的字段值不同
*/
struct input_event {// 字段1:事件发生的时间戳(由内核自动填充)#if (__BITS_PER_LONG != 32 || !defined(__USE_TIME_BITS64)) && !defined(_KERNEL_)struct timeval time;  // 32位系统/用户态:秒+微秒#define input_event_sec time.tv_sec    // 秒(自1970年1月1日起)#define input_event_usec time.tv_usec  // 微秒(0~999999)#else__kernel_ulong_t __sec;   // 64位系统:秒__kernel_ulong_t __usec;  // 64位系统:微秒#define input_event_sec __sec#define input_event_usec __usec#endif__u16 type;   // 字段2:事件类型(区分是触摸、按键还是鼠标移动)__u16 code;   // 字段3:事件代码(对类型的进一步细分,如X轴/Y轴坐标)__s32 value;  // 字段4:事件值(具体数据,如坐标值、按键状态)
};

关键字段详解(触摸屏核心)

type:事件类型(区分事件大类)

type 值(宏定义) 含义 触摸屏关联度
EV_SYN 事件同步标志 高(必用)
EV_ABS 绝对位移事件 高(核心)
EV_KEY 按键 / 触摸按压事件 高(核心)
EV_REL 相对位移事件(如鼠标) 低(无关联)
EV_LED LED 灯控制事件 低(无关联)
  • EV_SYN:用于分割连续事件(如手指滑动时会产生多个坐标事件,EV_SYN表示 “这一组事件已完整”);
  • EV_ABS:触摸屏坐标是 “绝对位置”(如 X 轴 0~1023),所以用此类型;
  • EV_KEY:触摸屏的 “按压 / 松开” 动作,相当于 “按键按下 / 弹起”。

code:事件代码(细分事件类型)

需结合type值使用,触摸屏常用组合:

type 值 code 值(宏定义) 含义
EV_ABS ABS_X X 轴坐标事件(横向位置)
EV_ABS ABS_Y Y 轴坐标事件(纵向位置)
EV_KEY BTN_TOUCH 触摸按压 / 松开事件(相当于 “触摸键”)

value:事件值(具体数据)

需结合typecode值判断,触摸屏常用组合:

type 值 code 值 value 值含义 示例
EV_ABS ABS_X X 轴坐标值(硬件支持的范围,如 0~1023) 512(X 轴中点)
EV_ABS ABS_Y Y 轴坐标值(硬件支持的范围,如 0~599) 300(Y 轴中点)
EV_KEY BTN_TOUCH 触摸状态:>0 = 按压,0 = 松开 1(手指按下)、0(松开)
EV_SYN SYN_REPORT 同步标志值(固定为 0) 0(表示事件组完整)

触摸屏事件序列示例(手指按压→滑动→松开)

当手指在触摸屏上操作时,内核会连续发送多个input_event,典型序列如下:

  • type=EV_KEY, code=BTN_TOUCH, value=1 → 手指按压;
  • type=EV_ABS, code=ABS_X, value=300 → X 轴坐标 = 300;
  • type=EV_ABS, code=ABS_Y, value=200 → Y 轴坐标 = 200;
  • type=EV_SYN, code=SYN_REPORT, value=0 → 这组事件(按压 + 坐标)完整;
  • type=EV_ABS, code=ABS_X, value=350 → 手指滑动,X 轴 = 350;
  • type=EV_ABS, code=ABS_Y, value=250 → 手指滑动,Y 轴 = 250;
  • type=EV_SYN, code=SYN_REPORT, value=0 → 滑动事件完整;
  • type=EV_KEY, code=BTN_TOUCH, value=0 → 手指松开;
  • type=EV_SYN, code=SYN_REPORT, value=0 → 松开事件完整。

触摸屏设备文件

设备文件路径与类型

触摸屏属于字符设备(按字节流读写事件),驱动加载后,内核会在/dev/input/目录下自动生成设备文件,命名格式为eventX(X 为 0~31,如event0event1)。

  • 常见路径:/dev/input/event0(开发板默认触摸屏设备文件,不同板卡可能不同,如event1);
  • 确认方法:通过cat /proc/bus/input/devices查看设备信息,找到 “Touchscreen” 对应的eventX

设备文件权限与访问

  • 权限:默认是crw-rw----(root 用户和 input 组可读写),普通用户需用sudo运行程序,或修改权限(如chmod 666 /dev/input/event0);
  • 访问方式:通过open打开设备文件,read读取input_event事件,close关闭文件(与操作普通文件一致)。

如何判断设备是否为触摸屏?

Linux 输入子系统中没有专门的 “触摸屏类型数字”,而是通过设备支持的事件组合来判断。触摸屏的核心特征是同时支持以下事件:

  • EV_KEY类型下的BTN_TOUCH(触摸按压 / 松开事件);
  • EV_ABS类型下的ABS_XABS_Y(X/Y 轴绝对坐标事件)。

代码示例:检测设备是否为触摸屏

#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <stdlib.h>
/*** @brief 判断设备是否为触摸屏* @param fd 设备文件描述符* @return 1=是触摸屏;0=不是;-1=错误*/
int is_touchscreen(int fd) {unsigned char key_bits[16] = {0};  // 存储EV_KEY类型支持的事件代码unsigned char abs_bits[16] = {0};  // 存储EV_ABS类型支持的事件代码// 检查是否支持EV_KEY事件类型,且包含BTN_TOUCH(触摸按压事件)/*** @param fd           设备文件描述符* @param EVIOCGBIT(EV_KEY, sizeof(key_bits)) 命令:获取EV_KEY类型支持的事件代码* @param key_bits     缓冲区:存储事件代码掩码(每个bit代表一种事件)* @return             成功返回0;失败返回-1*/if (ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key_bits)), key_bits) == -1) {perror("ioctl 获取EV_KEY事件失败");return -1;}// 判断是否支持BTN_TOUCH:检查key_bits中对应bit是否为1int has_btn_touch = (key_bits[BTN_TOUCH / 8] & (1 << (BTN_TOUCH % 8))) != 0;// 检查是否支持EV_ABS事件类型,且包含ABS_X和ABS_Y(坐标事件)/*** @param fd           设备文件描述符* @param EVIOCGBIT(EV_ABS, sizeof(abs_bits)) 命令:获取EV_ABS类型支持的事件代码* @param abs_bits     缓冲区:存储事件代码掩码* @return             成功返回0;失败返回-1*/if (ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_bits)), abs_bits) == -1) {perror("ioctl 获取EV_ABS事件失败");return -1;}// 判断是否支持ABS_X和ABS_Yint has_abs_x = (abs_bits[ABS_X / 8] & (1 << (ABS_X % 8))) != 0;int has_abs_y = (abs_bits[ABS_Y / 8] & (1 << (ABS_Y % 8))) != 0;// 同时满足:支持触摸按压+X/Y坐标 → 是触摸屏return (has_btn_touch && has_abs_x && has_abs_y) ? 1 : 0;
}int main(int argc, char const *argv[]) {if (argc != 2) {printf("用法:%s <设备文件>\n", argv[0]);printf("示例:%s /dev/input/event0\n", argv[0]);exit(1);}int fd = open(argv[1], O_RDONLY);if (fd == -1) {perror("open 设备失败");exit(1);}int result = is_touchscreen(fd);if (result == 1) {printf("%s 是触摸屏\n", argv[1]);} else if (result == 0) {printf("%s 不是触摸屏\n", argv[1]);}close(fd);return 0;
}

运行结果

# 检测触摸屏设备
sudo ./check_touch /dev/input/event0
/dev/input/event0 是触摸屏# 检测鼠标设备
sudo ./check_touch /dev/input/mouse0
/dev/input/mouse0 不是触摸屏

用 ioctl 读取触摸屏参数

触摸屏的硬件参数(如坐标范围、支持的事件类型)可通过ioctl系统调用动态获取,无需硬编码,适配不同设备更灵活。

常用 ioctl 命令

命令 命令缩写含义 作用 参数类型 用途
EVIOCGID EVIOC(EVent device I/O Control,事件设备输入输出控制) + G(Get,获取) + ID(Identifier,设备标识) 获取设备 ID(厂商、产品型号、版本等信息) struct input_id * 识别触摸屏具体型号,区分不同硬件
EVIOCGBIT EVIOC(事件设备输入输出控制) + G(Get,获取) + BIT(Bitmask,位掩码) 获取设备支持的事件类型 / 事件代码的位掩码 unsigned long, void *, size_t 检查是否支持EV_KEY(触摸按压)、EV_ABS(坐标)等核心事件
EVIOCGABS EVIOC(事件设备输入输出控制) + G(Get,获取) + ABS(Absolute,绝对坐标) 获取绝对坐标(X/Y 轴)的详细参数 int, struct input_absinfo * 读取 X/Y 轴的最小值、最大值、分辨率等,用于坐标转换

结构体:input_absinfo(坐标范围参数)

#include <linux/input.h>
/*** @brief 绝对坐标参数结构体(存储X/Y轴的范围、误差等信息)*/
struct input_absinfo {__s32 value;       // 当前实时坐标值__s32 minimum;     // 该轴最小值(如X轴0)__s32 maximum;     // 该轴最大值(如X轴1023)__s32 fuzz;        // 坐标误差允许范围(硬件波动值)__s32 flat;        // 死区范围(小于此值的变化被忽略)__s32 resolution;  // 分辨率(单位:坐标单位/毫米)
};

代码示例:读取触摸屏参数

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/input.h>
#include <sys/ioctl.h>
int main(int argc, char const *argv[]) {if (argc != 2) {printf("用法:%s <触摸屏设备文件>\n", argv[0]);printf("示例:%s /dev/input/event0\n", argv[0]);exit(1);}// 打开触摸屏设备文件int ts_fd = open(argv[1], O_RDONLY);if (ts_fd == -1) {perror("open 触摸屏失败");exit(1);}// 用EVIOCGBIT获取设备支持的事件类型unsigned char event_bits[16] = {0};  // 事件类型掩码(16字节足够覆盖所有类型)/*** @param ts_fd      触摸屏文件描述符* @param EVIOCGBIT(0, sizeof(event_bits)) 命令:获取所有事件类型掩码(0表示不限制类型)* @param event_bits 缓冲区:存储掩码(每个bit代表一种事件类型)* @return           成功返回0;失败返回-1*/if (ioctl(ts_fd, EVIOCGBIT(0, sizeof(event_bits)), event_bits) == -1) {perror("ioctl 获取事件类型失败");close(ts_fd);exit(1);}// 解析事件类型:检查是否支持触摸核心事件printf("支持的事件类型:\n");if (event_bits[EV_KEY / 8] & (1 << (EV_KEY % 8))) {printf("  - EV_KEY(触摸按压/松开事件)\n");}if (event_bits[EV_ABS / 8] & (1 << (EV_ABS % 8))) {printf("  - EV_ABS(绝对坐标事件)\n");}if (event_bits[EV_SYN / 8] & (1 << (EV_SYN % 8))) {printf("  - EV_SYN(事件同步标志)\n");}// 用EVIOCGABS获取X轴坐标范围struct input_absinfo x_abs;/*** @param ts_fd      触摸屏文件描述符* @param EVIOCGABS(ABS_X) 命令:获取X轴参数* @param &x_abs     结构体:存储X轴的min/max等参数* @return           成功返回0;失败返回-1*/if (ioctl(ts_fd, EVIOCGABS(ABS_X), &x_abs) == -1) {perror("ioctl 获取X轴参数失败");close(ts_fd);exit(1);}// 用EVIOCGABS获取Y轴坐标范围struct input_absinfo y_abs;if (ioctl(ts_fd, EVIOCGABS(ABS_Y), &y_abs) == -1) {perror("ioctl 获取Y轴参数失败");close(ts_fd);exit(1);}// 打印X/Y轴参数(坐标转换的关键依据)printf("X轴参数:\n");printf("  最小值:%d\n  最大值:%d\n  分辨率:%d(坐标单位/毫米)\n",x_abs.minimum, x_abs.maximum, x_abs.resolution);printf("Y轴参数:\n");printf("  最小值:%d\n  最大值:%d\n  分辨率:%d(坐标单位/毫米)\n",y_abs.minimum, y_abs.maximum, y_abs.resolution);// 关闭设备close(ts_fd);return 0;
}

运行结果

sudo ./ts_params /dev/input/event0
支持的事件类型:- EV_KEY(触摸按压/松开事件)- EV_ABS(绝对坐标事件)- EV_SYN(事件同步标志)
X轴参数:最小值:0最大值:1023分辨率:15(坐标单位/毫米)
Y轴参数:最小值:0最大值:599分辨率:15(坐标单位/毫米)

坐标转换原理(触摸屏→LCD)

为什么需要转换?

触摸屏和 LCD 的分辨率不同,直接使用触摸屏原始坐标会导致 “点击位置与显示位置不匹配”。

  • 示例:开发板触摸屏原始分辨率为1024×600(X:01023,Y:0599),LCD 分辨率为800×480(X:0799,Y:0479);若触摸触摸屏 X=1023(最右),对应 LCD 应显示 X=799(最右),而非 1023(超出 LCD 范围)。

转换公式(等比例缩放)

核心原则:触摸屏坐标与 LCD 坐标 “成比例”,公式如下:

  • LCD_X(LCD 上的 X 坐标)= 触摸屏原始 X × LCD 宽度 ÷ 触摸屏宽度;
  • LCD_Y(LCD 上的 Y 坐标)= 触摸屏原始 Y × LCD 高度 ÷ 触摸屏高度。

示例计算

  • 触摸屏原始坐标(X=512,Y=300),触摸屏分辨率 1024×600,LCD 分辨率 800×480;
  • LCD_X = 512 × 800 ÷ 1024 = 400;
  • LCD_Y = 300 × 480 ÷ 600 = 240;
  • 结果:触摸屏中点(512,300)对应 LCD 中点(400,240),点击位置匹配。

代码实现(坐标转换函数)

/*** @brief 触摸屏坐标→LCD坐标转换函数* @param ts_x      触摸屏原始X坐标(0~1023)* @param ts_y      触摸屏原始Y坐标(0~599)* @param lcd_x     输出参数:转换后的LCD X坐标(0~799)* @param lcd_y     输出参数:转换后的LCD Y坐标(0~479)* @note 触摸屏分辨率1024×600,LCD分辨率800×480,需根据实际硬件调整*/
void ts_to_lcd(int ts_x, int ts_y, int *lcd_x, int *lcd_y) {// 等比例缩放公式*lcd_x = ts_x * 800 / 1024;*lcd_y = ts_y * 480 / 600;// 边界保护:确保坐标不超出LCD范围(防止硬件误差导致坐标超界)if (*lcd_x < 0) *lcd_x = 0;if (*lcd_x >= 800) *lcd_x = 799;if (*lcd_y < 0) *lcd_y = 0;if (*lcd_y >= 480) *lcd_y = 479;
}

触摸屏实战程序设计(读取坐标)

核心功能

打开触摸屏设备文件,循环读取触摸事件,解析 X/Y 坐标,转换为 LCD 坐标并输出。

完整代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/input.h>
#include <unistd.h>
// 触摸屏与LCD分辨率(根据实际硬件调整)
#define TS_WIDTH  1024  // 触摸屏X轴范围:0~1023
#define TS_HEIGHT 600   // 触摸屏Y轴范围:0~599
#define LCD_WIDTH  800  // LCD X轴范围:0~799
#define LCD_HEIGHT 480  // LCD Y轴范围:0~479
/*** @brief 坐标转换函数(触摸屏→LCD)* @param ts_x  触摸屏原始X* @param ts_y  触摸屏原始Y* @param lcd_x 输出:LCD X* @param lcd_y 输出:LCD Y*/
void ts_to_lcd(int ts_x, int ts_y, int *lcd_x, int *lcd_y) {*lcd_x = ts_x * LCD_WIDTH / TS_WIDTH;*lcd_y = ts_y * LCD_HEIGHT / TS_HEIGHT;// 边界保护*lcd_x = (*lcd_x < 0) ? 0 : (*lcd_x >= LCD_WIDTH ? LCD_WIDTH-1 : *lcd_x);*lcd_y = (*lcd_y < 0) ? 0 : (*lcd_y >= LCD_HEIGHT ? LCD_HEIGHT-1 : *lcd_y);
}int main(int argc, char const *argv[]) {// 定义变量:存储设备文件描述符、输入事件、坐标int ts_fd;                     // 触摸屏设备文件描述符struct input_event ts_event;   // 输入事件结构体int ts_x = 0, ts_y = 0;        // 触摸屏原始坐标int lcd_x = 0, lcd_y = 0;      // 转换后的LCD坐标int cnt = 0;                   // 计数:累计读取到的坐标数量(X+Y=2个)// 打开触摸屏设备文件(读写模式)/*** @param "/dev/input/event0"  触摸屏设备文件路径(需根据实际板卡确认)* @param O_RDWR                打开模式:读写(也可O_RDONLY,只读事件)* @return                      成功返回文件描述符(非负);失败返回-1*/ts_fd = open("/dev/input/event0", O_RDWR);if (ts_fd == -1) {perror("open 触摸屏失败");  // 打印错误原因(如文件不存在、权限不足)exit(1);                  // 退出程序,状态码1表示异常}printf("触摸屏设备打开成功,fd=%d\n", ts_fd);// 循环读取触摸事件(阻塞式:无事件时会等待)while (1) {/*** @brief read函数:从设备文件读取输入事件* @param ts_fd        触摸屏文件描述符* @param &ts_event    存储事件的结构体地址* @param sizeof(ts_event) 读取的字节数(input_event固定大小)* @return            成功返回读取的字节数(=sizeof(ts_event));失败返回-1*/int ret = read(ts_fd, &ts_event, sizeof(ts_event));if (ret != sizeof(ts_event)) {perror("read 触摸事件失败");close(ts_fd);exit(1);}// 解析事件:区分事件类型,提取坐标// 若为绝对位移事件(EV_ABS),提取X/Y坐标if (ts_event.type == EV_ABS) {// 提取X轴坐标if (ts_event.code == ABS_X) {ts_x = ts_event.value;  // 存储原始X坐标cnt++;                  // 计数+1(已获取X)}// 提取Y轴坐标else if (ts_event.code == ABS_Y) {ts_y = ts_event.value;  // 存储原始Y坐标cnt++;                  // 计数+1(已获取Y)}}// 若为触摸松开事件(EV_KEY+BTN_TOUCH+value=0),重置计数else if (ts_event.type == EV_KEY && ts_event.code == BTN_TOUCH && ts_event.value == 0) {cnt = 0;  // 松开后,下一次按压重新计数}// 当同时获取到X和Y坐标(cnt>=2),转换并输出if (cnt >= 2) {ts_to_lcd(ts_x, ts_y, &lcd_x, &lcd_y);  // 坐标转换printf("触摸屏原始坐标:(%d, %d) → LCD坐标:(%d, %d)\n", ts_x, ts_y, lcd_x, lcd_y);cnt = 0;  // 重置计数,等待下一次触摸}}// 关闭设备文件(循环不会退出,此句仅为语法完整)close(ts_fd);return 0;
}

编译与运行

# 编译代码(假设文件名为ts_read.c)
gcc ts_read.c -o ts_read# 运行程序(需root权限,否则无权限访问/dev/input/event0)
sudo ./ts_read

运行结果(手指触摸屏幕时输出):

触摸屏设备打开成功,fd=3
触摸屏原始坐标:(512, 300) → LCD坐标:(400, 240)
触摸屏原始坐标:(1023, 599) → LCD坐标:(799, 479)
触摸屏原始坐标:(0, 0) → LCD坐标:(0, 0)

触摸屏实战程序设计(滑动检测)

核心功能

适配触摸屏分辨率1024×600,实时读取触摸「按下起点」和「松开终点」坐标;通过等比例转换适配 LCD 屏幕(默认 800×480,可修改);计算 X/Y 方向坐标差值,优先判断差值绝对值大的方向(横向 / 纵向),最终输出左滑 / 右滑 / 上滑 / 下滑结果,仅在完整滑动(按下→滑动→松开)后触发检测,避免无效数据。

完整代码

#include <stdio.h>          // 输入输出(printf)
#include <sys/types.h>      // 文件类型定义
#include <sys/stat.h>       // 文件状态定义
#include <fcntl.h>          // 文件操作(open)
#include <stdlib.h>         // 标准库(exit)
#include <linux/input.h>    // 输入事件结构体/宏(核心)
#include <unistd.h>         // 系统调用(read/close)
#include <math.h>           // 数学函数(abs,计算绝对值)// 硬件分辨率配置(重点:触摸屏1024×600,LCD可按需修改)
#define TS_WIDTH  1024      // 触摸屏原始X范围:0~1023(必须与硬件一致)
#define TS_HEIGHT 600       // 触摸屏原始Y范围:0~599(必须与硬件一致)
#define LCD_WIDTH  800      // LCD目标X范围:0~799(适配显示的分辨率)
#define LCD_HEIGHT 480      // LCD目标Y范围:0~479(适配显示的分辨率)/*** @brief 触摸屏坐标→LCD坐标转换(核心:等比例缩放,解决分辨率不匹配)* @param ts_x  输入:触摸屏原始X坐标* @param ts_y  输入:触摸屏原始Y坐标* @param lcd_x 输出:转换后LCD的X坐标* @param lcd_y 输出:转换后LCD的Y坐标*/
void ts_to_lcd(int ts_x, int ts_y, int *lcd_x, int *lcd_y) {// 等比例公式:LCD坐标 = 触摸屏坐标 × LCD分辨率 / 触摸屏分辨率*lcd_x = ts_x * LCD_WIDTH / TS_WIDTH;*lcd_y = ts_y * LCD_HEIGHT / TS_HEIGHT;// 边界保护:防止转换后坐标超出LCD范围(避免后续显示/判断异常)if (*lcd_x < 0)                  *lcd_x = 0;else if (*lcd_x >= LCD_WIDTH)    *lcd_x = LCD_WIDTH - 1;if (*lcd_y < 0)                  *lcd_y = 0;else if (*lcd_y >= LCD_HEIGHT)   *lcd_y = LCD_HEIGHT - 1;
}int main(int argc, char const *argv[]) {// 定义核心变量int ts_fd;                     // 触摸屏设备文件描述符(类似设备"ID")struct input_event ts_event;   // 存储读取到的输入事件(Linux标准结构体)// 滑动坐标:press=按下起点,release=松开终点(均为转换后的LCD坐标)int press_lcd_x = 0, press_lcd_y = 0;int release_lcd_x = 0, release_lcd_y = 0;// 临时变量:存储触摸屏原始坐标、标记是否已记录起点int ts_x = 0, ts_y = 0;int has_press = 0;             // 1=已记录按下起点,0=未记录(避免重复记录)// 打开触摸屏设备(只读模式,仅需读取触摸事件)/*** 设备路径说明:/dev/input/event0是常见路径,若失败需通过以下命令确认:* cat /proc/bus/input/devices → 查找"Touchscreen"对应的"Handlers=eventX"(X为数字)*/ts_fd = open("/dev/input/event0", O_RDONLY);if (ts_fd == -1) {             // 错误处理:打开失败(权限/路径问题)perror("open 触摸屏失败");  // 打印具体错误(如"Permission denied"或"No such file")exit(1);                   // 异常退出,状态码1表示运行失败}printf("✅ 触摸屏设备打开成功(文件描述符:%d)\n", ts_fd);printf("📌 触摸屏分辨率:1024×600 → LCD适配分辨率:%d×%d\n", LCD_WIDTH, LCD_HEIGHT);printf("提示:手指在屏幕滑动(左/右/上/下),按Ctrl+C退出\n");printf("-------------------------\n");// 循环读取触摸事件(阻塞式:无事件时等待,不占用CPU资源)while (1) {/*** read系统调用:从设备文件读取事件数据* 返回值:成功=读取的字节数(固定=sizeof(ts_event)),失败=-1*/int read_ret = read(ts_fd, &ts_event, sizeof(ts_event));if (read_ret != sizeof(ts_event)) {  // 错误处理:读取事件失败perror("read 触摸事件失败");close(ts_fd);                    // 先关闭设备,避免资源泄漏exit(1);}// 解析事件:分3类处理(坐标更新、触摸按下、触摸松开)// 情况1:事件类型=绝对位移事件(EV_ABS)→ 更新当前触摸坐标if (ts_event.type == EV_ABS) {if (ts_event.code == ABS_X) {    // 子类型=X轴坐标 → 记录原始Xts_x = ts_event.value;} else if (ts_event.code == ABS_Y) {  // 子类型=Y轴坐标 → 记录原始Yts_y = ts_event.value;}}// 情况2:事件类型=按键事件(EV_KEY)+ 触摸按下(value=1)→ 记录起点坐标else if (ts_event.type == EV_KEY && ts_event.code == BTN_TOUCH && ts_event.value == 1) {if (!has_press) {  // 仅在未记录起点时执行(避免重复触发)// 将触摸屏原始坐标转换为LCD坐标,作为滑动起点ts_to_lcd(ts_x, ts_y, &press_lcd_x, &press_lcd_y);has_press = 1;  // 标记:已记录起点,等待松开// (可选)调试用:打印起点坐标// printf("按下起点(LCD):(%d, %d)\n", press_lcd_x, press_lcd_y);}}// 情况3:事件类型=按键事件(EV_KEY)+ 触摸松开(value=0)→ 计算滑动方向else if (ts_event.type == EV_KEY && ts_event.code == BTN_TOUCH && ts_event.value == 0) {if (has_press) {  // 仅在已记录起点时执行(确保是完整滑动)// 将松开时的原始坐标转换为LCD坐标,作为滑动终点ts_to_lcd(ts_x, ts_y, &release_lcd_x, &release_lcd_y);// 计算滑动差值(终点 - 起点)→ 差值正负表示方向int diff_x = release_lcd_x - press_lcd_x;  // X方向差值(正=右,负=左)int diff_y = release_lcd_y - press_lcd_y;  // Y方向差值(正=下,负=上)// 输出坐标信息(原始+转换后,方便调试)printf("📌 按下起点(LCD):(%d, %d) | 松开终点(LCD):(%d, %d)\n",press_lcd_x, press_lcd_y, release_lcd_x, release_lcd_y);printf("📊 滑动差值:X=%d,Y=%d\n", diff_x, diff_y);// 判断滑动方向(优先横向/纵向,再判断具体方向)if (abs(diff_x) > abs(diff_y)) {  // 横向差值绝对值大 → 横向滑动if (diff_x > 0) {printf("✅ 滑动方向:右滑\n");} else {printf("✅ 滑动方向:左滑\n");}} else {  // 纵向差值绝对值大 → 纵向滑动if (diff_y > 0) {printf("✅ 滑动方向:下滑\n");} else {printf("✅ 滑动方向:上滑\n");}}printf("-------------------------\n");// 重置标记,等待下一次完整滑动has_press = 0;}}}// 关闭设备(注:while(1)无限循环,需按Ctrl+C终止,此句仅为语法完整)close(ts_fd);return 0;
}

编译与运行

编译命令

假设代码文件名为ts_slide_detect.c,使用 GCC 编译(嵌入式开发需替换为交叉编译器,如arm-linux-gcc):

# 编译:生成可执行文件ts_slide_detect(-lm链接数学库,用于abs函数)
gcc ts_slide_detect.c -o ts_slide_detect -lm

运行命令

触摸屏设备文件默认仅 root 用户可访问,需用sudo获取权限:

# 运行程序(按Ctrl+C终止)
sudo ./ts_slide_detect

预期运行结果

手指在屏幕滑动后,终端输出示例(具体数值随滑动位置变化):

✅ 触摸屏设备打开成功(文件描述符:3)
📌 触摸屏分辨率:1024×600 → LCD适配分辨率:800×480
提示:手指在屏幕滑动(左/右/上/下),按Ctrl+C退出
-------------------------
📌 按下起点(LCD):(100, 80) | 松开终点(LCD):(600, 80)
📊 滑动差值:X=500,Y=0
✅ 滑动方向:右滑
-------------------------
📌 按下起点(LCD):(500, 300) | 松开终点(LCD):(500, 100)
📊 滑动差值:X=0,Y=-200
✅ 滑动方向:上滑
-------------------------

综合练习:触摸屏控制界面(开机→登录→主界面)

需求分析

  • 流程:开机动画(显示 3 秒)→ 登录界面(含 “登录”“退出” 按钮)→ 主界面(含 “返回登录” 按钮);
  • 交互:触摸 “登录” 进入主界面,触摸 “退出” 关闭程序,触摸 “返回登录” 回到登录界面;
  • 限制:不使用goto语句(用 “状态机” 实现界面切换)。

核心设计:状态机

用枚举类型定义界面状态,主循环根据当前状态执行对应逻辑,触摸事件触发状态切换:

// 定义界面状态
typedef enum {STATE_BOOT,    // 状态1:开机动画STATE_LOGIN,   // 状态2:登录界面STATE_MAIN,    // 状态3:主界面STATE_EXIT     // 状态4:退出程序
} UI_STATE;

完整代码(含 LCD 显示与触摸控制)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <linux/input.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
// 硬件分辨率定义
#define TS_WIDTH  1024
#define TS_HEIGHT 600
#define LCD_WIDTH  800
#define LCD_HEIGHT 480
// 颜色定义(32位ARGB,A=0x00不透明)
#define COLOR_BLACK  0x00000000  // 黑色
#define COLOR_WHITE  0x00FFFFFF  // 白色
#define COLOR_RED    0x00FF0000  // 红色
#define COLOR_BLUE   0x000000FF  // 蓝色
#define COLOR_GREEN  0x0000FF00  // 绿色
// 界面状态枚举
typedef enum {STATE_BOOT,STATE_LOGIN,STATE_MAIN,STATE_EXIT
} UI_STATE;// 全局变量(LCD内存映射地址、触摸屏文件描述符)
unsigned int *lcd_mem = NULL;
int ts_fd = -1;/*** @brief 坐标转换(触摸屏→LCD)*/
void ts_to_lcd(int ts_x, int ts_y, int *lcd_x, int *lcd_y) {*lcd_x = ts_x * LCD_WIDTH / TS_WIDTH;*lcd_y = ts_y * LCD_HEIGHT / TS_HEIGHT;*lcd_x = (*lcd_x < 0) ? 0 : (*lcd_x >= LCD_WIDTH ? LCD_WIDTH-1 : *lcd_x);*lcd_y = (*lcd_y < 0) ? 0 : (*lcd_y >= LCD_HEIGHT ? LCD_HEIGHT-1 : *lcd_y);
}/*** @brief LCD初始化(打开设备、内存映射)* @return 成功返回0;失败返回-1*/
int lcd_init(void) {int lcd_fd = open("/dev/fb0", O_RDWR);if (lcd_fd == -1) { perror("open LCD失败"); return -1; }struct fb_var_screeninfo lcd_var;if (ioctl(lcd_fd, FBIOGET_VSCREENINFO, &lcd_var) == -1) {perror("ioctl 获取LCD参数失败"); close(lcd_fd); return -1;}size_t lcd_map_len = lcd_var.xres * lcd_var.yres * (lcd_var.bits_per_pixel / 8);lcd_mem = (unsigned int *)mmap(NULL, lcd_map_len, PROT_READ | PROT_WRITE, MAP_SHARED, lcd_fd, 0);if (lcd_mem == MAP_FAILED) {perror("mmap LCD失败"); close(lcd_fd); return -1;}close(lcd_fd);  // 映射后可关闭文件描述符return 0;
}/*** @brief 在LCD指定位置画矩形(填充颜色)* @param x1,y1  矩形左上角坐标* @param x2,y2  矩形右下角坐标* @param color  填充颜色(32位ARGB)*/
void lcd_draw_rect(int x1, int y1, int x2, int y2, unsigned int color) {for (int y = y1; y <= y2; y++) {for (int x = x1; x <= x2; x++) {if (x >= 0 && x < LCD_WIDTH && y >= 0 && y < LCD_HEIGHT) {lcd_mem[y * LCD_WIDTH + x] = color;}}}
}/*** @brief 开机动画(显示3秒,黑色背景+白色文字提示)*/
void boot_animation(void) {// 清屏(黑色背景)lcd_draw_rect(0, 0, LCD_WIDTH-1, LCD_HEIGHT-1, COLOR_BLACK);// 显示文字提示(简化:用矩形模拟“开机中...”,实际项目用字体库)lcd_draw_rect(300, 200, 500, 240, COLOR_WHITE);  // 文字背景lcd_draw_rect(310, 210, 490, 230, COLOR_BLACK);  // 文字区域printf("开机动画中...\n");sleep(3);  // 显示3秒后进入登录界面
}/*** @brief 读取触摸坐标(阻塞,直到触摸按压)* @param lcd_x 输出:LCD X坐标* @param lcd_y 输出:LCD Y坐标*/
void ts_read_press(int *lcd_x, int *lcd_y) {struct input_event ts_event;int ts_x = 0, ts_y = 0;int cnt = 0;// 等待触摸按压(EV_KEY+BTN_TOUCH+value>0)while (1) {read(ts_fd, &ts_event, sizeof(ts_event));if (ts_event.type == EV_KEY && ts_event.code == BTN_TOUCH && ts_event.value > 0) {break;  // 检测到按压,退出等待}}// 读取按压时的坐标while (cnt < 2) {read(ts_fd, &ts_event, sizeof(ts_event));if (ts_event.type == EV_ABS) {if (ts_event.code == ABS_X) { ts_x = ts_event.value; cnt++; }else if (ts_event.code == ABS_Y) { ts_y = ts_event.value; cnt++; }}}ts_to_lcd(ts_x, ts_y, lcd_x, lcd_y);  // 转换为LCD坐标
}/*** @brief 登录界面(显示“登录”“退出”按钮,处理触摸)* @return 下一个界面状态(STATE_MAIN/STATE_EXIT)*/
UI_STATE login_ui(void) {// 绘制登录界面lcd_draw_rect(0, 0, LCD_WIDTH-1, LCD_HEIGHT-1, COLOR_WHITE);  // 白色背景// 绘制“登录”按钮(蓝色,300,200 → 500,260)lcd_draw_rect(300, 200, 500, 260, COLOR_BLUE);// 绘制“退出”按钮(红色,300,300 → 500,360)lcd_draw_rect(300, 300, 500, 360, COLOR_RED);// 提示信息printf("登录界面:触摸蓝色按钮登录,红色按钮退出\n");// 读取触摸坐标,判断点击区域int lcd_x, lcd_y;ts_read_press(&lcd_x, &lcd_y);  // 等待触摸按压printf("触摸坐标:(%d, %d)\n", lcd_x, lcd_y);// 判断是否点击“登录”按钮(300≤x≤500,200≤y≤260)if (lcd_x >= 300 && lcd_x <= 500 && lcd_y >= 200 && lcd_y <= 260) {printf("点击登录,进入主界面\n");return STATE_MAIN;}// 判断是否点击“退出”按钮(300≤x≤500,300≤y≤360)else if (lcd_x >= 300 && lcd_x <= 500 && lcd_y >= 300 && lcd_y <= 360) {printf("点击退出,程序关闭\n");return STATE_EXIT;}// 点击其他区域,重新显示登录界面else {printf("点击区域无效,请重新点击\n");return STATE_LOGIN;}
}/*** @brief 主界面(显示“返回登录”按钮,处理触摸)* @return 下一个界面状态(STATE_LOGIN)*/
UI_STATE main_ui(void) {// 绘制主界面lcd_draw_rect(0, 0, LCD_WIDTH-1, LCD_HEIGHT-1, COLOR_GREEN);  // 绿色背景// 绘制“返回登录”按钮(白色,300,400 → 500,460)lcd_draw_rect(300, 400, 500, 460, COLOR_WHITE);// 提示信息printf("主界面:触摸白色按钮返回登录界面\n");// 读取触摸坐标,判断点击区域int lcd_x, lcd_y;ts_read_press(&lcd_x, &lcd_y);  // 等待触摸按压printf("触摸坐标:(%d, %d)\n", lcd_x, lcd_y);// 判断是否点击“返回登录”按钮(300≤x≤500,400≤y≤460)if (lcd_x >= 300 && lcd_x <= 500 && lcd_y >= 400 && lcd_y <= 460) {printf("点击返回,回到登录界面\n");return STATE_LOGIN;}// 点击其他区域,保持主界面else {printf("点击区域无效,请重新点击\n");return STATE_MAIN;}
}int main(int argc, char const *argv[]) {// 初始化LCD和触摸屏if (lcd_init() != 0) { exit(1); }ts_fd = open("/dev/input/event0", O_RDONLY);if (ts_fd == -1) { perror("open 触摸屏失败"); exit(1); }// 状态机主循环(根据当前状态执行对应逻辑)UI_STATE current_state = STATE_BOOT;  // 初始状态:开机动画while (current_state != STATE_EXIT) {switch (current_state) {case STATE_BOOT:boot_animation();current_state = STATE_LOGIN;  // 动画结束→登录界面break;case STATE_LOGIN:current_state = login_ui();  // 登录界面返回下一个状态break;case STATE_MAIN:current_state = main_ui();   // 主界面返回下一个状态break;default:current_state = STATE_EXIT;  // 异常状态→退出break;}}// 释放资源munmap(lcd_mem, LCD_WIDTH * LCD_HEIGHT * 4);  // 解除LCD映射close(ts_fd);printf("程序退出\n");return 0;
}

注意事项

  • 设备文件路径确认:不同开发板的触摸屏设备文件可能不是event0,可通过cat /proc/bus/input/devices查看,找到 “Touchscreen” 对应的eventX
  • 权限问题/dev/input/event0默认只有 root 用户可读写,普通用户需用sudo运行程序,或修改权限(sudo chmod 666 /dev/input/event0);
  • 坐标边界保护:触摸屏硬件可能存在误差,导致原始坐标超出0~1023范围,转换后需判断 LCD 坐标是否在0~7990~479内,避免数组越界;
  • 触摸事件同步:手指滑动时会产生多个input_event,需通过EV_SYN判断事件组是否完整(简化程序可忽略,但精准处理需关注);
  • 界面绘制效率:示例中用双重循环画矩形效率较低,实际项目可使用 “显存批量写入” 或硬件加速,避免界面卡顿。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/942502.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2025年最新游戏机和游艺机的屏幕驱动方案(含音乐播放和功放芯片)

当扫码支付普及率在一线城市突破85%,当消费者已习惯"无现金"生活,游艺机行业也走向数字化转型。深耕语音芯片领域二十六年的唯创知音电子有限公司,正是看准了这一机遇。推出了WT2606B TFT显示驱动方案。1…

2025 年最新推荐!国内加工厂家排行榜:含车铣复合 / 精密零件 / CNC 车床等领域优质企业

引言 当前国内加工行业发展迅猛,细分领域不断拓展,从车铣复合数控车床加工到尼龙塑胶精密零件制造,从非标丝杆生产到高精度走心机加工,市场需求持续攀升。但与此同时,行业内品牌数量激增,既有深耕多年的资深企业…

2025年精密球轴承厂家权威推荐榜:半导体设备/加工中心/机床主轴/直联主轴/电主轴/定制/国产高端/不锈钢/陶瓷/耐腐蚀/超高真空/真空泵/晶圆搬运机械手臂/进口替代/国产半导体/低温泵轴承精选

2025年精密球轴承厂家权威推荐榜:半导体设备/加工中心/机床主轴/直联主轴/电主轴/定制/国产高端/不锈钢/陶瓷/耐腐蚀/超高真空/真空泵/晶圆搬运机械手臂/进口替代/国产半导体/低温泵轴承精选 行业背景与发展趋势 精密…

【安徽财经大学主办】第七届管理科学信息化与经济创新发展国际学术会议 (MSIEID 2025)

第七届管理科学信息化与经济创新发展国际学术会议 2025 7th Management Science Informatization and Economic Innovation Development Conference (MSIEID 2025) 高届数 年度重磅会议 安徽财经大学主办、上海电力大学…

2025 盐城美术培训机构最新推荐榜单:涵盖全龄段课程 + 4A 信用单位,优质机构助你精准选课

引言 随着盐城美术培训需求持续增长,市场上机构数量激增但质量良莠不齐,给学员及家长选课带来极大困扰。部分机构缺乏专业师资,教学效果难以保障;有的课程体系单一,无法满足高考、中考、成人兴趣等不同场景需求;…

2025年冷水机组厂家权威推荐榜:水冷螺杆/风冷螺杆/水冷式/风冷式/螺杆式冷水机专业选购指南

2025年冷水机组厂家权威推荐榜:水冷螺杆/风冷螺杆/水冷式/风冷式/螺杆式冷水机专业选购指南 在工业制冷领域,冷水机组作为关键温控设备,其性能表现直接影响生产效率和能源消耗。随着2025年节能减排政策的深入推进,…

再看 AI 网关:助力 AI 应用创新的关键基础设施

本文将从 AI 网关的诞生、AI 网关的产品能力、AI 网关的开放生态,以及新推出的 Serverless 版,对其进行一个全面的介绍,期望对正在进行 AI 应用落地的朋友,在 AI 基础设施选型方面提供一些参考。作者:子丑 AI 网关…

2025 年独立游戏公司开发 AI 美术平台最新推荐榜单:覆盖全流程创作需求,助力团队突破美术瓶颈

引言 当前独立游戏行业蓬勃发展,但美术资源设计与生产的短板却愈发凸显,成为制约团队发展的关键因素。多数独立团队面临资金有限、专业美术人员匮乏的困境,难以完成从概念设计到最终资产的全流程创作,且传统美术流…

通过HTML演示JVM的垃圾回收-新生代与老年代 - 详解

通过HTML演示JVM的垃圾回收-新生代与老年代 - 详解pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas"…

先收藏系列 工业相机的六问六答!

​ 工业相机是机器视觉系统的核心组成部分,通过光电转换原理将光信号转为电信号,关键技术特性包含高分辨率、高帧率、全局快门等,为计算机提供高质量、高可靠性的图像数据,以便进行自动化的检测、测量、识别和引导…

凌晨 2 点的朋友圈,她靠微擎实现了 “带娃赚钱两不误”

“以前总觉得做电商要懂代码、要囤货,直到遇见微擎,才发现普通人也能轻松当老板。” 家住济南的宝妈李姐,在朋友圈晒出当月营收截图时,底下满是熟人的惊叹。 去年刚休完产假的她,想找份能兼顾孩子的工作,尝试过几…

git pull中有 merge功能解释

git commit、git push、git pull、 git fetch、git merge 的含义与区别git commit:是将本地修改过的文件提交到本地库中; git push:是将本地库中的最新信息发送给远程库; git pull:是从远程获取最新版本到本地,并…

2025年信息流代运营服务商权威推荐榜:专业投放策略与高转化效果深度解析,助力品牌精准营销

2025年信息流代运营服务商权威推荐榜:专业投放策略与高转化效果深度解析,助力品牌精准营销 信息流代运营行业发展趋势与价值分析 随着数字营销进入精细化运营时代,信息流广告已成为企业获取流量、实现转化的重要渠道…

用AI帮忙,开发刷题小程序:微信小程序在线答题框架架构解析

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025 国内西服定制品牌精选榜:婚礼/高级/高端/高档/男士/女士/轻奢西装定制厂家,从智能智造到匠心传承的多元之选

在消费升级与技术迭代的双重浪潮下,西服定制这一承载着传统工艺的行业正焕发新的生机。从面料的精挑细选到版型的个性化打造,从手工缝制的温度到智能生产的效率,西服定制已从单纯的服饰制作升级为融合工艺、科技与服…

2025年工厂维保服务厂家权威推荐榜:机电维修、应急维修、设备安装维修、运维服务全方位解决方案精选

2025年工厂维保服务厂家权威推荐榜:机电维修、应急维修、设备安装维修、运维服务全方位解决方案精选 随着制造业数字化转型的深入推进,工厂维保服务已经从传统的被动维修向主动预防、智能运维转变。在工业4.0和智能制…

腾讯TCCL和阿里ACCL对比

腾讯的 TCCL(Tencent Collective Communication Library)和阿里云的 ACCL(Alibaba Collective Communication Library)都是各自云厂商为应对大规模 AI 模型训练场景,基于或参考 NVIDIA NCCL 构建的高性能集合通信…

BOS中多选基础资料字段设置默认值

F_VTR_OrgIds = [ @CURRENTORGID ] //[ 1,2,3 ]

垃圾回收器总览

垃圾回收器总览🔴 垃圾回收器概述 #JVM/垃圾回收器 🔴 垃圾回收器是JVM中负责自动内存管理的核心组件,通过不同的算法和策略实现堆内存的自动回收,确保Java程序的内存安全。不同的垃圾回收器适用于不同的应用场景…

软件工程第三次作业——结对项目

这个作业属于哪个课程 计科23级12班这个作业要求在哪里 作业要求这个作业的目标 实现一个自动生成小学四则运算题目的命令行程序,并能检验题目答案正确性一、小组&项目信息:姓名 学号欧俊希 3123002980梁展榕 31…