• 【正点原子I.MX6U-MINI应用篇】7、输入设备(鼠标、键盘、触摸屏、按钮)的应用编程和tslib库


    什么是输入设备?输入设备其实就是能够产生输入事件的设备就称为输入设备,常见的输入设备包括鼠标、键盘、触摸屏、按钮等等,它们都能够产生输入事件,产生输入数据给计算机系统

    对于输入设备的应用编程其主要是获取输入设备上报的数据、输入设备当前状态等,譬如获取触摸屏当前触摸点的 X、Y 轴位置信息以及触摸屏当前处于按下还是松开状态。

    一、输入类设备编程介绍

    1.1 什么是输入设备

    先来了解什么是输入设备(也称为 input 设备),常见的输入设备有鼠标、键盘、触摸屏、遥控器、电脑画图板等,用户通过输入设备与系统进行交互。

    1.2 input子系统

    由上面的介绍可知,输入设备种类非常多,每种设备上报的数据类型又不一样,那么 Linux 系统如何管理呢?Linux 系统为了统一管理这些输入设备,实现了一套能够兼容所有输入设备的框架,那么这个框架就是 input 子系统。驱动开发人员基于 input 子系统开发输入设备的驱动程序,input 子系统可以屏蔽硬件的差异,向应用层提供一套统一的接口。
    基于 input 子系统注册成功的输入设备,都会在/dev/input 目录下生成对应的设备节点(设备文件),设备节点名称通常为 eventX(X 表示一个数字编号 0、1、2、3 等),譬如/dev/input/event0、/dev/input/event1、/dev/input/event2 等,通过读取这些设备节点可以获取输入设备上报的数据。

    1.3 读取数据的流程

    如果我们要读取触摸屏的数据,假设触摸屏设备对应的设备节点为/dev/input/event0,那么数据读取流程如下:

    • ①、应用程序打开/dev/input/event0设备文件;
    • ②、应用程序发起读操作(譬如调用read),如果没有数据可读则会进入休眠(阻塞 I/O 情况下);
    • ③、当有数据可读时,应用程序会被唤醒,读操作获取到数据返回;
    • ④、应用程序对读取到的数据进行解析。

    当无数据可读时,程序会进入休眠状态(也就是阻塞),譬如应用程序读触摸屏数据,如果当前并没有去触碰触摸屏,自然是无数据可读;当我们用手指触摸触摸屏或者在屏上滑动时,此时就会产生触摸数据、应用程序就有数据可读了,应用程序会被唤醒,成功读取到数据。那么对于其它输入设备亦是如此,无数据可读时应用程序会进入休眠状态(阻塞式 I/O 方式下),当有数据可读时才会被唤醒。

    1.4 应用程序如何解析数据

    首先我们要知道,应用程序打开输入设备对应的设备文件,向其发起读操作,那么这个读操作获取到的是什么样的数据呢?其实每一次 read 操作获取的都是一个 struct input_event 结构体类型数据,该结构体定义头文件中,它的定义如下:

    struct input_event {
    struct timeval time;
    __u16 type;
    __u16 code;
    __s32 value;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    结构体中的 time 成员变量是一个 struct timeval 类型的变量,该结构体在前面给大家介绍过,内核会记录每个上报的事件其发生的时间,并通过变量 time 返回给应用程序。时间参数通常不是那么重要,而其它3 个成员变量 type、code、value 更为重要。

    • type :type 用于描述发生了哪一种类型的事件(对事件的分类),Linux 系统所支持的输入事件类型如下所示:
    /*
    * Event types
    */
    #define EV_SYN 0x00  //同步类事件,用于同步事件
    #define EV_KEY 0x01  //按键类事件
    #define EV_REL 0x02  //相对位移类事件(譬如鼠标)
    #define EV_ABS 0x03  //绝对位移类事件(譬如触摸屏)
    #define EV_MSC 0x04  //其它杂类事件
    #define EV_SW  0x05
    #define EV_LED 0x11
    #define EV_SND 0x12
    #define EV_REP  0x14
    #define EV_FF  0x15
    #define EV_PWR 0x16
    #define EV_FF_STATUS  0x17
    #define EV_MAX 0x1f
    #define EV_CNT (EV_MAX+1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    以上这些宏定义也是在头文件中,所以在应用程序中需要包含该头文件;一种输入设备通常可以产生多种不同类型的事件,譬如点击鼠标按键(左键、右键,或鼠标上的其它按键)时会上报按键类事件,移动鼠标时则会上报相对位移类事件。

    • code :code 表示该类事件中的哪一个具体事件,以上列举的每一种事件类型中都包含了一系列具体事件,譬如一个键盘上通常有很多按键,譬如字母 A、B、C、D 或者数字 1、2、3、4 等,而 code变量则告知应用程序是哪一个按键发生了输入事件。每一种事件类型都包含多种不同的事件,譬如按键类事件:
    #define KEY_RESERVED 0
    #define KEY_ESC 1  //ESC 键
    #define KEY_1  2  //数字 1 键
    #define KEY_2  3  //数字 2 键
    #define KEY_TAB 15 //TAB 键
    #define KEY_Q  16 //字母 Q 键
    #define KEY_W  17 //字母 W 键
    #define KEY_E  18 //字母 E 键
    #define KEY_R  19 //字母 R 键
    ……
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    相对位移事件

    #define REL_X  0x00  //X 轴
    #define REL_Y  0x01  //Y 轴
    #define REL_Z  0x02  //Z 轴
    #define REL_RX 0x03
    #define REL_RY 0x04
    #define REL_RZ  0x05
    #define REL_HWHEEL 0x06
    #define REL_DIAL  0x07
    #define REL_WHEEL 0x08
    #define REL_MISC  0x09
    #define REL_MAX 0x0f
    #define REL_CNT (REL_MAX+1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    绝对位移事件

    触摸屏设备是一种绝对位移设备,它能够产生绝对位移事件;譬如对于触摸屏来说,一个触摸点所包含的信息可能有多种,譬如触摸点的 X 轴坐标、Y 轴坐标、Z 轴坐标、按压力大小以及接触面积等,所以 code变量告知应用程序当前上报的是触摸点的哪一种信息(X 坐标还是 Y 坐标、亦或者其它);绝对位移事件如下:

    #define ABS_X  0x00  //X 轴
    #define ABS_Y  0x01  //Y 轴
    #define ABS_Z  0x02  //Z 轴
    #define ABS_RX 0x03
    #define ABS_RY 0x04
    #define ABS_RZ 0x05
    #define ABS_THROTTLE  0x06
    #define ABS_RUDDER 0x07
    #define ABS_WHEEL 0x08
    #define ABS_GAS 0x09
    #define ABS_BRAKE 0x0a
    #define ABS_HAT0X 0x10
    #define ABS_HAT0Y 0x11
    #define ABS_HAT1X 0x12
    #define ABS_HAT1Y 0x13
    #define ABS_HAT2X 0x14
    #define ABS_HAT2Y 0x15
    #define ABS_HAT3X 0x16
    #define ABS_HAT3Y 0x17
    #define ABS_PRESSURE  0x18
    #define ABS_DISTANCE  0x19
    #define ABS_TILT_X 0x1a
    #define ABS_TILT_Y 0x1b
    #define ABS_TOOL_WIDTH  0x1c
    ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    除了以上列举出来的之外,还有很多,大家可以自己浏览头文件(这些宏其实是定义在input-event-codes.h 头文件中,该头文件被所包含了),关于这些具体的事件,后面再给大家进行介绍。

    • value :内核每次上报事件都会向应用层发送一个数据 value,对 value 值的解释随着 code 的变化而变化。譬如对于按键事件(type=1)来说,如果 code=2(键盘上的数字键 1,也就是 KEY_1),那么如果 value 等于 1,则表示 KEY_1 键按下;value 等于 0 表示 KEY_1 键松开,如果 value 等于 2则表示 KEY_1键长按。再比如,在绝对位移事件中(type=3),如果 code=0 (触摸点 X坐标 ABS_X),那么 value 值就等于触摸点的 X 轴坐标值;同理,如果 code=1(触摸点 Y 坐标 ABS_Y),此时value 值便等于触摸点的 Y 轴坐标值;所以对 value 值的解释需要根据不同的 code 值而定!

    数据 同步
    上面我们提到了同步事件类型 EV_SYN,同步事件用于实现同步操作、告知接收者本轮上报的数据已经完整。应用程序读取输入设备上报的数据时,一次 read 操作只能读取一个 struct input_event 类型数据,譬如对于触摸屏来说,一个触摸点的信息包含了 X 坐标、Y 坐标以及其它信息,对于这样情况,应用程序需要执行多次 read 操作才能把一个触摸点的信息全部读取出来,这样才能得到触摸点的完整信息。

    那么应用程序如何得知本轮已经读取到完整的数据了呢?其实这就是通过同步事件来实现的,内核将
    本轮需要上报、发送给接收者的数据全部上报完毕后,接着会上报一个同步事件,以告知应用程序本轮数据已经完整、可以进行同步了。

    同步类事件中也包含了多种不同的事件,如下所示:

    /*
    * Synchronization events.
    */
    #define SYN_REPORT 0
    #define SYN_CONFIG 1
    #define SYN_MT_REPORT 2
    #define SYN_DROPPED  3
    #define SYN_MAX  0xf
    #define SYN_CNT (SYN_MAX+1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    所以的输入设备都需要上报同步事件,上报的同步事件通常是 SYN_REPORT,而 value 值通常为 0。

    1.5 读取struct input_event数据

    根据前面的介绍可知,对输入设备调用 read()会读取到一个 struct input_event 类型数据,本小节编写一个简单地应用程序,将读取到的 struct input_event 类型数据中的每一个元素打印出来、并对它们进行解析。

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char *argv[])
    {
        struct input_event in_ev = {0};
        int fd = -1;
    
        /* 校验传参 */
        if (2 != argc) {
            fprintf(stderr, "usage: %s \n", argv[0]);
            exit(-1);
        }
    
        /* 打开文件 */
        if (0 > (fd = open(argv[1], O_RDONLY))) {
            perror("open error");
            exit(-1);
        }
    
        for ( ; ; ) {
    
            /* 循环读取数据 */
            if (sizeof(struct input_event) !=
                read(fd, &in_ev, sizeof(struct input_event))) {
                perror("read error");
                exit(-1);
            }
    
            printf("type:%d code:%d value:%d\n",
                    in_ev.type, in_ev.code, in_ev.value);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    执行程序时需要传入参数,这个参数就是对应的输入设备的设备节点(设备文件),程序中会对传参进行校验。程序中首先调用 open()函数打开设备文件,之后在 for 循环中调用 read()函数读取文件,将读取到的数据存放在 struct input_event 结构体对象中,之后将结构体对象中的各个成员变量打印出来。注意,程序中使用了阻塞式 I/O 方式读取设备文件,所以当无数据可读时 read 调用会被阻塞,知道有数据可读时才会被唤醒!

    Tips:设备文件不同于普通文件,读写设备文件之前无需设置读写位置偏移量。

    使用交叉编译工具编译上述代码得到可执行文件,在使用交叉编译链之前要使能环境变量。

    source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
    ${CC} -o read_input read_input.c
    
    • 1
    • 2

    1.6 上传程序到开发板执行

    开发板启动后通过nfs挂载Ubuntu目录的方式,将相应的文件拷贝到开发板上。简单来说,就是通过NFS在开发板上通过网络直接访问ubuntu虚拟机上的文件,并且就相当于自己本地的文件一样。

    开发板想访问/home/zhiguoxin/myproject/alientek_app_development_source这个目录中的文件,就要把/home/zhiguoxin/myproject/alientek_app_development_source挂载到开发板的mnt目录,这样就可以通过nfs来访问/home/zhiguoxin/myproject/alientek_app_development_source了。

    因为我的代码都放在/home/zhiguoxin/myproject/alientek_app_development_source这个目录下,所以我们将这个目录作为NFS共享文件夹。设置方法参考移植SQLite3、OpenCV到RV1126开发板上开发人脸识别项目第一章。

    Ubuntu IP为192.168.10.100,然后一般都是挂载在开发板的mnt目录下,这个目录是专门用来给我们作为临时挂载的目录。

    文件系统目录简介

    然后使用MobaXterm软件通过SSH访问开发板。

    ubuntu ip:192.168.10.100
    windows ip:192.168.10.200
    开发板ip:192.168.10.50
    
    • 1
    • 2
    • 3

    在开发板上执行以下命令:

    mount -t nfs -o nolock,vers=3 192.168.10.100:/home/zhiguoxin/myproject/alientek_app_development_source /mnt
    
    • 1

    就将开饭的mnt目录挂载在ubuntu的/home/zhiguoxin/myproject/alientek_app_development_source目录下了。这样我们就可以在Ubuntu下修改文件,然后可以直接在开发板上执行可执行文件了。当然我这里的/home/zhiguoxin/myproject/windows之间是一个共享目录,我也可以直接在windows上面修改文件,然后ubuntu和开发板直接进行文件同步了。

    1.7 在开发板上验证

    Mini 开发板上都有一个用户按键 KEY0,它就是一个典型的输入设备,如下图所示:

    该按键是提供给用户使用的一个 GPIO 按键,在出厂系统中,该按键驱动基于 input 子系统而实现,所以在/dev/input目录下存在 KEY0 的设备节点,可以通过查看/proc/bus/input/devices文件得知,查看该文件可以获取到系统中注册的所有输入设备相关的信息,如下所示:

    cat /proc/bus/input/devices
    
    • 1

    查看/proc/bus/input/devices 文件
    接下来我们使用这个按键进行测试,执行下面的命令:



    程序运行后,执行按下KEY0、松开KEY0等操作,终端将会打印出相应的信息,如上图所示。

    第一行中type等于 1,表示上报的是按键事件EV_KEY,code=114,打开 input-event-codes.h 头文件进行查找,可以发现code=114对应的是键盘上的KEY_VOLUMEDOWN按键,这个是Mini开发板出厂系统已经配置好的。而value=1表示按键按下,所以整个第一行的意思就是按键KEY_VOLUMEDOWN被按下。

    第二行,表示上报了EV_SYN同步类事件(type=0)中的SYN_REPORT事件(code=0),表示本轮数据已经完整、报告同步。

    第三行,type等于1,表示按键类事件,code等于 114、value 等于0,所以表示按键KEY_VOLUMEDOWN被松开。

    第四行,又上报了同步事件。

    所以整个上面4行的打印信息就是开发板上的KEY0按键被按下以及松开这个过程,内核所上报的事件以及发送给应用层的数据value。

    我们试试长按按键 KEY0,按住不放,如下所示:


    可以看到上报按键事件时,对应的value等于 2,表示长按状态。

    1.8 按键应用编程

    本小节编写一个应用程序,获取按键状态,判断按键当前是按下、松开或长按状态。从上面打印的信息可知,对于按键来说,它的事件上报流程如下所示:

    # 以字母 A 键为例
    KEY_A //上报 KEY_A 事件
    SYN_REPORT  //同步
    
    • 1
    • 2
    • 3

    如果是按下,则上报KEY_A事件时,value=1;如果是松开,则value=0;如果是长按,则value=2。
    接下来编写按键应用程序,读取按键状态并将结果打印出来,代码如下所示:

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char *argv[])
    {
        struct input_event in_ev = {0};
        int fd = -1;
        int value = -1;
    
        /* 校验传参 */
        if (2 != argc) {
            fprintf(stderr, "usage: %s \n", argv[0]);
            exit(-1);
        }
    
        /* 打开文件 */
        if (0 > (fd = open(argv[1], O_RDONLY))) {
            perror("open error");
            exit(-1);
        }
    
        for ( ; ; ) {
    
            /* 循环读取数据 */
            if (sizeof(struct input_event) !=
                read(fd, &in_ev, sizeof(struct input_event))) {
                perror("read error");
                exit(-1);
            }
    
            if (EV_KEY == in_ev.type) { //按键事件
                switch (in_ev.value) {
                case 0:
                    printf("code<%d>: 松开\n", in_ev.code);
                    break;
                case 1:
                    printf("code<%d>: 按下\n", in_ev.code);
                    break;
                case 2:
                    printf("code<%d>: 长按\n", in_ev.code);
                    break;
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    在for循环中,调用read()读取输入设备上报的数据,当按键按下或松开(以及长按)动作发生时,read()会读取到输入设备上报的数据,首先判断此次上报的事件是否是按键类事件(EV_KEY),如果是按键类事件、接着根据value值来判断按键当前的状态是松开、按下还是长按。

    将上述代码进行编译:

    然后在开发板中测试

    ./read_key /dev/input/event2
    
    • 1

    运行程序之后,按下 KEY0 或松开 KEY0 以及长按情况下,终端会打印出相应的信息,如上图所示。code=114(KEY_VOLUMEDOWN 按键)。

    除了测试开发板上的 KEY0 按键之外,我们还可以测试键盘上的按键,首先找到一个 USB 键盘连接到开发板的 USB HOST接口上,驱动加载成功之后,可以查看下该键盘设备对应的设备节点,使用命令cat /proc/bus/input/devices,在打印信息中找到键盘设备的信息:


    我使用的是一个Ducky的USB键盘"Ducky Akko Keyboard",对应的设备节点为/dev/input/event3,
    运行测试程序并按下、松开键盘上的按键:

    测试 USB 键盘
    大家可以根据code值查询到对应的按键(通过input-event-codes.h头文件),譬如code=30对应的是键盘上的字母A键,code=48对应的是字母B键。

    二、使用tslib库

    上面介绍了如何编写触摸屏应用程序,包括单点触摸和多点触摸,主要是对读取到的struct input_event类型数据进行剖析,得到各个触摸点的坐标。tslib 库,这是 Linux 系统下,专门为触摸屏开发的应用层函数库。

    2.1 tslib简介

    tslib是专门为触摸屏设备所开发的 Linux 应用层函数库,并且是开源,也就意味着我们可以直接获取到tslib 的源代码。

    tslib为触摸屏驱动和应用层之间的适配层,它把应用程序中读取触摸屏struct input_event类型数据(这是输入设备上报给应用层的原始数据)并进行解析的操作过程进行了封装,向使用者提供了封装好的 API 接口。tslib 从触摸屏中获得原始的坐标数据,并通过一系列的去噪、去抖、坐标变换等操作,来去除噪声并将原始的触摸屏坐标转换为相应的屏幕坐标。

    tslib 有一个配置文件ts.conf,该配置文件中提供了一些配置参数、用户可以对其进行修改,具体的配置信息稍后介绍!

    tslib可以作为Qt的触摸屏输入插件,为Qt提供触摸输入支持,如果在嵌入式 Linux 硬件平台下开发过
    Qt 应用程序的读者应该知道;当然,并不是只有 tslib 才能作为 Qt 的插件、为其提供触摸输入支持,还有很多插件都可以,只不过大部分都会选择使用 tslib。

    2.2 tslib下载和安装

    2.2.1 下载tslib源码

    首先下载tslib源码包,进入到tslib的 git 仓库下载源码https://github.com/libts/tslib/releases,如下:

    所以为了统一,我们也下载 1.16 版本的 tslib,往下翻找到 1.16 版本的下载链接:

    将其解压到当前目录下:

    tar -xzf tslib-1.16.tar.gz
    
    • 1

    解压之后会生成tslib-1.16目录,在/home/zhiguoxin/linux/tool目录下创建 tslib 目录,等会编译tslib库的时候将安装目录指定到这里,如下所示:


    进入到 tslib-1.16 目录,准备进行编译 tslib 源码:

    接下来进行编译,整个源码的编译分为3个步骤:

    • 首先第一步是配置工程;
    • 第二步是编译工程;
    • 第三步是安装,将编译得到的库文件、可执行文件等安装到一个指定的目录下。首先在配置工程之前,先对交叉编译工具的环境进行设置,使用source执行交叉编译工具安装目录下的environment-setup-cortexa7hf-neon-poky-linux-gnueabi脚本文件:
    source /opt/fsl-imx-x11/4.1.15-2.1.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
    
    • 1

    执行下面这条命令对 tslib 源码工程进行配置:

    ./configure --host=arm-poky-linux-gnueabi --prefix=/home/zhiguoxin/linux/tool/tslib/
    
    • 1

    至于工程是如何配置的,大家可以执行./configure --help查看它的配置选项以及含义,--host选项用于指定交叉编译得到的库文件是运行在哪个平台,通常将--host设置为交叉编译器名称的前缀,譬如 arm-poky-linux-gnueabi-gcc前缀就是arm-poky-linux-gnueabi--prefix选项则用于指定库文件的安装路径,我们将安装路径设置为之前创建的/home/zhiguoxin/linux/tool/tslib目录。

    配置工程

    接着编译工程,直接执行 make:

    make
    
    • 1

    make 编译

    最后执行 make install 安装:

    make install
    
    • 1

    安装

    2.2.2 tslib安装目录下的文件夹介绍

    进入到tslib安装目录下:

    cd /home/zhiguoxin/linux/tool/tslib
    
    • 1

    tslib安装目录下的文件夹

    bin目录

    bin目录下有一些 tslib 提供的小工具,可以用于测试触摸屏,如下所示:

    bin 目录下的文件

    etc目录

    etc目录下有一个配置文件 ts.conf,前面给大家提到过,

    配置文件 ts.conf

    打开ts.conf文件看看它有哪些配置选项:

    ts.conf 文件的内容

    这里的配置不需要去改动了,直接使用默认的配置就行了。

    • module_raw input:取消注释,使能支持 input 输入事件;
    • module pthres pmin=1:如果我们的设备支持按压力大小测试,那么可以把它的注释取消,pmin 用于调节按压力灵敏度,默认就是等于 1。
    • module dejitter delta=100:tslib 提供了触摸屏去噪算法插件,如果需要过滤噪声样本,取消注释,默认参数delta=100。
    • module linear:tslib 提供了触摸屏坐标变换的功能,譬如将 X、Y 坐标互换、坐标旋转等之类,如果我们需要实现坐标变换,可以把注释去掉。
    include目录

    include 目录下只有一个头文件tslib.h,该头文件中包含了一些结构体数据结构以及 API 接口的申明,使用tslib提供的API就需要包含该头文件。

    lib目录

    lib目录下包含了编译tslib源码所得到的库文件,默认这些都是动态库文件,也可以通过配置tslib工程使其生成静态库文件;ts目录下存放的是一些插件库。

    share目录

    可以忽略!

    2.3 在开发板上测试 tslib

    移植的最后一步就是把tslib安装目录下的库文件、etc下的配置文件以及编译得到的测试工具拷贝到开发板Linux系统目录下,由于开发板出厂系统中已经移植了tslib库,所以我们这里就不用拷贝了。但如果大家是自己做的根文件系统,并没有移植tslib,那么就需要把这些库、可执行文件以及配置文件拷贝到根文件系统中,只需要下面四步,这里就不做过多的演示了:

    • 将安装目录bin/目录下的所有可执行文件拷贝到开发板/usr/bin 目录下;
    • 将安装目录etc/目录下的配置文件 ts.conf 拷贝到开发板/etc 目录下;
    • 将安装目录lib/目录下的所有库文件拷贝到开发板/usr/lib 目录下。
    • 将安装目录下的测试工具、库文件以及配置文件拷贝到开发板之后,接着需要配置一些环境变量,因为tslib工作的时候它需要依赖于一些环境变量,譬如它会通过读取环境变量来得知ts.conf配置文件、库文件的路径以及我们要测试的触摸屏对应的设备节点等。
    export TSLIB_CONSOLEDEVICE=none
    export TSLIB_FBDEVICE=/dev/fb0
    export TSLIB_TSDEVICE=/dev/input/event1
    export TSLIB_CONFFILE=/etc/ts.conf
    export TSLIB_PLUGINDIR=/usr/lib/ts
    
    • 1
    • 2
    • 3
    • 4
    • 5

    执行下面命令后,点击屏幕可以试一试效果:

    ts_print
    ts_print_mt
    
    • 1
    • 2

    ts_print
    ts_print_mt

    2.4 tslib库函数介绍

    使用 tslib 编程其实非常简单,基本步骤如下所示:

    • 第一步打开触摸屏设备;
    • 第二步配置触摸屏设备;
    • 第三步读取触摸屏数据。

    2.4.1 打开触摸屏设备

    #include "tslib.h"
    struct tsdev *ts_open(const char *dev_name, int nonblock);
    
    • 1
    • 2
    • dev_name:指定了触摸屏的设备节点;
    • nonblock:表示是否以非阻塞方式打开触摸屏设备,如果nonblock 等于 0 表示阻塞方式,如果为非 0 值则表示以非阻塞方式打开。
    • struct tsdev *:指针,指向触摸屏设备句柄;如果打开设备失败,将返回 NULL。

    除了使用 ts_open()打开设备外,还可以使用 ts_setup()函数。ts_setup()相比 ts_open(),除了打开触摸屏设备外,还对触摸屏设备进行了配置。所以如果你使用了ts_setup()函数,就不需要在使用ts_config()函数进行配置了

    #include "tslib.h"
    struct tsdev *ts_setup(const char *dev_name, int nonblock)
    
    • 1
    • 2
    • dev_name:指定了触摸屏的设备节点,可以设置为 NULL,当dev_name设置为NULL 时,ts_setup()函数内部会读取TSLIB_TSDEVICE 环境变量,获取该环境变量的内容以得知触摸屏的设备节点。
    • nonblock:表示是否以非阻塞方式打开触摸屏设备,如果nonblock 等于 0 表示阻塞方式,如果为非 0 值则表示以非阻塞方式打开。
    • struct tsdev *:指针,指向触摸屏设备句柄;如果打开设备失败,将返回 NULL。

    2.4.2 配置触摸屏设备

    调用ts_config()函数进行配置,其函数原型如下所示:

    #include "tslib.h"
    int ts_config(struct tsdev *ts)
    
    • 1
    • 2
    • ts:指向触摸屏句柄。
    • 成功返回 0,失败返回-1。

    所谓配置其实指的就是解析 ts.conf 文件中的配置信息,加载相应的插件。

    2.4.3 读取触摸屏数据

    读取触摸屏数据使用 ts_read()或 ts_read_mt()函数,区别在于 ts_read 用于读取单点触摸数据,而
    ts_read_mt 则用于读取多点触摸数据,其函数原型如下所示:

    #include "tslib.h"
    int ts_read(struct tsdev *ts, struct ts_sample *samp, int nr)
    
    • 1
    • 2
    • ts:指向一个触摸屏设备句柄,参数nr表示对一个触摸点的采样数,设置为1即可!
    • samp:一个 struct ts_sample *类型的指针,指向一个 struct ts_sample 对象,structts_sample 数据结构描述了触摸点的信息;调用ts_read()函数获取到的数据会存放在samp指针所指向的内存中。
    struct ts_sample {
    int x; //X 坐标
    int y; //Y 坐标
    unsigned int pressure; //按压力大小
    struct timeval tv; //时间
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    读取触摸屏数据还有一个函数就是ts_read_mt()函数。

    #include "tslib.h"
    int ts_read_mt(struct tsdev *ts, struct ts_sample_mt **samp, int max_slots, int nr)
    
    • 1
    • 2
    • ts:指向一个触摸屏设备句柄,参数nr表示对一个触摸点的采样数,设置为1即可!
    • samp: struct ts_sample_mt **类型的指针,多点触摸应用程序,每一个触摸点的信息使用struct ts_sample_mt数据结构来描述;一个触摸点的数据使用一个struct ts_sample_mt对象来装载,将它们组织成一个struct ts_sample_mt数组,调用ts_read_mt()时,将数组地址赋值给samp参数
    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; //触摸点 slot
    	int tracking_id;  //ID
    	int tool_type;
    	int tool_x;
    	int tool_y;
    	unsigned int touch_major;
    	unsigned int width_major;
    	unsigned int touch_minor;
    	unsigned int  width_minor;
    	int orientation;
    	int distance;
    	int blob_id;
    	struct timeval tv; //时间
    	/* BTN_TOUCH state */
    	short pen_down; //BTN_TOUCH 的状态
    	/* 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; //此次样本是否有效标志 触摸点数据是否发生更新
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    2.5 基于tslib编写触摸屏应用程序

    2.5.1 单点触摸应用程序

    #include 
    #include 
    #include       //包含tslib.h头文件
    
    int main(int argc, char *argv[])
    {
        struct tsdev *ts = NULL;
        struct ts_sample samp;
        int pressure = 0;//用于保存上一次的按压力,初始为0,表示松开
    
        /* 打开并配置触摸屏设备 */
        ts = ts_setup(NULL, 0);
        if (NULL == ts) {
            fprintf(stderr, "ts_setup error");
            exit(EXIT_FAILURE);
        }
    
        /* 读数据 */
        for ( ; ; ) {
    
            if (0 > ts_read(ts, &samp, 1)) {
                fprintf(stderr, "ts_read error");
                ts_close(ts);
                exit(EXIT_FAILURE);
            }
    
            if (samp.pressure) {//按压力>0
                if (pressure)   //若上一次的按压力>0
                    printf("移动(%d, %d)\n", samp.x, samp.y);
                else
                    printf("按下(%d, %d)\n", samp.x, samp.y);
            }
            else
                printf("松开\n");//打印坐标
    
            pressure = samp.pressure;
        }
    
        ts_close(ts);
        exit(EXIT_SUCCESS);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    代码非常简单,就不再解释了,直接打开、配置设备,接着读取数据即可!通过判断按压力大小确定触摸的状态,如果按压力等于 0 则表示手指已经松开;按压力大于 0,则需根据上一次的按压力是否大于 0 来判断。读取数据出错时,ts_read()返回一个负数。

    接下来编译应用程序,编译代码时,需要通过交叉编译器来指定头文件、库文件的路径以及动态链接库文件名:

    ${CC} -I /home/zhiguoxin/linux/tool/tslib/include -L /home/zhiguoxin/linux/tool/tslib/lib -lts -o ts_read ts_read.c
    
    • 1

    -I 选项指定头文件的路径,也就是指定tslib安装目录下的include目录,如果不指定头文件路径,编译时将会找不到 tslib.h 头文件;-L选项用于指定库文件的路径,也就是指定tslib安装目录下的lib目录;我们将tslib编译成了动态库文件,以库文件的形式提供,编译时需要链接到这些库文件;而-l选项则用于指定链接库(也可写成-l ts,也就是libts.so 库文件,Linux 中,动态库文件的命名方式为 lib+名字+.so)。

     如果不想指定目录怎么办?我们要把tslib的头文件和库文件,也就是include和lib文件夹中的文件拷贝到编译器下面include和lib文件夹中

    确定工具链中头文件、库文件目录,对于 IMX6ULL ,命令如 下

    echo 'main(){}'| arm-linux-gnueabihf-gcc -E -v -
    
    • 1

     找到了编译器arm-linux-gnueabihf的include和lib文件夹

    /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/include
    
    • 1
    /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib
    
    • 1

    进入tslib的安装目录,我的ubuntu的tslib安装在/home/zhiguoxin/linux/tool/tslib中

    cd /home/zhiguoxin/linux/tool/tslib
    
    • 1

    然后将include文件夹下的所有文件和lib文件夹下的所有文件都拷贝到编译器arm-linux-gnueabihf的include和lib文件夹下

    cp include/* /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/include
    
    • 1
    cp -d lib/*so* /usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/arm-linux-gnueabihf/libc/usr/lib
    
    • 1

    2.5.2 上传程序到开发板执行

    开发板启动后通过nfs挂载Ubuntu目录的方式,将相应的文件拷贝到开发板上。简单来说,就是通过NFS在开发板上通过网络直接访问ubuntu虚拟机上的文件,并且就相当于自己本地的文件一样。

    开发板想访问/home/zhiguoxin/myproject/alientek_app_development_source这个目录中的文件,就要把/home/zhiguoxin/myproject/alientek_app_development_source挂载到开发板的mnt目录,这样就可以通过nfs来访问/home/zhiguoxin/myproject/alientek_app_development_source了。

    因为我的代码都放在/home/zhiguoxin/myproject/alientek_app_development_source这个目录下,所以我们将这个目录作为NFS共享文件夹。设置方法参考移植SQLite3、OpenCV到RV1126开发板上开发人脸识别项目第一章。

    Ubuntu IP为192.168.10.100,然后一般都是挂载在开发板的mnt目录下,这个目录是专门用来给我们作为临时挂载的目录。

    文件系统目录简介

    然后使用MobaXterm软件通过SSH访问开发板。

    ubuntu ip:192.168.10.100
    windows ip:192.168.10.200
    开发板ip:192.168.10.50
    
    • 1
    • 2
    • 3

    在开发板上执行以下命令:

    mount -t nfs -o nolock,vers=3 192.168.10.100:/home/zhiguoxin/myproject/alientek_app_development_source /mnt
    
    • 1

    就将开饭的mnt目录挂载在ubuntu的/home/zhiguoxin/myproject/alientek_app_development_source目录下了。这样我们就可以在Ubuntu下修改文件,然后可以直接在开发板上执行可执行文件了。当然我这里的/home/zhiguoxin/myproject/windows之间是一个共享目录,我也可以直接在windows上面修改文件,然后ubuntu和开发板直接进行文件同步了。

     然后进入18_tslib目录下执行命令即可

    ./ts_read
    
    • 1

    2.5.1 多点触摸应用程序

    #include 
    #include 
    #include 
    #include 
    #include 
    
    int main(int argc, char *argv[])
    {
        struct tsdev *ts = NULL;
        struct ts_sample_mt *mt_ptr = NULL;
        struct input_absinfo slot;
        int max_slots;
        unsigned int pressure[12] = {0};   //用于保存每一个触摸点上一次的按压力,初始为0,表示松开
        int i;
    
        /* 打开并配置触摸屏设备 */
        ts = ts_setup(NULL, 0);
        if (NULL == ts) {
            fprintf(stderr, "ts_setup error");
            exit(EXIT_FAILURE);
        }
    
        /* 获取触摸屏支持的最大触摸点数 */
        if (0 > ioctl(ts_fd(ts), EVIOCGABS(ABS_MT_SLOT), &slot)) {
            perror("ioctl error");
            ts_close(ts);
            exit(EXIT_FAILURE);
        }
    
        max_slots = slot.maximum + 1 - slot.minimum;
        printf("max_slots: %d\n", max_slots);
    
        /* 内存分配 */
        mt_ptr = calloc(max_slots, sizeof(struct ts_sample_mt));
    
        /* 读数据 */
        for ( ; ; ) {
    
            if (0 > ts_read_mt(ts, &mt_ptr, max_slots, 1)) {
                perror("ts_read_mt error");
                ts_close(ts);
                free(mt_ptr);
                exit(EXIT_FAILURE);
            }
    
            for (i = 0; i < max_slots; i++) {
    
                if (mt_ptr[i].valid) {//有效表示有更新!
                    if (mt_ptr[i].pressure) { //如果按压力>0
                        if (pressure[mt_ptr[i].slot])//如果上一次的按压力>0
                            printf("slot<%d>, 移动(%d, %d)\n", mt_ptr[i].slot, mt_ptr[i].x, mt_ptr[i].y);
                        else
                            printf("slot<%d>, 按下(%d, %d)\n", mt_ptr[i].slot, mt_ptr[i].x, mt_ptr[i].y);
                    }
                    else
                        printf("slot<%d>, 松开\n", mt_ptr[i].slot);
    
                    pressure[mt_ptr[i].slot] = mt_ptr[i].pressure;
                }
            }
        }
    
        /* 关闭设备、释放内存、退出 */
        ts_close(ts);
        free(mt_ptr);
        exit(EXIT_SUCCESS);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    操作于与上面差不对,就不多说了。

  • 相关阅读:
    Windows自带虚拟机的使用方法
    Matlab直接求贝塞尔函数的导函数
    nodejs+vue+elementui 青少年编程在线考试系统python java php
    循环神经网络(RNN)之长短期记忆(LSTM)
    接口开发知识点整理三
    明白这3个规则,行走职场简直没有难度
    使用Java客户端发送消息和消费的应用
    R and RStudio的安装教程【2023】
    Vue
    【排序算法】快速排序
  • 原文地址:https://blog.csdn.net/qq_39400113/article/details/127669803