Linux驱动 2026年7月9日 51 分钟

tslib 学习手册2

tslib 学习手册 目录 学习路线图 推荐学习顺序 新手(只想使用): 开发者(深入理解): 系统集成(移植适配): …

tslib 学习手册

目录


学习路线图

入门(1-2小时)    原理(2-3小时)      源码(3-5小时)      实践(按需)
    ↓                  ↓                   ↓                   ↓
 阅读文档          理解配置文件        阅读核心源码        编写应用程序
 运行示例          数据流分析          模块化设计          自定义过滤器
 简单测试          过滤器原理          API实现细节         嵌入式移植

推荐学习顺序

新手(只想使用)

  1. 阅读本手册”第一阶段”
  2. 运行 ts_calibrate 和 ts_test_mt
  3. 编写简单的触摸应用

开发者(深入理解)

  1. 完整学习四个阶段
  2. 阅读关键源码文件
  3. 理解过滤器设计模式

系统集成(移植适配)

  1. 理解架构和数据流
  2. 学习交叉编译
  3. 编写设备驱动接口

第一阶段:快速入门

学习目标

  • ✅ 理解 tslib 是什么
  • ✅ 运行第一个示例程序
  • ✅ 掌握基本 API 使用

1.1 什么是 tslib?

核心功能

触摸硬件 → 驱动 → tslib 过滤 → 应用程序
                    ↓
            [去噪声 + 校准]

三个关键作用

  1. 校准:触摸坐标 → 屏幕坐标
  2. 过滤:去除噪声和抖动
  3. 抽象:统一的 API 接口

为什么需要 tslib?

  • 触摸屏硬件精度不够,需要校准
  • 原始信号有噪声,需要过滤
  • 不同硬件接口不同,需要统一

1.2 阅读第一个示例

打开 tests/ts_print.c(115行,最简单):

// 核心三步骤
ts = ts_setup(NULL, 0);              // 1. 初始化
ret = ts_read(ts, &samp, 1);         // 2. 读取数据
printf("%d %d %d\n", samp.x, samp.y, // 3. 使用数据
       samp.pressure);
ts_close(ts);                         // 4. 清理

学习要点

  • ts_setup() 自动完成设备查找、打开、配置
  • ts_read() 返回过滤和校准后的数据
  • pressure > 0 表示按下,== 0 表示松开

1.3 数据结构

查看 src/tslib.h:58-97

// 单点触摸
struct ts_sample {
    int x;                    // 屏幕坐标 X
    int y;                    // 屏幕坐标 Y
    unsigned int pressure;    // 压力值(0=松开)
    struct timeval tv;        // 时间戳
};

// 多点触摸
struct ts_sample_mt {
    int x, y;
    unsigned int pressure;
    int slot;                 // 第几个触摸点(0,1,2...)
    int tracking_id;          // 跟踪ID(-1=松开)
    short valid;              // 数据有效标志
    // ... 更多字段
};

1.4 编写第一个程序

创建 my_first_touch.c

#include <stdio.h>
#include <tslib.h>

int main(void) {
    struct tsdev *ts;
    struct ts_sample samp;
    
    // 初始化
    ts = ts_setup(NULL, 0);
    if (!ts) {
        perror("ts_setup");
        return 1;
    }
    
    printf("请触摸屏幕...\n");
    
    // 循环读取
    while (1) {
        if (ts_read(ts, &samp, 1) == 1) {
            printf("X=%d, Y=%d, 压力=%d\n",
                   samp.x, samp.y, samp.pressure);
        }
    }
    
    ts_close(ts);
    return 0;
}

编译运行:

gcc my_first_touch.c -lts -o my_first_touch
./my_first_touch

1.5 实践练习

练习 1:修改程序,只在按下时打印

if (ts_read(ts, &samp, 1) == 1) {
    if (samp.pressure > 0) {  // 只有按下时
        printf("触摸: X=%d, Y=%d\n", samp.x, samp.y);
    }
}

练习 2:检测屏幕四个角

// 假设屏幕 800x480
if (samp.x < 100 && samp.y < 100) {
    printf("左上角\n");
} else if (samp.x > 700 && samp.y < 100) {
    printf("右上角\n");
}
// ... 其他角

练习 3:统计触摸次数

int touch_count = 0;
int last_pressure = 0;

while (1) {
    ts_read(ts, &samp, 1);
    if (samp.pressure > 0 && last_pressure == 0) {
        touch_count++;
        printf("第 %d 次触摸\n", touch_count);
    }
    last_pressure = samp.pressure;
}

1.6 小结

掌握的知识

  • ✅ tslib 的作用
  • ✅ 基本 API:ts_setup(), ts_read(), ts_close()
  • ✅ 数据结构:ts_sample
  • ✅ 编写简单的触摸应用

下一步:理解配置和过滤原理


第二阶段:理解原理

学习目标

  • ✅ 理解数据流和过滤器链
  • ✅ 掌握配置文件
  • ✅ 了解常用过滤器

2.1 数据流分析

完整的数据路径

1. 触摸硬件
   ↓ (电容/电阻变化)
2. 内核驱动 (/dev/input/eventX)
   ↓ (input event)
3. raw 模块 (input-raw.c)
   ↓ (读取原始坐标)
4. 过滤器链
   ├─ median   (去尖峰噪声)
   ├─ dejitter (去抖动)
   └─ linear   (校准)
   ↓ (过滤后的坐标)
5. 应用程序 (ts_read)

查看原始数据 vs 过滤后数据

# 原始数据(未过滤)
ts_print --raw

# 过滤后数据
ts_print

对比观察:

  • 原始数据跳动大
  • 过滤后更平滑
  • 校准后坐标对应屏幕

2.2 配置文件深度解析

配置文件:/etc/ts.conf

基础配置

module_raw input              # 第1步:读取数据
module median depth=3         # 第2步:中值滤波
module dejitter delta=100     # 第3步:去抖动
module linear                 # 第4步:校准

执行顺序:从上到下,数据依次通过每个模块

各模块详解

1. median – 中值滤波

module median depth=5

原理:

输入序列: [100, 105, 300, 110, 108]  ← 300 是噪声
排序后:   [100, 105, 108, 110, 300]
中值:     108  ← 输出

作用:去除偶然的错误读数

2. dejitter – 去抖动

module dejitter delta=100

原理:

当前点: (100, 100)
上一点: (102, 99)
距离:   sqrt((100-102)^2 + (100-99)^2) = 2.2

如果距离 < delta:
    输出 = 加权平均(历史点)  ← 平滑
否则:
    输出 = 当前点  ← 快速移动,不平滑

3. linear – 线性校准

module linear
module linear rot=1  # 旋转90度

原理:

触摸坐标 (xt, yt) → 屏幕坐标 (xs, ys)

xs = a0 + a1*xt + a2*yt
ys = b0 + b1*xt + b2*yt

系数 a0,a1,a2,b0,b1,b2 由 ts_calibrate 计算
保存在 /etc/pointercal

2.3 配置实验

实验 1:对比不同配置

配置 A:无过滤

module_raw input
module linear

配置 B:强过滤

module_raw input
module median depth=7
module iir N=8 D=10
module dejitter delta=50
module linear

运行 ts_test_mt 画圆,观察:

  • 配置 A:轨迹有锯齿
  • 配置 B:轨迹平滑但有延迟

实验 2:压力阈值

module_raw input
module pthres pmin=50    # 忽略轻触
module median depth=3
module linear

轻轻触摸 → 无响应 用力触摸 → 正常响应

2.4 过滤器原理图解

原始信号(有噪声):
  y
  ^
  |    *
  |  *   *  ← 噪声尖峰
  | *     *
  |*       *
  +---------> x

median 过滤后:
  y
  ^
  |   
  |  * * *  ← 尖峰被削平
  | *     *
  |*       *
  +---------> x

dejitter 过滤后:
  y
  ^
  |   
  |  ----  ← 更平滑
  | /    \
  |/      \
  +---------> x

2.5 校准原理

为什么需要校准?

触摸屏坐标系        屏幕坐标系
(0,0)------+        (0,0)------+
|          |   ≠    |          |
|  Touch   |        |  Screen  |
+-------(max,max)   +-------(w,h)

可能不同:
- 原点位置不同
- 分辨率不同  
- 有旋转/镜像

校准过程

ts_calibrate
  1. 显示 5 个校准点:


    1 ←─────────→ 2
    ↑ ↑
    │ 5 │
    ↓ ↓
    3 ←─────────→ 4

  2. 你点击每个点,记录:

    • 触摸坐标 (xt, yt)
    • 屏幕坐标 (xs, ys)
  3. 计算变换矩阵:


    [xs]   [a0]   [a1 a2] [xt]
    [ys] = [b0] + [b1 b2] [yt]

  4. 保存到 /etc/pointercal


    1234 5678 9012 3456 7890 1234 65536

2.6 实践练习

练习 1:自己编辑配置文件

sudo nano /etc/ts.conf

# 尝试不同的 depth 值
module median depth=3
module median depth=5
module median depth=7

# 运行 ts_test_mt 观察区别

练习 2:理解校准数据

# 查看校准文件
cat /etc/pointercal

# 删除后重新校准
sudo rm /etc/pointercal
ts_calibrate

练习 3:对比原始和过滤数据

# 同时运行两个终端
# 终端1:
ts_print --raw > raw.txt

# 终端2:
ts_print > filtered.txt

# 触摸屏幕后对比文件

2.7 小结

掌握的知识

  • ✅ 数据流和过滤器链
  • ✅ 配置文件语法
  • ✅ 常用过滤器原理
  • ✅ 校准原理

下一步:深入源码实现


第三阶段:深入源码

学习目标

  • ✅ 理解 tslib 架构设计
  • ✅ 掌握核心 API 实现
  • ✅ 学习模块化设计

3.1 源码目录结构

tslib-1.21/
├── src/                      # 核心库源码
│   ├── tslib.h              # 公共 API 头文件 ⭐
│   ├── tslib-private.h      # 内部数据结构
│   ├── ts_setup.c           # 初始化 ⭐
│   ├── ts_open.c            # 打开设备 ⭐
│   ├── ts_config.c          # 读取配置 ⭐
│   ├── ts_read.c            # 读取数据 ⭐
│   ├── ts_read_raw.c        # 读取原始数据
│   └── ts_load_module.c     # 加载模块
├── plugins/                  # 过滤器模块
│   ├── input-raw.c          # Linux evdev 输入 ⭐
│   ├── median.c             # 中值滤波 ⭐
│   ├── dejitter.c           # 去抖动 ⭐
│   ├── linear.c             # 线性校准 ⭐
│   ├── iir.c                # IIR 滤波
│   ├── pthres.c             # 压力阈值
│   └── ...
├── tests/                    # 测试程序
│   ├── ts_print.c           # 打印示例 ⭐
│   ├── ts_print_mt.c        # 多点触摸打印
│   ├── ts_calibrate.c       # 校准工具 ⭐
│   └── ts_test_mt.c         # 图形测试
└── tools/                    # 工具程序
    └── ts_uinput.c          # 虚拟输入设备

3.2 核心数据结构

查看 src/tslib-private.h

// 设备句柄
struct tsdev {
    int fd;                   // 设备文件描述符
    char *eventpath;          // 设备路径
    struct tslib_module_info *list;  // 模块链表
    // ... 其他字段
};

// 模块信息
struct tslib_module_info {
    struct tslib_module_info *next;  // 下一个模块
    struct tslib_ops *ops;           // 操作函数表
    void *handle;                    // 动态库句柄
    // ... 其他字段
};

// 操作函数表
struct tslib_ops {
    int (*read)(struct tslib_module_info *, 
                struct ts_sample *, int);
    int (*read_mt)(struct tslib_module_info *, 
                   struct ts_sample_mt **, int, int);
    // ... 其他函数
};

3.3 初始化流程源码分析

ts_setup() – src/ts_setup.c:118

struct tsdev *ts_setup(const char *dev_name, int nonblock)
{
    struct tsdev *ts = NULL;
    
    // 1. 自动查找设备
    if (dev_name == NULL) {
        dev_name = getenv("TSLIB_TSDEVICE");  // 环境变量
        if (!dev_name) {
            // 尝试默认路径
            dev_name = "/dev/input/ts";
            // 或扫描 /dev/input/event*
        }
    }
    
    // 2. 打开设备
    ts = ts_open(dev_name, nonblock);
    if (!ts)
        return NULL;
    
    // 3. 加载配置
    if (ts_config(ts) != 0) {
        ts_close(ts);
        return NULL;
    }
    
    return ts;
}

学习要点

  • 自动查找设备的策略
  • 环境变量的使用
  • 错误处理

ts_config() – src/ts_config.c:54

int ts_config(struct tsdev *ts)
{
    FILE *f;
    char buf[512];
    
    // 1. 打开配置文件 /etc/ts.conf
    f = fopen(conffile, "r");
    
    // 2. 逐行解析
    while (fgets(buf, sizeof(buf), f)) {
        char *module_name, *params;
        
        // 跳过注释和空行
        if (buf[0] == '#' || buf[0] == '\n')
            continue;
        
        // 解析:module_raw input
        // 或:  module median depth=3
        
        // 3. 加载模块
        if (strncmp(tok, "module_raw", 10) == 0) {
            // 加载 raw 模块
        } else if (strncmp(tok, "module", 6) == 0) {
            // 加载过滤器模块
            ts_load_module(ts, module_name, params);
        }
    }
    
    fclose(f);
    return 0;
}

学习要点

  • 配置文件解析
  • 模块加载顺序
  • 链表构建

3.4 数据读取流程

ts_read() – src/ts_read.c

int ts_read(struct tsdev *ts, struct ts_sample *samp, int nr)
{
    // 调用第一个模块的 read 函数
    return ts->list->ops->read(ts->list, samp, nr);
}

关键点:数据从模块链表头部开始流动

模块链的执行

应用调用: ts_read(ts, &samp, 1)
    ↓
linear->read()           // 校准模块
    ↓ 调用下一个
dejitter->read()         // 去抖动模块
    ↓ 调用下一个
median->read()           // 中值滤波
    ↓ 调用下一个
input_raw->read()        // 从设备读取
    ↓ 返回原始数据
median 处理数据 → 返回
    ↓
dejitter 处理数据 → 返回
    ↓
linear 处理数据 → 返回
    ↓ 返回到应用
应用得到过滤后的数据

3.5 过滤器模块源码分析

median 模块 – plugins/median.c

查看核心函数:

static int median_read(struct tslib_module_info *info,
                       struct ts_sample *samp, int nr)
{
    struct tslib_median *m = (struct tslib_median *)info;
    int ret, i;
    
    // 1. 从下一个模块读取数据
    ret = info->next->ops->read(info->next, samp, nr);
    if (ret <= 0)
        return ret;
    
    // 2. 对每个样本进行中值滤波
    for (i = 0; i < ret; i++) {
        // 保存到历史缓冲区
        m->buf[m->index] = samp[i];
        m->index = (m->index + 1) % m->depth;
        
        // 计算中值
        samp[i] = calc_median(m->buf, m->depth);
    }
    
    return ret;
}

学习要点

  • 每个模块都调用下一个模块
  • 处理数据后返回
  • 形成责任链模式

linear 模块 – plugins/linear.c

查看校准计算:

static int linear_read(struct tslib_module_info *info,
                       struct ts_sample *samp, int nr)
{
    struct tslib_linear *lin = (struct tslib_linear *)info;
    int ret, i;
    
    // 1. 读取数据
    ret = info->next->ops->read(info->next, samp, nr);
    
    // 2. 应用线性变换
    for (i = 0; i < ret; i++) {
        int x = samp[i].x;
        int y = samp[i].y;
        
        // xs = a0 + a1*x + a2*y
        samp[i].x = (lin->a[0] + lin->a[1]*x + lin->a[2]*y) 
                    / lin->a[6];
        
        // ys = b0 + b1*x + b2*y
        samp[i].y = (lin->a[3] + lin->a[4]*x + lin->a[5]*y) 
                    / lin->a[6];
    }
    
    return ret;
}

3.6 模块加载机制

ts_load_module() – src/ts_load_module.c

int ts_load_module(struct tsdev *ts, 
                   const char *module, 
                   const char *params)
{
    void *handle;
    struct tslib_module_info *info;
    
    // 1. 动态加载 .so 文件
    snprintf(fn, sizeof(fn), "%s/%s.so", 
             PLUGIN_DIR, module);
    handle = dlopen(fn, RTLD_NOW);
    
    // 2. 查找 mod_init 函数
    init = dlsym(handle, "mod_init");
    
    // 3. 调用初始化函数
    info = init(ts, params);
    
    // 4. 插入模块链表
    info->next = ts->list;
    ts->list = info;
    
    return 0;
}

学习要点

  • 动态库加载
  • 插件架构
  • 链表插入顺序(头插法)

3.7 设计模式分析

责任链模式

Client → Handler1 → Handler2 → Handler3
          ↓          ↓          ↓
       Process    Process    Process

在 tslib 中:

ts_read() → linear → dejitter → median → input_raw
             ↓        ↓          ↓         ↓
           校准     去抖动    中值滤波   读取设备

插件模式

每个模块都是独立的 .so 文件:

  • 通过 mod_init() 初始化
  • 提供统一的 ops 接口
  • 可以动态加载/卸载

3.8 实践练习

练习 1:追踪数据流

# 添加调试信息
cd plugins
# 在 median.c 的 median_read 函数中添加 printf
# 重新编译观察数据流动

练习 2:阅读源码清单 按顺序阅读以下文件:

  1. src/tslib.h – API 定义
  2. src/ts_setup.c – 初始化
  3. src/ts_config.c – 配置解析
  4. plugins/input-raw.c – 设备读取
  5. plugins/median.c – 过滤器示例

练习 3:画出调用关系图

ts_setup()
  ├─ ts_open()
  │   └─ open("/dev/input/eventX")
  └─ ts_config()
      ├─ fopen("/etc/ts.conf")
      └─ ts_load_module()
          ├─ dlopen("median.so")
          └─ mod_init()

3.9 小结

掌握的知识

  • ✅ tslib 架构设计
  • ✅ 核心 API 实现
  • ✅ 模块加载机制
  • ✅ 责任链模式

下一步:实践项目


第四阶段:实践项目

学习目标

  • ✅ 编写实用的触摸应用
  • ✅ 实现自定义过滤器
  • ✅ 嵌入式系统移植

4.1 项目:简单绘图程序

创建 draw_app.c 绘图程序,实现简单的手势识别,以及自定义过滤器。

4.2 嵌入式系统移植

交叉编译步骤

# 设置交叉编译器
export CC=arm-linux-gnueabihf-gcc
export CXX=arm-linux-gnueabihf-g++

# 配置
./configure --host=arm-linux-gnueabihf \
            --prefix=/opt/tslib \
            --enable-static

# 编译安装
make -j4
make DESTDIR=$PWD/install install

4.3 小结

完成实践项目后,你已经掌握了 tslib 的实际应用。


附录:源码导航

A.1 核心文件索引

公共 API(必读)

  • src/tslib.h – API 定义 ⭐⭐⭐⭐⭐
  • src/ts_setup.c – 初始化流程 ⭐⭐⭐⭐⭐
  • src/ts_read.c – 读取接口 ⭐⭐⭐⭐
  • src/ts_config.c – 配置解析 ⭐⭐⭐⭐

示例程序(入门)

  • tests/ts_print.c – 最简单示例 (115行)
  • tests/ts_print_mt.c – 多点触摸示例
  • tests/ts_calibrate.c – 校准工具

过滤器模块(深入)

  • plugins/median.c – 中值滤波
  • plugins/dejitter.c – 去抖动
  • plugins/linear.c – 线性校准 ⭐⭐⭐⭐
  • plugins/input-raw.c – Linux输入

A.2 学习路线代码清单

第1天:快速入门

# 阅读文档
cat README.md

# 运行测试
ts_print
ts_test_mt

# 编写程序
gcc my_first_touch.c -lts

第2天:理解原理

# 编辑配置
sudo nano /etc/ts.conf

# 对比数据
ts_print --raw
ts_print

# 校准
ts_calibrate

第3天:阅读源码 按顺序阅读:

  1. src/tslib.h
  2. src/ts_setup.c
  3. src/ts_config.c
  4. plugins/input-raw.c
  5. plugins/median.c

第4天:实践项目 编写实际应用程序。

A.3 常用命令速查

# 查找设备
ts_finddev

# 打印事件
ts_print                  # 过滤后
ts_print --raw            # 原始数据

# 校准
ts_calibrate

# 图形测试
ts_test_mt

# 查看配置
cat /etc/ts.conf
cat /etc/pointercal

# 编译程序
gcc app.c -lts -o app

A.4 环境变量

export TSLIB_TSDEVICE=/dev/input/event0
export TSLIB_CONFFILE=/etc/ts.conf
export TSLIB_CALIBFILE=/etc/pointercal
export TSLIB_PLUGINDIR=/usr/local/lib/ts
export LD_LIBRARY_PATH=/usr/local/lib

A.5 故障排除

问题解决方案
ts_setup() 返回 NULLchmod 666 /dev/input/event*
找不到 tslib.h设置 C_INCLUDE_PATH
找不到 libts.so设置 LD_LIBRARY_PATH
坐标不准运行 ts_calibrate
无响应检查 /etc/ts.conf

A.6 参考资源


总结

核心知识点

  1. tslib 三大作用

    • 校准:触摸坐标到屏幕坐标
    • 过滤:去噪声、抖动
    • 抽象:统一 API
  2. 数据流
    硬件 → 驱动 → raw模块 → 过滤器链 → 应用


  3. 核心 API

    • ts_setup() – 初始化
    • ts_read() – 读取数据
    • ts_close() – 关闭
  4. 配置文件结构

    • module_raw input
    • module median depth=3
    • module dejitter delta=100
    • module linear

学习成果

完成本手册后,你应该能够:

  • ✅ 解释 tslib 的作用和原理
  • ✅ 配置和校准触摸屏
  • ✅ 编写触摸屏应用程序
  • ✅ 阅读和理解 tslib 源码
  • ✅ 移植 tslib 到嵌入式系统

继续学习

  • 深入 Linux input 子系统
  • 学习 GUI 框架集成
  • 参与开源社区

文档版本:v1.0
适用版本:tslib 1.21
最后更新:2026-07-06

祝你学习愉快!

上一篇 tslib 使用手册1

tslib 使用手册 目录 简介 什么是 tslib? tslib 是一个 C 语言编写的触摸屏事件过滤库,主要功能包括...

下一篇 tslib 交叉编译完整指南(x86 Linux → ARM)

tslib 交叉编译完整指南(x86 Linux → ARM) 本文档详细介绍如何在 x86 Linux 平台上交叉编译...