tslib 学习手册
目录
学习路线图
入门(1-2小时) 原理(2-3小时) 源码(3-5小时) 实践(按需)
↓ ↓ ↓ ↓
阅读文档 理解配置文件 阅读核心源码 编写应用程序
运行示例 数据流分析 模块化设计 自定义过滤器
简单测试 过滤器原理 API实现细节 嵌入式移植
推荐学习顺序
新手(只想使用):
- 阅读本手册”第一阶段”
- 运行 ts_calibrate 和 ts_test_mt
- 编写简单的触摸应用
开发者(深入理解):
- 完整学习四个阶段
- 阅读关键源码文件
- 理解过滤器设计模式
系统集成(移植适配):
- 理解架构和数据流
- 学习交叉编译
- 编写设备驱动接口
第一阶段:快速入门
学习目标
- ✅ 理解 tslib 是什么
- ✅ 运行第一个示例程序
- ✅ 掌握基本 API 使用
1.1 什么是 tslib?
核心功能
触摸硬件 → 驱动 → tslib 过滤 → 应用程序
↓
[去噪声 + 校准]
三个关键作用:
- 校准:触摸坐标 → 屏幕坐标
- 过滤:去除噪声和抖动
- 抽象:统一的 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
显示 5 个校准点:
1 ←─────────→ 2
↑ ↑
│ 5 │
↓ ↓
3 ←─────────→ 4你点击每个点,记录:
- 触摸坐标 (xt, yt)
- 屏幕坐标 (xs, ys)
计算变换矩阵:
[xs] [a0] [a1 a2] [xt]
[ys] = [b0] + [b1 b2] [yt]保存到
/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:阅读源码清单 按顺序阅读以下文件:
src/tslib.h– API 定义src/ts_setup.c– 初始化src/ts_config.c– 配置解析plugins/input-raw.c– 设备读取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天:阅读源码 按顺序阅读:
- src/tslib.h
- src/ts_setup.c
- src/ts_config.c
- plugins/input-raw.c
- 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() 返回 NULL | chmod 666 /dev/input/event* |
| 找不到 tslib.h | 设置 C_INCLUDE_PATH |
| 找不到 libts.so | 设置 LD_LIBRARY_PATH |
| 坐标不准 | 运行 ts_calibrate |
| 无响应 | 检查 /etc/ts.conf |
A.6 参考资源
- 官网:http://tslib.org
- GitHub:https://github.com/libts/tslib
- 文档:README.md 和 man 手册
总结
核心知识点
tslib 三大作用:
- 校准:触摸坐标到屏幕坐标
- 过滤:去噪声、抖动
- 抽象:统一 API
数据流:
硬件 → 驱动 → raw模块 → 过滤器链 → 应用核心 API:
- ts_setup() – 初始化
- ts_read() – 读取数据
- ts_close() – 关闭
配置文件结构:
- 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
祝你学习愉快!