目录
- 1. tslib开源库介绍
- 1.1 tslib主要功能
- 1.2 架构
- 2. tslib代码简单分析
- 2.1 ts_print_mt.c分析代码
- 2.2 ts_setup代码分析
- 2.3 ts_open代码分析
- 2.4 ts_config代码分析
- 2.5 ts_read_mt代码分析
- 2.6 tslib中4个模块的含义
- 3. 使用tslib库打印触摸屏2点之间的距离
基于韦东山IMX6ULL开发板和配套资料学习
参考教程:韦东山老师教程
1. tslib开源库介绍
tslib开源库地址是:http://www.tslib.org/
tslib
是一个开源的触摸屏校准和事件处理库,广泛用于嵌入式系统和 Linux 系统中。它提供了一套工具和库函数,用于校准触摸屏、处理触摸事件,并将原始触摸数据转换为可用于应用程序的标准化事件。
1.1 tslib主要功能
-
触摸屏校准:
tslib
提供了一个校准工具ts_calibrate
,用于校准触摸屏,生成校准参数文件。 -
事件处理:
tslib
可以处理触摸屏的原始事件,过滤噪声,平滑触摸轨迹,并将处理后的事件传递给应用程序。 -
插件架构:
tslib
采用插件架构,支持多种输入设备和不同的校准算法。 -
标准化输出:
tslib
将不同触摸屏设备的原始数据转换为标准化的事件格式,便于应用程序使用。
1.2 架构
tslib
的架构主要包括以下几个部分:
-
核心库:
-
libts
:核心库,提供了触摸屏事件处理的基本功能,包括读取事件、校准、滤波等。
-
-
工具:
-
ts_calibrate
:用于校准触摸屏。 -
ts_test
:用于测试触摸屏的响应。 -
ts_print
:用于打印触摸屏事件。 -
ts_uinput
:用于生成虚拟的触摸屏事件。
-
-
插件:
-
linear
:线性校准插件。 -
dejitter
:去抖动插件。 -
palm_detect
:手掌检测插件。 -
其他插件:根据需要扩展的其他处理模块。
-
核心在于“plugins”目录里的“插件”,或称为“module”。这个目录下的每个文件都是一个module,每个module都提供2个函数:read、read_mt,前者用于读取单点触摸屏的数据,后者用于读取多点触摸屏的数据。
参考ts_test.c和ts_test_mt.c,前者用于一般触摸屏(比如电阻屏、单点电容屏),后者用于多点触摸屏。
2. tslib代码简单分析
tslib的框架图:
顺着tslib的框架图对ts_print_mt.c代码进行逐步分析。
2.1 ts_print_mt.c分析代码
ts_print_mt.c文件大致内容分析:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <tslib.h>
#include <linux/input.h> // 可能需要包含此头文件以支持ioctl和ABS_MT_SLOT
// 假设usage函数在其他地方定义,用于打印程序的使用方法
void usage(char **argv);
// 假设errfn和openfn函数在其他地方定义,用于错误处理和特定打开逻辑
void errfn(const char *fmt, ...);
int openfn(const char *device, int flags);
int main(int argc, char **argv)
{
struct tsdev *ts; // 指向触摸屏设备的指针
char *tsdevice = NULL; // 触摸屏设备名称
struct ts_sample_mt **samp_mt = NULL; // 指向多点触控样本数据的指针数组
#ifdef TS_HAVE_EVDEV
struct input_absinfo slot; // 用于存储触摸槽位信息的结构体
#endif
int32_t user_slots = 0; // 用户指定的槽位数
int32_t max_slots = 1; // 最大槽位数,默认为1
int ret, i, j; // 返回值、循环变量
int read_samples = 1; // 要读取的样本数
short non_blocking = 0; // 是否非阻塞模式
short raw = 0; // 是否读取原始数据
struct ts_lib_version_data *ver = ts_libversion(); // 获取tslib版本信息
// 检查tslib版本,如果低于1.10,则提示升级
#ifndef TSLIB_VERSION_MT /* < 1.10 */
printf("You are running an old version of tslib. Please upgrade.\n");
#endif
// 解析命令行参数
while (1) {
const struct option long_options[] = {
{ "help", no_argument, NULL, 'h' },
{ "idev", required_argument, NULL, 'i' },
{ "samples", required_argument, NULL, 's' },
{ "non-blocking", no_argument, NULL, 'n' },
{ "raw", no_argument, NULL, 'r' },
{ "slots", required_argument, NULL, 'j' },
{ "version", no_argument, NULL, 'v' },
};
int option_index = 0;
int c = getopt_long(argc, argv, "hvi:s:nrj:", long_options, &option_index);
errno = 0;
if (c == -1)
break;
switch (c) {
case 'h':
usage(argv);
return 0;
case 'v':
printf("%s\n", tslib_version());
return 0;
case 'i':
tsdevice = optarg; // 设置触摸屏设备名称
break;
case 'n':
non_blocking = 1; // 设置非阻塞模式
break;
case 'r':
raw = 1; // 设置读取原始数据
break;
case 's':
read_samples = atoi(optarg); // 设置要读取的样本数
if (read_samples <= 0) {
usage(argv);
return 0;
}
break;
case 'j':
user_slots = atoi(optarg); // 设置用户指定的槽位数
if (user_slots <= 0) {
usage(argv);
return 0;
}
break;
default:
usage(argv);
return 0;
}
if (errno) {
char str[9];
sprintf(str, "option ?");
str[7] = c & 0xff;
perror(str);
}
}
ts_error_fn = errfn; // 设置错误处理函数
#ifdef TSLIB_VERSION_OPEN_RESTRICTED
// 如果tslib支持受限打开功能,则设置特定的打开函数
if (ver->features & TSLIB_VERSION_OPEN_RESTRICTED)
ts_open_restricted = openfn;
#endif
// 根据是否非阻塞模式,设置触摸屏设备
if (non_blocking)
ts = ts_setup(tsdevice, 1);
else
ts = ts_setup(tsdevice, 0);
if (!ts) {
perror("ts_setup");
return errno;
}
// 打印打开的tslib版本和设备路径
printf("libts %06X opened device %s\n",
ver->version_num, ts_get_eventpath(ts));
#ifdef TS_HAVE_EVDEV
// 获取触摸槽位信息,并计算最大槽位数
if (ioctl(ts_fd(ts), EVIOCGABS(ABS_MT_SLOT), &slot) < 0) {
perror("ioctl EVIOGABS");
ts_close(ts);
return errno;
}
max_slots = slot.maximum + 1 - slot.minimum;
#endif
// 如果用户指定了槽位数,则使用用户指定的值
if (user_slots > 0)
max_slots = user_slots;
// 分配内存以存储样本数据
samp_mt = malloc(read_samples * sizeof(struct ts_sample_mt *));
if (!samp_mt) {
ts_close(ts);
return -ENOMEM;
}
for (i = 0; i < read_samples; i++) {
samp_mt[i] = calloc(max_slots, sizeof(struct ts_sample_mt));
if (!samp_mt[i]) {
free(samp_mt);
ts_close(ts);
return -ENOMEM;
}
}
// 循环读取触摸数据
while (1) {
if (raw)
ret = ts_read_raw_mt(ts, samp_mt, max_slots, read_samples); // 读取原始多点触控数据
else
ret = ts_read_mt(ts, samp_mt, max_slots, read_samples); // 读取多点触控数据
if (ret < 0) {
if (non_blocking) {
printf("ts_print_mt: read returns %d\n", ret);
continue;
}
perror("ts_read_mt");
ts_close(ts);
exit(1);
}
// 打印读取到的触摸数据
for (j = 0; j < ret; j++) {
for (i = 0; i < max_slots; i++) {
if (!(samp_mt[j][i].valid & TSLIB_MT_VALID))
continue;
// 假设YELLOW和RESET是定义的宏,用于改变输出文本的颜色
printf(YELLOW "sample %d - %ld.%06ld -" RESET " (slot %d) %6d %6d %6d\n",
j,
samp_mt[j][i].tv.tv_sec,
samp_mt[j][i].tv.tv_usec,
samp_mt[j][i].slot,
samp_mt[j][i].x,
samp_mt[j][i].y,
samp_mt[j][i].pressure);
}
}
}
ts_close(ts); // 关闭触摸屏设备
}
遵循tslib架构图中ts_setup、ts_read_mt、ts_close顺序来读取触摸屏的输入数据。
2.2 ts_setup代码分析
tslib中ts_setup函数简单分析,ts_setup中主要是执行ts_open和ts_config函数,对tsdev结构体进行设置:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <tslib.h>
// 定义默认设备名称数组
const char *ts_name_default[] = {
"/dev/input/touchscreen0",
"/dev/input/event0",
NULL
};
// 设置错误处理函数
void ts_error(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
}
// 扫描设备函数(假设已实现)
char *scan_devices(void) {
// 实现扫描设备的逻辑
return "/dev/input/event0"; // 示例返回值
}
/**
* @brief 设置并打开触摸屏设备
*
* @param dev_name 设备名称,如果为NULL则使用环境变量TSLIB_TSDEVICE或默认设备
* @param nonblock 是否使用非阻塞模式
* @return 成功返回触摸屏设备句柄,失败返回NULL
*/
struct tsdev *ts_setup(const char *dev_name, int nonblock)
{
const char * const *defname;
struct tsdev *ts = NULL;
#if defined (__linux__)
char *fname = NULL;
#endif /* __linux__ */
// 如果dev_name为空,则尝试从环境变量TSLIB_TSDEVICE获取设备名称
dev_name = dev_name ? dev_name : getenv("TSLIB_TSDEVICE");
// 尝试打开指定的设备
if (dev_name != NULL) {
ts = ts_open(dev_name, nonblock); // 打开设备
} else {
// 如果没有指定设备名称,尝试打开默认设备列表中的设备
defname = &ts_name_default[0];
while (*defname != NULL) {
ts = ts_open(*defname, nonblock); // 尝试打开默认设备
if (ts != NULL)
break; // 找到设备后跳出循环
++defname; // 继续下一个默认设备
}
}
#if defined (__linux__)
// 如果仍然没有找到设备,尝试扫描所有设备
if (!ts) {
fname = scan_devices(); // 扫描设备
if (!fname)
return NULL; // 扫描失败,返回NULL
ts = ts_open(fname, nonblock); // 打开扫描到的设备
free(fname); // 释放扫描结果的内存
}
#endif /* __linux__ */
// 如果成功打开设备,尝试配置设备
if (ts && ts_config(ts) != 0) {
ts_error("ts_config: %s\n", strerror(errno)); // 配置失败,打印错误信息
ts_close(ts); // 关闭设备
return NULL; // 返回NULL
}
return ts; // 返回设备句柄
}
其中tsdev结构体:
/**
* @brief 触摸屏设备结构体
*
* 该结构体用于表示触摸屏设备及其相关信息。
*/
struct tsdev {
int fd; // 设备文件描述符
char *eventpath; // 设备文件路径
struct tslib_module_info *list; // 模块链表头指针
/**
* 指向模块链表中提供原始读取功能的模块。
* 默认情况下,指向 `ts_read_raw` 模块。
*/
struct tslib_module_info *list_raw;
unsigned int res_x; // 触摸屏的X轴分辨率
unsigned int res_y; // 触摸屏的Y轴分辨率
int rotation; // 触摸屏的旋转角度
};
2.3 ts_open代码分析
ts_open函数的主要作用是打开设备文件,把文件描述符保存到tsdev的fd设备描述符中:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <tslib.h>
// 假设 print_host_os 函数已经实现
void print_host_os(void) {
// 打印主机操作系统信息
printf("Host OS: Linux\n"); // 示例输出
}
// 假设 ts_open_restricted 函数已经实现
int (*ts_open_restricted)(const char *path, int flags, void *data) = NULL;
/**
* @brief 打开触摸屏设备
*
* @param name 设备文件名
* @param nonblock 是否使用非阻塞模式
* @return 成功返回触摸屏设备句柄,失败返回NULL
*/
struct tsdev *ts_open(const char *name, int nonblock)
{
struct tsdev *ts;
int flags = O_RDWR; // 默认以读写模式打开设备
#ifdef DEBUG
print_host_os(); // 打印主机操作系统信息
printf(", trying to open %s\n", name); // 打印尝试打开的设备文件名
#endif
// 如果需要非阻塞模式
if (nonblock) {
#ifndef WIN32
flags |= O_NONBLOCK; // 在非Windows系统上设置非阻塞标志
#endif
}
// 分配内存用于tsdev结构体
ts = malloc(sizeof(struct tsdev));
if (!ts)
return NULL; // 内存分配失败,返回NULL
// 初始化tsdev结构体
memset(ts, 0, sizeof(struct tsdev));
// 复制设备文件名
ts->eventpath = strdup(name);
if (!ts->eventpath)
goto free; // 复制设备文件名失败,跳转到free标签
// 如果设置了受限打开函数
if (ts_open_restricted) {
ts->fd = ts_open_restricted(name, flags, NULL); // 使用受限打开函数打开设备
if (ts->fd == -1)
goto free; // 打开设备失败,跳转到free标签
return ts; // 打开设备成功,返回设备句柄
}
// 使用标准open函数打开设备
ts->fd = open(name, flags);
/*
* 如果打开失败且错误码为EACCES(权限不足),尝试以只读模式打开设备
* 这对于大多数驱动程序来说是足够的
*/
if (ts->fd == -1 && errno == EACCES) {
#ifndef WIN32
flags = nonblock ? (O_RDONLY | O_NONBLOCK) : O_RDONLY; // 设置只读模式和非阻塞标志
#else
flags = O_RDONLY; // Windows系统上设置只读模式
#endif
ts->fd = open(name, flags); // 以只读模式重新打开设备
}
if (ts->fd == -1)
goto free; // 打开设备失败,跳转到free标签
return ts; // 打开设备成功,返回设备句柄
free