Building a Linux system for the STM32MP1: setting up a Qt5 application development environment

After showing how to build a minimal Linux system for the STM32MP157 platform, how to connect and use an I2C based pressure/temperature/humidity sensor and how to integrate Qt5 in our system, in this blog post, we are going to see how to set up a development environment to write our own Qt5 application, with QtCreator.

List of articles in this series:

  1. Building a Linux system for the STM32MP1: basic system
  2. Building a Linux system for the STM32MP1: connecting an I2C sensor
  3. Building a Linux system for the STM32MP1: enabling Qt5 for graphical applications
  4. Building a Linux system for the STM32MP1: setting up a Qt5 application development environment
  5. Building a Linux system for the STM32MP1: developing a Qt5 graphical application
  6. Building a Linux system for the STM32MP1: implementing factory flashing
  7. Building a Linux system for the STM32MP1: remote firmware updates

A minimal Qt5 application

We’ll call our application qt-sensor-demo, so create a directory with this name, outside of Buildroot. It’s important to not mix up your application code with your build system: you could very well decide to use another build system one day, while keeping your application code. To keep things simple, create this qt-sensor-demo side-by-side with Buildroot, as this will be important for a future step in this blog post.

In this directory, create a main.cpp file with the following code:

#include <QApplication>
#include <QPushButton>

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    QPushButton hello("Hello world!");
    hello.resize(100,30);
    hello.show();
    return app.exec();
}

It should be fairly straight-forward to understand that this program creates a QApplication object, a push button with the Hello world! label, sets the button size to 100 by 30 pixels, shows the button, and enters the application event loop. It is obviously a very basic application, because it doesn’t do anything useful, but that’s good enough as a starting point.

Now, we need to build this application. Building Qt applications by hand is definitely not reasonable, as Qt may need to run several tools on the source code before it gets built, and requires a number of compiler and linker flags. So, we won’t write a Makefile by hand, but instead use a build tool that generates the Makefile for us. We have a number of options here:

In this blog post, we’ll simply stick to qmake, which is good enough for a number of Qt-based applications. qmake takes as input one or several .pro files describing the project, and uses that to generate Makefiles (on Linux systems).

In our case, the qt-sensor-demo.pro file will be as simple as:

QT += widgets
SOURCES = main.cpp

Building our application

We have two ways to build our application:

  1. Manually outside of Buildroot. In this case, we’ll use the Buildroot-provided compiler and tools, but we will trigger the build of our application separately from the Buildroot build.
  2. Using Buildroot. In this case, our application would have a corresponding Buildroot package, that would automate building the application as part of the complete system build process.

Ultimately, we definitely want to have a Buildroot package for our application, to make sure the entire build is fully automated. However, during the active development of the application, it may be useful to build it manually outside of Buildroot, so we are going to see both solutions, which are not mutually exclusive: you can have a Buildroot package for your application, and still build it manually when you’re doing active development/debugging.

Building manually outside of Buildroot

To build manually, we simply need to first invoke Buildroot’s provided qmake:

/path/to/buildroot/output/host/bin/qmake

This will generate a Makefile, that we can use to build our application:

make

At this point, you should have:

$ ls
main.cpp  main.o  Makefile  qt-sensor-demo  qt-sensor-demo.pro

The qt-sensor-demo executable is compiled for ARM, and linked against the various libraries built by Buildroot.

Now, we need this executable on our STM32MP15 target. For now, we’ll simply add it to the SD card image:

cp qt-sensor-demo /path/to/buildroot/output/target/usr/bin/
cd /path/to/buildroot/
make

Hello World Qt application running on the STM32MP15 Discovery platform
Hello World Qt application running on the STM32MP15 Discovery platform
This will copy the executable to the output/target folder, which contains the root filesystem produced by Buildroot. Then invoking Buildroot’s make will ensure that the root filesystem and SD card images get re-generated. Of course, beware that if you run a Buildroot make clean, all the contents of output/, including output/target/ get removed. So this technique is only suitable for temporary changes. This is fine since anyway as discussed above, ultimately we’ll have a proper Buildroot package to build our qt-sensor-demo application.

Reflash your SD card with the new image, and on the target, run the demo:

# qt-sensor-demo -platform linuxfb

Setting SSH for communication with the board

Regenerating the SD card image and reflashing the entire SD card every time we want to change our application is not going to be very efficient during the application development/debugging. So instead, we’ll set up networking communication with the board, and use SSH to transfer files. This will also be useful for Qt Creator, as it uses SFTP to deploy files to the target.

Let’s start by enabling a small SSH client/server, called Dropbear. Go in Buildroot menuconfig, and enable the BR2_PACKAGE_DROPBEAR option (in Target packages, Networking applications, dropbear). While Dropbear provides SSH access, it does not support SFTP which will be needed by Qt Creator, so we’ll also enable an SFTP server, gesftpserver. So, we’ll enable BR2_PACKAGE_GESFTPSERVER as well (in Target packages, Networking applications, gesftpserver).

Then, in order to log in through SSH as root, we must have a non-empty root password, so set BR2_TARGET_GENERIC_ROOT_PASSWD (in System configuration, Root password) to a value you like.

You can now exit menuconfig, as we have enabled all features we needed. Before restarting the build, we need to do one last thing: set up a network configuration file so that our STM32MP15 system configures an IP address. To do this, we’ll create a /etc/network/interfaces file, and add it to the root filesystem using the root filesystem overlay mechanism, which was presented in the first post of this series. So, in your Buildroot sources, just create a file board/stmicroelectronics/stm32mp157-dk/overlay/etc/network/interfaces, with the following contents:

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static
      address 192.168.42.2
      netmask 255.255.255.0

This will ensure the eth0 interface of our target gets configured with the 192.168.42.2 IP address. Of course, feel free to use a different IP address.

Then, run make in Buildroot, reflash your SD card, and boot your system. At boot time, you should see:

Starting dropbear sshd: OK

You can also run ip addr show dev eth0 to check the IP address of the eth0 interface:

2: eth0:  mtu 1500 qdisc mq qlen 1000
    link/ether 00:80:e1:42:4d:e3 brd ff:ff:ff:ff:ff:ff
    inet 192.168.42.2/24 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::280:e1ff:fe42:4de3/64 scope link 
       valid_lft forever preferred_lft forever

So the IPv4 address is properly set to 192.168.42.2, as expected.

Now, on your workstation, we need to configure the 192.168.42.1 static IP address so that you can connect to your board. It is very likely that the Linux system on your workstation is using NetworkManager. Let’s add a connection:

$ nmcli con add con-name buildroot-target type ethernet ifname enp57s0u1u3 ip4 192.168.42.1/24
Connection 'buildroot-target' (234e0d9a-5c4f-4eac-9277-c3587bbd370d) successfully added.

Make sure to replace enp57s0u1u by the name of your PC wired interface, to which the board is connected. We of course assume you have an Ethernet cable directly connecting your PC to the board.

Finally, enable the connection:

$ nmcli con up id buildroot-target
Connection successfully activated (D-Bus active path: /org/freedesktop/NetworkManager/ActiveConnection/10)

We can now ping our target:

$ ping 192.168.42.2
PING 192.168.42.2 (192.168.42.2) 56(84) bytes of data.
64 bytes from 192.168.42.2: icmp_seq=1 ttl=64 time=1.33 ms

Log-in over SSH:

$ ssh root@192.168.42.2
root@192.168.42.2's password: 
# uname -a
Linux buildroot 4.19.26 #1 SMP PREEMPT Wed Aug 28 15:54:58 CEST 2019 armv7l GNU/Linux
# cat /etc/issue 
Welcome to Buildroot
# 

And verify that SFTP is working:

$ sftp root@192.168.42.2
root@192.168.42.2's password: 
Connected to root@192.168.42.2.
sftp> ls /
/bin            /boot           /dev            /etc            
/lib            /lib32          /linuxrc        /lost+found     
/media          /mnt            /opt            /proc           
/root           /run            /sbin           /sys            
/tmp            /usr            /var            
sftp> 

So, now we can make a change to our Qt5 application, for example changing the label of the button, recompile by running make in the application directory, and directly copy the application using scp, and run it over ssh:

$ make
[...]
$ scp qt-sensor-demo root@192.168.42.2:/usr/bin/
root@192.168.42.2's password: 
qt-sensor-demo                                    100%   12KB 634.7KB/s   00:00
$ ssh root@192.168.42.2
root@192.168.42.2's password: 
# qt-sensor-demo -platform linuxfb

Much nicer, we don’t have to reflash our SD card every time we want to test a change in our application!

Note that we could create a public/private key pair, with the public key on our target, and this way not have to enter our password every time we want to transfer a file or log-in to the target. Since this blog post is already very long, we’ll live that as an exercise for the reader, there are plenty of resources on the Web about this topic.

Setting up Qt Creator

Some people (such as your author) are happy with using a powerful text editor (such as Vim or Emacs) and a terminal to do their application development. But others are sometimes more comfortable with an integrated development environment (IDE). So in this section, we’ll see how to set up Qt Creator to write, build, deploy and debug a Qt5 application.

Installing Qt Creator

First of all, you’ll have to install Qt Creator, which you can do using the package management system of your distribution. On Fedora systems, this would be:

$ sudo dnf install qt-creator

On Debian/Ubuntu systems:

$ sudo apt install qtcreator

The following instructions have been written and tested against Qt Creator version 4.9.2.

Creating a kit

After starting Qt Creator, the first thing to do is to create a kit, which describes the cross-compiler and Qt installation provided by Buildroot. Go to Tools -> Options, and the first item should be Kits:

Click on Add, and fill in the different fields as follows:

  • Name: Buildroot ARM
  • Device type: Generic Linux Device
  • Sysroot: /path/to/buildroot/output/host/arm-buildroot-linux-gnueabihf/sysroot/. Of course, replace /path/to/buildroot/ with the appropriate path on your system.
  • For the compiler, click on Manage, then in the Compiler panel:
    • Add one GCC C compiler, with the name Buildroot GCC and pointing to /path/to/buildroot/output/host/bin/arm-linux-gnueabihf-gcc
    • Add one GCC C++ compiler, with the name Buildroot G++ and pointing to /path/to/buildroot/output/host/bin/arm-linux-gnueabihf-g++
  • Back in the Kits panel, select Buildroot GCC and Buildroot G++ as the C and C++ compilers, respectively.
  • For the debugger, click on Manage, then in the debugger panel add one debugger named Buildroot GDB, and pointing to /path/to/buildroot/output/host/bin/arm-linux-gnueabihf-gdb. Back in the Kits panel, select Buildroot GDB as our debugger.
  • For the Qt version, click on Manage, then on Add, and point to the qmake binary in /path/to/buildroot/output/host/bin/. It will auto-detect that Buildroot has built Qt 5.11.3. You may want to adjust the version name from Qt %{Qt:Version} (host) to Qt %{Qt:Version} (Buildroot), as this Qt version is clearly not built for our host PC. Then back in the Kits panel, select this new Qt version.
  • For the Qt mkspec, enter devices/linux-buildroot-g++, which is the name of the mkspec configuration Buildroot generates.

You’ll find below screenshots of the various panels, with the details related to the Buildroot cross-compiler, cross-debugger and Qt installation:

Qt Creator Kits panel
Qt Creator Kits panel, filled in with the details of the Buildroot cross-compiler, cross-debugger and Qt installation
Qt Creator compiler panel, C compiler
Qt Creator compiler panel, filled in with the details of the Buildroot C compiler
Qt Creatoer compiler panel, C++ compiler
Qt Creator compiler panel, filled in with the details of the Buildroot C++ compiler
Qt Creator debugger panel
Qt Creator debugger panel, filled in with the details of the Buildroot cross-debugger
Qt Creator Qt version panel
Qt Creator Qt version panel, filled in with the details of the Buildroot Qt installation

We’re now done configuring a Kit!

Creating a device

In order to allow Qt Creator to deploy our application to the device, run it and debug it, we need to create a Device. Go again in Tools -> Options, and this time go to the Devices panel.

In the first window, select Generic Linux Device.

Qt Creator device creation, step 1

Then, for the device name, use STM32MP15 Discovery board for example, for the IP address, 192.168.42.2 and for the user, root, which should give:

Qt Creator device creation, step 2

In the next step about Key deployment, simply skip to the next section, as we have not created a private/public key pair, as explained previously in this blog post. You can then finalize the device creation. Qt Creator will now test that it can communicate as expected with our device:

Qt Creator testing our new device

As you can see, it doesn’t find rsync on the target, because we have not installed it. It will use sftp instead, which is fine.

Back in the Device panel, you should see our device definition as follows:

Qt Creator device panel
Qt Creator device panel, filled in with the details of our STM32MP15 Discovery board

You can click on Open Remote Shell to directly open a shell over SSH to your target, or Show Running processes.

Our device is now set up correctly, time to create our first application!

Importing our project

We now want to import our qt-sensor-demo project in Qt Creator. To do so, go in File -> Open File or Project, then browse to the directory containing our qt-sensor-demo application, and select both the main.cpp and qt-sensor-demo.pro files, and click Open. Qt Creator should now switch to a Configure project window, where it asks you to select the Kit to use for this project. Obviously, select the Buildroot ARM kit we have just created, and validate by clicking Configure Project:

Qt Creator configure project

You should now see our project imported, with both of its files, and main.cpp is opened by default:

Qt Creator project

If we now use Build -> Build All, and then go in the Compile Output panel, we see:

13:11:58: Running steps for project qt-sensor-demo...
13:11:59: Starting: "/home/thomas/projets/outputs/st/host/bin/qmake" /home/thomas/qt-sensor-demo/qt-sensor-demo.pro -spec devices/linux-buildroot-g++ CONFIG+=debug CONFIG+=qml_debug
Info: creating stash file /home/thomas/build-qt-sensor-demo-Buildroot_ARM-Debug/.qmake.stash
13:11:59: The process "/home/thomas/projets/outputs/st/host/bin/qmake" exited normally.
13:11:59: Starting: "/usr/bin/make" -f /home/thomas/build-qt-sensor-demo-Buildroot_ARM-Debug/Makefile qmake_all
make: Nothing to be done for 'qmake_all'.
13:11:59: The process "/usr/bin/make" exited normally.
13:11:59: Starting: "/usr/bin/make" -j4
/home/thomas/projets/outputs/st/host/bin/arm-linux-gnueabihf-g++ -c -pipe -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -Os -Og --sysroot=/home/thomas/projets/outputs/st/host/arm-buildroot-linux-gnueabihf/sysroot -g -Wall -W -D_REENTRANT -fPIC -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I../qt-sensor-demo -I. -I../projets/outputs/st/host/arm-buildroot-linux-gnueabihf/sysroot/usr/include/qt5 -I../projets/outputs/st/host/arm-buildroot-linux-gnueabihf/sysroot/usr/include/qt5/QtWidgets -I../projets/outputs/st/host/arm-buildroot-linux-gnueabihf/sysroot/usr/include/qt5/QtGui -I../projets/outputs/st/host/arm-buildroot-linux-gnueabihf/sysroot/usr/include/qt5/QtCore -I. -I../projets/outputs/st/host/mkspecs/devices/linux-buildroot-g++ -o main.o ../qt-sensor-demo/main.cpp
/home/thomas/projets/outputs/st/host/bin/arm-linux-gnueabihf-g++ --sysroot=/home/thomas/projets/outputs/st/host/arm-buildroot-linux-gnueabihf/sysroot -o qt-sensor-demo main.o   -lQt5Widgets -lQt5Gui -lQt5Core -lrt -ldl -latomic -lpthread 
13:12:00: The process "/usr/bin/make" exited normally.
13:12:00: Elapsed time: 00:02.

So we see that it is invoking qmake from Buildroot, and then running make, which builds our application, with the appropriate cross-compiler provided by Buildroot!

The application has been built in /home/thomas/build-qt-sensor-demo-Buildroot_ARM-Debug, which contains:

-rw-rw-r-- 1 thomas thomas 620760 30 août  13:12 main.o
-rw-rw-r-- 1 thomas thomas  31522 30 août  13:11 Makefile
-rwxrwxr-x 1 thomas thomas 516504 30 août  13:12 qt-sensor-demo

Running the application on the target

In order for Qt to deploy our application on the target, we need to adjust our .pro file so that it has directives to install the application. We’ll simply make our .pro file look like this:

QT += widgets
SOURCES = main.cpp
INSTALLS += target
target.path = /usr/bin

We invite you to read the relevant part of the Qt documentation to get details about the INSTALLS directive and the special target keyword.

Before we can really deploy on your target, we need to adjust the Run configuration, so click on the Project icon in the left bar, which should bring you to:

Qt Creator project build settings

We’re seeing the Build settings, so click on Run to see the Run settings. Everything should already be auto-detected: we want to deploy qt-sensor-demo to /usr/bin on the target, the target is STM32MP15 Discovery board. The only thing we need to change is to set Command line arguments to -platform linuxfb. Your settings should then look like this:

Qt Creator run settings

Now, you can finally do Build -> Run. Qt Creator will prompt you for the root password of your target, and automatically deploy and run the application!

Just to test it, make a change to the QPushButton label, and do Build -> Run again. You’ll see the new version of your application running!

Debugging your application

The last part in setting up our development environment is to be able to debug our application from Qt Creator. This involves remote debugging, where the debugger runs on your workstation, while the program being debugged runs on a separate target. As part of the Kit definition done previously, we have already told Qt Creator where the cross debugger provided by Buildroot is.

Now, we need to have gdbserver on the target, which is the program with which the cross-debugger will communicate to control the execution of our application on the target. To achieve this, go to the Buildroot menuconfig, and enable the option BR2_TOOLCHAIN_EXTERNAL_GDB_SERVER_COPY, in Toolchain -> Copy gdb server to the Target. With this done, we now need to have Buildroot take this change into account. Unfortunately simply running make will not take this change into account (see here for more details). We could do a full clean rebuild of Buildroot (make clean all), but that would take quite some time, so we’ll ask Buildroot to only reinstall the toolchain package and regenerate the root filesystem image:

make toolchain-external-arm-arm-reinstall all

Reflash your SD card, and reboot the system. You should now have gdbserver available on the target:

# ls -l /usr/bin/gdbserver 
-rwxr-xr-x    1 root     root        355924 Aug 29  2019 /usr/bin/gdbserver

We’ll now change a bit our program with some additional dummy code to play around with the debugger:

#include <QApplication>
#include <QPushButton>

int main(int argc, char* argv[])
{
    int a = 42;
    QApplication app(argc, argv);
    QPushButton hello("Hello world!");
    a++;
    qDebug("Test 1");
    a++;
    qDebug("Test 2");
    hello.resize(100,30);
    hello.show();
    return app.exec();
}

Place a breakpoint on the line QApplication app(argc, argv) by clicking to the left of this line, it should show a red dot, like this:

Qt Creator breakpoint

Then you can start debugging by clicking on the following button in the left bar:

Qt Creator debug button

It will switch to the debug view, with the program stopped at our breakpoint:

Qt Creator debug view

At the bottom of the screen, click on Application Output so that we can see the stdout of the application running on the target. Now hit F10 to step through our code line by line. You should then see the value of the variable a updated in the top right panel, and the Test 1 and then Test 2 messages printed in the application output:

Qt Creator debugging

So, as expected, we are able to debug our application! This concludes the setup of Qt Creator, which allows us to very easily make a change to our application, build it, deploy it on the target and debug it.

Building using a Buildroot package

Before we conclude this article, we want to see how to integrate the build of our application with Buildroot. Indeed, building the application manually or through Qt Creator is perfectly fine during the active development of the application. But in the end, we want Buildroot to be able to build our complete system, including all the applications and libraries we have developed, in a fully automated and reproducible fashion.

To achieve this, in this section, we’ll create a Buildroot package for our qt-sensor-demo application. A package in Buildroot speak is a small set of metadata that tells Buildroot how to retrieve and build a particular piece of software.

To learn how to create a Buildroot package, we suggest you to read the relevant section of the Buildroot manual, or to read the slides of our Buildroot training course. The following steps will however guide you in the process of creating our qt-sensor-demo package.

First, in the Buildroot source tree, create a package/qt-sensor-demo/ directory. Then, create a file named package/qt-sensor-demo/Config.in, which describes one configuration option to be able to enable/disable this package from Buildroot’s menuconfig:

config BR2_PACKAGE_QT_SENSOR_DEMO
        bool "qt-sensor-demo"
        depends on BR2_PACKAGE_QT5
        select BR2_PACKAGE_QT5BASE_WIDGETS
        help
          This is the qt-sensor-demo application.

Note that the bool, depends on, select and help keywords need to be prefixed with a tab (not spaces), and that the BR2_PACKAGE_QT_SENSOR_DEMO string should be exactly as-is, as it needs to match the name of the directory qt-sensor-demo.

This Config.in file basically creates a boolean option which will appear as qt-sensor-demo in menuconfig. The depends on BR2_PACKAGE_QT5 definition ensures that our option will only be selectable if Qt5 is available, while select BR2_PACKAGE_QT5BASE_WIDGETS makes sure Qt5 will be built with QtWidgets support, as we use them.

Now, edit the existing package/Config.in file, and at a relevant place (perhaps Graphic libraries and applications, submenu Graphic applications), you need to add:

source "package/qt-sensor-demo/Config.in"

So that Buildroot’s menuconfig properly includes and reads our new package Config.in file. Now, if you run make menuconfig in Buildroot, you should be able to see our new option and enable it. Of course for now, it doesn’t do anything useful.

The next step is to create a qt-sensor-demo.mk file in package/qt-sensor-demo/ to teach Buildroot how to build our package. This .mk file is a Makefile, which uses a number of Buildroot-specific variables and macros, to a point where it doesn’t really look like a typical Makefile. In our case, qt-sensor-demo.mk will look like this:

################################################################################
#
# qt-sensor-demo
#
################################################################################

QT_SENSOR_DEMO_SITE = $(TOPDIR)/../qt-sensor-demo
QT_SENSOR_DEMO_SITE_METHOD = local

QT_SENSOR_DEMO_DEPENDENCIES = qt5base

define QT_SENSOR_DEMO_CONFIGURE_CMDS
	(cd $(@D); $(QT5_QMAKE))
endef

define QT_SENSOR_DEMO_BUILD_CMDS
	$(TARGET_MAKE_ENV) $(MAKE) -C $(@D)
endef

define QT_SENSOR_DEMO_INSTALL_TARGET_CMDS
	$(INSTALL) -D -m 0755 $(@D)/qt-sensor-demo $(TARGET_DIR)/usr/bin/qt-sensor-demo

endef

$(eval $(generic-package))

The first two variables, QT_SENSOR_DEMO_SITE and QT_SENSOR_DEMO_SITE_METHOD tell Buildroot how to retrieve the source code for this application. Most Buildroot packages retrieve tarballs of source from HTTP servers, or clone source code from Git repositories. But in the case of our package, we are simply taking the source from the qt-sensor-demo directory, located just one level up from the main Buildroot source directory.

The QT_SENSOR_DEMO_DEPENDENCIES variable tells Buildroot that the qt5base package needs to be built before qt-sensor-demo gets built.

The QT_SENSOR_DEMO_CONFIGURE_CMDS variable describes the commands to run to configure our package. Here, we simply call Qt’s qmake utility, using the Buildroot-provided variable QT5_QMAKE.

The QT_SENSOR_DEMO_BUILD_CMDS variable describes the commands to run to build our package. In our case, we invoke make in the application directory, $(@D), passing appropriate variables in the environment ($(TARGET_MAKE_ENV)).

Then, the QT_SENSOR_DEMO_INSTALL_TARGET_CMDS variable describes the commands to run to install our package. We simply copy the qt-sensor-demo executable from the build directory ($(@D)) to usr/bin in the target directory.

Finally, the generic-package macro invocation is what triggers the Buildroot machinery to create a package. Read the Buildroot manual and/or our Buildroot training slides for more details.

With this in place, if you have already enabled qt-sensor-demo in menuconfig, when you run make in Buildroot, you should see:

>>> qt-sensor-demo  Syncing from source dir /home/thomas/qt-sensor-demo
rsync -au --chmod=u=rwX,go=rX --exclude .svn --exclude .git --exclude .hg --exclude .bzr --exclude CVS  /home/thomas/qt-sensor-demo/ /home/thomas/buildroot/output/build/qt-sensor-demo
>>> qt-sensor-demo  Configuring
(cd /home/thomas/buildroot/output/build/qt-sensor-demo; /home/thomas/buildroot/output/host/bin/qmake -spec devices/linux-buildroot-g++)
>>> qt-sensor-demo  Building
PATH="..." /usr/bin/make -j5 -C /home/thomas/buildroot/output/build/qt-sensor-demo
/home/thomas/buildroot/output/host/bin/arm-linux-gnueabihf-g++ --sysroot=/home/thomas/buildroot/output/host/arm-buildroot-linux-gnueabihf/sysroot -Wl,-O1 -o qt-sensor-demo main.o   -L/home/thomas/buildroot/output/host/arm-buildroot-linux-gnueabihf/sysroot/usr/lib -lQt5Widgets -lQt5Gui -lQt5Core -lrt -ldl -latomic -lpthread 
>>> qt-sensor-demo  Installing to target
/usr/bin/install -D -m 0755 /home/thomas/buildroot/output/build/qt-sensor-demo/qt-sensor-demo /home/thomas/buildroot/output/target/usr/bin/qt-sensor-demo

You’re seeing:

  1. Buildroot copying the source code from its original location to the Buildroot build directory
  2. Buildroot configuring the build of our package by invoking qmake
  3. Buildroot building our application
  4. Buildroot installing our application

The application being installed in Buildroot’s target directory, it is automatically part of the root filesystem image, and consequently the SD card image sdcard.img. You can flash it again, and see that you have the same application.

Now, if you want to change the source code of your application, you can simply change it in its original location, the qt-sensor-demo directory, and issue the following command in Buildroot:

$ make qt-sensor-demo-rebuild all

Buildroot will synchronize again the source code from its original directory to Buildroot’s build directory, and rebuild the application. It will only transfer the files that have changed, and only rebuild the files that have changed. The all target ensures that the root filesystem and SD card images get regenerated with the new version of the code.

Conclusion

In this article, we’ve seen many things:

  • How to create and manually build our first Qt5 application
  • How to deploy our application to the target by adding it to the SD card
  • How to set up network communication, and SSH, to deploy our application more efficiently during development
  • How to set up Qt Creator as a development environment to write, build, deploy and debug our application
  • How to create a Buildroot package to automate the build of our application

You can find the Buildroot changes corresponding to this blog post in the 2019.02/stm32mp157-dk-blog-4 branch of our repository. The qt-sensor-demo application code can be found in the blog-4 branch of this application Git repository.

In our next blog post, we’ll extend our qt-sensor-demo application to make it really useful!

Thomas Petazzoni

Author: Thomas Petazzoni

Thomas Petazzoni is Bootlin's co-owner and CEO. Thomas joined Bootlin in 2008 as a kernel and embedded Linux engineer, became CTO in 2013, and co-owner/CEO in 2021. More details...

39 thoughts on “Building a Linux system for the STM32MP1: setting up a Qt5 application development environment”

  1. This is a truly excellent article.

    You completely address all my concerns about using the open source version of QtCreator to build and debug Qt apps. And now we see how it all ties in with Buildroot.

    Thank you! Awesome!

    You’ve convinced me to order an MP157C discovery board and get to work.

    I did notice one small potential error. Are there missing includes in main.cpp?

    #include xxxxxx
    #include xxxxxx
    Or are the #includes unnecessary?

    1. Hello ClarkS. Thanks for your comment, glad to hear that the blog post was useful. Regarding the #include, the less than and greater than sign were interpreted as an HTML tag, which obviously didn’t work. I fixed that up, and now the blog post should hopefully show correct and working code. Thanks!

  2. That is a great tutorial. Thank you very much. I like this chip. I come from an RTOS background so this seems like an ideal transitional path
    The only issue I see with it is that there is no flash on chip so you need to load the M4 on powerup. Maybe ST should have put some flash in for the M4.

    I am looking at using the M4 to support an RTOS application and have some sort of IPC to the main A7 Linux Application.

    Any tips on loading the M4 and using OpenAmp?

    1. Hello Eoin. Thanks for your comment. There is no need for a flash for the M4. Indeed, since there is an application processor running Linux next to it, either U-Boot (the bootloader) or Linux can load the appropriate code from flash/MMC/network and ask the M4 to run it.

      There are obviously several IPC mechanisms between the A7 and the M4: shared memory and mailbox. I encourage you to browse https://wiki.st.com/stm32mpu/wiki/Category:Coprocessor_management_STM32Cube which contains a number of pages related to this. It explains how Linux can control the M4 (load code and run it on the M4) and how Linux can communicate with the M4.

      This is something we hope to cover in a future article, but we are not sure we will get there. Stay tuned!

  3. thanks again for this great blog !
    I have a question:
    Whenever I deploy and run my qt program on stm32mp157c-dk2, I see the qt application interface on the MIPI-DSI touch display and it is great but after I finish the running process of the program, the qt app interface is still there on the touch display. Why ?
    And I think since the first version of program was not terminated, I couldnt start to remotely debug my modified program. How can I really terminate the app on target and clear teh display ?
    here is the output:

    14:34:00: Checking available ports…
    14:34:00: Found 101 free ports.
    14:34:00: Starting gdbserver…
    14:34:00: Unable to create a debugging engine.
    14:34:00: User requested stop. Shutting down…
    Listening on port 10000
    14:34:05: Timeout waiting for remote process to finish.

    1. Hello Carl. Thanks for your comment. The contents of the screen remains visible simply because there is nothing that cleans up the framebuffer when the application exits, so the last frame displayed by the application remains visible.

      Regarding your debugging problem, why wasn’t your program terminated ? If you’re using Qt Creator, you can stop the program, and start a new debugging session. I’m not sure how you got into the situation you’re describing.

  4. hi,

    At first, thank you so much.
    MIPI-DSI touch display is functioning which is a part of discovery kit stm32mp157c-dk2.
    I have also a linux compatible HDMI touch display that I connect to offical weston image and running well. But When I connect it to this buildroot image, it doesn’t function.
    What should I do ?

    thank you !

    1. Hello Ibrahim. Thanks for your comment. Regarding your HDMI touch display, I believe the HDMI output should work as-is: you can try with the modetest application described in the previous blog post, it should show the HDMI output connected. Use that first to make sure that your HDMI display is working as expected. Then for Qt, you’ll probably have to pass something like QT_QPA_PLATFORM=linuxfb:fb=/dev/fb1 in the environment to make it work. I don’t have the STM32MP15 board with me, so I can’t formally verify.

      Regarding the touchscreen, if I understand correctly in most HDMI touch displays, there is a separate USB cable that is used for the touchscreen. Then you will need the appropriate Linux kernel drivers: you may need to adjust your Linux kernel configuration. A first tip is to run “lsusb” with and without the display connected, and see which USB device appears. Based on that you can figure out which driver should be used.

  5. Your blog posts have been very helpful with getting buildroot working and enabling periferals. I am now stuck on writing an application for the a7 to interact with the m4 using openamp. I have gone through all the documentation but it seem the interrupts are not being handled correctly and reliability is bad.

    I hope you might cover the interprocessor communication in a later post.

    1. Hello Cristian. We are not sure yet whether we will have the chance to cover interprocessor communication in this series. We will try to do so, but we can’t guarantee it.

      Do not hesitate to contact us if you need commercial support for the STM32MP1 platform, Bootlin offers engineering services for custom development and support.

  6. Hello Mr. Petazzoni

    1. As you know we copy our qt binary to /path/to/buildroot/output/target/usr/bin/ before building the system. I want two additional files be in the same path (.elf and .sh). What should I do ? just copy the files to the path before building ? Or which config files need to be edited and how ?

    2. I want my qt application run automatically after booting the system (power on). What should I do ?

    a possible answer will be really helpfull for my thesis. thanks in advance !

    1. Hello Carl,

      (1) Everything that is in output/target/ will be part of the final root filesystem image. However, as explained in the blog post, it is important to understand that output/target/ is a temporary location: if you do a “make clean” in Buildroot, the contents of output/target/ will disappear, and the system will be entirely rebuilt from scratch at the next Buildroot “make”. So, copying files manually to output/target/ is only good during experimentation/development: for the final integration, you really want to have either a package or a root filesystem overlay install whatever files you need.

      (2) The topic of creating an init script to start the application at boot time will be covered in our next blog post.

  7. Hi! In the section “Debugging your application” these to lines are not displayed completely due to the HTML error:
    #include
    #include
    They should be written in the HTML code as:
    #include &lt:QApplication>
    #include &ltQPushButton>

  8. Hi Thomas,

    I was following your tutorial for buildroot, and I have problem setting up the kit. qmake binary is missing from /path/to/buildroot/output/host/bin/

    Any idea why is that and how can i fix it?

    Thank you for the tutorial

    1. Hello Ema. If the qmake binary is not present in output/host/bin, then it means that Qt5 was not built. Either your build failed, or Qt5 was not enabled in your configuration. If you want more support on this issue, could you check (1) that BR2_PACKAGE_QT5=y in your .config and (2) that you’ve done a full rebuild using “make clean all” ?

      1. Hi Thomas,
        Love your tutorials!
        I, like Ema am not getting the qmake binary built.

        Checking .conf I get:
        ~/st-buildroot/buildroot$ grep BR2_PACKAGE_QT5 .config
        BR2_PACKAGE_QT5_JSCORE_AVAILABLE=y
        # BR2_PACKAGE_QT5 is not set
        # BR2_PACKAGE_QT5QUICK1 is not set

        Is there a glage I would set in menuconfig to get this setup properly to build qmake?

        Thanks

          1. Hi Thomas,

            Please forgive my missing this part of your tutorials. It worked perfectly! It is truly astonishing how well you mix theory and practice as an effortless flow! It’s not just that you take the time to write these, but that someone of your expertise takes the time to follow up on all the questions that come through. Even if it is as simple as my missing the whole tutorial…..

            If you allow me to add, like so many have commented, doing the inter-processor communication to the M4 would really be the jewel on the crown on this outstanding accomplishment. As an upside, I am sure wetting peoples appetite with this type of getting started guide for the M4, would open so many opportunities for professional services to complete the communication model for these designs.

            Thank you again for this gift of an invitation, to all, to join the world of Linux!
            Gearge

  9. Hello,
    Thanks for the tutorial. I’m using a different target, but I followed your instructions for setting up a QT development environment. When I run my application, I get the following message:
    /lib64/libstdc++.so.6: no version information available (required by /usr/lib64/libGLESv2.so.2)
    It seems this might be related to having a build environment that isn’t using the correct toolchain (https://stackoverflow.com/questions/58496314/lib-libstdc-so-6-no-version-information-available) but I’m not sure where I went wrong, given that I’m compiling using QT Creator using the kit that is set up with my buildroot output.
    Any ideas?
    Thanks!

    1. It’s difficult to help you without more details, especially about the toolchain you’re using. However, we have seen some Linux toolchains provided by ARM where the libstdc++ was built without version information… while the OpenGL blobs provided by vendors do expect libstdc++ to have the version information. So here, your only luck is to switch to another toolchain, which has a libstdc++ properly built with version information.

  10. Hi Thomas,
    and many thanks for your tutorial series. I had trouble with using mkspec devices/linux-buildroot-g++. The effect is, that the QtCreator (4.8.2.) Debug config did not include the “-g” flag for generating debug info, i.e. no source level debugging. I changed to devices/linux-generic-g++ and that worked.
    When I checked package/qt5/qt5base/qmake.conf.in I found lines:
    # Remove all optimisation flags …

    QMAKE_CXX_FLAGS_DEBUG =
    Is that the reason for the missing -g and what’s the rationale behind that?
    kind regards
    Frank
    berlin area

    1. Hello Frank. Thanks for your comment. The idea is that Buildroot builds everything with -g when BR2_ENABLE_DEBUG=y in the Buildroot configuration. So we “expose” that also to things built with qmake, and that’s why our qmake file contains:

      QMAKE_CFLAGS += $${BR_COMPILER_CFLAGS}
      QMAKE_CXXFLAGS += $${BR_COMPILER_CXXFLAGS}

      The BR_COMPILER_CFLAGS variable will contain -g when BR2_ENABLE_DEBUG=y in Buildroot.

  11. Hallo Thomas,
    i am just lerning buildroot with your wonderful blogs. But now i am stuck. I don’t get the GESFTPSERVER working. Message is always like “-sh: sftp: not found”.
    The relevant part of the .config file:
    #
    # ejabberd needs erlang, toolchain w/ C++
    #
    # BR2_PACKAGE_ETHTOOL is not set
    # BR2_PACKAGE_FAIFA is not set
    # BR2_PACKAGE_FASTD is not set
    # BR2_PACKAGE_FCGIWRAP is not set
    # BR2_PACKAGE_FLANNEL is not set
    # BR2_PACKAGE_FPING is not set
    # BR2_PACKAGE_FREESWITCH is not set
    # BR2_PACKAGE_GERBERA is not set
    BR2_PACKAGE_GESFTPSERVER=y
    # BR2_PACKAGE_GLORYTUN is not set

    OS is Ubuntu 18.041 LTS /64-Bit PC
    Linux 5.4.0-72-generic #80~18.04.1-Ubuntu
    I think i made a mistake, but can’t find it.

    Marcus

    1. Where do you see this message “-sh: sftp: not found” ? On the board or on your workstation ? gesftpserver only provides a SFTP server, not a SFTP client, so on the board, it is normal to not have the “sftp” command. If this error is on your workstation: install the sftp client, which is needed by Qt Creator to upload your application to the board.

      1. Hallo Thomas,
        both the terminal (connection by ETH) and the ScriptCommander (connecting St-Link) showed the message on the PC. As I assumed I made the mistake. I tried to connect to the SFTP when I was already connected to the STM32 via ssh.
        I needed some time to figure that out. I work with Linux for 8 weeks, the learning curve is steep….
        Thank you
        Marc

  12. Hi, thanks for great tutorials, do you know what packages do I need to install to run quick application? because currently I get this error
    “qt.qpa.plugin: Could not find the Qt platform plugin “eglfs” in “” “

    1. The error you’re getting is not related to Quick applications, any graphical Qt application would exhibit this error. It seems like you are requesting to run the application with the eglfs Qt plugin, but this plugin was not compiled into your Qt configuration. Indeed, this blog post only shows how to use the linuxfb Qt plugin, without OpenGL acceleration. You’ll need to adjust the Buildroot configuration to enable an OpenGL provider package + enable eglfs in Qt. Contact us at Bootlin if you need support to achieve this.

  13. Hi, excellent tutorial!
    However I’m having trouble with QT’s camera. Camera is based on adv7280 chip on our custom board. It works on ST’s yocto based system, but in buildroot there are some problems with DMA settings, I think. In yocto based STlinux there are some patches that are missing in buildroot?

    1. Glad you found the tutorial useful. Buildroot doesn’t have kernel patches: it builds whichever Linux kernel version and configuration you tell Buildroot to build. So if you have a working setup, make sure to re-use the same Linux kernel version and configuration in Buildroot.

      Of course, Bootlin can provide commercial support if you need help with this project. We have expertise with both Buildroot and camera support in Linux.

      1. Thanks! I’m using the same kernel and uboot versions and I ran those patches to kernel and uboot. It compiles in buildroot without warnings, but in boot phase something goes wrong and it stops in the “starting Kernel” point. I’ll check the configurations again, maybe I’ve missed something.

      2. Hi! I managed to implement those kernel patches, and Buildroot boots now. I tried to use the GPU driver (etnaviv) with Qt, platform eglfs, but I guess there is something wrong, as the qt-examples run extremely slow. I’m using the 5.10.61 kernel version. Something has changed and there is no fb0 device anymore, so linuxfb platform doesn’t work. Any idea how to enable it ?

          1. Thanks a lot!
            This did the trick! I got my camera working in Qt5 environment, and it’s now easy to make changes to the device tree. There was some problem with i2c5 clock but I found the setting in the device tree. I’m using eglfs platform and it works now.

            Best Regards,
            Raimo

  14. Hello ,
    Thanks for those excellent tutorial.
    I am using buildroot-external-st.

    I only have one things that doesn’t works, it’s the breakpoint :
    The application don’t stop on any breakpoint, don’t understand why ?
    Everything else works fine, any idde ?

    1. Hello Gabriel. Glad to hear that buildroot-external-st was useful. For your breakpoint issue, could you provide more details? Did you build your application with debugging symbols? Did you build the Buildroot system with debugging symbols (BR2_ENABLE_DEBUG=y in the Buildroot configuration) ? What are the exact issues that you observe ?

      1. I am a bit confuse, it’s working now. As many variable are “optimized out” i dont think that debug symbol are enable in my application.
        I don’t know where to config “debugging symbols” with qmake

  15. Hi Thomas,

    Thank you so much for this tremendous resource. Having these tools to work with is quite a windfall. I am working with the STM32MP157F-DK2 board and the 2022.02 configuration stm32mp157f-dk2-demo-defconfig file. My platform is Ubuntu 22.04 with Qt Creator and the goal it to run embedded projects on the target over the ethernet link on eth0. For this I have used menuconfig to include the gesftpserver module (no other changes) so that I can deploy Qt creator builds directly to the target board by SFTP. I have been able to boot the project on the board and interact by ST-link using picocom, and I’ve been able to ssh into the dropbear server as well. The problem that I’m running into is that the SFTP link is not functional and will not log in either on the command line as instructed above in this tutorial or through Qt Creator. The message that I get from the target is that there is no SFTP server. Might there be any advise or debugging clues regarding this problem?

    On a secondary note, I tried a work-around by removing dropbear and gesftpserver from the menuconfig and activating the openssh as an alternative because as I understand it, openssh has an sftp server. Here I run into problems in that I cannot log into the target server and an $ ssh -v root @192.168.42.2 shows that the connection process hangs at “debug 1: expecting SSH2-MSG_KEX_ECDH_REPLY”, followed by a connection reset. A web search turns up suggestions of changing the mtu value, but at least a change from 1500 to 1200 or 1000 has not been helpful. Another recent link throws suspicion on glibc but I just don’t the experience with openssh or embedded linux to properly evaluate the thread.

    Thanks in advance for any insight on this, I am so looking forward to using this system!

Leave a Reply