title: 输入设备与tslib tags: [嵌入式Linux, Linux应用编程, input子系统, 输入设备, 触摸屏, tslib, IMX6ULL] created: 2026-09-18 updated: 2026-09-18
💡 关联知识:[[03-外设与高级IO编程/02-GPIO与LED应用编程]]、[[03-外设与高级IO编程/04-FrameBuffer与LCD应用编程]]、[[03-外设与高级IO编程/06-摄像头串口与音频]];延伸阅读:[[嵌入式Linux驱动开发实战/03-Linux驱动开发核心/08-misc与input子系统]]、[[嵌入式Linux驱动开发实战/05-Linux外设驱动实战/04-触摸屏驱动]]
输入设备(鼠标、键盘、触摸屏、按键……)把用户动作变成数据交给系统。Linux 用 input 子系统统一了它们的接口:应用层只要 open 一个 /dev/input/eventX,再 read 出 struct input_event,就得到原始输入数据。本篇先讲清 input 子系统的数据格式与解析方法(按键、单点触摸、多点触摸),再引入 tslib —— 把"读原始 event + 去噪 + 坐标变换"封装好的触摸屏应用层函数库。
输入设备(input 设备)指能够产生输入事件的设备:鼠标、键盘、触摸屏、遥控器、画图板等。设备种类繁多、上报数据类型各异,Linux 为统一管理实现了 input 子系统:
驱动人员基于它开发驱动,它屏蔽硬件差异、向应用层提供统一接口;注册成功的设备在 /dev/input 下生成 eventX 节点,应用层读取节点即可获取数据。
flowchart TB
subgraph 用户态
APP["应用程序 open/read /dev/input/eventX"]
end
subgraph 内核态
CORE["input 子系统核心"]
DRV1["按键驱动"]
DRV2["触摸屏驱动"]
DRV3["USB 键盘/鼠标驱动"]
end
HW["硬件 GPIO 按键 / 触摸 IC / USB 设备"]
APP -->|"read struct input_event"| CORE
CORE <--> DRV1
CORE <--> DRV2
CORE <--> DRV3
DRV1 --> HW
DRV2 --> HW
DRV3 --> HW
读取流程(以触摸屏 /dev/input/event0 为例):
read,无数据可读时在阻塞 I/O 下休眠;3. 有数据时被唤醒、读操作返回;4. 应用解析数据。Tips:设备文件不同于普通文件,读写设备文件之前无需设置读写位置偏移量。
应用层每次 read 得到一个 struct input_event,定义在 <linux/input.h>:
struct input_event {
struct timeval time;
__u16 type;
__u16 code;
__s32 value;
};
time 是事件发生时间(通常不是重点),重点是其馀三个成员:
| 成员 | 含义 |
|---|---|
type |
事件类型(哪一大类事件) |
code |
事件代码(该类中的哪一个具体事件) |
value |
事件值,解释随 code 变化 |
事件类型宏(<linux/input.h>): |
#define EV_SYN 0x00 //同步类事件
#define EV_KEY 0x01 //按键类事件
#define EV_REL 0x02 //相对位移类事件(譬如鼠标)
#define EV_ABS 0x03 //绝对位移类事件(譬如触摸屏)
#define EV_MSC 0x04 //其它杂类事件
/* 还有 EV_SW(0x05)、EV_LED(0x11)、EV_SND(0x12)、EV_REP(0x14) 等 */
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
常见 code:
/* 按键类 */
#define KEY_1 2 //数字 1 键
#define KEY_A 30 //字母 A 键
#define KEY_VOLUMEDOWN 114 //出厂系统 KEY0 使用的键值
/* 相对位移类 */
#define REL_X 0x00 //X 轴
#define REL_Y 0x01 //Y 轴
/* 绝对位移类(触摸屏) */
#define ABS_X 0x00 //X 轴
#define ABS_Y 0x01 //Y 轴
#define ABS_MT_SLOT 0x2f //当前更新的触摸点 slot
#define ABS_MT_POSITION_X 0x35 //触摸点 X 坐标
#define ABS_MT_POSITION_Y 0x36 //触摸点 Y 坐标
#define ABS_MT_TRACKING_ID 0x39 //触摸点 ID
#define BTN_TOUCH 330 //触摸按下/松开
value 的解释随 code 变化:按键事件中 value=1 按下、0 松开、2 长按;绝对位移事件中 code=ABS_X 时 value 就是 X 坐标,code=ABS_Y 时就是 Y 坐标。这些宏定义在 input-event-codes.h,被 <linux/input.h> 包含。
一次 read 只能读一个 event,而一个触摸点含 X、Y 等多项,需多次 read 才读全。内核把本轮数据全部上报后,会再上报一个同步事件告知"本轮数据已完整":
#define SYN_REPORT 0 // 本轮数据完整
#define SYN_DROPPED 3 // 数据丢失
所有输入设备都需上报同步事件,通常是 SYN_REPORT、value 通常为 0。
例程 read_input.c 是最基础的读取框架:传参为设备节点路径,用 open(argv[1], O_RDONLY) 只读打开,在死循环中每次 read 恰好读出一个 event(阻塞 I/O,无数据时在此休眠),再把 in_ev.type、in_ev.code、in_ev.value 打印出来供手工分析。完整程序含 #include、传参校验与错误处理,见例程 17_input/read_input.c。
arm-linux-gnueabihf-gcc -o testApp read_input.c
scp testApp root@192.168.1.10:/home/root/
ALPHA/Mini 都有一个用户按键 KEY0,出厂系统中其驱动基于 input 子系统。用 cat /proc/bus/input/devices 确定设备节点后运行程序,示例中 KEY0 对应 /dev/input/event2。按下、松开 KEY0 的输出为:
type:1 code:114 value:1 // EV_KEY, code=114=KEY_VOLUMEDOWN, value=1 按下
type:0 code:0 value:0 // EV_SYN/SYN_REPORT,本轮数据完整
type:1 code:114 value:0 // 松开
type:0 code:0 value:0 // 同步
长按(按住不放)时 value=2。
按键上报流程为 KEY_A → SYN_REPORT,value 1/0/2 分别表示按下/松开/长按:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>
int main(int argc, char *argv[])
{
struct input_event in_ev = {0};
int fd = -1;
int value = -1;
/* 校验传参 */
if (2 != argc) {
fprintf(stderr, "usage: %s <input-dev>\n", argv[0]);
exit(-1);
}
/* 打开文件 */
if (0 > (fd = open(argv[1], O_RDONLY))) {
perror("open error");
exit(-1);
}
for ( ; ; ) {
/* 循环读取数据 */
if (sizeof(struct input_event) !=
read(fd, &in_ev, sizeof(struct input_event))) {
perror("read error");
exit(-1);
}
if (EV_KEY == in_ev.type) { //按键事件
switch (in_ev.value) {
case 0:
printf("code<%d>: 松开\n", in_ev.code);
break;
case 1:
printf("code<%d>: 按下\n", in_ev.code);
break;
case 2:
printf("code<%d>: 长按\n", in_ev.code);
break;
}
}
}
}
拿到数据后先判断 EV_KEY,再按 value 区分松开/按下/长按。
arm-linux-gnueabihf-gcc -o testApp read_key.c
scp testApp root@192.168.1.10:/home/root/
./testApp /dev/input/event2 # 测试 KEY0
把 USB 键盘插到开发板 USB HOST 口,也可按同样方法在 /proc/bus/input/devices 找到 eventX 测试;根据 code 查 input-event-codes.h 即知按键,如 code=30 对应 A 键、code=48 对应 B 键。
读取多个输入设备:一个进程一次只能阻塞读取一个设备节点。若要同时读取多个设备(如按键 + 触摸屏),可用 poll()/select() 同时监听多个 fd(见 [[03-外设与高级IO编程/01-高级IO]]),或为每个设备各开一个线程/进程;input_event.time 时间戳可用于还原不同设备事件的先后顺序。
触摸屏是绝对位移设备,上报 EV_ABS:
| 类型 | 特点 | 承载事件 |
|---|---|---|
| 单点触摸 | 一轮完整数据只含一个触摸点 | ABS_X、ABS_Y 等 ABS_XXX |
| 多点触摸 | 一轮完整数据可含多个触摸点 | ABS_MT_POSITION_X/Y 等 ABS_MT_XXX |
除位移事件外还会上报:
BTN_TOUCH(code=0x14a,即 330):点击触摸屏或手指离开时上报,滑动时不上报。它不支持长按,value 不会等于 2;对多点设备,只有第一个点按下时 value=1、最后一个点离开时 value=0。单点设备上报顺序:
# 点击 # 滑动 # 松开
BTN_TOUCH ABS_X BTN_TOUCH
ABS_X ABS_Y SYN_REPORT
ABS_Y SYN_REPORT
SYN_REPORT
不同设备信息量不同(有的仅 X/Y,有的带按压力、接触面积),这些数据都会在 SYN_REPORT 之前上报。
Linux 用多点触摸(MT)协议上报各触摸点数据,分 Type A 与 Type B。Type A 使用很少、几乎淘汰,开发板配套触摸屏都属于 Type B。
Type B 适用于能追踪并区分触摸点的设备,重点是用 ABS_MT_SLOT 上报各触摸点信息的更新:
ABS_MT_SLOT 的 value 告知当前正在更新哪个 slot;ABS_MT_TRACKING_ID 上报,用于触摸点的创建、替换和销毁——>=0 为有效触摸点,-1 表示被移除,以前不存在的 ID 表示新触摸点;Type B 只上报发生变更的数据:若只改变 X 坐标,内核只发新的 ABS_MT_POSITION_X。
ABS_MT_SLOT 0
ABS_MT_TRACKING_ID 10
ABS_MT_POSITION_X
ABS_MT_POSITION_Y
ABS_MT_SLOT 1
ABS_MT_TRACKING_ID 11
ABS_MT_POSITION_X
ABS_MT_POSITION_Y
SYN_REPORT
| 概念 | 层级 | 含义 |
|---|---|---|
| slot | 硬件 | 触摸点编号,按触碰先后分配 |
| ID | 软件 | 触摸点生命周期的标识;手离开后销毁,再触碰即为新 ID |
同一手指触碰、离开、再触碰,两次都是 slot=0,但生命周期不同,因此 ID 不同。
一个手指点击触摸屏不松开,read_input.c 打印:
要点:ABS_MT_TRACKING_ID 非负表示新建触摸点(按下);多点设备也通过 ABS_X/ABS_Y 上报坐标,但通常只有触摸点 0 支持,故可当单点用;有的屏幕因触摸芯片不同没有 EV_KEY,也就没有 BTN_TOUCH。增加第二个触摸点时会先上报 ABS_MT_SLOT(code=47)value=1;手指松开时上报 ABS_MT_TRACKING_ID 且 value=-1。
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
fd 文件描述符;request 请求指令;可变参数随 request 决定。input 设备的请求指令宏(input.h):
#define EVIOCGNAME(len) _IOC(_IOC_READ, 'E', 0x06, len) /* get device name */
#define EVIOCGKEY(len) _IOC(_IOC_READ, 'E', 0x18, len) /* get global key state */
#define EVIOCGABS(abs) _IOR('E', 0x40 + (abs), struct input_absinfo) /* get abs value/limits */
EVIOCG(get)获取信息,EVIOCS(set)设置。重点 EVIOCGABS(abs) 可获取某 ABS_XXX 事件的取值范围,第三个参数为 struct input_absinfo *:
struct input_absinfo {
__s32 value; //最新的报告值
__s32 minimum; //最小值
__s32 maximum; //最大值
__s32 fuzz;
__s32 flat;
__s32 resolution;
};
例程 17_input/read_slot.c 打开设备后执行:
struct input_absinfo info;
if (0 > ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &info))
perror("ioctl error");
max_slots = info.maximum + 1 - info.minimum;
printf("max_slots: %d\n", max_slots);
结果为 max_slots: 5 时说明是 5 点触摸屏,该值正是下面多点程序申请 struct ts_mt 数组的依据。
把多点触摸屏当单点用:用 x/y 保存坐标,down 记录状态(1 按下、0 松开、-1 滑动),valid 标记本轮关注信息是否更新。解析逻辑为:EV_ABS 事件中,ABS_MT_TRACKING_ID 的 value 判断按下(0)/松开(-1),ABS_MT_POSITION_X/Y 更新坐标并置 valid;收到 SYN_REPORT 时数据完整,按 down 打印"按下/松开/移动"并重置标志。完整程序见例程 17_input/read_ts.c。
arm-linux-gnueabihf-gcc -o testApp read_ts.c
scp testApp root@192.168.1.10:/home/root/
./testApp /dev/input/event1
按 slot 分别维护每个触摸点的坐标与 ID:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <linux/input.h>
/* 用于描述MT多点触摸每一个触摸点的信息 */
struct ts_mt {
int x; //X坐标
int y; //Y坐标
int id; //对应ABS_MT_TRACKING_ID
int valid; //数据有效标志位(=1表示触摸点信息发生更新)
};
/* 一个触摸点的x坐标和y坐标 */
struct tp_xy {
int x;
int y;
};
static int ts_read(const int fd, const int max_slots,
struct ts_mt *mt)
{
struct input_event in_ev;
static int slot = 0;//用于保存上一个slot
static struct tp_xy xy[12] = {0};//用于保存上一次的x和y坐标值,假设触摸屏支持的最大触摸点数不会超过12
int i;
/* 对缓冲区初始化操作 */
memset(mt, 0x0, max_slots * sizeof(struct ts_mt)); //清零
for (i = 0; i < max_slots; i++)
mt[i].id = -2;//将id初始化为-2, id=-1表示触摸点删除, id>=0表示创建
for ( ; ; ) {
if (sizeof(struct input_event) !=
read(fd, &in_ev, sizeof(struct input_event))) {
perror("read error");
return -1;
}
switch (in_ev.type) {
case EV_ABS:
switch (in_ev.code) {
case ABS_MT_SLOT:
slot = in_ev.value;
break;
case ABS_MT_POSITION_X:
xy[slot].x = in_ev.value;
mt[slot].valid = 1;
break;
case ABS_MT_POSITION_Y:
xy[slot].y = in_ev.value;
mt[slot].valid = 1;
break;
case ABS_MT_TRACKING_ID:
mt[slot].id = in_ev.value;
mt[slot].valid = 1;
break;
}
break;
//case EV_KEY://按键事件对单点触摸应用比较有用
// break;
case EV_SYN:
if (SYN_REPORT == in_ev.code) {
for (i = 0; i < max_slots; i++) {
mt[i].x = xy[i].x;
mt[i].y = xy[i].y;
}
}
return 0;
}
}
}
int main(int argc, char *argv[])
{
struct input_absinfo slot;
struct ts_mt *mt = NULL;
int max_slots;
int fd;
int i;
/* 参数校验 */
if (2 != argc) {
fprintf(stderr,"usage: %s <input_dev>\n", argv[0]);
exit(EXIT_FAILURE);
}
/* 打开文件 */
fd = open(argv[1], O_RDONLY);
if (0 > fd) {
perror("open error");
exit(EXIT_FAILURE);
}
/* 获取触摸屏支持的最大触摸点数 */
if (0 > ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &slot)) {
perror("ioctl error");
close(fd);
exit(EXIT_FAILURE);
}
max_slots = slot.maximum + 1 - slot.minimum;
printf("max_slots: %d\n", max_slots);
/* 申请内存空间并清零 */
mt = calloc(max_slots, sizeof(struct ts_mt));
/* 读数据 */
for ( ; ; ) {
if (0 > ts_read(fd, max_slots, mt))
break;
for (i = 0; i < max_slots; i++) {
if (mt[i].valid) {//判断每一个触摸点信息是否发生更新(关注的信息发生更新)
if (0 <= mt[i].id)
printf("slot<%d>, 按下(%d, %d)\n", i, mt[i].x, mt[i].y);
else if (-1 == mt[i].id)
printf("slot<%d>, 松开\n", i);
else
printf("slot<%d>, 移动(%d, %d)\n", i, mt[i].x, mt[i].y);
}
}
}
/* 关闭设备、退出 */
close(fd);
free(mt);
exit(EXIT_FAILURE);
}
main 先用 ioctl(fd, EVIOCGABS(ABS_MT_SLOT), &slot) 求最大触摸点数,再按 max_slots 申请 struct ts_mt 数组:mt[0] 对应 slot 0,依次类推;ts_read() 把一轮数据填入数组:ABS_MT_SLOT 更新当前 slot,后续 X/Y、TRACKING_ID 都写入该 slot;SYN_REPORT 时同步坐标并返回;id 初值 -2 表示未上报,-1 表示删除(松开),>=0 表示创建(按下)。单点靠 BTN_TOUCH 判断动作,多点靠 ID 判断各手指动作。
arm-linux-gnueabihf-gcc -o testApp read_mt.c
scp testApp root@192.168.1.10:/home/root/
./testApp /dev/input/event1
tslib 是专门为触摸屏开发的 Linux 应用层函数库,开源。它是触摸屏驱动与应用层之间的适配层:
struct input_event 并解析的过程封装,向上提供 API;ts.conf 提供配置参数;可作为 Qt 的触摸屏输入插件(也可用其它插件,但多数选择 tslib)。
flowchart LR
HW["触摸屏硬件"] --> DRV["内核 input 驱动"]
DRV -->|"/dev/input/eventX 原始 input_event"| TS["tslib 去噪/去抖/坐标变换"]
TS -->|"封装 API"| APP["触摸屏应用程序"]
TS -->|"ts.conf / pointercal"| CFG["配置与校准文件"]
进入 git 仓库 https://github.com/libts/tslib/releases 下载源码。出厂系统已移植 1.16,为统一版本下载 tslib-1.16.tar.gz。编译分三步:配置 → 编译 → 安装。
tar -xzf tslib-1.16.tar.gz
mkdir -p ~/tools/tslib
# 设置交叉编译 SDK 环境
source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
cd tslib-1.16
./configure --host=arm-poky-linux-gnueabi --prefix=/home/dt/tools/tslib/
make
make install
--host:库文件运行的平台,通常设为交叉编译器名称前缀(arm-poky-linux-gnueabi-gcc 的前缀即 arm-poky-linux-gnueabi);--prefix:安装路径;./configure --help 可看全部选项。
安装目录:bin/(测试工具)、etc/ts.conf(配置)、include/tslib.h(头文件)、lib/(库文件与 ts/ 插件库)、share/(可忽略)。
ts.conf 常用配置:| 配置 | 作用 |
|---|---|
module_raw input |
取消注释后使能支持 input 输入事件 |
module pthres pmin=1 |
支持按压力测试时启用,pmin 调节灵敏度 |
module dejitter delta=100 |
去噪插件,过滤噪声样本 |
module linear |
坐标变换(X/Y 互换、旋转等) |
部署到自己做的根文件系统时:bin/ 下可执行文件拷到 /usr/bin,ts.conf 拷到 /etc,lib/ 下库文件拷到 /usr/lib。运行时依赖以下环境变量(出厂系统已在 /etc/profile 中配置):
| 变量 | 含义 |
|---|---|
TSLIB_CONSOLEDEVICE |
控制台设备名,直接为 none |
TSLIB_FBDEVICE |
显示设备节点(画线测试要在 LCD 显示) |
TSLIB_TSDEVICE |
触摸屏设备节点,按实际配置 |
TSLIB_CONFFILE |
ts.conf 路径 |
TSLIB_PLUGINDIR |
插件路径 |
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux C应用编程指南》内容,为扩展知识。
校准相关变量
TSLIB_CALIBFILE用于指定校准数据文件(默认/etc/pointercal)。工具ts_calibrate会在屏幕四角与中心共五个点提示点击,用采集到的原始坐标与理论屏幕坐标做线性变换拟合,求出参数写入pointercal,运行时module linear插件读取它完成坐标变换。若设备坐标已稳定,也可不生成该校准文件。
测试工具:ts_print/ts_test(单点,后者可在 LCD 画线)、ts_print_mt/ts_test_mt(多点)。执行后在触摸屏上按下、滑动、松开,终端会打印信息或画线。查看设备与版本可用 ts_finddev。这些工具源码位于 tslib 源码目录 tests/ 下,内部最终仍落实到前面的 input event 解析。
使用需包含 #include <tslib.h>,步骤为:打开设备 → 配置设备 → 读取数据。
| 函数 | 说明 |
|---|---|
ts_open |
打开触摸屏设备;成功返回句柄,失败返回 NULL |
ts_setup |
打开并配置设备;dev_name 传 NULL 时读取 TSLIB_TSDEVICE 环境变量 |
ts_config |
解析 ts.conf、加载插件;成功 0、失败 -1 |
ts_close |
关闭设备 |
ts_read |
读单点触摸数据 |
ts_read_mt |
读多点触摸数据;max_slots 为最大触摸点数 |
nonblock:0 阻塞、非 0 非阻塞。nr 为对一个触摸点的采样数,设为 1 即可;出错时 ts_read 返回负数。
单点样本结构体:
struct ts_sample {
int x; //X 坐标
int y; //Y 坐标
unsigned int pressure; //按压力大小
struct timeval tv; //时间
};
多点样本结构体 struct ts_sample_mt 常用字段:
| 字段 | 含义 |
|---|---|
x / y |
X / Y 坐标 |
pressure |
按压力大小 |
slot |
触摸点 slot |
tracking_id |
触摸点 ID |
tv |
时间 |
pen_down |
BTN_TOUCH 状态 |
valid |
本次样本是否有效(数据是否更新) |
其余字段(tool_type、tool_x/y、touch_major/minor、width_major/minor、orientation、distance、blob_id)为更细的描述,一般可忽略。 |
#include <stdio.h>
#include <stdlib.h>
#include <tslib.h> //包含tslib.h头文件
int main(int argc, char *argv[])
{
struct tsdev *ts = NULL;
struct ts_sample samp;
int pressure = 0;//用于保存上一次的按压力,初始为0,表示松开
/* 打开并配置触摸屏设备 */
ts = ts_setup(NULL, 0);
if (NULL == ts) {
fprintf(stderr, "ts_setup error");
exit(EXIT_FAILURE);
}
/* 读数据 */
for ( ; ; ) {
if (0 > ts_read(ts, &samp, 1)) {
fprintf(stderr, "ts_read error");
ts_close(ts);
exit(EXIT_FAILURE);
}
if (samp.pressure) {//按压力>0
if (pressure) //若上一次的按压力>0
printf("移动(%d, %d)\n", samp.x, samp.y);
else
printf("按下(%d, %d)\n", samp.x, samp.y);
}
else
printf("松开\n");//打印坐标
pressure = samp.pressure;
}
ts_close(ts);
exit(EXIT_SUCCESS);
}
ts_setup(NULL, 0) 内部读取 TSLIB_TSDEVICE 得到设备节点并完成配置;通过按压力判断状态:pressure=0 表示松开,大于 0 时再根据上一次按压力判断"按下/移动"。
arm-linux-gnueabihf-gcc -I /home/dt/tools/tslib/include -L /home/dt/tools/tslib/lib -lts -o testApp ts_read.c
scp testApp root@192.168.1.10:/home/root/
./testApp
| 选项 | 含义 |
|---|---|
-I |
头文件路径(tslib 的 include) |
-L |
库文件路径(tslib 的 lib) |
-lts |
链接 libts.so(Linux 动态库命名 lib+名字+.so) |
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <tslib.h>
int main(int argc, char *argv[])
{
struct tsdev *ts = NULL;
struct ts_sample_mt *mt_ptr = NULL;
struct input_absinfo slot;
int max_slots;
unsigned int pressure[12] = {0}; //用于保存每一个触摸点上一次的按压力,初始为0,表示松开
int i;
/* 打开并配置触摸屏设备 */
ts = ts_setup(NULL, 0);
if (NULL == ts) {
fprintf(stderr, "ts_setup error");
exit(EXIT_FAILURE);
}
/* 获取触摸屏支持的最大触摸点数 */
if (0 > ioctl(ts_fd(ts), EVIOCGABS(ABS_MT_SLOT), &slot)) {
perror("ioctl error");
ts_close(ts);
exit(EXIT_FAILURE);
}
max_slots = slot.maximum + 1 - slot.minimum;
printf("max_slots: %d\n", max_slots);
/* 内存分配 */
mt_ptr = calloc(max_slots, sizeof(struct ts_sample_mt));
/* 读数据 */
for ( ; ; ) {
if (0 > ts_read_mt(ts, &mt_ptr, max_slots, 1)) {
perror("ts_read_mt error");
ts_close(ts);
free(mt_ptr);
exit(EXIT_FAILURE);
}
for (i = 0; i < max_slots; i++) {
if (mt_ptr[i].valid) {//有效表示有更新!
if (mt_ptr[i].pressure) { //如果按压力>0
if (pressure[mt_ptr[i].slot])//如果上一次的按压力>0
printf("slot<%d>, 移动(%d, %d)\n", mt_ptr[i].slot, mt_ptr[i].x, mt_ptr[i].y);
else
printf("slot<%d>, 按下(%d, %d)\n", mt_ptr[i].slot, mt_ptr[i].x, mt_ptr[i].y);
}
else
printf("slot<%d>, 松开\n", mt_ptr[i].slot);
pressure[mt_ptr[i].slot] = mt_ptr[i].pressure;
}
}
}
/* 关闭设备、释放内存、退出 */
ts_close(ts);
free(mt_ptr);
exit(EXIT_SUCCESS);
}
ts_fd(ts) 从 tslib 句柄取出底层 fd,再 ioctl 获取最大触摸点数;max_slots 申请 struct ts_sample_mt 数组;ts_read_mt(ts, &mt_ptr, max_slots, 1) 读一轮数据;只处理 valid 为真的触摸点;每个 slot 用 pressure[] 记录上一次按压力,区分"按下/移动"。
arm-linux-gnueabihf-gcc -I /home/dt/tools/tslib/include -L /home/dt/tools/tslib/lib -lts -o testApp ts_read_mt.c
cat /proc/bus/input/devices 确认触摸屏设备节点(如 goodix-ts);read_input.c 打印原始数据,对照第 6.2 节分析事件规则;read_slot.c 查看最大触摸点数,再运行 read_ts.c、read_mt.c;ts_print/ts_print_mt/ts_test/ts_test_mt 验证 tslib;ts_read.c/ts_read_mt.c(带 -I、-L、-lts),拷到开发板运行。| 现象 | 可能原因 | 排查手段 |
|---|---|---|
open error: No such file or directory |
设备节点写错 | cat /proc/bus/input/devices 确认 eventX |
open error: Permission denied |
权限不足 | 用 root 或检查节点权限 |
read 阻塞无输出 |
该设备无事件(正常) | 触发按键/触摸;或改用非阻塞 |
| 坐标与屏幕不符 | 未做坐标变换/校准 | 用 tslib module linear 或校准 |
编译找不到 tslib.h |
未指定头文件路径 | 加 -I <tslib>/include |
链接找不到 libts |
未指定库路径或库名 | 加 -L <tslib>/lib -lts |
cat /proc/bus/input/devices # 查看所有输入设备及 eventX
hexdump /dev/input/event2 # 十六进制查看原始事件流(需触发事件)
ts_finddev # 查看 tslib 设备与版本
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux C应用编程指南》内容,为扩展知识。
| 维度 | I.MX6ULL(本教程) | STM32(裸机/RTOS) | RK3568 |
|---|---|---|---|
| 输入框架 | Linux input 子系统,/dev/input/eventX |
无统一框架,直接读寄存器或自写驱动 | Linux input 子系统,接口一致 |
| 数据单元 | struct input_event |
自定义结构/寄存器值 | struct input_event |
| 多点触摸 | MT Type B,slot + tracking_id | 触摸 IC 自定义报文 | MT 协议,接口一致 |
| 用户态库 | tslib(去噪/校准/坐标变换,Qt 插件) | 通常无,需自行滤波校准 | tslib 同样可用 |
| 交叉工具链 | arm-linux-gnueabihf-gcc |
arm-none-eabi-gcc |
aarch64-linux-gnu-gcc |
I.MX6ULL 与 RK3568 同属 Linux 应用编程,input 子系统与 tslib 用法基本通用;STM32 裸机没有设备节点与 input_event,不能套用本篇模型。
⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux C应用编程指南》内容,为扩展知识。
答:input 子系统是 Linux 为统一管理各种输入设备(键盘、鼠标、触摸屏、按键等)实现的兼容框架。驱动人员基于它开发输入设备驱动,它屏蔽硬件差异、向应用层提供统一接口。注册成功的设备在 /dev/input 下生成 eventX 节点,应用层 open 后 read,每次得到一个 struct input_event(time、type、code、value)。
struct input_event 的 type、code、value 分别是什么?如何判断按键按下?答:type 描述事件大类(EV_KEY 按键、EV_REL 相对位移、EV_ABS 绝对位移、EV_SYN 同步等);code 指明该类中的具体事件;value 是事件值,解释随 code 变化。对按键事件,code 为键值,value=1 按下、0 松开、2 长按;先判断 type==EV_KEY,再读 value 即可判断状态,code 用于区分具体按键。
答:一次 read 只能读一个 struct input_event,而一个触摸点含 X、Y 等多项,需多次 read 才读全。内核把本轮数据全部上报后,再上报 SYN_REPORT(type=EV_SYN、code=0)告知本轮数据完整。应用层收到它时才认为信息到齐、可以解析。
答:slot 是硬件层面概念,代表触摸点编号,按触碰时间先后分配、从 0 开始,ABS_MT_SLOT 告知当前更新哪个 slot。tracking_id 是软件层面概念,标识触摸点生命周期,由 ABS_MT_TRACKING_ID 上报:>=0 为有效触摸点,-1 表示被移除。同一手指触碰、离开、再触碰,两次都是 slot=0,但属于不同生命周期,tracking_id 不同。
答:tslib 是开源的 Linux 触摸屏应用层函数库,是驱动与应用层之间的适配层。它把"读取原始 input_event 并解析"封装起来,经去噪、去抖、坐标变换,把原始触摸坐标转换为屏幕坐标,并通过 ts.conf 配置和校准文件完成变换;还可作为 Qt 的触摸屏输入插件。使用步骤:ts_setup/ts_open 打开、ts_config 配置(ts_setup 已含)、ts_read/ts_read_mt 读取、ts_close 关闭。需设置 TSLIB_TSDEVICE、TSLIB_CONFFILE、TSLIB_PLUGINDIR 等环境变量,编译时用 -I、-L、-lts。
内容来源:《I.MX6U嵌入式Linux C应用编程指南》第十七章 输入设备应用编程、第十八章 使用tslib库;例程源码 17_input(read_input.c、read_key.c、read_slot.c、read_ts.c、read_mt.c)、18_tslib(ts_read.c、ts_read_mt.c)