嵌入式Linux基于IMX6ULL tslib学习总结

时间:2024-11-25 10:15:37

目录

    • 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