参考文章:
https://blog.csdn.net/vickycheung3/article/details/82182136
https://blog.csdn.net/lizuobin2/article/details/52673494
前面移植了LittlevGL到嵌入式ARM系统,想到LittlevGL毕竟是一个小型的图形库,无法完成复杂的应用,同时之前还没有接触过QT应用开发,这里想试一下QT。QT号称是一次编写处处编译,叼叼哒,但是相比较java(C#)的一次编译处处运行而言,似乎也没有那么闪亮,不过就运行速度而言,原生指令集的QT程序肯定比在虚拟机上运行的java和C#程序要快。
QT有一套比较特殊的机制(信号与槽机制),其核心原理参考文章:https://blog.51cto.com/devbean/355100、https://blog.csdn.net/hyongilfmmm/article/details/83015045。
QT使用qmake进行工程的构建和管理,也可以使用cmake或者Qbs进行工程构建,不过好像qmake更加好用,而且qmake不仅仅只能针对QT应用程序,其他工程也能使用qmake进行构建,qmake的man介绍为“qmake - cross-platform makefile generator for Qt”,qmake的语法这里不做介绍,参考文章:https://blog.51cto.com/9291927/2112865。
下面是QT移植(不论是X86/64还是ARM平台)的主要步骤(主要说原理,很多细节可能没有):
1、下载QT源码并解压,我使用的是qt-everywhere-opensource-src-5.4.1版本的源码。
2、在源码文件夹中创建一个脚本文件build,写入下面的内容:
./configure -prefix /opt/qt5.4.1 -confirm-license -opensource -release -make libs -xplatform linux-arm-gnueabi-g++ -optimized-qmake -pch -qt-sql-sqlite -qt-libjpeg -qt-libpng -qt-zlib -tslib -no-opengl -no-sse2 -no-openssl -no-nis -no-cups -no-glib -no-dbus -no-xcb -no-xcursor -no-xfixes -no-xrandr -no-xrender -no-separate-debug-info -make examples -nomake tools -nomake tests -no-iconv
这个文件就是为了方便配置QT创建的,调用源码中的configure脚本来配置QT,其中-no-xxx表示配置qt不支持xxx, 而-qt-xxx表示qt支持xxx,没有指定的保持默认值。
3、上一步配置QT的时候有一个选项 -xplatform linux-arm-gnueabi-g++ ,表示生成的目标文件的平台是arm-linux,当然也可以使用其它平台。不过这个配置选项没有实际意义,它只是一个路径前缀而已,我们可以在源码目录下的 qtbase/mkspec/ 文件夹下找到 linux-arm-gnueabi-g++ 文件夹,并修改其中的 qmake.conf 文件,下面是我参考其它资料修改的内容(带注释,关于tslib的移植这里就不介绍了,原理上很简单):
#
# qmake configuration for building with arm-linux-gnueabi-g++
#
MAKEFILE_GENERATOR = UNIX
CONFIG += incremental
QMAKE_INCREMENTAL_STYLE = sublib
QT_QPA_DEFAULT_PLATFORM = linuxfb #使用Linux的frame buffer作为显示接口
QMAKE_CFLAGS_RELEASE += -O2 -march=armv7-a #设置编译优化级别以及CPU类型
QMAKE_CXXFLAGS_RELEASE += -O2 -march=armv7-a
include(../common/linux.conf)
include(../common/gcc-base-unix.conf)
include(../common/g++-unix.conf)
QMAKE_INCDIR += /opt/tslib/include #设置tslib的头文件路径
QMAKE_LIBDIR += /opt/tslib/lib #设置tslib的库文件路劲
# modifications to g++.conf
QMAKE_CC = arm-linux-gnueabi-gcc -lts #设置gcc编译器,lts表示在链接时链接tslib库
QMAKE_CXX = arm-linux-gnueabi-g++ -lts
QMAKE_LINK = arm-linux-gnueabi-g++ -lts
QMAKE_LINK_SHLIB = arm-linux-gnueabi-g++ -lts
# modifications to linux.conf
QMAKE_AR = arm-linux-gnueabi-ar cqs
QMAKE_OBJCOPY = arm-linux-gnueabi-objcopy
QMAKE_NM = arm-linux-gnueabi-nm -P
QMAKE_STRIP = arm-linux-gnueabi-strip
load(qt_config)
4、现在已经配置好QT的源码了,准备编译:
export –n QMAKESPEC #-n 删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中。
./build
5、经过上一步的build操作生成了一个Makefile文件,直接 make 编译时间会很长,为了加快编译速度:make -j4,top指令查看了一下CPU状态,笔记本CPU的4个核满满的在运行。
6、编译完成之后(如果没有错误),可以执行make install,记得脚本文件build中有一个 -prefix /opt/qt5.4.1 ,安装路径就是/opt/qt5.4.1,完成之后本机的opt文件夹下多了一个qt5.4.1文件夹,这就是我们要的结果。看看里面有什么:
其中bin、lib、plugin文件夹是最重要的,lib和plugin中的是QT程序运行所依赖的库文件,bin文件夹中的文件则是在编写QT应用程序的时候会用到的工具,例如 qmake 工具。所以我们需要将安装好的qt5.4.1文件夹拷贝到QT运行的目标平台的 /opt 文件夹中去(只需要lib和plugin文件夹即可,example文件夹中的例程也可以拷贝过去进行测试使用)。到此,QT的运行环境已经搭建好了。
6、假设现在tslib已经安装好了,并且路径是我们在 qmake.conf 文件中配置的 /opt/tslib/lib 和 /opt/tslib/include。创建一个脚本文件qtenv.sh,用于配置QT和tslib的环境变量,使用点命令(source)运行:
export T_ROOT=/opt/tslib
export LD_LIBRARY_PATH=$T_ROOT/lib:$LD_LIBRARY_PATH
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0
export TSLIB_TSDEVICE=/dev/input/event3
export TSLIB_PLUGINDIR=$T_ROOT/lib/ts
export TSLIB_CONFFILE=$T_ROOT/etc/ts.conf
export POINTERCAL_FILE=/etc/pointercal
export TSLIB_CALIBFILE=/etc/pointercal
export QTEDIR=/opt/qt5.4.1
export LD_LIBRARY_PATH=$QTEDIR/lib:$LD_LIBRARY_PATH
export QT_QPA_GENERIC_PLUGINS=tslib
export QT_QPA_FONTDIR=$QTEDIR/lib/fonts
export QT_QPA_PLATFORM_PLUGIN_PATH=$QTEDIR/plugins
export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb0:size=800x480:mmSize=480x272:offset=0x0:tty=/dev/ttyS0
export QT_QPA_FB_TSLIB=1
7、进入到 /opt/qt5.4.1/examples/touch/pinchzoom/ 文件夹中,执行ldd查看一下文件的动态链接库的依赖:
[root@dragonboard pinchzoom]# ldd pinchzoom
libts-1.0.so.0 => /opt/tslib/lib/libts-1.0.so.0 (0xb6fcc000)
libQt5Widgets.so.5 => /opt/qt5.4.1/lib/libQt5Widgets.so.5 (0xb6bec000)
libQt5Gui.so.5 => /opt/qt5.4.1/lib/libQt5Gui.so.5 (0xb68f4000)
libQt5Core.so.5 => /opt/qt5.4.1/lib/libQt5Core.so.5 (0xb64cd000)
libpthread.so.0 => /lib/libpthread.so.0 (0xb64ad000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0xb63d6000)
libm.so.6 => /lib/libm.so.6 (0xb6331000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0xb631e000)
libc.so.6 => /lib/libc.so.6 (0xb61d7000)
libdl.so.2 => /lib/libdl.so.2 (0xb61ca000)
librt.so.1 => /lib/librt.so.1 (0xb61ba000)
/lib/ld-linux.so.3 (0xb6fd6000)
依赖路径都正确,运行pinchzoom:
运行没有问题,但是触摸没有反应并报下面的问题。测试了很多应用都没有用,然后又试了tslib自带的工具 ts_test ,发现会也会出现下面的问题:
tslib: Selected device is not a touchscreen (must support ABS_X and ABS_Y events)
意思是我选的这个设备不支持ABS_X和ABS_Y,参考文章:https://blog.csdn.net/a694543965/article/details/79935086里面关于ABS的说明。之前使用 ts_calibrate、ts_test 等工具都是能够正常使用的,但是我当前使用的这个tslib是我使用tslib源码编译出来的,就出现这个问题了,硬件和驱动程序都没有改动,说明可能是tslib软件版本兼容性的问题,因为使用我之前移植LittlevGL的Demo程序的时候是可以正常使用触摸运行的。先测试一下使用鼠标,将上面的环境变量配置文件qtenv.sh中的QT_QPA_GENERIC_PLUGINS改成evdevmouse:/dev/input/event6,这里event6是通过查看 cat /proc/bus/input/devices 看出来的,Handlers指向的是mouse0和event6:
I: Bus=0003 Vendor=1532 Product=001c Version=0111
N: Name="Razer Razer Abyssus"
P: Phys=usb-sunxi-ehci-1.2/input0
S: Sysfs=/devices/platform/sunxi-ehci.1/usb1/1-1/1-1.2/1-1.2:1.0/input/input6
U: Uniq=
H: Handlers=mouse0 event6
B: PROP=0
B: EV=17
B: KEY=7f0000 0 0 0 0 0 0 0 0
B: REL=103
B: MSC=10
运行 /opt/qt5.4.1/examples/widgets/mainwindows/mainwindow/mainwindow 应用,可以使用正常使用鼠标,但是流畅性好差劲:
至于tslib的问题,后面再说,先看看怎么编写自己的QT程序。编写一个能够在上面搭建的目标板上运行的QT应用主要有下面几个步骤:
1、http://download.qt.io/official_releases/qtcreator/4.9/4.9.2/下载qt-creator-opensource-linux-x86_64-4.9.2.run文件。
2、执行qt-creator-opensource-linux-x86_64-4.9.2.run安装Qt Creator,Qt Creator可运行文件在: <安装目录>/bin/qtcreator。
3、打开Qt Creator,进入Tools->Options->Kits,我们需要创建一个新的 Kit(工具箱),这其实就是创建一个针对目标板的编译调试环境,不过创建 Kit 之前,我们先把 Kit 里面的工具都准备好。
4、Tools->Options->Kits->Qt Versions,手动添加一个Qt Version,位置就是我们在QT源码中make install 的时候生成目标路径,这里就是 /opt/qt5.4.1/bin/qmake ,新建的Qt Version名为Qt 5.4.1(自动命名),添加完这个qmake之后相当于配置了一个版本的QT系统。
5、Tools->Options->Kits->Comilers,手动添加一个GCC编译器,包括C和C++编译器,并重命名,这里使用我们编译QT和tslib源码的时候使用的交叉编译器的路径即可。
6、Tools->Options->Kits->Debuggers,手动添加上诉交叉编译器的gdb,并重命名。
7、其它的选项卡中的配置,还没有测试,现在进入Tools->Options->Kits->Kits,新建一个 Kit 并重命名为AllwinnerA33,然后将其中的Qt Version、Comiler、Debugger等设置为上面新建的工具的名字。
8、新建Qt Widget Application命名为qttest,选择Kits为我们创建的AllwinnerA33,其它保持默认,然后在mainwindow.ui界面文件中拖拽一个Push Button,右击按键选择Go to slot,双击 clicked() 自动跳入事件处理函数,添加下面内容:
void MainWindow::on_pushButton_clicked()
{
QMessageBox::information(NULL, "message", "qt test",
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
}
功能就是按下按键的时候弹出一个消息框,点击Qt Creator最左下角的小锤子Build Project,然后在创建工程的会生成一个build-qttest-AllwinnerA33-Debug文件夹,里面有一个可执行文件qttest ,在开发板上执行这个文件即可:
好复古的界面,和WIN98既视感。
下面来解决一下tslib的问题。前面说过出现了如下问题:
tslib: Selected device is not a touchscreen (must support ABS_X and ABS_Y events)
找到自己编译的tslib源码目录并执行命令:grep "Selected device is not a touchscreen" -rnsw ,结果如下:
root@tq:/home/tangquan/baidunetdiskdownload/02_qt_sdk/tslib-src# grep "Selected device is not a touchscreen" -rnsw
Binary file plugins/.libs/input-raw.o matches
Binary file plugins/.libs/input.so matches
Binary file plugins/.libs/input.soT matches
plugins/input-raw.c:94: fprintf(stderr, "tslib: Selected device is not a touchscreen (must support ABS and KEY event types)\n");
plugins/input-raw.c:101: fprintf(stderr, "tslib: Selected device is not a touchscreen (must support ABS_X and ABS_Y events)\n");
plugins/input-raw.c:116: fprintf(stderr, "tslib: Selected device is not a touchscreen (must support BTN_TOUCH or BTN_LEFT events)\n");
查看plugin/input-raw.c:101行内容:
static int check_fd(struct tslib_input *i)
{
struct tsdev *ts = i->module.dev;
int version;
long evbit[BITS_TO_LONGS(EV_CNT)];
long absbit[BITS_TO_LONGS(ABS_CNT)];
long keybit[BITS_TO_LONGS(KEY_CNT)];
if (ioctl(ts->fd, EVIOCGVERSION, &version) < 0) {
fprintf(stderr, "tslib: Selected device is not a Linux input event device\n");
return -1;
}
if (version < EV_VERSION) {
fprintf(stderr, "tslib: Selected device uses a different version of the event protocol than tslib was compiled for\n");
return -1;
}
if ( (ioctl(ts->fd, EVIOCGBIT(0, sizeof(evbit)), evbit) < 0) ||
!(evbit[BIT_WORD(EV_ABS)] & BIT_MASK(EV_ABS)) ||
!(evbit[BIT_WORD(EV_KEY)] & BIT_MASK(EV_KEY)) ) {
fprintf(stderr, "tslib: Selected device is not a touchscreen (must support ABS and KEY event types)\n");
return -1;
}
if ((ioctl(ts->fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit)) < 0 ||
!(absbit[BIT_WORD(ABS_X)] & BIT_MASK(ABS_X)) ||
!(absbit[BIT_WORD(ABS_Y)] & BIT_MASK(ABS_Y))) {
fprintf(stderr, "tslib: Selected device is not a touchscreen (must support ABS_X and ABS_Y events)\n");
return -1;
}
......
发现该版本的tslib程序中检查了设备的ABS_X和ABS_Y位,如果这两位没有置位,说明该设备不支持该功能。我的板子带的触摸屏的信息为:
I: Bus=0000 Vendor=0000 Product=0000 Version=0000
N: Name="ft5x_ts"
P: Phys=
S: Sysfs=/devices/virtual/input/input4
U: Uniq=
H: Handlers=ddrfreq_dsm event4
B: PROP=0
B: EV=b
B: KEY=400 0 0 0 0 0 0 0 0 0 0
B: ABS=2650000 0
ABS_X、ABS_Y的掩码定义在 input-event-codes.h 文件中:
#define ABS_X 0x00
#define ABS_Y 0x01
#define ABS_Z 0x02
#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
#define ABS_VOLUME 0x20
#define ABS_MISC 0x28
#define ABS_MT_SLOT 0x2f /* MT slot being modified */
#define ABS_MT_TOUCH_MAJOR 0x30 /* Major axis of touching ellipse */
#define ABS_MT_TOUCH_MINOR 0x31 /* Minor axis (omit if circular) */
#define ABS_MT_WIDTH_MAJOR 0x32 /* Major axis of approaching ellipse */
#define ABS_MT_WIDTH_MINOR 0x33 /* Minor axis (omit if circular) */
#define ABS_MT_ORIENTATION 0x34 /* Ellipse orientation */
#define ABS_MT_POSITION_X 0x35 /* Center X touch position */
#define ABS_MT_POSITION_Y 0x36 /* Center Y touch position */
#define ABS_MT_TOOL_TYPE 0x37 /* Type of touching device */
#define ABS_MT_BLOB_ID 0x38 /* Group a set of packets as a blob */
#define ABS_MT_TRACKING_ID 0x39 /* Unique ID of initiated contact */
#define ABS_MT_PRESSURE 0x3a /* Pressure on contact area */
#define ABS_MT_DISTANCE 0x3b /* Contact hover distance */
#define ABS_MT_TOOL_X 0x3c /* Center X tool position */
#define ABS_MT_TOOL_Y 0x3d /* Center Y tool position */
显然我的触摸屏设置是不支持ABS_X、ABS_Y的,但是支持ABS_HAT0X、ABS_HAT0Y以及ABS_DISTANCE,然后我的LittlevGL移植的测试程序里面读取的时候是读取到了ABS_MT_POSITION_X和ABS_MT_POSITION_Y数据,有点混乱了。算了,把之前的旧版本(或者说是更新版本)的tslib库移植回来试试(原来版本的ts.conf文件在/etc目录下,现在放在tslib中的etc文件夹),按道理这个版本的的源码也有判断设备是否支持该设备的部分,但是这里只有动态库文件,没有源码,不过库文件里面照样会存放字符串常量的,不妨 grep 查找一下试试,发现在 lib/ts/input.so 中有搜索到,查看lib/ts/input.so并搜索:
tslib: Selected device is not a touchscreen (must support ABS_MT_POSITION_X and ABS_MT_POSITION_Y events)
嗯哼,这个库中判断的是设备是否支持ABS_MT_POSITION_X和ABS_MT_POSITION_Y,而不是ABS_X和ABS_Y,说明前后两个库的源码是有区别的。那我们我们可以使用这个库试试了,修改 qtenv.sh 脚本并点命令执行修改环境变量:
export T_ROOT=/mnt/tslib
export LD_LIBRARY_PATH=$T_ROOT/lib:$LD_LIBRARY_PATH
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0
export TSLIB_TSDEVICE=/dev/input/event4
export TSLIB_PLUGINDIR=$T_ROOT/lib/ts
export TSLIB_CONFFILE=$T_ROOT/etc/ts.conf
export POINTERCAL_FILE=/etc/pointercal
export TSLIB_CALIBFILE=/etc/pointercal
......
运行 ts_test 工具进行测试:
原先版本的tslib是可以识别我的触摸屏设备的,再修改qtenv.sh 脚本设置输入设备为tslib,测试qt例程,没有问题,可以正常使用触摸。关于tslib的差异问题,我去tslib的官方github工程:https://github.com/libts/tslib,看了看最新的tslib源码,其中 plugins/input-raw.c 有做修改:
if ((ioctl(ts->fd, EVIOCGBIT(EV_ABS, sizeof(absbit)), absbit)) < 0 ||
!(absbit[BIT_WORD(ABS_X)] & BIT_MASK(ABS_X)) ||
!(absbit[BIT_WORD(ABS_Y)] & BIT_MASK(ABS_Y))) {
if (!(absbit[BIT_WORD(ABS_MT_POSITION_X)] & BIT_MASK(ABS_MT_POSITION_X)) ||
!(absbit[BIT_WORD(ABS_MT_POSITION_Y)] & BIT_MASK(ABS_MT_POSITION_Y))) {
fprintf(stderr,
"tslib: Selected device is not a touchscreen (must support ABS_X/Y or ABS_MT_POSITION_X/Y events)\n");
return -1;
}
}
这里判断的是设备是否支持ABS_MT_POSITION_X和ABS_MT_POSITION_Y或者ABS_X和ABS_Y,只要支持一样就可以啦,移植最新的tslib吧还是。