- 参考正点原子的Qt移植教程,整理自己实际的应用步骤
- 同样的步骤测试Qt5.9.5也适用
一、安装虚拟机环境
- 当前使用版本:Ubuntu16.04.7
$ lsb_release -a # 查看当前系统信息
二、安装交叉编译器
2.1 下载交叉编译器
- 下载地址:Linaro Releases
- 交叉编译器版本:4.9.4
- 【TIPS】不推荐选择过高版本,建议采用实测版本
- 根据芯片类型选择,不带
- 根据当前系统版本选择对应的文件
$ uname -a
Linux ubuntu 4.15.0-112-generic #113~16.04.1-Ubuntu SMP Fri Jul 10 04:37:08 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
# x86_64: 当前系统为64位系统,选择x86_64版本的交叉编译器
2.2 安装交叉编译器
- 新建arm文件
$ sudo mkdir /usr/local/arm
- 解压交叉编译器压缩包到arm路径下
$ sudo tar -xvf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi.tar.xz
## 建议重新修改名称
$ mv gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabi /usr/local/arm/linaro-4.9.4
- 配置环境变量
$ sudo vim /etc/profile
## 将下行内容添加到文件末尾
export PATH=$PATH:/usr/local/arm/linaro-4.9.4/bin
## /etc/profile 开机加载;通过source指令立即生效
$ source /etc/profile
2.3 验证交叉编译器
## 使用该交叉编译器前,需要在Ubuntu安装一些库 【必要!】
$ sudo apt-get install lsb-core lib32stdc++6
## 验证交叉编译器,有版本信息输出则配置完成
$ arm-linux-gnueabi-gcc -v
...
gcc version 4.9.4 (Linaro GCC 4.9-2017.01)
三、获取和编译tslib
- Qt支持触摸屏,还需要编译tslib,以产生相关插件
3.1 下载tslib
- 下载地址:tslib
- 项目页面的右侧,在release中选择1.21版本
3.2 编译tslib
- 安装辅助软件
$ sudo apt-get update
$ sudo apt-get install autoconf automake libtool
- 调用脚本
$ tar -xvf tslib-1.21 ## 解压
$ cd tslib-1.21
$ ./autogen.sh # 软件文件夹自带脚本
- 执行configure指令
$ ./configure \
--host=arm-linux-gnueabi \ # 指定编译器
ac_cv_func_malloc_0_nonnull=yes \
--cache-file=arm-linux.cache \
-prefix=${PWD}/arm-tslib # 指定编译输出路径;根据自己的实际路径设置
- 编译
$ make
$ make install
3.3 验证编译生成文件
$ file /arm-tslib/bin/ts_calibrate # 查看编译生成文件的类型; 32-bit ARM 即为正确
bin/ts_calibrate: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV),
dynamically linked, interpreter /lib/ld-linux.so.3, for GNU/Linux 2.6.31,
BuildID[sha1]=235a90a36a321ae53ee3f6f63f44d7aed8e83e64, not stripped
四、编译Qt5.12.9源码
4.1 下载源码
- 下载地址:Qt5.12.9
- 解压源码
$ tar -xvf qt-everywhere-src-5.12.9.tar.xz
## 建议修改文件名
$ mv qt-everywhere-src-5.12.9 qt-5.12.9
$ cd qt-5.12.9
4.2 修改qmake.conf
$ gedit qtbase/mkspecs/linux-arm-gnueabi-g++/qmake.conf
## 添加下列内容;位置在 include(../common/linux.conf) 之前
QT_QPA_PLATFORM = linuxfb:fb=/dev/fb0
QMAKE_CFLAGS_RELEASE += -O2 -march=armv7-a
QMAKE_CXXFLAGS_RELEASE += -O2 -march=armv7-a
4.3 修改qlinuxfbscreen
- 在嵌入式中运行,Qt4采用QWS系统,Qt5采用QPA方案
- 在Qt4中可设置QWS_DISPLAY旋转软件
- 在Qt5中只能通过修改qlinuxfbscreen源码处理【通过QGraphicsScene的方案,事件无法往子控件传递】
$ gedit qtbase/src/plugins/platforms/linuxfb/qlinuxfbscreen.h # 修改头文件
$ gedit qtbase/src/plugins/platforms/linuxfb/qlinuxfbscreen.cpp # 修改源文件
class QLinuxFbScreen : public QFbScreen
{
Q_OBJECT
public:
QLinuxFbScreen(const QStringList &args);
~QLinuxFbScreen();
bool initialize() override;
QPixmap grabWindow(WId wid, int x, int y, int width, int height) const override;
QRegion doRedraw() override;
private:
QStringList mArgs;
int mFbFd;
int mTtyFd;
// add by immortal start
int mRotation;
// add by immortal end
QImage mFbScreenImage;
int mBytesPerLine;
int mOldTtyMode;
struct {
uchar *data;
int offset, size;
} mMmap;
QPainter *mBlitter;
};
QLinuxFbScreen::QLinuxFbScreen(const QStringList &args)
// : mArgs(args), mFbFd(-1), mTtyFd(-1), mBlitter(0) // modify by immortal
: mArgs(args), mFbFd(-1), mTtyFd(-1), mBlitter(0),mRotation(0)
{
mMmap.data = 0;
}
QLinuxFbScreen::~QLinuxFbScreen()
{
if (mFbFd != -1) {
if (mMmap.data)
munmap(mMmap.data - mMmap.offset, mMmap.size);
close(mFbFd);
}
if (mTtyFd != -1)
resetTty(mTtyFd, mOldTtyMode);
delete mBlitter;
}
bool QLinuxFbScreen::initialize()
{
QRegularExpression ttyRx(QLatin1String("tty=(.*)"));
QRegularExpression fbRx(QLatin1String("fb=(.*)"));
QRegularExpression mmSizeRx(QLatin1String("mmsize=(\\d+)x(\\d+)"));
QRegularExpression sizeRx(QLatin1String("size=(\\d+)x(\\d+)"));
QRegularExpression offsetRx(QLatin1String("offset=(\\d+)x(\\d+)"));
// add by immorta start
QRegularExpression rotationRx(QLatin1String("rotation=(0|90|180|270)"))
// add by immorta end
QString fbDevice, ttyDevice;
QSize userMmSize;
QRect userGeometry;
bool doSwitchToGraphicsMode = true;
// Parse arguments
foreach (const QString &arg, mArgs) {
QRegularExpressionMatch match;
if (arg == QLatin1String("nographicsmodeswitch"))
doSwitchToGraphicsMode = false;
else if (arg.contains(mmSizeRx, &match))
userMmSize = QSize(match.captured(1).toInt(), match.captured(2).toInt());
else if (arg.contains(sizeRx, &match))
userGeometry.setSize(QSize(match.captured(1).toInt(), match.captured(2).toInt()));
else if (arg.contains(offsetRx, &match))
userGeometry.setTopLeft(QPoint(match.captured(1).toInt(), match.captured(2).toInt()));
else if (arg.contains(ttyRx, &match))
ttyDevice = match.captured(1);
else if (arg.contains(fbRx, &match))
fbDevice = match.captured(1);
// add by immortal start
else if (arg.contains(rotationRx, &match))
mRotation = match.captured(1).toInt();
// add by immortal end
}
if (fbDevice.isEmpty()) {
fbDevice = QLatin1String("/dev/fb0");
if (!QFile::exists(fbDevice))
fbDevice = QLatin1String("/dev/graphics/fb0");
if (!QFile::exists(fbDevice)) {
qWarning("Unable to figure out framebuffer device. Specify it manually.");
return false;
}
}
// Open the device
mFbFd = openFramebufferDevice(fbDevice);
if (mFbFd == -1) {
qErrnoWarning(errno, "Failed to open framebuffer %s", qPrintable(fbDevice));
return false;
}
// Read the fixed and variable screen information
fb_fix_screeninfo finfo;
fb_var_screeninfo vinfo;
memset(&vinfo, 0, sizeof(vinfo));
memset(&finfo, 0, sizeof(finfo));
if (ioctl(mFbFd, FBIOGET_FSCREENINFO, &finfo) != 0) {
qErrnoWarning(errno, "Error reading fixed information");
return false;
}
if (ioctl(mFbFd, FBIOGET_VSCREENINFO, &vinfo)) {
qErrnoWarning(errno, "Error reading variable information");
return false;
}
mDepth = determineDepth(vinfo);
mBytesPerLine = finfo.line_length;
QRect geometry = determineGeometry(vinfo, userGeometry);
// add by immortal start
QRect originalGeometry = geometry;
if( 90 == mRotation || 270 == mRotation )
{
int tmp = geometry.width();
geometry.setWidth(geometry.height());
geometry.setHeight(tmp);
}
// add by immortal end
mGeometry = QRect(QPoint(0, 0), geometry.size());
mFormat = determineFormat(vinfo, mDepth);
// modify by immortal start
// mPhysicalSize = determinePhysicalSize(vinfo, userMmSize, geometry.size());
mPhysicalSize = determinePhysicalSize(vinfo, userMmSize, originalGeometry.size());
// modify by immortal end
// mmap the framebuffer
mMmap.size = finfo.smem_len;
uchar *data = (unsigned char *)mmap(0, mMmap.size, PROT_READ | PROT_WRITE, MAP_SHARED, mFbFd, 0);
if ((long)data == -1) {
qErrnoWarning(errno, "Failed to mmap framebuffer");
return false;
}
// modify by immortal start
// mMmap.offset = geometry.y() * mBytesPerLine + geometry.x() * mDepth / 8;
mMmap.offset = originalGeometry.y() * mBytesPerLine + originalGeometry.x() * mDepth / 8;
// modify by immortal end
mMmap.data = data + mMmap.offset;
QFbScreen::initializeCompositor();
// modify by immortal start
// mFbScreenImage = QImage(mMmap.data, geometry.width(), geometry.height(), mBytesPerLine, mFormat);
mFbScreenImage = QImage(mMmap.data, originalGeometry.width(), originalGeometry.height(), mBytesPerLine, mFormat);
// modify by immortal end
mCursor = new QFbCursor(this);
mTtyFd = openTtyDevice(ttyDevice);
if (mTtyFd == -1)
qErrnoWarning(errno, "Failed to open tty");
switchToGraphicsMode(mTtyFd, doSwitchToGraphicsMode, &mOldTtyMode);
blankScreen(mFbFd, false);
return true;
}
QRegion QLinuxFbScreen::doRedraw()
{
QRegion touched = QFbScreen::doRedraw();
if (touched.isEmpty())
return touched;
if (!mBlitter)
mBlitter = new QPainter(&mFbScreenImage);
const QVector<QRect> rects = touched.rects();
mBlitter->setCompositionMode(QPainter::CompositionMode_Source);
for (int i = 0; i < rects.size(); ++i)
// add by immortal start
{
if( 90 == mRotation || 270 == mRotation )
{
mBlitter->translate(mGeometry.height()/2, mGeometry.width()/2);
}
else if( 180 == mRotation )
{
mBlitter->translate(mGeometry.width()/2, mGeometry.height()/2);
}
if( mRotation != 0 )
{
mBlitter->rotate(mRotation);
mBlitter->translate(-mGeometry.width()/2, -mGeometry.height()/2);
}
// add by immortal end
mBlitter->drawImage(rects[i], *mScreenImage, rects[i]);
// add by immortal start
mBlitter->resetTransform();
// add by immortal end
}
return touched;
}
// grabWindow() grabs "from the screen" not from the backingstores.
// In linuxfb's case it will also include the mouse cursor.
QPixmap QLinuxFbScreen::grabWindow(WId wid, int x, int y, int width, int height) const
{
if (!wid) {
if (width < 0)
width = mFbScreenImage.width() - x;
if (height < 0)
height = mFbScreenImage.height() - y;
return QPixmap::fromImage(mFbScreenImage).copy(x, y, width, height);
}
QFbWindow *window = windowForId(wid);
if (window) {
const QRect geom = window->geometry();
if (width < 0)
width = geom.width() - x;
if (height < 0)
height = geom.height() - y;
QRect rect(geom.topLeft() + QPoint(x, y), QSize(width, height));
rect &= window->geometry();
return QPixmap::fromImage(mFbScreenImage).copy(rect);
}
return QPixmap();
}
4.4 配置编译选项
## 配置选项保存为脚本文件
$ gedit autoconfigure.sh
$ sudo chmod a+x autoconfigure.sh # 添加执行权限
$ ./autoconfigure.sh
...
Qt is now configure for building, Just run 'make' # 看到此条信息,可直接编译
...
## autoconfigure.sh文件内容
#!/bin/bash
./configure -prefix ${PWD}/arm-qt \
-opensource \
-confirm-license \
-release \
-strip \
-shared \
-xplatform linux-arm-gnueabi-g++ \
-optimized-qmake \
-c++std c++11 \
--rpath=no \
-pch \
-make libs \
-nomake examples -nomake tools \
-nomake tests \
-gui \
-widgets \
-dbus-runtime \
--glib=no \
--iconv=no \
--pcre=qt \
--zlib=qt \
-no-openssl \
-no-opengl \
--freetype=qt \
--harfbuzz=qt \
-linuxfb \
--xcb=no \
-tslib \
--libpng=qt \
--libjpeg=qt \
--sqlite=qt \
-plugin-sql-sqlite \
-I/usr/local/arm/arm-tslib/include \ # 设定为实际路径
-L/usr/local/arm/arm-tslib/lib \ # 设置为实际路径
-recheck-all
4.5 编译Qt源码
$ make # 等 1-2 h完成,也可以使用 make -j n;全速编译
make[1]: Leaving directory ...
$ make install
五、移植Qt到示教器
5.1 移植tslib
## 压缩编译输出文件
$ tar -zcvf arm-tslib.tar.gz arm-tslib
- 通过scp、sftp或U盘将文件传送到示教器中
$ tar -zxvf arm-tslib.tar.gz # 解压文件
- 配置环境变量
## 示教器使用 vim.tiny;显示有时有问题,改用nano编辑
$ nano /etc/bash.bashrc
## 将之间的配置信息注释
# export T_ROOT=/usr/local/tslib
# export TSLIB_CONFFILE=$T_ROOT/etc/ts.conf
# export TSLIB_TSEVENTTYPE=H3600
# export TSLIB_CONSOLEDEVICE=none
# export TSLIB_FBDEVICE=/dev/fb0
# export TSLIB_PLUGINDIR=$T_ROOT/lib/ts
# export TSLIB_CALIBFILE=/etc/pointercal
## 修改环境变量
export TSLIB_ROOT=/usr/local/arm/tslib-1.21 # 实际存放路径
export TSLIB_CONSOLEDEVICE=none
export TSLIB_FBDEVICE=/dev/fb0
export TSLIB_CONFFILE=$TSLIB_ROOT/etc/ts.conf
export TSLIB_PLUGINDIR=$TSLIB_ROOT/lib/ts
export TSLIB_CALIBFILE=/etc/pointercal
export LD_PRELOAD=$TSLIB_ROOT/lib/libts.so
if [ -e /dev/input/mouse1 ]; then
export TSLIB_TSDEVICE=/dev/input/event2
else
export TSLIB_TSDEVICE=/dev/input/event1
rm -f /dev/input/mouse0
fi
- 测试tslib
$ source /etc/bash.bashrc
$ /usr/local/arm/tslib-1.21/bin/ts_test # 运行ts_test测试触摸屏是否正常
5.2 移植Qt
## 压缩编译输出文件
$ tar -zcvf arm-qt.tar.gz arm-qt
- 传输方式同上即可
$ tar -zxvf arm-qt.tar.gz
- 配置环境变量
$ nano /etc/bash.bashrc
## Qt5 使用 QPA 环境变量;Qt4 使用 QWS 环境变量
export QT_ROOT=/usr/local/arm/qt-5.12 # 实际路径
export QT_QPA_GENERIC_PLUGINS=tslib:/dev/input/event1
export QT_QPA_FONTDIR=/usr/share/fonts # 如果没有,找到系统自带的;指定为实际路径
export QT_QPA_PLATFORM_PIUGIN_PATH=$QT_ROOT/plugins
export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb0:rotation=90 # 指定旋转度数
export QT_PLUGIN_PATH=$QT_ROOT/plugins
export LD_LIBRARY_PATH=$QT_ROOT/lib:$QT_ROOT/plugins/platforms
export QML2_IMPORT_PATH=$QT_ROOT/qml
export QT_QPA_FB_TSLIB=1
- 测试Qt运行
$ source /etc/bash.bashrc
## 由于编译Qt源码跳过了examples 和 tests,无现成的测试应用;需要新建工程测试,结合下一节实现
六、搭建ARM平台的Qt Creator环境
6.1 安装Qt Creator
-
下载地址:Qt5.12.9
-
下载完成,在虚拟机环境双击安装
6.2 配置Qt Creator Kit
-
打开选项
-
配置qmake:找到编译生成arm-qt下的qmake
-
配置gcc编译器:安装的linaro
-
配置Kits:以实际版本名称,或自定义命名
6.3 编译程序
- 选择Arm Kit编译Qt工程
七、运行程序
- 将文件传输到示教器
- 添加可执行权限
- 直接执行
7.1 旋转tslib
- 软件旋转后,触摸的坐标系没有匹配;导致点击屏幕的位置不对
$ /usr/local/arm/tslib-1.21/bin/ts_calibrate -h # 查看帮助信息
$ ts_calibrate -r 1 # 旋转90°,调出屏幕校准;文字阅读方向即为当前正方向
- 校准屏幕后直接运行程序
$ ./Icon # 直接运行即可