Developing hardware-accelerated Qt5 GUI applications for the Raspberry Pi.
With the release of Qt 5.7 and the new Qt Quick Controls 2 combined with the Raspberry Pi’s working OpenGL hardware drivers (something most SOCs never get right) it seems like a good time to try out QML.
Hardware
I am primarily testing with RPi3s, but I the same projects work on RPi2s and the single core RPi boards including the RPi compute module.
I am testing with the following displays
- The official RPi 7” touchscreen
- Standard HDMI 1080p displays
- Adafruit 3.5 inch and 2.8 inch resistive touchscreens
This Raspberry Pi TFT Displays and Qt5 post has more notes on working with the PiTFT displays.
System Software
My development systems are built using Yocto.
You can find instructions here and download an image here.
I recommend you build your own images though. The meta-rpi images contain packages that are interesting to me. You might have different interests.
On these systems Qt5 has been configured to use the use the EGLFS platform plugin. This means only one full-screen GUI process at a time, but that’s fairly typical for the embedded products I work on.
The linuxfb platform plugin is also installed, so you can use that instead if you only require Qt Widgets and want to use the GPU for something else. Qt Widgets work fine with the eglfs plugin.
The majority of the system is from the latest stable branch of Yocto, currently 2.1.1, the [krogoth]
branch.
I am using the [master]
branch of meta-qt5 because it uses Qt 5.7 and has the build patches for OpenGL from the RPi userland package.
Here’s a sample of the Qt stuff currently installed on my qt5-images
root@rpi3:~# g++ --version
g++ (GCC) 5.3.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
root@rpi3:~# qmake -v
QMake version 3.0
Using Qt version 5.7.0 in /usr/lib
root@rpi3:~# ls /usr/lib/qt5/plugins/platforms
libqeglfs.so libqminimal.so libqminimalegl.so libqoffscreen.so
root@rpi3:~# opkg list-installed | grep -e '^qt' | grep -v mkspecs | grep -v dev
qt3d - 5.7.0+git0+c3fdb888fb-r0
qt3d-plugins - 5.7.0+git0+c3fdb888fb-r0
qt3d-qmlplugins - 5.7.0+git0+c3fdb888fb-r0
qt5-env - 1.0-r1
qtbase - 5.7.0+git0+69b43e74d7-r0
qtbase-plugins - 5.7.0+git0+69b43e74d7-r0
qtbase-tools - 5.7.0+git0+69b43e74d7-r0
qtconnectivity - 5.7.0+git0+8755a1f246-r0
qtconnectivity-qmlplugins - 5.7.0+git0+8755a1f246-r0
qtdeclarative - 5.7.0+git0+d48b397cc7-r0
qtdeclarative-plugins - 5.7.0+git0+d48b397cc7-r0
qtdeclarative-qmlplugins - 5.7.0+git0+d48b397cc7-r0
qtgraphicaleffects - 5.7.0+git0+d3023be0d8-r0
qtgraphicaleffects-qmlplugins - 5.7.0+git0+d3023be0d8-r0
qtlocation - 5.7.0+git0+4e1008b4ac-r0
qtlocation-plugins - 5.7.0+git0+4e1008b4ac-r0
qtlocation-qmlplugins - 5.7.0+git0+4e1008b4ac-r0
qtmultimedia - 5.7.0+git0+1be4f74843-r0
qtmultimedia-plugins - 5.7.0+git0+1be4f74843-r0
qtmultimedia-qmlplugins - 5.7.0+git0+1be4f74843-r0
qtquickcontrols - 5.7.0+git0+37f8b753be-r0
qtquickcontrols-qmldesigner - 5.7.0+git0+37f8b753be-r0
qtquickcontrols-qmlplugins - 5.7.0+git0+37f8b753be-r0
qtquickcontrols2 - 5.7.0+git0+cc0ee8e4f3-r0
qtquickcontrols2-qmldesigner - 5.7.0+git0+cc0ee8e4f3-r0
qtquickcontrols2-qmlplugins - 5.7.0+git0+cc0ee8e4f3-r0
qtvirtualkeyboard - 5.7.0+git0+626e78c966-r0
qtvirtualkeyboard-plugins - 5.7.0+git0+626e78c966-r0
qtvirtualkeyboard-qmlplugins - 5.7.0+git0+626e78c966-r0
root@rpi3:~# opkg list-installed | grep libqt | grep -v mkspecs | grep -v dev
libqt5charts-qmldesigner - 5.7.0+git0+03a6177a32-r0
libqt5charts-qmlplugins - 5.7.0+git0+03a6177a32-r0
libqt5charts5 - 5.7.0+git0+03a6177a32-r0
libqt5sensors-plugins - 5.7.0+git0+e03c37077e-r0
libqt5sensors-qmlplugins - 5.7.0+git0+e03c37077e-r0
libqt5sensors5 - 5.7.0+git0+e03c37077e-r0
libqt5serialbus-plugins - 5.7.0+git0+88554d068d-r0
libqt5serialbus5 - 5.7.0+git0+88554d068d-r0
libqt5serialport5 - 5.7.0+git0+7346857f4f-r0
libqt5svg-plugins - 5.7.0+git0+64ca369c7e-r0
libqt5svg5 - 5.7.0+git0+64ca369c7e-r0
libqt5websockets-qmlplugins - 5.7.0+git0+8d17ddfc2f-r0
libqt5websockets5 - 5.7.0+git0+8d17ddfc2f-r0
libqt5xmlpatterns5 - 5.7.0+git0+574d92a43e-r0
That’s most but not all of the Qt packages in meta-qt5
.
Running Qt Apps
The Qt runtime needs to be told which platform plugin to use.
You can use the -platform eglfs
command line argument or you can set an environment variable QT_QPA_PLATFORM.
That’s what I’ve done for the qt5-images
.
root@rpi3:~# env
TERM=xterm
SHELL=/bin/sh
SSH_CLIENT=192.168.10.4 50720 22
SSH_TTY=/dev/pts/0
USER=root
TITLEBAR=\[\033]0;\u@\h: \w\007\]
MAIL=/var/mail/root
PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/usr/bin/qt5
PWD=/home/root
EDITOR=vi
QT_QPA_EGLFS_PHYSICAL_WIDTH=155
QT_QPA_PLATFORM=eglfs
PS1=\[\033]0;\u@\h: \w\007\]\u@\h:\w\$
SHLVL=1
HOME=/home/root
LOGNAME=root
QT_QPA_EGLFS_PHYSICAL_HEIGHT=86
SSH_CONNECTION=192.168.10.4 50720 192.168.10.101 22
_=/usr/bin/env
You can see that the PATH has /usr/bin/qt5
added as well.
The environment customization comes from /etc/profile.d/qt5-env.sh
which in turn comes from this recipe in the Yocto build
meta-rpi/recipes-qt/qt5-env/qt5-env.bb
Useful to know if you want to modify it.
I also have QT_QPA_EGLFS_PHYSICAL_WIDTH and QT_QPA_EGLFS_PHYSICAL_HEIGHT set for the RPi Touchscreen.
Details on the QPA_EGLFS environment variables can be found here.
Building Qt Apps on the RPi
Native building is the quickest way to get started.
Git is installed, so you can pull source from a Github repo.
The qqtest project is just the default Qt Quick Controls 2 skeleton app that Qt Creator 4.0.2 generates with an Exit button added
root@rpi3:~# git clone https://github.com/scottellis/qqtest.git
Cloning into 'qqtest'...
remote: Counting objects: 17, done.
remote: Compressing objects: 100% (12/12), done.
remote: Total 17 (delta 5), reused 17 (delta 5), pack-reused 0
Unpacking objects: 100% (17/17), done.
Checking connectivity... done.
root@rpi3:~# cd qqtest/
root@rpi3:~/qqtest# qmake && make -j4
...
g++ -Wl,-O1 -o qqtest main.o qrc_qml.o -lQt5Quick -L/oe4/rpi/tmp-krogoth/sysroots/raspberrypi2/usr/lib -lQt5Gui -lQt5Qml -lQt5Network -lQt5Core -lGLESv2 -lpthread
root@rpi3:~/qqtest# ./qqtest
qml: Button 1 clicked.
qml: Button 2 clicked.
qml: Button 1 clicked.
qml: Button 2 clicked.
qml: Exit clicked
Another run
root@rpi3:~/qqtest# ./qqtest
qml: Exit clicked
*** Error in `./qqtest': double free or corruption (fasttop): 0x72700920 ***
Aborted
Hmm, an ugly error on exit. See this post for some analysis.
Using Qt Creator to cross-compile, deploy and debug Qt apps
I have another post here on setting up Qt Creator to use the Yocto built SDK.
Cross-compiling Qt apps from the command line
This is not something I typically do, but the setup is not difficult. It does assume you have already built your system with Yocto.
Source your bitbake environment as normal and then build the meta-toolchain-qt5
recipe.
Make sure you have SDKMACHINE in local.conf
set appropriately for the target workstation you plan on using the SDK. The SDK is self-contained and can be transferred to other machines.
Setup the Yocto environment as normal
scott@fractal:~$ source poky-krogoth/oe-init-build-env ~/rpi/build
then build the SDK
scott@fractal:~/rpi/build$ bitbake meta-toolchain-qt5
When that completes, copy and install the SDK on the machine you want to work from. It does not have to be the same machine you built it on.
The SDK installation script can be found in ${TMPDIR}/deploy/sdk
.
In my local.conf
I have TMPDIR=/oe4/rpi/tmp-krogoth, so the SDK installer can be found here
scott@fractal:~/rpi/build$ cd /oe4/rpi/tmp-krogoth/deploy/sdk
scott@fractal:/oe4/rpi/tmp-krogoth/deploy/sdk$ ls -l
total 601760
-rw-r--r-- 1 scott scott 10154 Sep 1 13:54 poky-glibc-x86_64-meta-toolchain-qt5-cortexa7hf-neon-vfpv4-toolchain-2.1.1.host.manifest
-rwxr-xr-x 1 scott scott 616157351 Sep 1 13:57 poky-glibc-x86_64-meta-toolchain-qt5-cortexa7hf-neon-vfpv4-toolchain-2.1.1.sh
-rw-r--r-- 1 scott scott 24508 Sep 1 13:54 poky-glibc-x86_64-meta-toolchain-qt5-cortexa7hf-neon-vfpv4-toolchain-2.1.1.target.manifest
Run the installer as root (I haven’t tried a non-root install)
scott@fractal:/oe4/rpi/tmp-krogoth/deploy/sdk$ sudo ./poky-glibc-x86_64-meta-toolchain-qt5-cortexa7hf-neon-vfpv4-toolchain-2.1.1.sh
The default install location is /opt/poky/2.1.1
, but you can change it.
To use the SDK, source the SDK environment using a provided script
/opt/poky/2.1.1/environment-setup-cortexa7hf-neon-vfpv4-poky-linux-gnueabi
Here is a complete cross-build example run from a headless 64-bit Linux server that does not have any native Qt software installed.
The meta-qt5 SDK was installed to /opt/poky/rpi-meta-qt5-2.2.1
.
scott@fractal:~$ cd projects/
First download a project to build
scott@fractal:~/projects$ git clone https://github.com/scottellis/qqtest
Cloning into 'qqtest'...
remote: Counting objects: 20, done.
remote: Compressing objects: 100% (17/17), done.
remote: Total 20 (delta 7), reused 16 (delta 3), pack-reused 0
Unpacking objects: 100% (20/20), done.
Checking connectivity... done.
scott@fractal:~/projects$ cd qqtest/
Source the Qt cross-build environment
scott@fractal:~/projects/qqtest$ source /opt/poky/rpi-meta-qt5-2.1.1/environment-setup-cortexa7hf-neon-vfpv4-poky-linux-gnueabi
And build
scott@fractal:~/projects/qqtest$ qmake && make -j8
Cannot read /opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/lib/qt5/mkspecs/oe-device-extra.pri: No such file or directory
sh: OE_QMAKE_CXX: command not found
sh: OE_QMAKE_CXXFLAGS: command not found
Info: creating stash file /home/scott/projects/qqtest/.qmake.stash
arm-poky-linux-gnueabi-g++ -march=armv7ve -marm -mfpu=neon-vfpv4 -mfloat-abi=hard -mcpu=cortex-a7 --sysroot=/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi -c -pipe -O2 -pipe -g -feliminate-unused-debug-types -fdebug-prefix-map=/oe4/rpi/tmp-krogoth/work/x86_64-nativesdk-pokysdk-linux/meta-environment-raspberrypi2/1.0-r8=/usr/src/debug/meta-environment-raspberrypi2/1.0-r8 -fdebug-prefix-map=/oe4/rpi/tmp-krogoth/sysroots/x86_64-linux= -fdebug-prefix-map=/oe4/rpi/tmp-krogoth/sysroots/x86_64-nativesdk-pokysdk-linux= -O2 -std=gnu++11 -Wall -W -D_REENTRANT -fPIC -DQT_NO_DEBUG -DQT_QUICK_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -I. -I/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/include/qt5 -I/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/include/qt5/QtQuick -I/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/include/qt5/QtGui -I/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/include/qt5/QtQml -I/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/include/qt5/QtNetwork -I/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/include/qt5/QtCore -I. -I/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/lib/qt5/mkspecs/linux-oe-g++ -o main.o main.cpp
/opt/poky/rpi-meta-qt5-2.1.1/sysroots/x86_64-pokysdk-linux/usr/bin/qt5/rcc -name qml qml.qrc -o qrc_qml.cpp
arm-poky-linux-gnueabi-g++ -march=armv7ve -marm -mfpu=neon-vfpv4 -mfloat-abi=hard -mcpu=cortex-a7 --sysroot=/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi -c -pipe -O2 -pipe -g -feliminate-unused-debug-types -fdebug-prefix-map=/oe4/rpi/tmp-krogoth/work/x86_64-nativesdk-pokysdk-linux/meta-environment-raspberrypi2/1.0-r8=/usr/src/debug/meta-environment-raspberrypi2/1.0-r8 -fdebug-prefix-map=/oe4/rpi/tmp-krogoth/sysroots/x86_64-linux= -fdebug-prefix-map=/oe4/rpi/tmp-krogoth/sysroots/x86_64-nativesdk-pokysdk-linux= -O2 -std=gnu++11 -Wall -W -D_REENTRANT -fPIC -DQT_NO_DEBUG -DQT_QUICK_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -I. -I/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/include/qt5 -I/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/include/qt5/QtQuick -I/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/include/qt5/QtGui -I/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/include/qt5/QtQml -I/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/include/qt5/QtNetwork -I/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/include/qt5/QtCore -I. -I/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/lib/qt5/mkspecs/linux-oe-g++ -o qrc_qml.o qrc_qml.cpp
arm-poky-linux-gnueabi-g++ -march=armv7ve -marm -mfpu=neon-vfpv4 -mfloat-abi=hard -mcpu=cortex-a7 --sysroot=/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi -Wl,-O1 -Wl,--hash-style=gnu -Wl,--as-needed -Wl,-O1 -o qqtest main.o qrc_qml.o -L/opt/poky/rpi-meta-qt5-2.1.1/sysroots/cortexa7hf-neon-vfpv4-poky-linux-gnueabi/usr/lib -lQt5Quick -L/oe4/rpi/tmp-krogoth/sysroots/raspberrypi2/usr/lib -lQt5Gui -lQt5Qml -lQt5Network -lQt5Core -lGLESv2 -lpthread
The resulting executable
scott@fractal:~/projects/qqtest$ ls -l qqtest
-rwxrwxr-x 1 scott scott 399172 Sep 1 14:29 qqtest
You can verify it is for an ARM board
scott@fractal:~/projects/qqtest$ file qqtest
qqtest: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 2.6.32, BuildID[sha1]=c15301ddc862ea976d8928fad21e45e9615846e3, not stripped
Copy it to the RPi
scott@fractal:~/projects/qqtest$ scp qqtest root@192.168.10.114:/home/root
Warning: Permanently added '192.168.10.114' (ECDSA) to the list of known hosts.
qqtest
Then over on the RPi, the qqtest app should run fine.
Creating Bitbake recipes for your Qt apps
Eventually you will want Yocto to build and install your app automatically in the image rootfs.
The Yocto documentation is the official resource for recipes, but I’ve found the easiest way to learn is looking at existing examples. The meta-qt5 repository has a number of Qt5 examples.
The meta-qt5 layer provides some extra tools that handle Qt5 specifics. The require qt5.inc line brings them in.
Here is an example recipe for the qqtest application.
The source is pulled from the Github repository.
SUMMARY = "Qt5 QML test app"
HOMEPAGE = "https://jumpnowtek.com"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"
DEPENDS += "qtdeclarative"
PR = "r0"
SRCREV = "${AUTOREV}"
SRC_URI = "git://github.com/scottellis/qqtest.git"
S = "${WORKDIR}/git"
require recipes-qt/qt5/qt5.inc
do_install() {
install -d ${D}${bindir}
install -m 0755 ${B}/${PN} ${D}${bindir}
}
FILES_${PN} = "${bindir}"
RDEPENDS_${PN} = "qtdeclarative-qmlplugins"
You can find the recipe here meta-rpi/recipes-qt/qqtest/qqtest_git.bb
And the qqtest package was added to the rootfs here meta-rpi/images/qt5-image.bb