嵌入式Linux基于IMX6ULL tslib学习总结
目录
- 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_EVDEVstruct input_absinfo slot; // 用于存储触摸槽位信息的结构体
#endifint32_t user_slots = 0; // 用户指定的槽位数int32_t max_slots = 1; // 最大槽位数,默认为1int 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);elsets = 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); // 读取原始多点触控数据elseret = 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; // 扫描失败,返回NULLts = 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 DEBUGprint_host_os(); // 打印主机操作系统信息printf(", trying to open %s\n", name); // 打印尝试打开的设备文件名
#endif// 如果需要非阻塞模式if (nonblock) {
#ifndef WIN32flags |= 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 WIN32flags = nonblock ? (O_RDONLY | O_NONBLOCK) : O_RDONLY; // 设置只读模式和非阻塞标志
#elseflags = O_RDONLY; // Windows系统上设置只读模式
#endifts->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) {// 处理空tokenwhile (*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_STRSEPtok = ts_strsep(&p, " \t"); // 使用自定义的ts_strsep函数
#elsetok = strsep(&p, " \t"); // 使用标准的strsep函数
#endifdiscard_null_tokens(&p, &tok); // 处理空token// 忽略注释或空白行if (p == NULL || *tok == '#')continue;// 搜索选项if (strcasecmp(tok, "module") == 0) {
#if !defined HAVE_STRSEPmodule_name = ts_strsep(&p, " \t"); // 使用自定义的ts_strsep函数
#elsemodule_name = strsep(&p, " \t"); // 使用标准的strsep函数
#endifdiscard_null_tokens(&p, &module_name); // 处理空tokenif (!conffile_modules) {ret = ts_load_module(ts, module_name, p); // 加载模块} else {
#ifdef DEBUGprintf("TSLIB_CONFFILE: module %s %s\n", module_name, p); // 调试信息
#endifsprintf(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_STRSEPmodule_name = ts_strsep(&p, " \t"); // 使用自定义的ts_strsep函数
#elsemodule_name = strsep(&p, " \t"); // 使用标准的strsep函数
#endifdiscard_null_tokens(&p, &module_name); // 处理空tokenif (!conffile_modules) {ret = ts_load_module_raw(ts, module_name, p); // 加载原始模块} else {
#ifdef DEBUGprintf("TSLIB_CONFFILE: module_raw %s %s\n", module_name, p); // 调试信息
#endifsprintf(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);// 如果没有找到匹配的模块,返回NULLif (!result)return NULL;// 调用模块的初始化函数info = result->mod_init(ts, params);#ifdef DEBUG// 调试信息:模块初始化结果fprintf(stderr, "static module %s init %s\n", module,info ? "succeeded" : "failed");
#endif// 如果初始化成功,设置模块句柄为NULLif (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 DEBUGif (params)printf("Loading module %s (%s)\n", module, params); // 调试信息:加载模块及参数elseprintf("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// 如果加载失败,返回-1if (!info)return -1;// 根据是否为原始模块,调用相应的附加函数if (raw)ret = __ts_attach_raw(ts, info); // 附加原始模块elseret = __ts_attach(ts, info); // 附加普通模块// 如果附加失败,进行清理if (ret) {
#ifdef DEBUGts_error("Can't attach %s\n", module); // 调试信息:无法附加模块
#endifhandle = 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表示无新数据
};
相关文章:
嵌入式Linux基于IMX6ULL tslib学习总结
目录 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…...
go中的参数传递是值传递还是引用传递?
在Go语言中,参数传递机制是一个重要的概念,它决定了函数内部对参数的修改是否会影响到原始数据。关于Go中的参数传递是值传递还是引用传递的问题,可以从以下几个方面进行解答。 一、值传递与引用传递的定义 值传递:在值传递中&a…...
记录一种在内核空间向用户空间通知中断的方法
记录一种在内核空间向用户空间通知中断的方法 0.前言1.代码实现1)内核设备驱动实现2)消息通知实现3)测试程序 2.解析 参考文章:Linux驱动实践:中断处理函数如何【发送信号】给应用层? 0.前言 最近在项目中遇到一个需求,需要将一个…...
.NetCore 过滤器和拦截器 的区别
Asp.NET Core 中的过滤器(Filter)和拦截器(Interceptor)是两个不同的概念,但它们在某些方面有相似之处,也有明显的区别。 🔑过滤器(Filter) 过滤器是Asp.NET Core中用于…...
【笔记】自动驾驶预测与决策规划_Part7_数据驱动的预测方法
文章目录 0. 前言1. 多模态传感器的编码方式1.1 栅格化表示1.2 向量化表示 Vectornet1.3 基于点云或者多模态输入的预测1.4 基于Transformer的方法 2. 网络输出的表达形式2.1 多模态轨迹回归2.2 轨迹分类2.3 轨迹回归轨迹分类2.4 目标点预测 3.场景级别的预测和决策3.1 论文&am…...
React渲染相关内容——渲染流程API、Fragment、渲染相关底层API
React渲染过程依次遇到的函数 在React的渲染流程中,从组件的创建到其UI最终呈现到屏幕上,会经历一系列的生命周期方法和函数。这些方法和函数对于类组件(Class Components)和函数组件(Function Components)…...
Python中dict支持多个key的方法
在Python中,字典(dict)是一种非常强大的数据结构,它允许我们通过键(key)来存储和检索值(value)。有时候,我们可能想要根据多个键来检索或操作字典中的数据。虽然Python的…...
丹摩 | 基于PyTorch的CIFAR-10图像分类实现
从创建实例开始的新项目流程 第一步:创建实例 登录 DAMODEL 平台。创建一个 GPU 实例: GPU 配置:选择 NVIDIA H800 或其他可用高性能 GPU。 系统配置:推荐使用 Ubuntu 20.04,内存 16GB,硬盘 50GB。 启…...
C#变量和函数如何和unity组件绑定
1.Button On_click (1)GameObject通过Add component添加上Script (2)Button选GameObject组件而不是直接选Script,直接选Script出现不了Script中的函数 2.RawImage 上面是错的 3.Text 上面是错的,应该是直接在GameObject里面填上对应的值 总结: …...
AI模型---安装cuda与cuDNN
1.安装cuda 先打开cmd 输入nvidia-smi 查看显卡支持cuda对应的版本: 然后去英伟达官网下载cuda(外网多刷几次) https://developer.nvidia.com/cuda-toolkit-archive 注意对应版本 安装过程中如果显示如下图: 请安装visual Stu…...
【大数据学习 | Spark-Core】Spark提交及运行流程
spark的集群运行结构 我们要选择第一种使用方式 命令组成结构 spark-submit [选项] jar包 参数 standalone集群能够使用的选项。 --master MASTER_URL #集群地址 --class class_name #jar包中的类 --executor-memory MEM #executor的内存 --executor-cores NUM # executor的…...
内网渗透横向移动1
1.信息收集 (1)判断域控 shell net time /domain shell ping OWA2010CN-God.god.org (2)主机探测 浏览探测->网络探测 主机列表显示: (3)域用户收集: shell net user /domain…...
现代密码学
概论 计算机安全的最核心三个关键目标(指标)/为:保密性 Confidentiality、完整性 Integrity、可用性 Availability ,三者称为 CIA三元组 数据保密性:确保隐私或是秘密信息不向非授权者泄漏,也不被非授权者使…...
Pod 动态分配存储空间实现持久化存储
配置 Pod 以使用 PersistentVolume 作为存储 关于持久卷的介绍,可以看官方文档 https://kubernetes.io/zh-cn/docs/concepts/storage/persistent-volumes/ 持久卷根据存储位置,可以使用本地存储和云存储,如果有云服务平台,…...
Jackson、Gson、FastJSON三款JSON利器比拼
在Java领域,有多种JSON工具包,比如Jackson、Gson、FastJSON,每家都各有所长,下面我们从性能、特性、生态、易用 性等几个方面来展开下: 一、Jackson 性能 Jackson是一款高性能的JSON处理库。它在序列化和反序列化操作…...
php:nginx如何配置WebSocket代理?
在nginx配置中加入以下配置即可: server {listen 80;server_name test.com;# 配置 WebSocket 代理location /ws {proxy_pass http://127.0.0.1:8083;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade&qu…...
3349、检测相邻递增子数组 Ⅰ
3349、[简单] 检测相邻递增子数组 Ⅰ 1、题目描述 给你一个由 n 个整数组成的数组 nums 和一个整数 k,请你确定是否存在 两个 相邻 且长度为 k 的 严格递增 子数组。具体来说,需要检查是否存在从下标 a 和 b (a < b) 开始的 两个 子数组,…...
C++笔记之函数入参传递std::unique_ptr 时使用 std::move的场景
C++笔记之函数入参传递std::unique_ptr 时使用 std::move的场景 code review! 参考笔记 C++笔记之unique_ptr转移堆内空间的所有权 文章目录 C++笔记之函数入参传递std::unique_ptr 时使用 std::move的场景一.使用 std::unique_ptr 作为函数参数时的主要场景二.一个完整示例一…...
怎么只提取视频中的声音?从视频中提取纯音频技巧
在数字媒体的广泛应用中,提取视频中的声音已成为一项常见且重要的操作。无论是为了学习、娱乐、创作还是法律用途,提取声音都能为我们带来诸多便利。怎么只提取视频中的声音?本文将详细介绍提取声音的原因、工具、方法以及注意事项。 一、为什…...
数仓工具—Hive语法之窗口函数中的 case when
窗口函数中的 case when 今天我们看一下窗口函数和case when 的各种花活,最近的需求各种窗口,一个需求中十几个窗口,加上各种条件边界,所以写了大量的窗口函数和case when的组合,今天我们来看一下。 我们的数据如下 %spark.pyspark df2 = spark.createDataFrame([(&quo…...
利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表
1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/
使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题:docker pull 失败 网络不同,需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...
