嵌入式Linux应用程序开发-(10)i.MX6UL基于嵌入式QT实现电容屏多点触控

本文介绍如何在i.MX6UL平台上使用嵌入式QT实现电容屏多点触控,包括移植qTUIO库及编写多点触控应用程序的全过程。

i.MX6UL基于嵌入式QT实现电容屏多点触控

        基于i.MX6UL平台,使用嵌入式QT实现电容屏的多点触控,前提是开发板的电容触摸屏驱动已经支持多点触控,并且驱动程序能通过事件方式向应用程序上报触控数据。关于电容触摸屏多点触控驱动程序的介绍,不在本章节描述之列。本章节重在实现多点触控的QT应用程序。

        嵌入式QT电容屏多点触控应用程序,是基于qTUIO库和mtdev库来实现的。qTUIO是国外一位开发者编写的第三方库,这个第三方库在本地UDP套接字上实现了一个基于TUIO协议的监听器,然后把监听到的事件转发到QT的内部事件系统。mtdev是一个独立的库,它可以将来自内核的MT事件转换为时隙类型的B协议,这些MT事件可能来自内核中所有的MT设备。

        总的来说,mtdev库可以把内核上报的多点触摸事件,通过mtdev2tuio后台应用程序(后面会讲到这个应用程序的移植),转换为TUIO协议,然后QT应用程序可以使用qTUIO库对内核上报的触摸事件进行处理。

以下是多点触控应用程序(涂鸦画板)运行时的界面:

界面描述:

(1)手写板界面,可以用多个手指同时涂鸦,最多支持5点同时触摸。

(2)Options里面有clear screen按钮,可以进行清屏。

 

本章节主要分以下两个步骤进行讲述:

(1) 在i.MX6UL开发板上移植 qTUIO 库,使其支持嵌入式QT多点触摸应用程序开发。

(2) 基于 qTUIO 库,编写一个QT多点触控应用程序。(手写板应用程序)

一、在i.MX6UL开发板上移植qTUIO库

在i.MX6UL开发板上移植qTUIO库,需要依赖以下的第三方库:liblo,mtdev,mtdev2tuio,qTUIO。

通过以下网址,下载以上的第三方依赖库:

        https://github.com/x29a/qTUIO

        https://github.com/olivopaolo/mtdev2tuio

        http://bitmath.org/code/mtdev/

        http://liblo.sourceforge.net/

如果不能访问以上网址,也可以使用作者下载好的文件:

        链接:https://pan.baidu.com/s/17l6LonZuh8U7axVRQ4TV-w          提取码:gvo7

 

(1)下载完成后,把下载好的文件,通过FileZilla工具上传到ubuntu系统,作者上传到ubuntu的/opt/work/tools/qt_multitouch/目录,以上依赖库的交叉编译也是基于该目录进行,把下载好的文件进行解压,并创建好各个依赖库的安装目录,如下图所示。

以上依赖库的交叉编译顺序是:liblo  -->  mtdev  -->  mtdev2tuio  -->  qTUIO。交叉编译完的文件,统一存放在 imx6ul-rootfs目录,方便移植到开发板。

(2)在ubuntu系统的命令行终端,执行以下命令,配置交叉编译环境。

#export CC=/opt/EmbedSky/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc
#export CXX=/opt/EmbedSky/gcc-linaro-5.3.1-2016.05-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf-g++

(3)先交叉编译liblo库,执行以下命令进行交叉编译:

#cd /opt/work/tools/qt_multitouch/liblo-0.26
#export SKIP_RMDIR_CHECK=yes
#./configure --prefix=/opt/work/tools/qt_multitouch/liblo_install --host=arm
#make clean
#make
#make install

交叉编译成功后,会在 liblo_install 目录下生成bin、include、lib目录,如下图所示:

执行以下命令,把生成的三个目录都复制到imx6ul-rootfs文件夹:

#cp  /opt/work/tools/qt_multitouch/liblo_install/*  /opt/work/tools/qt_multitouch/imx6ul-rootfs/  -a

(4)然后,再交叉编译mtdev库,执行以下命令进行交叉编译:

#cd /opt/work/tools/qt_multitouch/mtdev-1.1.3
#./configure --prefix=/opt/work/tools/qt_multitouch/mtdev_install --host=arm
#make clean
#make
#make install

交叉编译成功后,会在 mtdev_install 目录下生成 bin、include、lib 目录,如下图所示:

执行以下命令,把生成的三个目录都复制到imx6ul-rootfs文件夹:

#cp  /opt/work/tools/qt_multitouch/mtdev_install/*  /opt/work/tools/qt_multitouch/imx6ul-rootfs/  -a

(5)接着,再交叉编译 mtdev2tuio-master 后台程序,mtdev2tuio 后台程序是连接mtdev与qTUIO的桥梁,在运行基于qTUIO库编写的应用程序前,需要先运行mtdev2tuio,与以上两个依赖库不同,mtdev2tuio-master源码里面已经有Makefile文件,交叉编译过程可以基于此Makefile进行,修改 /opt/work/tools/qt_multitouch/mtdev2tuio-master/Makefile 内容,如下图所示:

修改完Makefile文件后,执行以下命令,进行交叉编译:

#cd /opt/work/tools/qt_multitouch/mtdev2tuio-master
#make clean
#make

交叉编译完成后,会在源码目录下生成 mtdev2tuio 应用程序,如下图所示:

执行以下命令,把mtdev2tuio应用程序复制到 imx6ul-rootfs的bin目录:

#cd  /opt/work/tools/qt_multitouch/mtdev2tuio-master/
#cp  ./mtdev2tuio  /opt/work/tools/qt_multitouch/imx6ul-rootfs/bin  -a

(6)最后,交叉编译qTUIO库。qTUIO库是一个QT工程,其工程的.pro文件以及源码目录是:/opt/work/tools/qt_multitouch/qTUIO-master/src,可以使用qmake工具进行生成Makefile文件,再交叉编译。执行以下命令,进行交叉编译。

#cd /opt/work/tools/qt_multitouch/qTUIO-master
#rm -rf /opt/work/tools/qt_multitouch/qTUIO-master/lib/*
#cd src/
#/opt/EmbedSky/TQIMX6UL/TQ_COREB/imx6ul_rootfs/opt/qt-4.8.7/bin/qmake
#make clean
#make

交叉编译成功后,会在/opt/work/tools/qt_multitouch/qTUIO-master/lib文件夹下生成qTUIO的动态链接库,如下图所示:

执行以下命令,把这些动态链接库,复制到imx6ul-rootfs的lib目录:

#cd  /opt/work/tools/qt_multitouch/qTUIO-master/
#cp  ./lib/*  /opt/work/tools/qt_multitouch/imx6ul-rootfs/lib  -a

(7)完成以上工作后,imx6ul-rootfs目录下各个文件夹里面的内容分别如下图所示:

imx6ul-rootfs/bin目录的内容

imx6ul-rootfs/include目录的内容

imx6ul-rootfs/lib目录的内容

(8)最后,把imx6ul-rootfs/bin目录的文件复制到i.MX6UL开发板的/bin目录,把imx6ul-rootfs/include目录的文件复制到i.MX6UL开发板的/include目录,把imx6ul-rootfs/lib目录的文件复制到i.MX6UL开发板的/usr/lib目录,复制完成后,如下图所示:

(9)至此,qTUIO库已经全部移植到i.MX6UL开发板,开发板已经支持运行基于qTUIO库编写的多点触摸应用程序,为了方便应用程序运行前不用手动运行mtdev2tuio后台程序,我们把该后台程序设置为开机自启动,修改 /etc/embedsky_env文件,如下图所示:

/bin/mtdev2tuio  /dev/input/cap_ts  & 表示监控 /dev/input/cap_ts节点,该节点是电容触摸屏的设备节点,内核里面的的电容触摸事件都是通过该节点进行上报,应用程序通过该节点捕获电容触摸事件,然后进行处理。

 

二、基于qTUIO库编写QT多点触控应用程序

(1)先用Qt Creator构建一个工程,命名为:010_multi_touch,关于如何构建工程,请参考“第一个嵌入式QT应用程序”的具体内容。与以往的程序不同,此次构建的工程,基类选择QMainWindow类。

(2)在编写代码前,我们需要把上一步骤移植好的qTUIO动态库和头文件复制到工程中,复制完成后,如下图所示:

其中,3rdparty目录直接从 /opt/work/tools/qt_multitouch/qTUIO-master/src 复制,并删除目录里面所有的非 .h 文件,只保留 .h 头文件。

(3)在工程目录上,右键 --> 添加库,如下图所示:

(4)选择添加“外部库”,点击下一步,在弹出的对话框中,平台选择Linux,库文件选择刚刚添加进工程的libqTUIO.so,然后点击下一步,如下图所示:

(5)动态链接库添加完成后,010_multi_touch.pro文件如下图所示:

(6)为了方便在界面上进行涂鸦绘画,我们创建一个ScribbleArea类,这个类继承于QWidget类,包含了清屏函数,以及重构的绘画事件函数,窗口大小调整函数,系统事件捕获函数,等等。类的具体内容如下图所示:

class ScribbleArea : public QWidget
{
    Q_OBJECT

public:
    ScribbleArea(QWidget *parent = 0);

public slots:
    void clearImage();   //清屏函数

protected:
    void paintEvent(QPaintEvent *event);    //绘画事件函数
    void resizeEvent(QResizeEvent *event);  //窗口大小调整的事件函数
    bool event(QEvent *event);   //系统事件处理函数

private:
    void resizeImage(QImage *image, const QSize &newSize);   //重新调整窗口大小

    QList<QColor> myPenColors;  //画笔颜色
    QImage image;   //窗口图像
};

(7)在ScribbleArea类的构造函数中,我们设置该类对象可以捕获触摸事件,并初始化设置画笔的颜色,该类的构造函数如下图所示:

ScribbleArea::ScribbleArea(QWidget *parent) : QWidget(parent)
{
     setAttribute(Qt::WA_AcceptTouchEvents);   //接收touch事件
     setAttribute(Qt::WA_StaticContents);      //设置静态的窗口部件

     myPenColors
             << QColor("green")
             << QColor("purple")
             << QColor("red")
             << QColor("blue")
             << QColor("yellow")

             << QColor("pink")
             << QColor("orange")
             << QColor("brown")
             << QColor("grey")
             << QColor("black");
}

(8)当系统有触摸事件上报时,应用程序会调用bool ScribbleArea::event(QEvent *event)函数,实际上,这个被重构的event函数不仅仅只在触摸事件发生时被调用,对于一般的系统事件,这个函数也会被调用,在这个event函数里面,我们只处理QEvent::TouchBegin,QEvent::TouchUpdate,QEvent::TouchEnd事件,其他时间忽略不进行处理。event函数的内容如下图所示:

//系统事件处理函数
bool ScribbleArea::event(QEvent *event)
{
    switch (event->type())
    {
        case QEvent::TouchBegin:     //触摸开始事件
        case QEvent::TouchUpdate:    //触摸更新事件
        case QEvent::TouchEnd:       //触摸结束事件
        {
            //获取触摸事件中所有的触摸点列表
            QList<QTouchEvent::TouchPoint> touchPoints = static_cast<QTouchEvent *>(event)->touchPoints();
            foreach (const QTouchEvent::TouchPoint &touchPoint, touchPoints)
            {
                switch (touchPoint.state())
                {
                    case Qt::TouchPointStationary:
                    // don't do anything if this touch point hasn't moved
                    continue;
                    default:
                    {
                        QRectF rect = touchPoint.rect();  //获取触摸点的矩形区域
                        if (rect.isEmpty())   //如果矩形区域空白
                        {
                            qreal diameter = qreal(20);
                            rect.setSize(QSizeF(diameter, diameter));  //重置触摸点的大小
                            rect.setX(touchPoint.pos().x() - 0);    //重置触摸点的x,y位置
                            rect.setY(touchPoint.pos().y() - 0);
                        }

                        QPainter painter(&image);
                        painter.setPen(Qt::NoPen);   //不使用画笔
                        painter.setBrush(myPenColors.at(touchPoint.id() % myPenColors.count())); //使用画刷
                        painter.drawEllipse(rect); //根据给定的矩形区域,绘制一个椭圆点
                        painter.end();    //绘制完成

                        int rad = 2;
                        update(rect.toRect().adjusted(-rad,-rad, +rad, +rad));  //更新绘制的区域
                    }
                    break;
                }
            }
            break;
        }
        default:
        return QWidget::event(event);
    }
    return true;
}

在该函数里面,只处理三种触摸事件,先获取触摸事件里面的所有触摸点,然后根据该触摸点的坐标和矩形大小,绘制椭圆点,然后调用update函数更新绘制的区域,update函数被调用的时候,void ScribbleArea::paintEvent(QPaintEvent *event)事件函数就会被触发。

(9)图形会在void ScribbleArea::paintEvent(QPaintEvent *event)事件函数里面绘制,函数的内容如下图所示:

//paintEvent重绘事件,在调用QWidget::update()函数后被触发
void ScribbleArea::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    const QRect rect = event->rect();    //构造一个矩形
    painter.drawImage(rect.topLeft(), image, rect);  //重新绘制这个矩形
}

(10)在MainWindow的窗体构造函数里实例化一个ScribbleArea对象,并把该对象设置为MainWindow的中心窗体,然后绘制一个菜单栏,并重置窗体的大小。代码如下图所示:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    scribbleArea = new ScribbleArea;    //实例化一个scribbleArea对象
    setCentralWidget(scribbleArea);     //设置中心窗体

    createActions();     //设置清屏按钮
    createMenus();       //创建菜单栏

    setWindowTitle(tr("Finger Paint"));    //修改窗体title
    resize(800, 480);    //重置窗体大小
}

(11)在应用程序的入口int main(int argc, char *argv[])函数里面,使用QTuio类实例化一个对象qTUIO,该对象引用了MainWindow类对象进行实例化,然后运行qTUIO.run()函数,这样,qTUIO就可以监听系统的多点触摸事件,当系统有多点触摸事件上报时,qTUIO会把这些事件转换为Qt内部可以处理的系统事件,方便Qt应用程序进行处理,main函数的内容如下图所示:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MainWindow window;
    window.show();

    QTuio qTUIO(&window);
    qTUIO.run();
    return app.exec();
}

(12)至此,整个多点触摸应用程序已经开发完成,编译下载到开发板,应用程序运行的现象如下图所示。

        关于qTUIO库的实现机制和实现原理,作者没有进行太多深入的研究和分析,网上对于该库的讲解资料也比较少。根据移植过程的分析,大概可以推断系统的MultiTouch事件会被mtdev库捕获,然后这些事件会通过mtdev2tuio通过TUIO协议转发到qTUIO库,最后qTUIO库把这些多点触摸事件转换为Qt系统内部可处理的事件。qTUIO库的源代码里提供了这个库的使用例程,例程源码在 qTUIO-master/examples/ 目录里面,感兴趣的开发者可以进行移植测试。如对qTUIO库有比较深入的研究,或者发现以上的移植过程有纰漏,欢迎联系作者并指正,谢谢。

 

 

 

<think>好的,我现在需要回答用户的问题:“如何在Linux内核中实现多点触控(Multitouch)协议的支持?”用户已经提供了一些站内引用,我需要结合这些引用和之前的对话内容来组织回答。 首先,回顾用户提供的引用内容。引用[1]提到Linux内核文档中的多点触摸协议(MT协议),分为TypeA和TypeB,文档路径是Documentation/input/multitouch-protocol.txt。引用[2]则讨论了在i.MX6UL平台上使用嵌入式QT实现电容屏多点触控,前提是驱动已经支持,并且上报触控数据。用户现在想知道具体的实现方法和步骤。 接下来,我需要回忆之前作为面试官列出的问题,其中第一个相关问题就是如何在Linux内核中支持多点触控协议。之前的回答可能已经部分涉及,但用户现在需要更详细的步骤。 我需要考虑用户可能的背景:可能是驱动开发者,需要在内核中实现MT协议支持。因此,回答需要技术细节,包括协议类型、驱动开发步骤、代码示例、设备树配置、测试方法等。 根据引用[1],MT协议分为TypeA和TypeB,需要解释这两种类型的区别和应用场景。然后,步骤可能包括选择协议类型、初始化输入设备、上报触摸点数据、处理同步事件等。同时,要引用内核文档中的内容。 引用[2]提到驱动需要支持多点触控并向应用层上报数据,这可能涉及到输入子系统的使用,比如input_dev结构体、上报ABS_MT事件等。需要详细说明这些API的使用方法,如input_mt_init_slots、input_report_abs_slot等。 此外,设备树的配置也是关键,比如在设备树中指定触摸控制器、中断、I2C通信等。需要给出一个设备树节点的例子,说明各个参数的作用。 测试部分,用户可能需要使用工具如evtest或编写应用层代码来验证驱动是否正确工作。需要提到这些工具的使用方法,以及如何观察上报的数据。 最后,确保回答结构清晰,分步骤说明,并包含代码示例和实际操作的命令,这样用户能逐步实现。同时,要标注引用来源,如引用[1]和之前的回答中的引用[^1]和[^4]等。 需要检查是否有遗漏的关键点,例如同步事件的处理(如SYN_MT_REPORT和SYN_REPORT),以及TypeB中的跟踪ID管理。此外,功耗优化或错误处理是否需要提及?根据用户的问题,可能不需要深入,但简要提到可能更好。 现在组织内容: 1. 协议概述:TypeA和TypeB的区别。 2. 实现步骤: a. 选择协议类型 b. 初始化输入设备和MT slots c. 上报触摸点数据 d. 处理同步事件 3. 代码示例:展示关键函数和结构体使用。 4. 设备树配置:节点示例。 5. 测试与调试:工具使用。 6. 注意事项:如协议选择、固件要求、性能优化。 确保每个部分都结合引用和内核文档,保持技术准确性。例如,在TypeB中使用input_mt_slot和input_mt_report_slot_state,这些函数的具体用法需要正确描述。 可能还需要提到多点触控协议在输入子系统中的位置,如何通过input子系统将事件传递到用户空间,以及Android或QT等上层应用如何处理这些事件,如引用[2]中提到的QT应用依赖于驱动支持。 最后,总结关键点,确保用户能够按照步骤实现,并知道如何验证和调试驱动。</think>### Linux内核中实现多点触控(Multitouch)协议的方法与步骤 在Linux内核中实现多点触控(Multitouch, MT)协议支持,需遵循输入子系统的多点触控协议规范,并结合硬件特性进行驱动开发。以下是具体实现方法: --- #### **1. 协议类型选择** Linux内核的多点触控协议分为**TypeA**和**TypeB**两类[^1]: - **TypeA**:以顺序方式上报所有触控点,每次上报后需发送`SYN_MT_REPORT`信号标记单点结束,最后通过`SYN_REPORT`提交整个事件包。 - **适用场景**:触控点数量固定或无需动态跟踪触控点ID的硬件。 - **TypeB**:通过`ABS_MT_TRACKING_ID`为每个触控点分配唯一ID,支持动态增删触控点,直接使用`input_mt_report_slot_state()`上报数据。 - **适用场景**:支持动态触控点跟踪的电容屏(如手机、平板)。 --- #### **2. 驱动实现核心步骤** ##### **(1) 初始化输入设备与MT协议** ```c // 创建输入设备 struct input_dev *input = input_allocate_device(); input->name = "Capacitive Touchscreen"; input->id.bustype = BUS_I2C; // 根据硬件接口选择I2C/SPI // 设置输入事件类型 __set_bit(EV_ABS, input->evbit); // 配置多点触控参数(TypeB为例) input_set_abs_params(input, ABS_MT_POSITION_X, 0, SCREEN_MAX_X, 0, 0); input_set_abs_params(input, ABS_MT_POSITION_Y, 0, SCREEN_MAX_Y, 0, 0); input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); // 触点面积 // 初始化MT Slots(TypeB必须) input_mt_init_slots(input, MAX_TOUCH_POINTS, INPUT_MT_DIRECT); ``` ##### **(2) 上报触控点数据** - **TypeA示例**: ```c for (int i = 0; i < num_points; i++) { input_report_abs(input, ABS_MT_POSITION_X, x[i]); input_report_abs(input, ABS_MT_POSITION_Y, y[i]); input_mt_sync(input); // 发送SYN_MT_REPORT } input_sync(input); // 发送SYN_REPORT ``` - **TypeB示例**: ```c for (int slot = 0; slot < num_points; slot++) { input_mt_slot(input, slot); input_mt_report_slot_state(input, MT_TOOL_FINGER, true); // 触点状态 input_report_abs(input, ABS_MT_POSITION_X, x[slot]); input_report_abs(input, ABS_MT_POSITION_Y, y[slot]); } input_sync(input); // 提交事件包 ``` ##### **(3) 中断处理与数据解析** - 在中断处理函数中读取硬件寄存器或I2C/SPI数据,解析触控点坐标、压力值等信息。 - 若使用TypeB协议,需维护`ABS_MT_TRACKING_ID`以跟踪触点生命周期(如新增、移动、释放)。 --- #### **3. 设备树(Device Tree)配置** 在设备树中定义触摸控制器节点,指定协议类型、中断引脚、I2C地址等参数: ```dts &i2c1 { touchscreen@5d { compatible = "vendor,capacitive-touch"; reg = <0x5d>; interrupt-parent = <&gpio1>; interrupts = <9 IRQ_TYPE_EDGE_FALLING>; touchscreen-size-x = <800>; touchscreen-size-y = <480>; linux,input-mt-protocol = <2>; // 2表示TypeB协议[^1] }; }; ``` --- #### **4. 测试与调试** - **工具验证**: - 使用`evtest`工具查看原始输入事件: ```bash evtest /dev/input/eventX ``` - 检查是否上报`ABS_MT_POSITION_X/Y`及`SYN_REPORT`事件。 - **内核调试**: - 通过`dmesg`查看驱动加载日志,确认设备树解析正确。 - 使用`sysfs`接口(如`/sys/class/input/eventX/device/matrix`)校准坐标。 --- #### **5. 关键注意事项** 1. **协议选择**:电容屏优先使用TypeB协议以支持动态触点跟踪;电阻屏可选TypeA。 2. **固件要求**:部分触摸芯片需先加载固件才能启用多点触控功能[^2]。 3. **性能优化**:减少中断延迟,使用高精度定时器(`hrtimer`)优化事件上报频率[^4]。 --- ### 示例代码片段(TypeB协议) ```c static void touch_irq_handler(int irq, void *dev_id) { struct touch_data *data = read_touch_registers(); for (int i = 0; i < data->num_points; i++) { input_mt_slot(input_dev, i); input_mt_report_slot_state(input_dev, MT_TOOL_FINGER, data->points[i].active); if (data->points[i].active) { input_report_abs(input_dev, ABS_MT_POSITION_X, data->points[i].x); input_report_abs(input_dev, ABS_MT_POSITION_Y, data->points[i].y); } } input_sync(input_dev); } ``` ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

工程师进阶笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值