4. Qt环境搭建–ARM

上一章节我们已经配置好了交叉编译工具链,这一章节我们就使用这套工具链来交叉编译Qt库。

4.1. 编译Qt库前的准备

这里又不得不提到LubanCat了,我们的程序最终是在LubanCat上运行的。 LubanCat上有很多外设,比如触摸屏和声卡。

tslib是一个用于触摸屏设备的开源函数库,能够为触摸屏驱动获得的采样提供诸如滤波、去抖、校准等功能, 通常作为触摸屏驱动的适配层,为上层的应用提供了一个统一的接口,比如Qt就是上层应用,数据通过tslib传入Qt应用程序, Qt应用程序就知道哪里被触摸了,然后进行正确的响应。而且通过这样一个函数库,可以将编程者从繁琐的数据处理中解脱出来, 因为触摸屏的坐标和液晶显示屏之间的坐标并不是一一对应的,所以,要让从触摸屏上得到的坐标正确转换为液晶显示屏上的坐标, 需要经过一个转换过程,而tslib就是完成这个功能的。因此在这里预先编译安装tslib,这样在后面编译Qt的时候才能将tslib打包编译进去。

tslib http://www.tslib.org/

高级Linux声音体系(英语:Advanced Linux Sound Architecture,缩写为ALSA),在Linux内核中,ALSA为声卡提供的驱动组件。 ALSA支持声卡的自动配置,以及可以完美的处理系统中的多个声卡设备,所以可能会使用到ALSA, 此时就预先将ALSA交叉编译完成,以便在交叉编译Qt时能将ALSA包含编译进去。

alsa https://www.alsa-project.org/wiki/Main_Page

想要我们LubanCat的触摸屏和声卡设备能被Qt应用程序调用、控制,就需要在编译Qt库的时候加入tslib和alsa的配置。 加入他们的配置之后,Qt源码编译的时候就会去链接tslib和alsa的相关库, 因此我们要提前准备好tslib和alsa的arm架构的库,也就是要交叉编译tslib和alsa。

交叉编译tslib https://doc.embedfire.com/linux/qt/embed/zh/latest/ebf_qt/install/install_qtmod.html#qt

交叉编译alsa https://doc.embedfire.com/linux/qt/embed/zh/latest/ebf_qt/install/install_qtmod.html#qtlinux

4.2. 交叉编译Qt库

本次交叉编译Qt源码的版本选择5.11.3版本。

qt_cross_compiling003.png

野火提供 build-qt.sh 脚本一键下载、配置、安装依赖、编译及安装 qt

build-qt.sh 脚本内容如下:

embed_qt_develop_tutorial_code/script/build-qt.sh
  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
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
  #!/bin/sh
  # set -v

  PLATFORM=my-linux-arm-qt
  SCRIPT_PATH=$(pwd)

  #修改源码包解压后的名称
  MAJOR_NAME=qt-everywhere-src

  #修改需要下载的源码前缀和后缀
  OPENSRC_VER_PREFIX=5.11
  OPENSRC_VER_SUFFIX=.3

  #添加tslib交叉编译的动态库文件和头文件路径
  TSLIB_LIB=/opt/tslib-1.21/lib
  TSLIB_INC=/opt/tslib-1.21/include

  #添加alsa交叉编译的动态库文件和头文件路径
  ALSA_LIB=/opt/alsa-lib-1.2.2/lib
  ALSA_INC=/opt/alsa-lib-1.2.2/include

  #修改源码包解压后的名称
  PACKAGE_NAME=${MAJOR_NAME}-${OPENSRC_VER_PREFIX}${OPENSRC_VER_SUFFIX}

  #定义编译后安装--生成的文件,文件夹位置路径
  INSTALL_PATH=/opt/${PACKAGE_NAME}

  #添加交叉编译工具链路径
  # CROSS_CHAIN_PREFIX=/opt/arm-gcc/bin/arm-linux-gnueabihf
  CROSS_CHAIN_PREFIX=/opt/gcc-arm-linux-gnueabihf-8.3.0/bin/arm-linux-gnueabihf

  #定义压缩包名称
  COMPRESS_PACKAGE=${PACKAGE_NAME}.tar.xz

  #无需修改--自动组合下载地址
  OPENSRC_VER=${OPENSRC_VER_PREFIX}${OPENSRC_VER_SUFFIX}

  case ${OPENSRC_VER_PREFIX} in
        5.9 | 5.12 | 5.13 | 5.14 |5.15 )
        DOWNLOAD_LINK=http://download.qt.io/official_releases/qt/${OPENSRC_VER_PREFIX}/${OPENSRC_VER}/single/${COMPRESS_PACKAGE}
        ;;
     *)
        DOWNLOAD_LINK=http://download.qt.io/new_archive/qt/${OPENSRC_VER_PREFIX}/${OPENSRC_VER}/single/${COMPRESS_PACKAGE}
        ;;
  esac

  #无需修改--自动组合平台路径
  CONFIG_PATH=${SCRIPT_PATH}/${PACKAGE_NAME}/qtbase/mkspecs/${PLATFORM}

  #无需修改--自动组合配置平台路径文件
  CONFIG_FILE=${CONFIG_PATH}/qmake.conf

  #下载源码包
  do_download_src () {
     echo "\033[1;33mstart download ${PACKAGE_NAME}...\033[0m"

     if [ ! -f "${COMPRESS_PACKAGE}" ];then
        if [ ! -d "${PACKAGE_NAME}" ];then
           wget -c ${DOWNLOAD_LINK}
        fi
     fi

     echo "\033[1;33mdone...\033[0m"
  }

  #解压源码包
  do_tar_package () {
     echo "\033[1;33mstart unpacking the ${PACKAGE_NAME} package ...\033[0m"
     if [ ! -d "${PACKAGE_NAME}" ];then
        tar -xf ${COMPRESS_PACKAGE}
     fi
     echo "\033[1;33mdone...\033[0m"
     cd ${PACKAGE_NAME}

     # 修复5.11.3 版本的bug
     if [ ${OPENSRC_VER_PREFIX}=="5.11" -a ${OPENSRC_VER_SUFFIX}==".3" ]; then
        sed 's/asm volatile /asm /' -i qtscript/src/3rdparty/javascriptcore/JavaScriptCore/jit/JITStubs.cpp
     fi
  }

  #安装依赖项
  do_install_config_dependent () {
     sudo apt install python -y
     sudo apt install g++ make qt3d5-dev-tools -y
     sudo apt install qml-module-qtquick-xmllistmodel -y
     sudo apt install qml-module-qtquick-virtualkeyboard qml-module-qtquick-privatewidgets qml-module-qtquick-dialogs qml -y
     sudo apt install libqt53dquickscene2d5 libqt53dquickrender5 libqt53dquickinput5 libqt53dquickextras5 libqt53dquickanimation5 libqt53dquick5 -y
     sudo apt install qtdeclarative5-dev qml-module-qtwebengine qml-module-qtwebchannel qml-module-qtmultimedia qml-module-qtaudioengine -y
  }

  #修改配置平台
  do_config_before () {
     echo "\033[1;33mstart configure platform...\033[0m"

  if [ ! -d "${CONFIG_PATH}" ];then
     cp -a ${SCRIPT_PATH}/${PACKAGE_NAME}/qtbase/mkspecs/linux-arm-gnueabi-g++ ${CONFIG_PATH}
  fi

     echo "#" > ${CONFIG_FILE}
     echo "# qmake configuration for building with arm-linux-gnueabi-g++" >> ${CONFIG_FILE}
     echo "#" >> ${CONFIG_FILE}
     echo "" >> ${CONFIG_FILE}
     echo "MAKEFILE_GENERATOR      = UNIX" >> ${CONFIG_FILE}
     echo "CONFIG                 += incremental" >> ${CONFIG_FILE}
     echo "QMAKE_INCREMENTAL_STYLE = sublib" >> ${CONFIG_FILE}
     echo "" >> ${CONFIG_FILE}
     echo "include(../common/linux.conf)" >> ${CONFIG_FILE}
     echo "include(../common/gcc-base-unix.conf)" >> ${CONFIG_FILE}
     echo "include(../common/g++-unix.conf)" >> ${CONFIG_FILE}
     echo "" >> ${CONFIG_FILE}
     echo "# modifications to g++.conf" >> ${CONFIG_FILE}
     echo "QMAKE_CC                = ${CROSS_CHAIN_PREFIX}-gcc -lts" >> ${CONFIG_FILE}
     echo "QMAKE_CXX               = ${CROSS_CHAIN_PREFIX}-g++ -lts" >> ${CONFIG_FILE}
     echo "QMAKE_LINK              = ${CROSS_CHAIN_PREFIX}-g++ -lts" >> ${CONFIG_FILE}
     echo "QMAKE_LINK_SHLIB        = ${CROSS_CHAIN_PREFIX}-g++ -lts" >> ${CONFIG_FILE}
     echo "" >> ${CONFIG_FILE}
     echo "# modifications to linux.conf" >> ${CONFIG_FILE}
     echo "QMAKE_AR                = ${CROSS_CHAIN_PREFIX}-ar cqs" >> ${CONFIG_FILE}
     echo "QMAKE_OBJCOPY           = ${CROSS_CHAIN_PREFIX}-objcopy" >> ${CONFIG_FILE}
     echo "QMAKE_NM                = ${CROSS_CHAIN_PREFIX}-nm -P" >> ${CONFIG_FILE}
     echo "QMAKE_STRIP             = ${CROSS_CHAIN_PREFIX}-strip" >> ${CONFIG_FILE}
     echo "load(qt_config)" >> ${CONFIG_FILE}
     echo "" >> ${CONFIG_FILE}
     echo "QMAKE_INCDIR=${TSLIB_INC}" >> ${CONFIG_FILE}
     echo "QMAKE_LIBDIR=${TSLIB_LIB}" >> ${CONFIG_FILE}

     cat ${CONFIG_FILE}
     echo "\033[1;33mdone...\033[0m"
  }

  #配置选项
  do_configure () {
     echo "\033[1;33mstart configure ${PACKAGE_NAME}...\033[0m"

     export CC="${CROSS_CHAIN_PREFIX}-gcc"
     export CXX="${CROSS_CHAIN_PREFIX}-g++"

     ./configure \
     -prefix ${INSTALL_PATH} \
     -xplatform ${PLATFORM} \
     -release \
     -opensource \
     -confirm-license \
     -no-openssl \
     -no-opengl \
     -no-xcb \
     -no-eglfs \
     -no-compile-examples \
     -no-pkg-config \
     -no-iconv \
     -no-glib \
     -tslib \
     -I"${TSLIB_INC}" \
     -L"${TSLIB_LIB}" \
     -alsa \
     -I"${ALSA_INC}" \
     -L"${ALSA_LIB}" \

     echo "\033[1;33mdone...\033[0m"
  }


  #编译并且安装
  do_make_install () {
     echo "\033[1;33mstart make and install ${PACKAGE_NAME} ...\033[0m"
     make && make install
     echo "\033[1;33mdone...\033[0m"
  }

  #删除下载的文件
  do_delete_file () {
     cd ${SCRIPT_PATH}
     if [ -f "${COMPRESS_PACKAGE}" ];then
        sudo rm -f ${COMPRESS_PACKAGE}
     fi
  }

  do_download_src
  do_tar_package
  do_install_config_dependent
  do_config_before
  do_configure
  do_make_install
  # do_delete_file

  exit $?

简单介绍一下脚本的内容:

  1. 使用wget命令下载qt源码.

  2. 解压下载完的源码包。

  3. 进入源码目录中,进行配置, 为了不污染源码本身,重新拷贝一份 qtbase/mkspecs/linux-arm-gnueabi-g++ 中的配置, 并且命名为 my-linux-arm-qt, 然后修改qmake.conf文件的内容, 主要是指定编译Qt的编译器: /opt/gcc-arm-linux-gnueabihf-8.3.0/bin/arm-linux-gnueabihf-gcc

    当然,这部分操作均在脚本中完成的。

qt_cross_compiling004.png
qt_cross_compiling005.png
  1. 安装一些对应的依赖。

  2. 编译Qt并安装到指定目录下: /opt/qt-everywhere-src-5.11.3

安装结束,切换到/opt/qt-everywhere-src-5.11.3/lib下查看我们编译安装的环境。

root@lbb:/opt/qt-everywhere-src-5.11.3/lib# file libQt5Core.so.5.11.3
libQt5Core.so.5.11.3: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (GNU/Linux), dynamically linked, for GNU/Linux 4.11.0, stripped

如果你按照上一章也下载安装了Qt Creator的话,你也可以在/opt/Qt5.11.3/5.11.3/gcc_64/lib下面找到同样的文件。

root@lbb:/opt/Qt5.11.3/5.11.3/gcc_64/lib# file libQt5Core.so.5.11.3
libQt5Core.so.5.11.3: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=ca64024f0a3389da3356e162eaf5fd52cfe11c94, for GNU/Linux 2.6.28, not stripped

我们通过file命令,查看到两个文件是不一样的, 第一个是我们通过交叉编译的,适用于ARM平台的Qt5动态链接库,第二个则是x86架构的动态链接库。

4.3. 配置 lubancat 套件

上一章节的Qt Creator是安装在x86平台的,编译出来的程序,只能在通用计算机上工作。 要想我们的程序能在LubanCat执行,就必须使用我们这一章交叉编译的Qt库来编译程序。

Qt Creator 是Qt官方的IDE,那我们就在这个IDE中将交叉编译的Qt库和交叉编译工具链配置成 lubancat 构建套件(Kits)。

首先选择 【工具】 -> 【选项】

install_qt_creator014

在弹出来的选项配置界面中选择【Kits】->【编译器】, 点击【添加】按钮选择添加【GCC】 ->【C++】类型,自己定义一个名字(ebf_lubancat), 然后将我们之前安装的 arm-linux-gnueabihf-gcc 8.3.0 版本的交叉编译器添加进来, 注意要选择 /opt/gcc-arm-linux-gnueabihf-8.3.0/bin/arm-linux-gnueabihf-g++ , 点击【Apply】完成应用。

install_qt_creator015

同理将 /opt/gcc-arm-linux-gnueabihf-8.3.0/bin/arm-linux-gnueabihf-gcc 编译器添加进来。

install_qt_creator016

然后选择Qt的版本,我们在前面已经交叉编译并安装了Qt5.11.3版本,那么在这里只需要将qmake添加进来即可, 具体操作如下:在选项配置界面中选择【Kits】->【Qt Versions】,然后点击【添加】按钮,在Qt的安装目录 下选择qmake: /opt/qt-everywhere-src-5.11.3/bin ,然后添加完成后点击【Apply】完成应用。

install_qt_creator017
install_qt_creator018

最后要添加构建套件,在选项配置界面中选择【Kits】->【构建套件(Kit)】, 点击【添加】,然后设置名称,此处我的名称设置为 ebf_lubancat , 接着选择设备的类型,我选择了 *通用的Linux设备(GenericLinuxDevice) * , 因为这是为开发板构建的环境,然后选择编译器,此处使用我们刚刚添加的交叉编译工具即可, 最后选择Qt的版本,此处也是选择我们刚刚添加的交叉编译安装的版本,最后点击【Apply】完成应用。

install_qt_creator019

4.4. 交叉编译Qt自带的例程

4.4.1. 交叉编译demo

首先点击例程的项目配置,选择使用交叉编译环境编译,选择构建套件为刚刚添加的交叉编译套件 ebf_lubancat ,在编译时可以根据自己需求决定选择Debug或者Release版本:

install_qt_creator020
install_qt_creator021

点击“锤子”构建应用程序:

install_qt_creator022

在构建完成后,可以在 Qt5.11.3/Examples/Qt-5.11.3/widgets/widgets/build-analogclock-ebf_lubancat-Release 目录下看到对应的可执行文件analogclock:

install_qt_creator023

我们可以使用file查看文件的类型,可以发现它确实是32位的程序,是ARM类型的可执行文件。

install_qt_creator024

4.4.2. LubanCat上运行

首选确定LubanCat上的运行环境,请参考 野火demo 运行 章节。

然后将我们编译的程序analogclock拷贝到/usr/local/qt-app这个目录。

执行如下命令即可运行程序。

# sudo + 启动脚本 + 你App的路径
# 路径可以为绝对路径也可以为相对路径
sudo /usr/local/qt-app/run_myapp.sh /usr/local/qt-app/analogclock

想要程序在后台执行,在最后加一个 & 符号。

sudo /usr/local/qt-app/run_myapp.sh /usr/local/qt-app/analogclock &

我们就能看到LubanCat上的程序,其显示效果和在PC机上也是一样。

install_qt_creator025

如何开发板安装的镜像带有ebf-qtdemo,启动开发板的时候,默认会启动ebf-qtdemo, 再运行我们的程序的时候,就会出现两个程序显示重叠,这时我们就需要停止其中一个。

我们可以通过 pstop 等命令查询到ebf-qtdemo的pid, 然后通过 kill 命令停止App运行。

例如在命令行执行 top ,会出现下面的字符界面,滑动触摸屏,就会看到App以及它的PID,这里为879(每次都可能不相同)

killApp001

在控制台执行kill命令来停止App。

#879为当前App的PID值,每次可能不一样
sudo kill 879

当然我们也可以直接通过pkill来停止App。

#App为你当前在运行的程序,这里是ebf-qtdemo的程序名
sudo pkill App

我们不妨来看看run_myapp.sh这个脚本做了什么事情。

#! /bin/bash

if [ ! $1 ]; then
echo "- usage;"
echo "- sudo ./run_myapp.sh xxx"
echo "- xxx is your appname"
exit
else
echo "current app $1"
fi

type devscan

#判断devscan是否存在,不存在提示安装
if [ $? -eq 0 ]; then
   #未检查到触摸屏则一直检测不启动app
   timeout=0
   while [ ! $eventx ]
   do
      #寻找名叫goodix-ts的触摸屏驱动
      eventx=$(devscan "goodix-ts")
      #没有找到则寻找Goodix Capacitive TouchScreen
      if [ ! $eventx ]; then
               eventx=$(devscan "Goodix Capacitive TouchScreen")
      fi
      #没有找到则寻找iMX6UL Touchscreen Controller
      if [ ! $eventx ]; then
               eventx=$(devscan "iMX6UL Touchscreen Controller")
      fi
      ########################################################
      # 添加你自己的显示屏驱动
      # 首先 sudo evtest  查看是否存在显示屏驱动
      # 存在添加类似于上面的判断 改为你自己的显示屏驱动名称
      ########################################################
      if [ $timeout -ge 5 ]; then
            break
      fi
      let timeout=$timeout+1
      sleep 1
   done
   #输出当前触摸屏驱动
   echo "eventx=$eventx"

   if [ "$eventx " != " " ]; then
      #判断触摸屏校准文件是否存在,不存在则校准触摸屏,/etc/pointercal为触摸屏校准文件
      if [ ! -f "/etc/pointercal" ]; then
            #指定触摸屏设备
            export TSLIB_TSDEVICE=/dev/input/$eventx
            type ts_calibrate
            if [ $? -eq 0 ]; then
               ts_calibrate
            fi
      fi
      #同步QT默认的坐标轴和触摸屏的坐标轴
      #export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS=/dev/input/$eventx:rotate=90:invertx
      export QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS=/dev/input/$eventx:rotate=0
   else
      echo "eventx is null"
   fi
else
   echo "please install devscan"
   echo
   echo "sudo apt-get install devscan"
   exit
fi

#导出qtdemo的安装目录
export APP_DIR=/usr/local/qt-app
#指定qt插件路径
export QT_QPA_PLATFORM_PLUGIN_PATH=/usr/lib/plugins
#指定qt库路径
#export LD_LIBRARY_PATH=/lib:/usr/lib
#指定字体库
export QT_QPA_FONTDIR=/usr/lib/fonts
#qt命令路径
#export PATH=$PATH:$QT_DIR/libexec
#指定显示终端
export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb0
#禁用QT自带的输入检测
#export QT_QPA_FB_DISABLE_INPUT=1
#TS配置文件
export TSLIB_CONFFILE=/etc/ts.conf
#TS校准文件
export TSLIB_CALIBFILE=/etc/pointercal
#触摸配置
export QT_QPA_GENERIC_PLUGINS=tslib:/dev/input/$eventx
#指定鼠标设备
export QWS_MOUSE_PROTO=tslib
#启用tslib支持而不是依赖于Linux多点触控协议和事件设备
export QT_QPA_EGLFS_TSLIB=1
export QT_QPA_FB_TSLIB=1

echo "start app $1..."
#运行App
$1

里面的注释非常明确了,首先是扫描触摸屏设备文件,然后对触摸屏tslib进行配置。 当然里面也有指定Qt库、插件库、字体等等路径,若不使用则为默认路径。

最重要的一点就是 export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb0, 指定Qt程序以linuxfb的方式显示,输出设备为/dev/fb0。

最后一步,执行我们的程序,其中$1为shell变量,指脚本后面跟的第一个参数,也就是我们的程序analogclock。

细心的读者对比run.sh脚本便会发现,两个脚本几乎相同。

事实也正是如此,我们将/usr/local/qt-app/run.sh稍作修改, 将最后一行 $APP_DIR/App 修改为 $APP_DIR/analogclock,执行如下命令同样可以运行我们的程序。

sudo /usr/local/qt-app/run.sh