域名和网站名不一样,企业网站怎么搭建,营销类网站,泗阳网站设计目录 tslib的简介tslib的源码和make及make install后得到的文件下载tslib的主要功能tslib的工作原理tslib的核心组成部分tslib的框架和核心函数分析tslib的框架tslib的核心函数ts_setup()的分析(对如何获取设备名和数据处理流程的分析)函数ts_setup()自身的主要代码ts_setup()对… 目录 tslib的简介tslib的源码和make及make install后得到的文件下载tslib的主要功能tslib的工作原理tslib的核心组成部分tslib的框架和核心函数分析tslib的框架tslib的核心函数ts_setup()的分析(对如何获取设备名和数据处理流程的分析)函数ts_setup()自身的主要代码ts_setup()对ts_open()的调用ts_setup()对ts_config()的调用 tslib的核心函数ts_read()的分析 tslib的交叉编译上板测试 tslib的简介
tslib 是一个用于 Linux 系统中触摸屏输入设备的轻量级库特别常用于嵌入式开发。它主要提供了一组工具和 API用于对触摸屏输入事件进行校准、过滤和处理使得触摸屏可以更精确、更稳定地与系统交互。 应用场景如下:
嵌入式 Linux 设备如工业控制、车载系统、POS 终端。需要轻量级触摸屏处理库的开发环境。与 Qt、SDL 等图形界面结合使用。
tslib的源码和make及make install后得到的文件下载
tslib-1.21的源码下载地址 https://pan.baidu.com/s/1Yc1IiRqECn6SyJHI9-6Ksg?pwdt4zj
附tslib-1.21make及make install后得出的输入文件下载地址 https://pan.baidu.com/s/1U6_JUJEiJLAdniZW5wLEoQ?pwdx4w6
tslib的主要功能 校准 (Calibration) 提供 ts_calibrate 工具可以对触摸屏进行几何校准解决触摸位置和显示坐标不匹配的问题。 事件过滤 (Filtering) 提供了一系列的过滤器比如去抖动、线性化等保证输入事件的平滑性和准确性。 事件捕获 (Event Capture) 捕获触摸屏输入设备如 /dev/input/eventX 或 /dev/tsX的触摸事件并转化为标准格式供上层应用使用。 兼容性强 支持多种触摸屏控制器例如 eGalax、Goodix 等和多种 Linux 输入子系统。
tslib的工作原理
应用程序通过调用 tslib 的 API 读取触摸事件。tslib 内部从触摸屏设备获取原始事件。通过过滤器链对原始事件进行处理如校准、去抖动。输出处理后的事件数据供应用程序使用。
tslib的核心组成部分 库文件 提供了核心的触摸屏数据处理和 API比如 ts_read()、ts_config()。 工具程序 ts_calibrate: 校准工具用于生成校准数据。ts_test: 用于测试触摸屏事件和显示触摸点。ts_print: 打印触摸事件的工具。 配置文件 通常是 /etc/ts.conf可以配置过滤器、设备路径等。
tslib的框架和核心函数分析
tslib的框架 从上面的框架可以看出tslib有三个核心函数分别为ts_setup、ts_read、ts_read_mt。其中ts_setup依靠两个核心函数分别为ts_open和ts_config来实现。
tslib的核心函数ts_setup()的分析(对如何获取设备名和数据处理流程的分析)
函数ts_setup()自身的主要代码
我们打开源码\tslib-1.21\tests\ts_test.c转到主函数发现第134有如下代码 ts ts_setup(NULL, 0);我们转到函数ts_setup()的定义如下 位置\tslib-1.21\src\ts_setup.c
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 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;ts ts_open(fname, nonblock);free(fname);}
#endif /* __linux__ *//* if detected try to configure it */if (ts ts_config(ts) ! 0) {ts_error(ts_config: %s\n, strerror(errno));ts_close(ts);return NULL;}return ts;
}
我们可以看到它有两个输入参数一个是dev_name另一个是nonblock。 dev_name显然是表示设备名。 nonblock表示读取数据的时候是阻塞方式还是非阻塞方式当nonblock的值为1或非 0时表示非阻塞模式当nonblock的值为0时为阻塞方式所以在文件ts_test.c中是以阻塞方式打开触摸屏设置的因为给的参数值为0嘛这意味着从触摸屏读取数据时如果数据尚未准备好进程会阻塞直到数据可用。延伸阅读Linux系统的阻塞方式和非阻塞方式是什么意思
在这里参数dev_name的值传递为NULL即空指针那么在函数ts_setup()里有下在这一行
dev_name dev_name ? dev_name : getenv(TSLIB_TSDEVICE);可见如果有值则用它本身的值如果值为NULL那么 getenv(TSLIB_TSDEVICE) 的结果将作为 dev_name 的值。 所以源码\tslib-1.21\tests\ts_test.c里会去取 getenv(TSLIB_TSDEVICE) 的结果作为设备名。函数getenv)的意思是从环境变量里获取设备名比如这里会从环境变量TSLIB_TSDEVICE里获取设备名。
那如果既没有传递设备名也没有设置环境变函数ts_setup()它会像下面这样做
static const char * const ts_name_default[] {/dev/input/ts,/dev/input/touchscreen,/dev/touchscreen/ucb1x00,NULL
};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;}}即它会去遍历字符串数组ts_name_default中的各个成员发现第一个不为NULL值的成员并且能够打开成功的话即用这个成员作为设备名。
如果到这里还没有成功打开设备在定义了宏__linux__的情况下它会去扫描系统中的设备代码如下
#if defined (__linux__)if (!ts) {fname scan_devices();if (!fname)return NULL;ts ts_open(fname, nonblock);free(fname);}
#endif /* __linux__ */在scan_devices()函数中有下面的代码
ndev scandir(DEV_INPUT_EVENT, namelist, is_event_device, alphasort);DEV_INPUT_EVENT的宏定义如下
#define DEV_INPUT_EVENT /dev/input即它会去目录/dev/input下去扫描设备。扫描到的设备列表存放在ndev中然后一个个去判断是设是触摸类型的设备通过下面这句代码来判断 if ((ioctl(fd, EVIOCGPROP(sizeof(propbit)), propbit) 0) ||!(propbit[BIT_WORD(INPUT_PROP_DIRECT)] BIT_MASK(INPUT_PROP_DIRECT)))ioctl()函数会获取到设备信息这段代码通过查询设备属性位 INPUT_PROP_DIRECT判断设备是否为触摸屏类型设备。如果设备支持 INPUT_PROP_DIRECT 属性则认为是直接输入设备如触摸屏。否则它可能是间接输入设备或其他设备。具体的这段代码的详解这里就不展开了。
如果扫描到某个设备是触摸型输入设备它就会去在tslib库的层面上利用函数ts_open()去打开它具体的代码是下面的代码
ts_setup()对ts_open()的调用
ts ts_open(fname, nonblock);可见核心函数ts_setup()是依靠ts_open()函数实现的。另外核心函数ts_setup()还要依靠函数ts_config()那我们来看下是怎么依靠函数ts_config()的。
ts_setup()对ts_config()的调用
接下来的代码 /* if detected try to configure it */if (ts ts_config(ts) ! 0) {ts_error(ts_config: %s\n, strerror(errno));ts_close(ts);return NULL;}可见设备打开之后就开始配置函数ts_config()的定义如下
int ts_config(struct tsdev *ts)
{return __ts_config(ts, NULL, NULL, NULL);
}可见它以设备打开获得的结构体ts为输入参数并对这个结构体ts进行配置。具体的配置是在函数__ts_config()中进行的。函数__ts_config()以“__”开头说明它是一个内部函数供库或框架的内部代码调用。
函数__ts_config()的定义如下
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(Couldnt find tslib config file: %s\n,strerror(errno));return -1;}}f fopen(conffile, r);if (!f) {if (strdup_allocated)free(conffile);ts_error(Couldnt open tslib config file %s: %s\n,conffile, strerror(errno));return -1;}buf[BUF_SIZE - 2] \0;while ((p fgets(buf, BUF_SIZE, f)) ! NULL) {char *e;char *tok;char *module_name;line;/* Chomp */e strchr(p, \n);if (e)*e \0;/* Did we read a whole line? */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);#elsetok strsep(p, \t);#endifdiscard_null_tokens(p, tok);/* Ignore comments or blank lines.* Note: strsep modifies p (see man strsep)*/if (p NULL || *tok #)continue;/* Search for the option. */if (strcasecmp(tok, module) 0) {#if !defined HAVE_STRSEPmodule_name ts_strsep(p, \t);#elsemodule_name strsep(p, \t);#endifdiscard_null_tokens(p, module_name);if (!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);#elsemodule_name strsep(p, \t);#endifdiscard_null_tokens(p, module_name);if (!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(Couldnt 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;
}从这个代码中我们可以看出我们可以使用环境变量TSLIB_CONFFILE来确定使用哪个配置文件相关代码如下
if ((conffile getenv(TSLIB_CONFFILE)) NULL) {如果环境变量TSLIB_CONFFILE为空那么就会使用 strdup(TS_CONF)的返回值作为配置文件这个配置文件其实就是/etc/ts.conf 这里我们打开tslib源码里配的\tslib-1.21\etc\ts.conf文件看一下 打开它后把被注释的去掉没有注释的提取出来如下
module_raw input
module pthres pmin1
module dejitter delta100
module linear其中input、pthres、dejitter、linear都是模块名。pthres会处理input得到的数据、 dejitter会处理pthres得到的数据linear会处理dejitter得到的数据。具体的逻辑关系可以通过分析函数__ts_config()下面的代码得到 从这个关系我们可以看出当模块类型为module_raw时调用函数ts_load_module_raw()当模块类型为module时调用函数ts_load_module()这两个被调用的函数分别如下
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);
}可见它们其实都是调用的__ts_load_module()只是最后一个参数不一样一个最后为0一个最后为1再接着看__ts_load_module()它与这个问题相关的核心代码在 if (raw)ret __ts_attach_raw(ts, info);elseret __ts_attach(ts, info);这里面raw的值就是其最后一个参数的值info表示模块信息。 所以我们需要看去函数__ts_attach_raw()和__ts_attach()
int __ts_attach(struct tsdev *ts, struct tslib_module_info *info)
{info-dev ts;info-next ts-list;ts-list info;return 0;
}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;/** ensure the last item in the normal list now points to the* top of the raw list.*/if (ts-list NULL || ts-list prev_list) {/* main list is empty, ensure it points here */ts-list info;return 0;}for (next ts-list, prev next;next ! NULL next ! prev_list;next prev-next, prev next);prev-next info;return 0;
}
函数__ts_attach_raw()和__ts_attach()的输入参数都有一个关键结构体 tsdev
struct tsdev {int fd;char *eventpath;struct tslib_module_info *list;/* points to position in list where raw reads* come from. default is the position of the* ts_read_raw module.*/struct tslib_module_info *list_raw;unsigned int res_x;unsigned int res_y;int rotation;
};可见这个结构体有两个与模块信息有关的成员分别为list和list_raw。 当处理第一个模块时即语句
module_raw input时__ts_attach_raw()会把ts-list_raw的值设为input模块同时把ts-list的第一个值也设为input模块然后后面的几个模块配置语句
module pthres pmin1
module dejitter delta100
module linear由于不是raw类型所以__ts_attach()去执行相应的链接操作说白了就是把这些模块按顺序连接起来形成一个链表具体来说在ts-list_raw中存储了module_raw模块的信而在ts-list则把所有的模块以列表的形式存储起来示意图如下 通过这个示意图可以看出数据是一层一层地被处理和过滤这就是tslib对数据的核心处理流程即通过配置文件去配置对数据的处理流程。
tslib的核心函数ts_read()的分析
我们打开源码\tslib-1.21\tests\ts_test.c转到主函数发现第174有如下代码 ret ts_read(ts, samp, 1);我们进入函数ts_read()来看下它的流程
int ts_read(struct tsdev *ts, struct ts_sample *samp, int nr)
{int result;
#ifdef DEBUGint i;
#endifresult ts-list-ops-read(ts-list, samp, nr);
#ifdef DEBUGfor (i 0; i result; i) {fprintf(stderr, TS_READ---- x %d, y %d, pressure %d\n,samp-x, samp-y, samp-pressure);samp;}
#endifreturn result;}关键语如下
result ts-list-ops-read(ts-list, samp, nr);我们可以看到它是调取linear模块的ops成员的read()成员函数 然后我们再看下ts-list-ops-read()函数的定义 这个read()函数实际上是在文件\tslib-1.21\plugins\linear.c中定义的函数linear_read()然后它作为结构体ops的成员函数如下面的代码所示
static const struct tslib_ops linear_ops {.read linear_read,.read_mt linear_read_mt,.fini linear_fini,
};在文件\tslib-1.21\plugins\linear.c的第55行有对函数linear_read()定义的代码如下
ret info-next-ops-read(info-next, samp, nr_samples);我们看到它实际上是去调用下一个模块中成员ops中的read()函数实际上就是dejitter模块中的read()函数以此类推最终是去调用模块input中的read()函数。 可以推测模块input中的read()函数是直接去操作设备读取信息了我们看下是不是这样打开源文件\tslib-1.21\plugins\input-raw.c然后去看里面的函数ts_input_read(),发现关键代码如下
ret read(ts-fd, ev, sizeof(struct input_event));这个显然就是系统里的符合POSIX标准read()函数了 关于这个函数的介绍请参见我的另一篇博文 https://blog.csdn.net/wenhao_ir/article/details/144645178
tslib的交叉编译
首先把tslib的源码放到目录/home/book/usedlib下 然后解压
tar xjf tslib-1.21.tar进入目录
cd /home/book/usedlib/tslib-1.21配置编译
./configure --hostarm-buildroot-linux-gnueabihf --prefix/注意这里一定要根据自己系统中的交叉编译器的前缀来填可用下面的命令来测试自己的交叉编译器的前缀
arm-buildroot-linux-gnueabihf-gcc -v如果有结果返回则这里--host的值该为arm-buildroot-linux-gnueabihf 执行make命令
make安装到当前目录下的子目录tmp中
make install DESTDIR$PWD/tmp运行之后就在tmp目录下生成了如下目录 其中bin目录中是生成的可执行程序主要是一些测试实例 etc目录里的文件自然就是配置文件了因为我的前缀设置为根目录(--prefix/)这个得复制到开发板上的linux系统下的etc目录下因为根据上面的分析函数ts_config()在运行时需要使用到这个文件。
inlude目录里是头文件如果你在开发别的项目时用到了这个库那得在你的工程的include目录中放上这个头文件 lib目录里就是库文件了如果你在开发别的项目时用到了这个库那链接器就需要用到这个库里面的相关文件。 在这里我们首先要测试我们生成的二进制可执行文件是否是适用于ARM架构的因为如果你前边的编译器前缀填错了它并不会报错而会去用系统中其它的编译器比如×86架构的编译器。 运行下面的命令
file /home/book/usedlib/tslib-1.21/tmp/bin/ts_test_mt运行结果如下
/home/book/usedlib/tslib-1.21/tmp/bin/ts_test_mt: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 4.9.0, not stripped这就说明下面这条命令中参数 --host的设置是正确的。
./configure --hostarm-buildroot-linux-gnueabihf --prefix/在这里我们把它复制到目录/usr/local/lib中并重命为tslib这样编译器编译别的工程时就能用到这个库了这里得能命令复制因为目录/usr/local/lib不是能随便写文件的运行下面的命令
sudo cp -rfd /home/book/usedlib/tslib-1.21/tmp/lib /usr/local/lib/tslib附tslib-1.21make及make install后得出的输入文件下载地址 https://pan.baidu.com/s/1U6_JUJEiJLAdniZW5wLEoQ?pwdx4w6
上板测试
把文件 /home/book/usedlib/tslib-1.21/tmp/etc/ts.conf /home/book/usedlib/tslib-1.21/tmp/bin/ts_print_mt 复制到NFS文件中
打开开发板的串口终端→打开开发板→挂载网络文件
mount -t nfs -o nolock,vers3 192.168.5.11:/home/book/nfs_rootfs /mnt复制ts.conf到开发板的ect目录
cp /mnt/ts.conf /etc/复制文件ts_print_mt到开发板的/bin目录下
cp /mnt/ts_print_mt /bin为文件ts_print_mt添加执行权限
chmod x ts_print_mt由于ts_print_mt这个文件需要动态库支持所以我们还需要把生成的动态库复制到对应目录下
按下面的操作复制库先把整个make install命令生成的目录tmp复制到NFS文件中 然后按下面的命令复制
cp /mnt/tmp/lib/ts -rfd /lib
cp /mnt/tmp/lib/*so* -fd /lib其中的参数r表示递归复制f表示强制覆盖d表示保留符号链接而不是把符号链接所指向的文件进行复制。
复制完了运行可执行文件
/bin/ts_print_mt 用手指在触摸屏上划一下 这样就完美运行了~