嵌入式Linux C应用编程——tslib库的使用

tslib 库

在上一个笔记中,我们基于读取到的 input_event 类型的数据进行了解析,以得到各个触摸点的坐标,然后进一步进行开发。而这一章,为了使开发更为方便,我们将使用Linux系统下专门为触摸屏开发的tslib库进行开发。tslib 是专门为触摸屏设备所开发的 Linux 应用层函数库,并且是开源的。

tslib 为触摸屏驱动和应用层之间的适配层,它把应用程序中读取触摸屏 struct input_event 类型数据(这
是输入设备上报给应用层的原始数据)并进行解析的操作过程进行了封装,向使用者提供了封装好的 API 接
口。 tslib 从触摸屏中获得原始的坐标数据,并通过一系列的去噪、去抖、坐标变换等操作,来去除噪声并将
原始的触摸屏坐标转换为相应的屏幕坐标。

tslib 可以作为 Qt 的触摸屏输入插件,为 Qt 提供触摸输入支持。虽然不是唯一的输入开发插件,但是大部分时候都会选择使用tslib库。

tslib 移植

正点原子开发板的出厂系统中自带了tslib库,所以可以不用移植,但是还是有必要了解一下如何移植的。如果你不知道是不是已经移植了tslib,可以输入 ts_finddev来查看版本信息:
img

相关的源码可以在github上下载: https://github.com/libts/tslib/releases

下载并解压

将下载的 tar.bz2 或 tar.gz 格式压缩包拷贝到 Ubuntu 系统的用户家目录下并解压:

# 以 tslib-1.16 为例
tar -xzf tslib-1.16.tar.gz
cd tslib-1.16/

注意还要在~文件夹下创建一个tools的文件夹:
img

配置工程

配置前需要设置交叉编译器的路径(确保已安装交叉编译器并加入 PATH),然后执行 configure 脚本。注意: --prefix 路径要根据自己的用户名修改,不要照抄我写的路径。

# 配置 tslib 工程,arm-linux-gnueabihf-gcc 为交叉编译器
./configure --host=arm-linux-gnueabihf --prefix=$HOME/tools/tslib

各选项说明:

  • --host=arm-linux-gnueabihf:文档中使用的是 arm-poky-linux-gnueabi ,这里也可也用这个。
  • --prefix=$HOME/tools/tslib:指定安装路径,$HOME 会自动展开为当前用户的家目录。

编译并安装

make
make install

编译安装完成后,安装目录下会生成以下几个子目录:

安装目录介绍

bin/ — 测试工具目录

包含 tslib 提供的一些小工具,用于测试触摸屏是否工作正常:

  • ts_print — 单点触摸测试,在终端打印触摸点坐标信息
  • ts_print_mt — 多点触摸测试,可打印多个触摸点的信息
  • ts_test — 单点触摸画线测试,支持在 LCD 上画线
  • ts_test_mt — 多点触摸画线测试

etc/ — 配置文件目录

里面有一个核心配置文件 ts.conf,tslib 工作的时候会去解析这个文件,加载相应的插件。文件内容及含义如下:

配置项 作用
module_raw input 使能支持 input 输入事件(默认被注释,需要取消注释)
module pthres pmin=1 压力阈值插件,pmin 调节按压力灵敏度,默认=1
module dejitter delta=100 去噪算法插件,用于过滤噪声样本,delta=100 为默认参数
module linear 坐标变换插件,可实现 X/Y 坐标互换、坐标旋转等

img

如果开发板出厂系统已移植 tslib,一般使用默认配置即可,不需要改动。

include/ — 头文件目录

只包含一个头文件 tslib.h,里面定义了 tslib 相关的数据结构(如 struct ts_samplestruct ts_sample_mt)以及 API 函数的声明。编写应用程序时需要包含这个头文件。

lib/ — 库文件目录

包含编译 tslib 源码得到的动态库文件(如 libts.so),以及 ts/ 子目录下存放的各插件库(如 pthres.sodejitter.solinear.so 等)。如果需要静态库,可以在配置 tslib 工程时通过选项指定。

在开发板上测试 tslib

移植完成后,需要将编译得到的文件拷贝到开发板上。由于正点原子开发板的出厂系统已经移植了 tslib,所以一般不需要手动拷贝。但如果是自己做的根文件系统,需要按以下步骤操作:

① 拷贝文件到开发板
将安装目录下的测试工具(bin/)、配置文件(etc/ts.conf)以及动态库文件(lib/)分别拷贝到开发板对应的 /usr/bin/etc/usr/lib 目录下。

② 配置环境变量
tslib 工作时需要依赖以下环境变量来找到配置文件、库文件以及触摸屏设备节点:

export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0
export TSLIB_TSDEVICE=/dev/input/event1
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_PLUGINDIR=/usr/lib/ts
环境变量 作用
TSLIB_CONSOLEDEVICE 配置控制台设备文件名,设为 none 即可
TSLIB_FBDEVICE 显示设备节点,用于在 LCD 上显示画线内容
TSLIB_TSDEVICE 触摸屏对应的设备节点,根据实际情况配置
TSLIB_CONFFILE ts.conf 配置文件的路径
TSLIB_PLUGINDIR 插件库所在路径

注意: 在出厂系统中,输入 export 可能看不到这些环境变量,但 tslib 工具依然能正常使用。这是因为 tslib 内部有默认路径——当环境变量未设置时,tslib 会使用编译时内嵌的默认值(如 /etc/ts.conf/usr/lib/ts/dev/input/event1 等)。出厂系统已将 tslib 安装到标准系统路径,所以无需环境变量也能正常工作。这些环境变量主要在你将 tslib 安装到非标准路径自己移植时才需要手动设置。

如果想每次启动都自动生效,可以将这些命令添加到 /etc/profile 脚本中。出厂系统已默认配置好,无需用户配置。

③ 运行测试工具

  • ts_print — 单点触摸测试,按下/移动/松开触摸屏时在终端打印坐标信息
  • ts_print_mt — 多点触摸测试,可打印多个触摸点的信息
  • ts_test — 单点触摸画线测试,支持在 LCD 上画线
  • ts_test_mt — 多点触摸画线测试

执行 ts_print 后,在触摸屏上操作即可看到坐标输出:

$ ts_print

img

这些测试工具的源码可以在 tslib 源码目录的 tests/ 文件夹中找到。

tslib库函数的使用

使用 tslib 库函数需要在应用程序中包含头文件 tslib.h。tslib 编程的基本流程分为三步:打开设备 、 配置设备 、 读取数据

打开触摸屏设备

tslib 提供了两个函数来打开触摸屏设备:

ts_open() — 基础打开函数

#include "tslib.h"
struct tsdev *ts_open(const char *dev_name, int nonblock);
  • dev_name:触摸屏的设备节点(如 /dev/input/event1
  • nonblock:0 表示阻塞方式,非 0 表示非阻塞方式(非阻塞下CPU不会一直在此阻塞)
  • 返回值:成功返回 struct tsdev * 句柄,失败返回 NULL

ts_setup() — 推荐使用,打开 + 配置一步到位

struct tsdev *ts_setup(const char *dev_name, int nonblock);
  • ts_open() 的区别:dev_name 可设为 NULL,此时函数内部会自动读取环境变量 TSLIB_TSDEVICE 来获取设备节点
  • 调用 ts_setup() 后无需再单独调用 ts_config(),因为它内部已经完成了配置

ts_close() — 关闭设备

int ts_close(struct tsdev *ts);

ts_fd() — 获取文件描述符

int ts_fd(struct tsdev *ts);
  • 从 tslib 句柄中取出底层的文件描述符,用于传递给 ioctl() 等系统调用
  • 例如获取触摸屏最大触点数时:ioctl(ts_fd(ts), EVIOCGABS(ABS_MT_SLOT), &slot)

配置触摸屏设备

int ts_config(struct tsdev *ts);
  • 作用:解析 ts.conf 配置文件,加载相应的插件(去噪、去抖、坐标变换等)
  • 参数 ts:触摸屏句柄
  • 返回值:成功返回 0,失败返回 -1

如果使用 ts_setup() 打开设备,则不需要再调用 ts_config()

读取触摸屏数据

tslib 提供两个读取函数,分别对应单点和多点触摸:

ts_read() — 单点触摸

int ts_read(struct tsdev *ts, struct ts_sample *samp, int nr);
  • samp:指向 struct ts_sample 对象的指针,用于存放读取到的触摸数据
  • nr:采样数,表示每次调用读取几个样本数据,一般设置为 1

ts_read_mt() — 多点触摸

int ts_read_mt(struct tsdev *ts, struct ts_sample_mt **samp, int max_slots, int nr);
  • samp:指向 struct ts_sample_mt 数组的指针
  • max_slots:触摸屏支持的最大触摸点数,可通过 ioctl 获取
  • nr:采样数,表示每次调用读取几个样本数据,一般设置为 1

关于采样数 nr 这个参数表示每次调用 ts_read()/ts_read_mt() 时读取多少个样本数据。触摸屏上报数据是流式的、逐个到达的,每个样本对应一次触摸事件(按下、移动、松开)。设置为 1 意味着每次调用只读一个样本,处理完后再读取下一个,这种"一次读一个"的方式逻辑最简单、最常用。如果设置大于 1,函数会尝试一次读取多个样本,但需要底层有足够的数据堆积才会返回,实际使用中一般不需要。

获取最大触摸点数的方法:

struct input_absinfo slot;
ioctl(ts_fd(ts), EVIOCGABS(ABS_MT_SLOT), &slot);
max_slots = slot.maximum + 1 - slot.minimum;

数据结构

struct ts_sample — 单点触摸数据结构

struct ts_sample {
    int              x;         // X 坐标
    int              y;         // Y 坐标
    unsigned int     pressure;  // 按压力大小
    struct timeval   tv;        // 时间戳
};

struct ts_sample_mt — 多点触摸数据结构

struct ts_sample_mt {
    int              x;           // X 坐标
    int              y;           // Y 坐标
    unsigned int     pressure;    // 按压力大小
    int              slot;        // 触摸点 slot
    int              tracking_id; // 触摸点 ID
    int              tool_type;
    int              tool_x;
    int              tool_y;
    unsigned int     touch_major;
    unsigned int     width_major;
    unsigned int     touch_minor;
    unsigned int     width_minor;
    int              orientation;
    int              distance;
    int              blob_id;
    struct timeval   tv;          // 时间戳
    short            pen_down;    // BTN_TOUCH 状态
    short            valid;       // 样本是否有效(数据是否发生更新)
};

valid 字段很重要——只有当触摸点数据发生更新时该字段才为非 0 值。在多触摸应用程序中,应通过判断 valid 来确定哪些 slot 的数据发生了变化,而不是处理所有 slot。

基于 tslib 库编写的触摸屏应用程序

单点触摸

使用 ts_read() 读取单点触摸数据,通过判断 pressure(按压力)来确定触摸状态:

#include <stdio.h>
#include <stdlib.h>
#include <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)                  // 上一次也是按下状态 → 移动
                printf("移动(%d, %d)\n", samp.x, samp.y);
            else                           // 上一次是松开状态 → 按下
                printf("按下(%d, %d)\n", samp.x, samp.y);
        }
        else                               // 按压力 = 0 → 松开
            printf("松开\n");

        pressure = samp.pressure;          // 更新上一次的按压力
    }

    ts_close(ts);
    exit(EXIT_SUCCESS);
}

通过 pressure 变量记录上一次的按压力状态,结合当前 samp.pressure 来判断是"按下"、"移动"还是"松开"。读取错误时 ts_read() 返回负数。

编译命令:

arm-linux-gnueabihf-gcc -I /home/duanwenjie/tools/tslib/include \
                         -L /home/duanwenjie/tools/tslib/lib \
                         -lts -o ts_read_test ts_read.c

编译选项说明:

选项 含义 作用
-I Include(头文件路径) 指定头文件搜索目录,编译时需要找到 tslib.h
-L Library(库文件路径) 指定库文件搜索目录,链接时需要找到 libts.so
-lts Link library(链接库名) 指定链接 libts.so 库,-lts 会自动补全为 libts.so
-o Output(输出文件) 指定生成的可执行文件名

${CC} 是一个环境变量,代表交叉编译器的名称。如果你设置了 export CC=arm-linux-gnueabihf-gcc,那么 ${CC} 就等价于直接写 arm-linux-gnueabihf-gcc。为清晰起见,这里直接用实际的编译器名称。

之前编译时没有加 -I-L 这些选项,是因为当时只用了标准库(如 stdio.hstdlib.h),这些库是交叉编译器自带的,编译时会自动搜索。而 tslib 是第三方库,需要手动指定头文件和库文件的路径,编译器才能找到它们。

多点触摸

使用 ts_read_mt() 读取多点触摸数据,每个触摸点用 struct ts_sample_mt 描述:

#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};  // 保存每个触摸点上一次的按压力
    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])    // 上一次也是按下 → 移动
                        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                                 // 按压力 = 0 → 松开
                    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);
}

与单点的区别:

  • 先通过 ioctl(ts_fd(ts), EVIOCGABS(ABS_MT_SLOT)) 获取触摸屏支持的最大触摸点数
  • 使用 calloc 为每个触摸点分配一个 struct ts_sample_mt 结构体(一次性分配的内存是连续的,所以可以使用数组的形式来表示)
  • 每次读完数据后遍历所有 slot,通过 valid 字段判断该触摸点是否有数据更新
  • 每个触摸点的状态需要单独记录(pressure 数组)

编译命令与单点触摸相同。

posted @ 2026-07-18 18:15  Javenwww  阅读(40)  评论(0)    收藏  举报