03-输入设备与tslib.md 32 KB


title: 输入设备与tslib tags: [嵌入式Linux, Linux应用编程, input子系统, 输入设备, 触摸屏, tslib, IMX6ULL] created: 2026-09-18 updated: 2026-09-18

pdf_ref: "《I.MX6U嵌入式Linux C应用编程指南V1.6》第十七章 输入设备应用编程、第十八章 使用tslib库"

输入设备与tslib

💡 关联知识:[[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,再 readstruct input_event,就得到原始输入数据。本篇先讲清 input 子系统的数据格式与解析方法(按键、单点触摸、多点触摸),再引入 tslib —— 把"读原始 event + 去噪 + 坐标变换"封装好的触摸屏应用层函数库。


1. 输入设备与 input 子系统

输入设备(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 为例):

  1. 应用打开设备文件;2. 发起 read,无数据可读时在阻塞 I/O 下休眠;3. 有数据时被唤醒、读操作返回;4. 应用解析数据。

Tips:设备文件不同于普通文件,读写设备文件之前无需设置读写位置偏移量


2. struct input_event:应用层看到的数据单元

应用层每次 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_Xvalue 就是 X 坐标,code=ABS_Y 时就是 Y 坐标。这些宏定义在 input-event-codes.h,被 <linux/input.h> 包含。


3. 数据同步:EV_SYN 与 SYN_REPORT

一次 read 只能读一个 event,而一个触摸点含 X、Y 等多项,需多次 read 才读全。内核把本轮数据全部上报后,会再上报一个同步事件告知"本轮数据已完整":

#define SYN_REPORT     0    // 本轮数据完整
#define SYN_DROPPED    3    // 数据丢失

所有输入设备都需上报同步事件,通常是 SYN_REPORTvalue 通常为 0。


4. 读取 struct input_event 数据(read_input.c)

例程 read_input.c 是最基础的读取框架:传参为设备节点路径,用 open(argv[1], O_RDONLY) 只读打开,在死循环中每次 read 恰好读出一个 event(阻塞 I/O,无数据时在此休眠),再把 in_ev.typein_ev.codein_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/

4.1 在开发板上验证按键 KEY0

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


5. 按键应用编程(read_key.c)

按键上报流程为 KEY_ASYN_REPORTvalue 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 测试;根据 codeinput-event-codes.h 即知按键,如 code=30 对应 A 键、code=48 对应 B 键。

读取多个输入设备:一个进程一次只能阻塞读取一个设备节点。若要同时读取多个设备(如按键 + 触摸屏),可用 poll()/select() 同时监听多个 fd(见 [[03-外设与高级IO编程/01-高级IO]]),或为每个设备各开一个线程/进程;input_event.time 时间戳可用于还原不同设备事件的先后顺序。


6. 触摸屏:事件类型与上报流程

触摸屏是绝对位移设备,上报 EV_ABS

类型 特点 承载事件
单点触摸 一轮完整数据只含一个触摸点 ABS_XABS_YABS_XXX
多点触摸 一轮完整数据可含多个触摸点 ABS_MT_POSITION_X/YABS_MT_XXX

除位移事件外还会上报:

  • 同步事件:几乎每个输入设备都上报,告知本轮数据是否完整;
  • 按键事件 BTN_TOUCHcode=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 之前上报。

6.1 多点触摸与 MT Type B 协议

Linux 用多点触摸(MT)协议上报各触摸点数据,分 Type A 与 Type B。Type A 使用很少、几乎淘汰,开发板配套触摸屏都属于 Type B

Type B 适用于能追踪并区分触摸点的设备,重点是用 ABS_MT_SLOT 上报各触摸点信息的更新:

  • slot:硬件概念,触摸点的编号,通常按触碰时间先后分配、从 0 开始;ABS_MT_SLOTvalue 告知当前正在更新哪个 slot;
  • tracking_id:软件概念,由 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 不同。

6.2 上报数据实例

一个手指点击触摸屏不松开,read_input.c 打印:

要点:ABS_MT_TRACKING_ID 非负表示新建触摸点(按下);多点设备也通过 ABS_X/ABS_Y 上报坐标,但通常只有触摸点 0 支持,故可当单点用;有的屏幕因触摸芯片不同没有 EV_KEY,也就没有 BTN_TOUCH。增加第二个触摸点时会先上报 ABS_MT_SLOTcode=47value=1;手指松开时上报 ABS_MT_TRACKING_IDvalue=-1


7. 获取触摸屏信息:ioctl 与 input_absinfo

#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 数组的依据。

8. 单点触摸应用程序(read_ts.c)

把多点触摸屏当单点用:用 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

9. 多点触摸应用程序(read_mt.c)

按 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
    
    

10. tslib 库

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["配置与校准文件"]
    
    

10.1 tslib 移植

进入 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/bints.conf 拷到 /etclib/ 下库文件拷到 /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 解析。

10.2 tslib 库函数

使用需包含 #include <tslib.h>,步骤为:打开设备 → 配置设备 → 读取数据。

函数 说明
ts_open 打开触摸屏设备;成功返回句柄,失败返回 NULL
ts_setup 打开并配置设备;dev_nameNULL 时读取 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_typetool_x/ytouch_major/minorwidth_major/minororientationdistanceblob_id)为更细的描述,一般可忽略。

11. 基于 tslib 的单点触摸程序(ts_read.c)


#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

12. 基于 tslib 的多点触摸程序(ts_read_mt.c)

#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
    

13. 实验步骤与调试方法

13.1 实验步骤

  1. 将 LCD 屏连接到开发板 LCD 接口,上电启动出厂系统;点击屏幕进入设置页面,点击退出按钮退出出厂系统 GUI 应用;
  2. cat /proc/bus/input/devices 确认触摸屏设备节点(如 goodix-ts);
  3. read_input.c 打印原始数据,对照第 6.2 节分析事件规则;
  4. read_slot.c 查看最大触摸点数,再运行 read_ts.cread_mt.c
  5. 运行 ts_print/ts_print_mt/ts_test/ts_test_mt 验证 tslib;
  6. 交叉编译 ts_read.c/ts_read_mt.c(带 -I-L-lts),拷到开发板运行。

13.2 调试方法

现象 可能原因 排查手段
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 设备与版本

14. 跨平台对比:IMX6ULL vs STM32 vs RK3568

⚠️ 来源说明:本节不属于《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,不能套用本篇模型。


15. 面试精选

⚠️ 来源说明:本节不属于《I.MX6U嵌入式Linux C应用编程指南》内容,为扩展知识。

Q1:什么是 input 子系统?它对应用层提供了什么?

:input 子系统是 Linux 为统一管理各种输入设备(键盘、鼠标、触摸屏、按键等)实现的兼容框架。驱动人员基于它开发输入设备驱动,它屏蔽硬件差异、向应用层提供统一接口。注册成功的设备在 /dev/input 下生成 eventX 节点,应用层 openread,每次得到一个 struct input_event(time、type、code、value)。

Q2: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 用于区分具体按键。

Q3:为什么需要 EV_SYN / SYN_REPORT?一次 read 能读全一个触摸点吗?

:一次 read 只能读一个 struct input_event,而一个触摸点含 X、Y 等多项,需多次 read 才读全。内核把本轮数据全部上报后,再上报 SYN_REPORTtype=EV_SYNcode=0)告知本轮数据完整。应用层收到它时才认为信息到齐、可以解析。

Q4:多点触摸中 slot 与 tracking_id 有什么区别?

:slot 是硬件层面概念,代表触摸点编号,按触碰时间先后分配、从 0 开始,ABS_MT_SLOT 告知当前更新哪个 slot。tracking_id 是软件层面概念,标识触摸点生命周期,由 ABS_MT_TRACKING_ID 上报:>=0 为有效触摸点,-1 表示被移除。同一手指触碰、离开、再触碰,两次都是 slot=0,但属于不同生命周期,tracking_id 不同。

Q5:tslib 是什么?它做了什么,如何使用?

:tslib 是开源的 Linux 触摸屏应用层函数库,是驱动与应用层之间的适配层。它把"读取原始 input_event 并解析"封装起来,经去噪、去抖、坐标变换,把原始触摸坐标转换为屏幕坐标,并通过 ts.conf 配置和校准文件完成变换;还可作为 Qt 的触摸屏输入插件。使用步骤:ts_setup/ts_open 打开、ts_config 配置(ts_setup 已含)、ts_read/ts_read_mt 读取、ts_close 关闭。需设置 TSLIB_TSDEVICETSLIB_CONFFILETSLIB_PLUGINDIR 等环境变量,编译时用 -I-L-lts


延伸阅读

  • 驱动侧原理:[[嵌入式Linux驱动开发实战/03-Linux驱动开发核心/08-misc与input子系统]]
  • 触摸屏驱动实现:[[嵌入式Linux驱动开发实战/05-Linux外设驱动实战/04-触摸屏驱动]]
  • 本库相关:[[03-外设与高级IO编程/02-GPIO与LED应用编程]]、[[03-外设与高级IO编程/04-FrameBuffer与LCD应用编程]]、[[03-外设与高级IO编程/06-摄像头串口与音频]]

内容来源:《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)