目录
基于韦东山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:
// 释放已分配的内存
if (ts->eventpath)
free(ts->eventpath); // 释放设备文件名字符串
free(ts); // 释放tsdev结构体
return NULL; // 返回NULL
}
2.4 ts_config代码分析
ts_config读取/etc/ts.conf文件内容,和“plugins”目录里的“插件(module)”对应:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <tslib.h>
#define BUF_SIZE 1024
// 假设 discard_null_tokens 和 ts_load_module、ts_load_module_raw 函数已经实现
void discard_null_tokens(char **p, char **tok) {
// 处理空token
while (*tok && **tok == '\0') {
*tok = *p;
*p = strchr(*p, ' ');
if (*p) {
**p = '\0';
*p += 1;
}
}
}
int ts_load_module(struct tsdev *ts, const char *module, const char *params)
{
return __ts_load_module(ts, module, params, 0);
}
int ts_load_module_raw(struct tsdev *ts, const char *module, const char *params)
{
return __ts_load_module(ts, module, params, 1);
}
/**
* @brief 配置触摸屏设备
*
* @param ts 触摸屏设备句柄
* @param conffile_modules 模块名称数组
* @param conffile_params 模块参数数组
* @param raw 标记是否为原始模块的数组
* @return 成功返回0,失败返回-1
*/
static int __ts_config(struct tsdev *ts, char **conffile_modules,
char **conffile_params, int *raw)
{
char buf[BUF_SIZE], *p;
FILE *f;
int line = 0;
int ret = 0;
short strdup_allocated = 0;
char *conffile;
// 获取配置文件路径
if ((conffile = getenv("TSLIB_CONFFILE")) == NULL) {
conffile = strdup(TS_CONF); // 使用默认配置文件路径
if (conffile) {
strdup_allocated = 1; // 标记已分配内存
} else {
ts_error("Couldn't find tslib config file: %s\n", strerror(errno));
return -1; // 获取配置文件路径失败
}
}
// 打开配置文件
f = fopen(conffile, "r");
if (!f) {
if (strdup_allocated)
free(conffile); // 释放已分配的内存
ts_error("Couldn't open tslib config file %s: %s\n", conffile, strerror(errno));
return -1; // 打开配置文件失败
}
buf[BUF_SIZE - 2] = '\0'; // 确保缓冲区末尾为null终止
while ((p = fgets(buf, BUF_SIZE, f)) != NULL) {
char *e;
char *tok;
char *module_name;
line++; // 行号递增
// 去除行尾换行符
e = strchr(p, '\n');
if (e)
*e = '\0';
// 检查是否读取了完整的一行
if (buf[BUF_SIZE - 2] != '\0') {
ts_error("%s: line %d too long\n", conffile, line);
break; // 行太长,退出循环
}
#if !defined HAVE_STRSEP
tok = ts_strsep(&p, " \t"); // 使用自定义的ts_strsep函数
#else
tok = strsep(&p, " \t"); // 使用标准的strsep函数
#endif
discard_null_tokens(&p, &tok); // 处理空token
// 忽略注释或空白行
if (p == NULL || *tok == '#')
continue;
// 搜索选项
if (strcasecmp(tok, "module") == 0) {
#if !defined HAVE_STRSEP
module_name = ts_strsep(&p, " \t"); // 使用自定义的ts_strsep函数
#else
module_name = strsep(&p, " \t"); // 使用标准的strsep函数
#endif
discard_null_tokens(&p, &module_name); // 处理空token
if (!conffile_modules) {
ret = ts_load_module(ts, module_name, p); // 加载模块
} else {
#ifdef DEBUG
printf("TSLIB_CONFFILE: module %s %s\n", module_name, p); // 调试信息
#endif
sprintf(conffile_modules[line], "%s", module_name); // 存储模块名称
if (conffile_params)
sprintf(conffile_params[line], "%s", p); // 存储模块参数
}
} else if (strcasecmp(tok, "module_raw") == 0) {
#if !defined HAVE_STRSEP
module_name = ts_strsep(&p, " \t"); // 使用自定义的ts_strsep函数
#else
module_name = strsep(&p, " \t"); // 使用标准的strsep函数
#endif
discard_null_tokens(&p, &module_name); // 处理空token
if (!conffile_modules) {
ret = ts_load_module_raw(ts, module_name, p); // 加载原始模块
} else {
#ifdef DEBUG
printf("TSLIB_CONFFILE: module_raw %s %s\n", module_name, p); // 调试信息
#endif
sprintf(conffile_modules[line], "%s", module_name); // 存储模块名称
if (conffile_params)
sprintf(conffile_params[line], "%s", p); // 存储模块参数
if (raw)
raw[line] = 1; // 标记为原始模块
}
} else {
ts_error("%s: Unrecognised option %s:%d:%s\n", conffile, line, tok);
break; // 未知选项,退出循环
}
if (ret != 0) {
ts_error("Couldn't load module %s\n", module_name);
break; // 加载模块失败,退出循环
}
}
if (ts->list_raw == NULL) {
ts_error("No raw modules loaded.\n");
ret = -1; // 没有加载任何原始模块
}
fclose(f); // 关闭配置文件
if (strdup_allocated)
free(conffile); // 释放已分配的内存
return ret; // 返回结果
}
ts_load_module()和ts_load_module_raw()代码分析:
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h> // 用于动态链接库操作
#include <tslib.h>
// 假设 __ts_load_module_static 和 __ts_attach、__ts_attach_raw 函数已经实现
/**
* @brief 静态加载触摸屏模块
*
* @param ts 触摸屏设备句柄
* @param module 模块名称
* @param params 模块参数
* @return 成功返回模块信息指针,失败返回NULL
*/
static struct tslib_module_info *__ts_load_module_static(struct tsdev *ts,
const char *module,
const char *params)
{
struct tslib_module_info *info = NULL;
struct tslib_module_desc *result;
struct tslib_module_desc key;
// 设置查找键
key.name = module;
// 使用二分查找在模块列表中查找匹配的模块描述
result = bsearch(&key, tslib_modules, countof(tslib_modules),
sizeof(struct tslib_module_desc), cmp_name);
// 如果没有找到匹配的模块,返回NULL
if (!result)
return NULL;
// 调用模块的初始化函数
info = result->mod_init(ts, params);
#ifdef DEBUG
// 调试信息:模块初始化结果
fprintf(stderr, "static module %s init %s\n", module,
info ? "succeeded" : "failed");
#endif
// 如果初始化成功,设置模块句柄为NULL
if (info)
info->handle = NULL;
// 返回模块信息指针
return info;
}
/**
* @brief 将模块附加到触摸屏设备的原始模块链表
*
* @param ts 触摸屏设备句柄
* @param info 模块信息指针
* @return 成功返回0,失败返回-1
*/
int __ts_attach_raw(struct tsdev *ts, struct tslib_module_info *info)
{
struct tslib_module_info *next, *prev, *prev_list = ts->list_raw;
// 将模块信息中的设备指针设置为当前设备
info->dev = ts;
// 将新模块插入到原始模块链表的头部
info->next = prev_list;
ts->list_raw = info;
/*
* 确保正常模块链表的最后一个模块指向原始模块链表的头部。
*/
if (ts->list == NULL || ts->list == prev_list) {
/* 如果主链表为空,或者主链表的最后一个模块就是原始模块链表的第一个模块 */
/* 则将主链表的头部指向新插入的模块 */
ts->list = info;
return 0;
}
// 遍历正常模块链表,找到最后一个模块
for (next = ts->list, prev = next;
next != NULL && next != prev_list;
next = prev->next, prev = next);
// 将正常模块链表的最后一个模块的 next 指针指向新插入的模块
prev->next = info;
return 0;
}
/**
* @brief 将模块附加到触摸屏设备的模块链表
*
* @param ts 触摸屏设备句柄
* @param info 模块信息指针
* @return 成功返回0,失败返回-1
*/
int __ts_attach(struct tsdev *ts, struct tslib_module_info *info)
{
// 将模块信息中的设备指针设置为当前设备
info->dev = ts;
// 将新模块插入到模块链表的头部
info->next = ts->list;
ts->list = info;
// 返回0表示成功
return 0;
}
/**
* @brief 加载触摸屏模块
*
* @param ts 触摸屏设备句柄
* @param module 模块名称
* @param params 模块参数
* @param raw 是否为原始模块
* @return 成功返回0,失败返回-1
*/
static int __ts_load_module(struct tsdev *ts, const char *module,
const char *params, int raw)
{
struct tslib_module_info *info;
void *handle;
int ret;
#ifdef DEBUG
if (params)
printf("Loading module %s (%s)\n", module, params); // 调试信息:加载模块及参数
else
printf("Loading module %s\n", module); // 调试信息:加载模块
#endif
// 尝试静态加载模块
info = __ts_load_module_static(ts, module, params);
#ifdef HAVE_LIBDL
// 如果静态加载失败,尝试动态加载模块
if (!info)
info = __ts_load_module_shared(ts, module, params);
#endif
// 如果加载失败,返回-1
if (!info)
return -1;
// 根据是否为原始模块,调用相应的附加函数
if (raw)
ret = __ts_attach_raw(ts, info); // 附加原始模块
else
ret = __ts_attach(ts, info); // 附加普通模块
// 如果附加失败,进行清理
if (ret) {
#ifdef DEBUG
ts_error("Can't attach %s\n", module); // 调试信息:无法附加模块
#endif
handle = info->handle; // 获取模块句柄
// 调用模块的fini函数进行清理
if (info->ops->fini)
info->ops->fini(info);
#ifdef HAVE_LIBDL
// 如果模块句柄有效,关闭动态链接库
if (handle)
dlclose(handle);
#endif
}
return ret; // 返回结果
}
2.5 ts_read_mt代码分析
tslib架构中看到添加了4个模块:module_raw input、module pthres pmin=1、module dejitter delta=100、module linear,经过ts_setup配置后形成的链表是:
数据的流向是:触摸屏数据->input模块->pthres模块->dejitter模块->linear模块。
从代码层面分析:
2.6 tslib中4个模块的含义
-
module_raw input:读取原始触摸屏数据。
-
module pthres pmin=1:设置触摸屏的最小压力阈值,过滤掉压力值低于指定阈值的触摸点。
-
module dejitter delta=100:减少触摸点的抖动,通过平滑算法使触摸点的移动更加平滑。
-
module linear:进行线性校准,将触摸点的坐标映射到屏幕的实际坐标系中。
对每个模块的详细解析:
-
module_raw input
-
功能:读取原始触摸屏数据。
-
用途:这个模块通常作为其他模块的基础,提供最原始的触摸屏数据。它不进行任何处理,直接将数据传递给下一个模块。
-
示例配置:
module_raw input
-
说明:
- input 是一个常见的原始输入模块,它从触摸屏设备读取原始数据。
- 这个模块通常放在模块链的最前面,确保其他模块可以接收到未经处理的原始数据。
-
-
module pthres pmin=1
-
功能:设置触摸屏的最小压力阈值。
-
用途:这个模块用于过滤掉压力值低于指定阈值的触摸点,从而减少误触。
-
配置示例:
module pthres pmin=1
-
参数:
- pmin:最小压力阈值。只有当触摸点的压力值大于或等于 pmin 时,才会被认为是有效的触摸点。
-
说明:
- 通过设置 pmin,可以过滤掉轻微的触摸或误触,提高触摸屏的准确性。
- 例如,pmin=1 表示只有当压力值大于或等于 1 时,触摸点才被认为是有效的。
-
-
module dejitter delta=100
-
功能:减少触摸点的抖动。
-
用途:这个模块通过平滑算法来减少触摸点的微小抖动,提高触摸屏的稳定性。
-
配置示例:
module dejitter delta=100
-
参数:
- delta:抖动阈值。只有当触摸点的位置变化超过 delta 时,才会被认为是有效的移动。
-
说明:
- 通过设置 delta,可以过滤掉触摸点的微小抖动,使触摸点的移动更加平滑。
- 例如,delta=100 表示只有当触摸点的位置变化超过 100 个单位时,才会被认为是有效的移动。
-
-
module linear
-
功能:进行线性校准。
-
用途:这个模块通过线性变换将触摸点的坐标映射到屏幕的实际坐标系中。它通常用于校正触摸屏的偏移和缩放。
-
配置示例:
module linear
-
说明:
- linear 模块通过线性变换将触摸点的坐标从触摸屏的物理坐标系转换到屏幕的逻辑坐标系。
- 通常需要通过校准工具(如 ts_calibrate)来生成校准参数,并将其保存到配置文件中。
- 这个模块确保触摸点的坐标与屏幕的实际位置对齐,提高触摸屏的准确性。
-
3. 使用tslib库打印触摸屏2点之间的距离
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <linux/input.h>
#include <sys/ioctl.h>
#include <tslib.h>
/**
* @brief 计算两点之间的距离平方
*
* @param point1 第一个点
* @param point2 第二个点
* @return 两点之间的距离平方
*/
int distance(struct ts_sample_mt *point1, struct ts_sample_mt *point2)
{
int x = point1->x - point2->x;
int y = point1->y - point2->y;
return x * x + y * y;
}
/**
* @brief 主函数
*
* @param argc 参数个数
* @param argv 参数列表
* @return 0 表示成功,非0表示失败
*/
int main(int argc, char **argv)
{
struct tsdev *ts; // 触摸屏设备句柄
int i;
int ret;
struct ts_sample_mt **samp_mt; // 当前触摸点数据
struct ts_sample_mt **pre_samp_mt; // 上一次触摸点数据
int max_slots; // 最大触摸点数
int point_pressed[20]; // 存储按下的触摸点索引
struct input_absinfo slot; // 触摸点槽的信息
int touch_cnt = 0; // 当前按下的触摸点数量
// 初始化触摸屏设备
ts = ts_setup(NULL, 0);
if (!ts) {
printf("ts_setup err\n");
return -1;
}
// 获取触摸点槽的最大值和最小值
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;
// 分配内存用于存储当前触摸点数据
samp_mt = malloc(sizeof(struct ts_sample_mt *));
if (!samp_mt) {
ts_close(ts);
return -ENOMEM;
}
samp_mt[0] = calloc(max_slots, sizeof(struct ts_sample_mt));
if (!samp_mt[0]) {
free(samp_mt);
ts_close(ts);
return -ENOMEM;
}
// 分配内存用于存储上一次触摸点数据
pre_samp_mt = malloc(sizeof(struct ts_sample_mt *));
if (!pre_samp_mt) {
ts_close(ts);
return -ENOMEM;
}
pre_samp_mt[0] = calloc(max_slots, sizeof(struct ts_sample_mt));
if (!pre_samp_mt[0]) {
free(pre_samp_mt);
ts_close(ts);
return -ENOMEM;
}
// 初始化上一次触摸点数据的有效标志
for (i = 0; i < max_slots; i++) {
pre_samp_mt[0][i].valid = 0;
}
// 主循环
while (1) {
// 读取当前触摸点数据
ret = ts_read_mt(ts, samp_mt, max_slots, 1);
if (ret < 0) {
printf("ts_read_mt err\n");
ts_close(ts);
return -1;
}
// 更新上一次触摸点数据
for (i = 0; i < max_slots; i++) {
if (samp_mt[0][i].valid) {
memcpy(&pre_samp_mt[0][i], &samp_mt[0][i], sizeof(struct ts_sample_mt));
}
}
// 统计当前按下的触摸点数量
touch_cnt = 0;
for (i = 0; i < max_slots; i++) {
if (pre_samp_mt[0][i].valid && pre_samp_mt[0][i].tracking_id != -1) {
point_pressed[touch_cnt++] = i;
}
}
// 如果有两个触摸点按下,计算它们之间的距离
if (touch_cnt == 2) {
printf("distance: %08d\n", distance(&pre_samp_mt[0][point_pressed[0]], &pre_samp_mt[0][point_pressed[1]]));
}
}
// 释放资源
free(samp_mt[0]);
free(samp_mt);
free(pre_samp_mt[0]);
free(pre_samp_mt);
ts_close(ts);
return 0;
}
需要明确ts_sample_mt结构体的含义:
struct ts_sample_mt {
/* ABS_MT_* event codes. linux/include/uapi/linux/input-event-codes.h
* has the definitions.
*/
int x; // 触摸点的X坐标
int y; // 触摸点的Y坐标
unsigned int pressure; // 触摸点的压力值
int slot; // 触摸点的槽位编号,用于多点触控
int tracking_id; // 跟踪ID,用于标识同一个触摸点的连续事件
int tool_type; // 工具类型(例如手指、笔等)
int tool_x; // 工具的X坐标(如果有工具类型信息)
int tool_y; // 工具的Y坐标(如果有工具类型信息)
unsigned int touch_major; // 触摸区域的主要轴长度
unsigned int width_major; // 触摸工具的主要轴宽度
unsigned int touch_minor; // 触摸区域的次要轴长度
unsigned int width_minor; // 触摸工具的次要轴宽度
int orientation; // 触摸点的方向(角度)
int distance; // 触摸点与触摸屏表面的距离
int blob_id; // 触摸点的Blob ID,用于标识多个触摸点的合并
struct timeval tv; // 时间戳,记录事件发生的时间
short pen_down; // 笔触状态,1表示按下,0表示抬起
/* valid is set != 0 if this sample
* contains new data; see below for the
* bits that get set.
* valid is set to 0 otherwise
*/
short valid; // 标记该样本是否包含新数据,非0表示有新数据,0表示无新数据
};