Building a Linux system for the STM32MP1: developing a Qt5 graphical application

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, how to set up a development environment to write our own Qt5 application, we are finally going to write our Qt5 application.

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

Disclaimer

Before we get started in this blog post, it is important to mention that it is not meant to be a full introduction to programming applications with Qt5. This would require much more than a blog post, and the Qt web site has extensive documentation.

Also, we want to make it clear that Bootlin’s core expertise is in low-level embedded Linux development, not in Qt application development. Therefore, our example application may not show the best practices in terms of Qt development. We welcome comments and suggestions from our readers to improve the example used in this blog post.

Reading sensor data

As we’ve seen in a previous article, the sensor data is available by reading the following files:

  • /sys/bus/iio/devices/iio:device2/in_temp_input for the temperature
  • /sys/bus/iio/devices/iio:device2/in_pressure_input for the pressure
  • /sys/bus/iio/devices/iio:device2/in_humidityrelative_input for the humidity

So what we will do is writing a new class called DataProvider, which will read those files once per second, and emit a signal with the 3 values every second. Slots and signals is a fundamental mechanism in Qt, which allows to connect emitters of events to receivers for those events. In our case, the DataProvider class will emit a signal when new sensor values are read, while another class in charge of the graphical UI will receive those signals.

At this step, we don’t yet have a graphical UI, so we’ll simply add a few debugging messages in the DataProvider to make sure it works as expected.

Let’s start by adding a data-provider.h file to our project:

#ifndef DATA_PROVIDER_H
#define DATA_PROVIDER_H

#include <QtCore/QTimer>

class DataProvider: public QObject
{
    Q_OBJECT

public:
    DataProvider();

private slots:
    void handleTimer();

signals:
    void valueChanged(float temp, float pressure, float humidity);

private:
    QTimer timer;
};

#endif /* DATA_PROVIDER_H */

It creates a very simple class than inherits from QObject, with:

  • A constructor
  • A private slot handleTimer which will be used internally by the class QTimer’s instance to notify that a timer has expired. This is what will allow us to poll the sensor values every second.
  • A valueChanged signal, which will be emitted by the class every time new sensor values are available.

Then, the implementation of this class in data-provider.cpp is fairly straight-forward:

#include <QtCore/QFile>
#include <QDebug>
#include "data-provider.h"

DataProvider::DataProvider()
{
    QObject::connect(&timer, &QTimer::timeout,
		     this, &DataProvider::handleTimer);
    timer.setInterval(1000);
    timer.start();
}

void DataProvider::handleTimer()
{
    QFile temp_f("/sys/bus/iio/devices/iio:device2/in_temp_input");
    QFile pressure_f("/sys/bus/iio/devices/iio:device2/in_pressure_input");
    QFile humidity_f("/sys/bus/iio/devices/iio:device2/in_humidityrelative_input");

    if (!temp_f.open(QIODevice::ReadOnly | QIODevice::Text))
        return;
    if (!pressure_f.open(QIODevice::ReadOnly | QIODevice::Text))
        return;
    if (!humidity_f.open(QIODevice::ReadOnly | QIODevice::Text))
        return;

    float temp = QString(temp_f.readAll()).toDouble() / 1000;
    float pressure = QString(pressure_f.readAll()).toDouble() * 10;
    float humidity = QString(humidity_f.readAll()).toDouble() / 1000;

    qDebug() << "Temperature: " << temp << "Pressure: " << pressure << "Humidity: " << humidity;

    emit valueChanged(temp, pressure, humidity);
}

The constructor of the class connects the QTimer::timeout signal of the QTimer to this class handlerTimer slot, sets the timer interval to 1000 milliseconds, and starts the timer. This is what will ensure the handleTimer method gets called every second.

In the handleTimer method, we open the 3 files in sysfs, read their value and convert them to meaningful units: the temperature in Celcius, the pressure in hPA, and the humidity in percent. We then print a debugging message and emit the signal with the three values.

With this in place, we need to make sure those two files are properly taken into account by our project, by changing the .pro file as follows:

QT += widgets
SOURCES = main.cpp data-provider.cpp
HEADERS = data-provider.h
INSTALLS += target
target.path = /usr/bin

The data-provider.cpp file was added to SOURCES, while data-provider.h was added to the new HEADERS.

Now, we just need to change main.cpp to instantiate one DataProvider object:

#include <QApplication>
#include <QPushButton&ht;
#include "data-provider.h"

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

With this, you can now build and run the application, and you should see every second the debugging message showing the temperature, pressure and humidity values:

# qt-sensor-demo -platform linuxfb
Temperature:  28.12  Pressure:  1003.08  Humidity:  32.235
Temperature:  28.12  Pressure:  1003.07  Humidity:  32.246
Temperature:  28.12  Pressure:  1003.06  Humidity:  32.256
Temperature:  28.12  Pressure:  1003.08  Humidity:  32.267

Displaying sensor data

We now want to display the sensor data. For this, we'll create a UI with two panels, one to display the numeric value of the temperature, humidity and pressure, and another panel with a chart of the temperature. At the bottom of the screen, two buttons Values and Chart will allow to switch between both panels.

So, we'll create a Window class to encapsulate the overall window layout and behavior, and a Values class providing the widget showing the 3 values. We'll leave the chart implementation to the next section. To help you follow the code in this section, here is a diagram that shows the different widgets and how they will be grouped together in our user interface:

Qt Sensor demo UI

Let's start by implementing the Values widget, which will be used to show the 3 numeric values, one below each other. The values.h file will look like this:

#ifndef VALUES_H
#define VALUES_H

#include <QWidget>

class QLabel;

class Values : public QWidget
{
    Q_OBJECT

public:
    Values();

public slots:
    void handleValueChanged(float temp, float pressure, float humidity);

private:
    QLabel *temperature_v;
    QLabel *pressure_v;
    QLabel *humidity_v;
};

#endif /* VALUES_H */

So it has a simple constructor, a slot to be notified of new values available, and 3 text labels to display the 3 values. The implementation in values.cpp is:

// SPDX-License-Identifier: MIT
#include <QtWidgets>
#include "values.h"

Values::Values()
{
    QVBoxLayout *layout = new QVBoxLayout;

    QLabel *temperature_l = new QLabel(tr("Temperature (°C)"));
    QLabel *pressure_l = new QLabel(tr("Pressure (hPa)"));
    QLabel *humidity_l = new QLabel(tr("Humidity (%)"));

    temperature_v = new QLabel();
    pressure_v = new QLabel();
    humidity_v = new QLabel();

    QFont f = temperature_v->font();
    f.setPointSize(28);
    f.setBold(true);
    temperature_v->setFont(f);
    pressure_v->setFont(f);
    humidity_v->setFont(f);
    temperature_v->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
    pressure_v->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
    humidity_v->setAlignment(Qt::AlignRight | Qt::AlignVCenter);

    layout->addWidget(temperature_l);
    layout->addWidget(temperature_v);
    layout->addWidget(pressure_l);
    layout->addWidget(pressure_v);
    layout->addWidget(humidity_l);
    layout->addWidget(humidity_v);

    setLayout(layout);
}

void Values::handleValueChanged(float temp, float pressure, float humidity)
{
    temperature_v->setText(QString::number(temp, 'f', 2));
    pressure_v->setText(QString::number(pressure, 'f', 1));
    humidity_v->setText(QString::number(humidity, 'f', 1));
}

The constructor creates 3 text labels for the legends ("Temperature (°C)", "Pressure (hPA)" and "Humidity (%)"), then instantiates the 3 text labels for the values themselves. It sets up the font and text alignment properties for those labels, and then adds all widgets in a QVBoxLayout so that they all appear vertically below each other.

The handleValueChanged slot simply updates the text labels contents with the new sensor values, doing the proper text formatting on the way.

With the Values class implemented, we can now implement the main Window class. The window.h will contain:

#ifndef WINDOW_H
#define WINDOW_H

#include <QWidget>

class Values;

class Window : public QWidget
{
    Q_OBJECT

public slots:
    void handleValueChanged(float temp, float pressure, float humidity);

public:
    Window();

private:
    Values *values;
};

#endif

Beyond a simple constructor, it has a slot to receive new sensor values, and a reference to a Values widget instance.

The implementation in window.cpp is as follows:

#include <QtWidgets>

#include "window.h"
#include "values.h"

Window::Window()
{
    values = new Values;
    QVBoxLayout *layout = new QVBoxLayout;
    QHBoxLayout *buttons = new QHBoxLayout;

    QPushButton *values_button = new QPushButton("Values");
    QPushButton *chart_button = new QPushButton("Chart");

    buttons->addWidget(values_button);
    buttons->addWidget(chart_button);

    layout->addWidget(values);
    layout->addLayout(buttons);

    setLayout(layout);

    setWindowTitle(tr("Sensors"));
}

void Window::handleValueChanged(float temp, float pressure, float humidity)
{
    values->handleValueChanged(temp, pressure, humidity);
}

The constructor creates a horizontal layout QHBoxLayout with two buttons: Values and Chart. Those will be used in the next section to switch between the Values panel and the Chart panel. For now, they don't do anything.

Then, the constructor adds the Value widget, and the horizontal layout box with the buttons into a vertical box layout, assigns the main window layout and defines the window title.

The handleValueChanged slot implementation just forwards the call to the Values::handleValueChanged method.

Now, obviously main.cpp needs to be changed: instead of creating a button, we'll create our window, and do a bit of additional setup:

#include <QApplication>
#include "window.h"
#include "data-provider.h"

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    DataProvider dp;
    Window window;

    QObject::connect(&dp, &DataProvider::valueChanged,
		     &window, &Window::handleValueChanged);

    window.setFixedSize(480, 800);
    window.setStyleSheet("background-color: white;");
    window.show();
    return app.exec();
}

So, not only we create the Window, but more importantly, we connect the valueChanged signal of DataProvider to the handleValueChanged slot of Window. We define the window size (which is fixed, to match the STM32MP15 Discovery board panel) and set the background color of the application.

Obviously, the qt-sensor-demo.pro file needs to be adjusted to build our new files. It now looks like this:

QT += widgets
SOURCES = main.cpp data-provider.cpp window.cpp values.cpp
HEADERS = data-provider.h window.h values.h
INSTALLS += target
target.path = /usr/bin

With this done, we can run the Qt5 application on our target, and see:

Qt5 application showing the I2C sensor data

Graphing the temperature

The final part of developing our application is to implement a graph showing the evolution of temperature over time. For this, we are going to use the very convenient Qt Charts module, which is available in a separate Qt module from the base of Qt.

To implement the graph widget itself, we'll create a new Chart class:

#ifndef CHART_H
#define CHART_H

#include <QtCharts/QChart>

QT_CHARTS_BEGIN_NAMESPACE
class QSplineSeries;
class QValueAxis;
QT_CHARTS_END_NAMESPACE

QT_CHARTS_USE_NAMESPACE

class Chart: public QChart
{
    Q_OBJECT

public:
    Chart(QGraphicsItem *parent = 0, Qt::WindowFlags wFlags = 0);

public slots:
    void handleValueChanged(float temp, float pressure, float humidity);

private:
    QSplineSeries *m_series;
    QStringList m_titles;
    QValueAxis *m_axisX;
    QValueAxis *m_axisY;
    int xpos;
};

#endif /* CHART_H */

This class inherits from the QChart class provided by Qt. It provides a constructor and destructor, a slot that allows to receive notification of new sensor values, and it has a number of private variables to manage the chart itself.

Let's go through the implementation of this class now:

#include "chart.h"
#include <QtCharts/QAbstractAxis>
#include <QtCharts/QSplineSeries>
#include <QtCharts/QValueAxis>

Chart::Chart(QGraphicsItem *parent, Qt::WindowFlags wFlags):
    QChart(QChart::ChartTypeCartesian, parent, wFlags),
    m_series(0),
    m_axisX(new QValueAxis()),
    m_axisY(new QValueAxis()),
    xpos(0)
{
    m_series = new QSplineSeries(this);
    QPen pen(Qt::red);
    pen.setWidth(2);
    m_series->setPen(pen);
    m_series->append(xpos, 30);

    addSeries(m_series);

    addAxis(m_axisX,Qt::AlignBottom);
    addAxis(m_axisY,Qt::AlignLeft);
    m_series->attachAxis(m_axisX);
    m_series->attachAxis(m_axisY);
    m_axisX->setTickCount(5);
    m_axisX->setRange(0, 60);
    m_axisY->setRange(0, 50);

    QFont f = m_axisX->labelsFont();
    f.setPointSize(8);
    m_axisX->setLabelsFont(f);
    m_axisY->setLabelsFont(f);

    setMargins(QMargins(0,0,0,0));

    setTitle("Temperature (°C)");
    legend()->hide();
}

void Chart::handleValueChanged(float temp, float pressure, float humidity)
{
    Q_UNUSED(pressure);
    Q_UNUSED(humidity);
    m_series->append(xpos, temp);
    xpos++;
    if (xpos >= 60)
      scroll(plotArea().width() / 60, 0);
}

The constructor simply sets up the QChart we inherit from: defining the axis, their range, the pen width and color, etc. On the X axis (time), we are going to show 60 measurements, and since our handleValueChanged slot is going to be called every second, it means our graph will show the last 60 seconds of temperature measurement. On the Y axis (temperature), we can show temperatures from 0°C to 50°C. Of course, this is all very hardcoded in this example, for simplicity.

The handleValueChanged slot appends the new temperature value to the graph, and then updates the area displayed by the graph so that always the last 60 seconds are visible.

Now, we need to integrate this to our existing Window class, so that we can display the chart, and switch between the numeric values and the chart. First, we need to do some changes in window.h, and below we'll show only the diff to make the differences very clear:

diff --git a/window.h b/window.h
index 3d63d38..05d1f39 100644
--- a/window.h
+++ b/window.h
@@ -3,8 +3,12 @@
 #define WINDOW_H
 
 #include <QWidget>
+#include <QtCharts/QChartView>
+
+QT_CHARTS_USE_NAMESPACE
 
 class Values;
+class Chart;
 
 class Window : public QWidget
 {
@@ -13,11 +17,17 @@ class Window : public QWidget
 public slots:
     void handleValueChanged(float temp, float pressure, float humidity);
 
+private slots:
+    void chartButtonClicked();
+    void valuesButtonClicked();
+
 public:
     Window();
 
 private:
     Values *values;
+    QChartView *chartView;
+    Chart *chart;
 };
 
 #endif

So, we're defining two private slots that will be used for the two buttons that allow to switch between the numeric values and the chart, and then we add two variables, one for the chart itself, and one for the QChartView (which basically renders the graph into a widget).

Then, in window.cpp, we do the following changes:

diff --git a/window.cpp b/window.cpp
index aba2862..d654964 100644
--- a/window.cpp
+++ b/window.cpp
@@ -3,28 +3,54 @@
 
 #include "window.h"
 #include "values.h"
+#include "chart.h"
 
 Window::Window()
 {
     values = new Values;
+    chart = new Chart;
     QVBoxLayout *layout = new QVBoxLayout;
     QHBoxLayout *buttons = new QHBoxLayout;
 
     QPushButton *values_button = new QPushButton("Values");
     QPushButton *chart_button = new QPushButton("Chart");
 
+    QObject::connect(chart_button, &QPushButton::clicked,
+                    this, &Window::chartButtonClicked);
+    QObject::connect(values_button, &QPushButton::clicked,
+                    this, &Window::valuesButtonClicked);
+
     buttons->addWidget(values_button);
     buttons->addWidget(chart_button);
 
+    chartView = new QChartView(chart);
+    chartView->setRenderHint(QPainter::Antialiasing);
+
     layout->addWidget(values);
+    layout->addWidget(chartView);
     layout->addLayout(buttons);
 
     setLayout(layout);
 
+    chartView->hide();
+
     setWindowTitle(tr("Sensors"));
 }
 
 void Window::handleValueChanged(float temp, float pressure, float humidity)
 {
     values->handleValueChanged(temp, pressure, humidity);
+    chart->handleValueChanged(temp, pressure, humidity);
+}
+
+void Window::chartButtonClicked()
+{
+    values->hide();
+    chartView->show();
+}
+
+void Window::valuesButtonClicked()
+{
+    values->show();
+    chartView->hide();
 }

So, in the constructor we are connecting the clicked signals of the two buttons to their respective slots. We create the Chart object, and then the QChartView to render the graph. We add the latter as an additional widget in the QVBoxLayout, and we hide it.

The existing handleValueChanged slot is modified to also update the Chart object with the new sensor values.

Finally, the new chartButtonClicked and valuesButtonClicked slots implement the logic that is executed when the buttons are pressed. We simply hide or show the appropriate widget to display either the numeric values or the chart. There is probably a nicer way to achieve this in Qt, but this was good enough for our example.

Now that the source code is in place, we of course need to adjust the build logic in qt-sensor-demo.pro:

--- a/qt-sensor-demo.pro
+++ b/qt-sensor-demo.pro
@@ -1,6 +1,6 @@
 # SPDX-License-Identifier: MIT
-QT += widgets
-SOURCES = main.cpp data-provider.cpp window.cpp values.cpp
-HEADERS = data-provider.h window.h values.h
+QT += widgets charts
+SOURCES = main.cpp data-provider.cpp window.cpp values.cpp chart.cpp
+HEADERS = data-provider.h window.h values.h chart.h
 INSTALLS += target
 target.path = /usr/bin

Besides the obvious addition of the chart.cpp and chart.h file, the other important addition is charts to the QT variable. This tells qmake that our application is using the Qt Charts, and that we therefore need to link against the appropriate libraries.

Building the application

At this point, if you try to build the application, it will fail because QtCharts has not been built as part of our Buildroot configuration. In order to address this, run Buildroot's make menuconfig, enable the BR2_PACKAGE_QT5CHARTS option (in Target packages -> Graphic libraries and applications -> Qt5 -> qt5charts).

Then, run the Buildroot build with make, and reflash the resulting SD card image.

Now, you can build again your application, either with Qt Creator if you've been using Qt Creator, or manually. If you build it manually, you'll have to run qmake again to regenerate the Makefile, and then build with make.

When you run the application on the target, the GUI will display the same numeric values as before, but now if you press the Chart button, it will show something like:

Qt5 application with chart

Adjusting the Buildroot package

We have for now been building this application manually, but as explained in our previous blog post, we really want Buildroot to be able to build our complete system, including our application. For this reason, we had created a qt-sensor-demo package, which gets our application source code, configures it with qmake, builds it and installs it.

However, with the new use of Qt Charts, our qt-sensor-demo package needs a few adjustements:

  • The Config.in file needs an additional select BR2_PACKAGE_QT5CHARTS, to make sure Qt Charts are enabled in the Buildroot configuration
  • The qt-sensor-demo.mk file needs an additional qt5charts in the QT_SENSOR_DEMO_DEPENDENCIES variable to make sure the qt5charts package gets built before qt-sensor-demo

With this in place, you can run:

make qt-sensor-demo-rebuild
make

And you have an SD card image that includes our application!

Starting the application automatically at boot time

The next and almost final step for this blog post is to get our application automatically started at boot time. We can simply add a small shell script on the target in /etc/init.d/: the default Buildroot configuration for the init system will execute all scripts named Ssomething in /etc/init.d/. We'll add a file named package/qt-sensor-demo/S99qt-sensor-demo with these contents:

#!/bin/sh

DAEMON="qt-sensor-demo"
DAEMON_ARGS="-platform linuxfb"
PIDFILE="/var/run/qt-sensor-demo.pid"

start() {
	printf 'Starting %s: ' "$DAEMON"
	start-stop-daemon -b -m -S -q -p "$PIDFILE" -x "/usr/bin/$DAEMON" -- $DAEMON_ARGS
	status=$?
	if [ "$status" -eq 0 ]; then
		echo "OK"
	else
		echo "FAIL"
	fi
	return "$status"
}

stop () {
	printf 'Stopping %s: ' "$DAEMON"
	start-stop-daemon -K -q -p "$PIDFILE"
	status=$?
	if [ "$status" -eq 0 ]; then
		rm -f "$PIDFILE"
		echo "OK"
	else
		echo "FAIL"
	fi
	return "$status"
}

restart () {
	stop
	sleep 1
	start
}

case "$1" in
        start|stop|restart)
		"$1";;
	reload)
		# Restart, since there is no true "reload" feature.
		restart;;
        *)
                echo "Usage: $0 {start|stop|restart|reload}"
                exit 1
esac

This is the canonical init script used in Buildroot to start system daemons and services, and is modeled after the one in package/busybox/S01syslogd. It uses the start-stop-daemon program to start our application in the background.

Then, to get this init script installed, we need to adjust package/qt-sensor-demo/qt-sensor-demo.mk with the following additional lines:

define QT_SENSOR_DEMO_INSTALL_INIT_SYSV
        $(INSTALL) -D -m 755 package/qt-sensor-demo/S99qt-sensor-demo \
                $(TARGET_DIR)/etc/init.d/S99qt-sensor-demo
endef

This ensures that the init script gets installed in /etc/init.d/S99qt-sensor-demo as part of the build process of our qt-sensor-demo package. Note that an init script works fine if you're using the Busybox init implementation or the sysvinit init implementation (we're using the default Buildroot setup here, which uses the Busybox init implementation). If you want to use systemd as an init implementation, then a different setup is necessary.

With this done, you simply need to reinstall the application and regenerate the SD card image

$ make qt-sensor-demo-reinstall
$ make

You can now test your SD card image on you board, and you should see the application being started automatically, with the following messages at boot time

Starting dropbear sshd: OK
Starting qt-sensor-demo: OK

Welcome to Buildroot

Avoid unnecessary logging on the display panel

In our current setup, the kernel messages are being sent to both the serial port and the framebuffer console, which means they appear on the display panel. This is not very pretty, and we would like the display to remain black until the application starts, while keeping the kernel messages on the serial port for debugging purposes. Also, we would like the framebuffer console text cursor to not be displayed, to really have a fully black screen. To achieve this we will add two arguments on the Linux kernel command line:

  • console=ttySTM0,115200, which will tell the Linux kernel to only use the serial port as the console, and not all registered consoles, which would include the framebuffer console. This option will make sure the kernel messages are not displayed on the screen.
  • vt.global_cursor_default=0, which will tell the Linux kernel to not display any cursor on the framebuffer console.

So, to add those options, we simply modify board/stmicroelectronics/stm32mp157-dk/overlay/boot/extlinux/extlinux.conf in Buildroot as follows:

label stm32mp15-buildroot
  kernel /boot/zImage
  devicetree /boot/stm32mp157c-dk2.dtb
  append root=/dev/mmcblk0p4 rootwait console=ttySTM0,115200 vt.global_cursor_default=0

Of course, rebuild the SD card image with make, reflash and test the result on your STM32MP1 platform.

Conclusion

In this blog post, we have seen how to write a real (but admittedly very simple) Qt application, how to make it read and display sensor data, and how to integrate this application so that it gets started at boot time.

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

Stay tuned for our next blog post about factory flashing and OTA update!

Bootlin contributes Linux DRM driver for LogicBricks logiCVC-ML IP

LogicBricks is a vendor of numerous IP blocks, ranging from display controllers, audio controllers, 3D accelerators and many other specialized IP blocks. Most of these IP blocks are designed to work with the Xilinx Zynq 7000 system-on-chip, which includes an FPGA area. And indeed, because the Zynq 7000 does not have a display controller, one of Bootlin customers has selected the LogicBricks logiCVC-ML IP to provide display support for their Zynq 7000 design.

logiCVC-ML

LogiBricks provide one driver based on the framebuffer subsystem and another one based on the DRM subsystem, but none of these drivers are in the upstream Linux kernel. Bootlin engineer Paul Kocialkowski worked on a clean DRM driver for this IP block, and submitted the first version to the upstream Linux kernel. We already received some useful comments on the Device Tree binding for this IP block, which is pretty elaborate due to the number of aspects/features that can be tuned at IP synthesis time, and we will of course take into account those comments and send new iterations of the patch series until it gets merged.

In the e-mail containing the driver patch itself, Paul gives a summary of the IP features that are supported and tested, and those that re either untested or unsupported:

Introduces a driver for the LogiCVC display controller, a programmable
logic controller optimized for use in Xilinx Zynq-7000 SoCs and other
Xilinx FPGAs. The controller is mostly configured at logic synthesis
time so only a subset of configuration is left for the driver to
handle.

The following features are implemented and tested:
- LVDS 4-bit interface;
- RGB565 pixel formats;
- Multiple layers and hardware composition;
- Layer-wide alpha mode;

The following features are implemented but untested:
- Other RGB pixel formats;
- Layer framebuffer configuration for version 4;
- Lowest-layer used as background color;
- Per-pixel alpha mode.

The following features are not implemented:
- YUV pixel formats;
- DVI, LVDS 3-bit, ITU656 and camera link interfaces;
- External parallel input for layer;
- Color-keying;
- LUT-based alpha modes.

Additional implementation-specific notes:
- Panels are only enabled after the first page flip to avoid flashing a
  white screen.
- Depth used in context of the LogiCVC driver only counts color components
  to match the definition of the synthesis parameters.

Support is implemented for both version 3 and 4 of the controller.

With version 3, framebuffers are stored in a dedicated contiguous
memory area, with a base address hardcoded for each layer. This requires
using a dedicated CMA pool registered at the base address and tweaking a
few offset-related registers to try to use any buffer allocated from
the pool. This is done on a best-effort basis to have the hardware cope
with the DRM framebuffer allocation model and there is no guarantee
that each buffer allocated by GEM CMA can be used for any layer.
In particular, buffers allocated below the base address for a layer are
guaranteed not to be configurable for that layer. See the implementation of
logicvc_layer_buffer_find_setup for specifics.

Version 4 allows configuring each buffer address directly, which
guarantees that any buffer can be configured.

Improvements to Buildroot maintenance tooling

From mid-April to end of August, Victor Huesca, a student from the University of Toulouse joined Bootlin’s team for a 3.5 months internship. His internship was focused on the Buildroot project, and Victor’s mission was to improve various aspect of the tooling around Buildroot to help in the maintenance of this build system. In this blog post, we will present the different improvements and features implemented by Victor during his internship. This internship was funded by Bootlin, and entirely focused on contributing to the Buildroot project.

Notifications of new upstream versions of packages

Buildroot has over 2400 packages for a wide variety of software components, and it is a challenge to keep all of those packages updated with the latest releases from the upstream developers. Buildroot has a nice statistics page with all its packages, and in early 2019, your author added support for querying the release-monitoring.org service to find the latest upstream version of each package. This allowed our statistics page to show the current version in Buildroot and the latest version available upstream for all Builroot packages.

Victor improved this by implementing e-mail notifications to Buildroot developers about their package having new upstream releases available. Indeed, Buildroot has a DEVELOPERS file which associates the name and e-mail of Buildroot contributors/developers with the packages they take care of. So, what Victor did is:

  • Extend the pkg-stats script, which generates the statistics page, to not only generate a HTML output, but also a JSON output. A JSON output is obviously a lot more usable by other tools. Victor also improved the efficiency of this script in several ways, especially by parallelizing the requests made to release-monitoring.org.
  • Extend the daily-mail script, which until now was only sending autobuild results, to also send notifications about packages that are not up to date with their latest upstream version

The notifications are sent only once a week, both individually to the developers, and globally on the mailing list. They are grouped in a single e-mail with the existing autobuild results notifications, to minimize the amount of e-mails received by developers. You can see an example of such a notification in this e-mail, with a small excerpt below:

Packages having a newer version
===============================

             name              | found by |        link to release-monitoring.org        |   version    |   upstream   | orph?
-------------------------------+----------+----------------------------------------------+--------------+--------------+-------
                           4th |  DISTRO  | https://release-monitoring.org/project/20471 | 3.62.5       | 3625         |     
                        acpica |  DISTRO  | https://release-monitoring.org/project/00018 | 20190703     | 20190816     |     
                         acpid |  DISTRO  | https://release-monitoring.org/project/00019 | 2.0.30       | 2.0.32       | ORPH
                       acsccid |  DISTRO  | https://release-monitoring.org/project/15661 | 1.1.4        | 1.1.7        |     
            adwaita-icon-theme |  DISTRO  | https://release-monitoring.org/project/13117 | 3.22.0       | 3.33.92      |     
                       aespipe |  GUESS   | https://release-monitoring.org/project/21320 | 2.4d         | 2.4e         | ORPH
                 android-tools |  GUESS   | https://release-monitoring.org/project/13989 | 4.2.2+git... | 10.0.0_r2    |     
                      argparse |  GUESS   | https://release-monitoring.org/project/02100 | 0.7.0-1      | 1.0.10       |     
                         argus |  GUESS   | https://release-monitoring.org/project/00107 | 3.0.8        | 3.0.8.2      | ORPH
                     armadillo |  GUESS   | https://release-monitoring.org/project/07006 | 7.900.1      | 9.700.2      |     
                   at-spi2-atk |  GUESS   | https://release-monitoring.org/project/07840 | 2.26.2       | 2.33.92      |     
                  at-spi2-core |  GUESS   | https://release-monitoring.org/project/07841 | 2.28.0       | 2.33.92      |     
                         atkmm |  GUESS   | https://release-monitoring.org/project/07962 | 2.24.2       | 2.29.1       |     
                      automake |  DISTRO  | https://release-monitoring.org/project/00144 | 1.15.1       | 1.16.1       | ORPH
[...]

As part of this work, Victor also improved the matching of versions between the Buildroot package versions and the upstream versions. Indeed, for many packages, Buildroot used to use the full Git tag name as the version (for example v1.3), while release-monitoring.org removes any prefix and keeps only 1.3.

As of today, not all Buildroot packages match with a project known by release-monitoring.org, either because release-monitoring.org doesn’t know the project, or because the name is slightly different, but we are improving this progressively (the name mismatch can be handled by creating a mapping on release-monitoring.org, thanks to the concept of distribution they have).

The work of Victor has already proven to be very useful: a number of infrequent contributors suddenly started taking care of the packages they had contributed a long time ago and perhaps forgotten since then, which is very good.

Notifications of defconfig and runtime test failures

Buildroot provides a number of defconfig files, which are example Buildroot configuration for a wide range of hardware platforms (Raspberry Pi, BeagleBone, Qemu emulated machines, NXP or Microchip evaluation boards, and more). These defconfigs offers a very simple way for users to get a minimal Buildroot system up and running on those hardware platforms, making them a great starting point. Of course, to make them useful, they have to build properly, and we regularly build them using Gitlab CI to ensure they continue to build.

Buildroot also has runtime tests, which were initially introduced in the project by your author back in 2017. Those runtime tests are test cases that will each build a specific well-defined Buildroot configuration, boot it under Qemu, and verify that everything works properly. For example, the filesystem test cases will each make a Buildroot build with a specific filesystem image format selected, and boot the result under Qemu, to make sure that the filesystem image is correct and working. We also have a significant number of test cases for Perl or Python modules, which simply build the Perl or Python interpreter with a collection of modules, boot under Qemu, and verify that those modules can be loaded/imported. Just like the defconfigs, these runtime tests are already tested on a regular basis using Gitlab CI, to detect and fix any regression.

However, the results of those tests in Gitlab CI (and especially failures) were not notified to the Buildroot community in a meaningful way. This is where Victor filled in the gap, by adding the appropriate notifications.

He further extended the daily-mail script so that using the Gitlab CI API, the latest Gitlab CI pipelines for the Buildroot project are retrieved, the defconfig and runtime test failures are identified, and the appropriate Buildroot developers and contributors are notified. Indeed, just like packages are referenced in Buildroot’s DEVELOPERS file, the defconfigs and runtime tests are also referenced. The daily-mail script will notify individual developers about the defconfig and runtime tests they take care of, and it will also globally notify the mailing list about all defconfig and runtime test failures.

An example output, visible in this notification e-mail is:


Detail of defconfig failures
----------------------------

                        defconfig |                        link to the job                        | orph?
----------------------------------+---------------------------------------------------------------+------
                amarula_a64_relic |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126211  |      
               arcturus_ucls1012a |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126214  |      
                        bananapro |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126246  |      
                   beaglebone_qt5 |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126249  | ORPH 
        engicam_imx6qdl_icore_qt5 |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126255  |      
                 imx6-sabresd_qt5 |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126282  |      
                       imx6ulpico |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126286  |      
                        imx7dpico |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126288  |      
                    licheepi_zero |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126292  |      
               linksprite_pcduino |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126293  |      
                    orangepi_lite |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126321  |      
                   orangepi_lite2 |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126320  |      
                 orangepi_pc_plus |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126326  |      
                    orangepi_zero |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126331  |      
              orangepi_zero_plus2 |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126332  |      
                   pc_x86_64_bios |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126334  |      
                    pc_x86_64_efi |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126335  |      
               raspberrypi3_qt5we |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126382  | ORPH 
                            warp7 |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126413  |      
                        warpboard |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126414  | ORPH 


Detail of runtime-test failures
-------------------------------

             runtime-test |                        link to the job                        | orph?
--------------------------+---------------------------------------------------------------+------
...ystemSystemdRoIfupdown |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126471  | ORPH 
...ystemSystemdRoNetworkd |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126473  | ORPH 
...nitSystemSystemdRwFull |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126475  | ORPH 
...ystemSystemdRwIfupdown |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126476  | ORPH 
...ystemSystemdRwNetworkd |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126477  | ORPH 
             TestSyslogNg |  https://gitlab.com/buildroot.org/buildroot/-/jobs/289126581  | ORPH 

Overall, thanks to Victor’s work, a single e-mail now reports autobuilder failures, the need to update packages to a newer upstream versions, defconfig build failures and runtime tests failures. This is a really good improvement in the tooling of the Buildroot community!

Buildroot autobuilder search capabilities

Buildroot provides over 2400 packages, and many of them have configurable features and optional dependencies. This creates a massive amount of possible configuration combinations, making it impossible to test all of them. To make sure as many Buildroot configurations build properly, the project has been running for many years the Buildroot autobuilders. A number of build machines build random Buildroot configurations 24/7, and report their results to autobuild.buildroot.org. This helps tremendously the Buildroot developers and maintainers to detect the problematic packages and configurations.

For a long time, the autobuild.buildroot.org allowed to filter build results by architecture, C library, failing package, and a few other criterias. Such filtering is very often useful to understand when a package started failing to build, and in which situations it fails to build.

autobuild.buildroot.org was also collecting in a database all the configuration symbols (the BR2_something symbols) for every Buildroot configuration that was built. However, the size of this database made any query excessively long, so we were not able to make use of it so far. This was annoying because it would sometimes be useful to ask could you tell me which configuration had BR2_PACKAGE_STRACE=y and built successfully ?.

That’s where Victor jumped in:

  • He improved the database by adding the appropriate indexes and found a reasonably efficient way to query the database when configuration symbols are involved
  • He added filtering per configuration symbol, which can be done using GET arguments on the main autobuild.buildroot.org page: http://autobuild.buildroot.org/?status=OK&symbols[BR2_PACKAGE_STRACE]=y will show the builds that had BR2_PACKAGE_STRACE=y and that ended successfully. Multiple symbols[name]=value arguments can be passed.
  • Since writing such queries by hand was a bit cumbersome, Victor also added a new search page.
Autobuild search page
Autobuild search page

This work will be very useful in the future to analyze build failures and understand better in which situations they are happening.

Conclusion

Victor’s internship has been very productive in improving the tooling used by the Buildroot community to maintain the project. All the work done by Victor has been merged, is in production, and is already showing some useful results.

Bootlin at the X.Org Developers Conference 2019

Every year, the X.Org community organizes the X.Org Developers Conference, the main conference to discuss graphics support in Linux. Despite the name, the conference is no longer restricted to X.Org topics, but also covers Wayland, Mesa3D and many other topics.

XDC 2019

The 2019 edition will take place on October 2-4 in Montréal, Canada, and the schedule of this event is already available.

Bootlin engineer Paul Kocialkowski will participate to this conference. Paul is Bootlin’s display and graphics expert, he is one of the developer of the Allwinner VPU support in Linux and has made several contributions to the Allwinner DRM driver, as well as worked on the RaspberryPi graphics controller automated testing. Participating to this conference allows us to stay up-to-date with the latest developments in the Linux graphics community.

If you’re attending the conference, do not hesitate to get in touch with Paul!

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

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 
#include 

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!

Linux 5.2 released, Bootlin contributions inside

Penguin from Mylène Josserand
Drawing from Mylène Josserand, based on a picture from Samuel Blanc (https://commons.wikimedia.org/wiki/File:Manchot_royal_-_King_Penguin.jpg)
Linux 5.2 was released not long ago, and as usual it is time to have a look at the contributions that Bootlin engineers did to this kernel release. But before that, if you’re interested in having an overview of the major new features of this kernel, we recommend reading LWN articles covering the 5.2 merge window period: part 1 and part 2. KernelNewbies also has an interesting page about Linux 5.2.

With 350 patches contributed by Bootlin engineers, Bootlin is the 10th contributing company by number of commits for the Linux 5.2 release. With 186 commits, Alexandre Belloni is the second contributor to this release by number of commits (see also the detailed statistics from LWN.net).

Here are the highlights of our contributions:

  • In the RTC subsystem
    • Alexandre Belloni (maintainer of the subsystem) was very active in fixing a large number of drivers to use more modern APIs/mechanisms of the RTC core: use of rtc_time64_to_tm()/rtc_tm_to_time64(), use of devm_rtc_allocate_device(), etc.
    • Alexandre Belloni improved the pcf85063 driver with new features: alarm support, Micro Crystal RV8263 support, nvram support, offset correction support, RTC_VL_READ/RTC_VL_CLR support
  • In the support for the Microchip MPU (formerly Atmel) platforms
    • Alexandre Belloni continued the rework of the AT91/SAMA5 clock drivers, adding support for the new sam9x60 SoC using the new clock driver logic and DT binding
    • Alexandre Belloni dropped support for the AVR32 architecture and platform_data probing in the atmel_lcdfb driver, since AVR32 has been removed from the kernel
    • Alexandre Belloni also worked on the clocksource support on Microchip platforms. First, he made the TCB clocksource driver independent from the common atmel_tclib driver, so that TCB timers can be registered early enough in the kernel boot process. Then, he introduced the possibility of selecting the clocksource between TCB and PIT on Microchip platforms that support both. And finally modified the atmel_tclib driver to no longer use TCB timers that are used by the TCB clocksource driver registered early at boot time. The main motivation for this work is that some of the latest Microchip SoCs only have TCB timers available, and no longer the PIT, so we really needed to be able to use TCB as clocksource.
  • In the support for Marvell platforms
    • Maxime Chevallier contributed a number of patches to the mvpp2 Ethernet controller driver, adding support for classification offloading. This allows the HW to directly classify received network packets according to various details of their header, and steer them to the appropriate RX queue.
    • Maxime Chevallier contributed 2500Base-X support for the mvneta Ethernet controller driver.
  • In the support for Allwinner platforms
    • Miquèl Raynal contributed DMA support in the sunxi NAND controller driver, for the A23 and A33 SoC, significantly improving NAND read/write performance.
    • Paul Kocialkowski contributed a new Device Tree to support the RerVision H3-DVK platform
    • Quentin Schulz added support for the AXP813 PMIC in the axp20x_usb_power driver, and made a few other related contributions
    • Maxime Ripard made several improvements in the Allwinner DRM sun4i driver, especially improving the support for MIPI DSI display panels: the driver will now support a wider range of panels connected over DSI.
    • Maxime Ripard contributed many new DT bindings written in YAML, and as part of this, fixed a number of issues in the Allwinner platform Device Trees.
  • In the support for the NXP LPC3250 platform
    • Alexandre Belloni fixed a number of issues in the lpc32xx USB gadget driver, and added support for using the stotg04 USB PHY
    • Grégory Clement made a few cleanups in the lpc32xx IIO ADC driver, and added support for providing a scaled value and not just the raw ADC value
  • In the support for RaspberryPi platforms
    • Paul Kocialkowski finalized some work initially started by Boris Brezillon, to be able to track and detect the memory bandwidth used by the configuration of the RaspberryPi display engine, and prevent from making configurations that exhaust the available memory bandwidth.
  • In the MTD subsystem
    • Miquèl Raynal has become one of the co-maintainers of the MTD subsystem. He was already a co-maintainer for the NAND subsystem, which sits “under” MTD in the maintainer tree.
  • In the GPIO subsystem
    • Alexandre Belloni added support for the PCA6416 I2C GPIO expander to the existing gpio-pca953x driver
  • In the networking subsystem
    • Antoine Ténart implemented suspend/resume support in the Marvell 10G PHY driver, implemented a work-around for a HW errata in the Micrel PHY driver, and fixed an initialization issue in the same Micrel PHY driver.
  • In the IIO subsystem
    • Grégory Clement contributed a brand new IIO driver for the TI ADS8344 ADC chip, connected over SPI
  • Other topics
    • Maxime Ripard contributed some changes to the Device Tree address translation logic. Indeed, the Allwinner display engine does its DMA operations through a bus different from the bus used by the CPU core to access the display engine registers, and due to this, it sees the RAM at a different address. Thanks to the changes from Maxime, the DMA address translation now follows the interconnects property if it exists, instead of assuming the parent Device Tree node should be used.

Also, several of the engineers working at Bootlin are maintainers of specific parts of the kernel, and as part of their maintainer work, they review and merge patches from other contributors, before sending them to another upstream maintainer.

  • Miquèl Raynal, as one of the maintainers of the NAND and MTD subsystems, reviewed and merged 83 patches from other contributors
  • Maxime Ripard, one of two co-maintainers of the support for Allwinner processors, reviewed and merged 68 patches from other contributors
  • Alexandre Belloni, as the maintainer of the RTC subsystem and co-maintainer of the support for Microchip MPU processors, reviewed and merged 38 patches from other contributors
  • Grégory Clement, as the co-maintainer of the support for Marvell EBU processors, reviewed and merged 10 patches from other contributors

And as usual, the details of all contributions, commit by commit:

Building a Linux system for the STM32MP1: enabling Qt5 for graphical applications

After showing how to build a minimal Linux system for the STM32MP157 platform, and how to connect and use an I2C based pressure/temperature/humidity sensor, we are now going to enable Qt5 and run some example graphical Qt5 applications. This is a necessary requirement before developing our own Qt5 application, which will be the topic of the next article in this series.

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

Display support in the Linux kernel

First of all, it is very important to distinguish the display controller from the GPU, as many people getting started with embedded systems and Linux tend to confuse both, while they are completely different things, both in terms of functionality and software support.

A display controller is an hardware block that takes the content of a memory area containing values representing the color of all pixels on your screen (a framebuffer) and sends it out to a display panel over hardware interfaces such as HDMI, DisplayPort, parallel interfaces or MIPI DSI. Some display controllers can take several framebuffers as input and compose them, potentially rescaling the size of some of them, and overlaying them with transparency. For example, some display controllers can take a framebuffer containing video frames, overlay another framebuffer that contains transparent pixels (to see the video) and some other pixels showing the UI of a video player, and another framebuffer overlayed on top of the two other ones that contains the mouse cursor. There is no 3D rendering or OpenGL operations involved: a display controller’s job is purely about taking framebuffer(s) contents and showing them on a display.

A GPU (or Graphics Processing Unit) is a very flexible programmable hardware unit, which among other things can be used to implement the OpenGL 3D rendering API, but also other APIs such as OpenCL. OpenGL and a GPU allow to offload in hardware the complex calculations that need to be done to render a 3D scene into a framebuffer of pixels. But a GPU by itself is not responsible at all for displaying on a screen what was rendered: it is the responsibility of a display controller.

Some system-on-chips only have a display controller, and no GPU at all, so you can display contents on one or several display panels, but you cannot benefit from hardware acceleration for 3D rendering. There are also use-cases where GPUs are used, but never to render anything looking like a 3D scene: that is for example the use-case for the OpenCL API, which allows to leverage the processing power of GPUs for general purpose programming.

In Linux, both display controllers and GPUs are managed by a Linux kernel subsystem called DRM, for Direct Rendering Manager. The DRM drivers are located in drivers/gpu/drm in the Linux kernel source code. If the hardware also has a GPU in addition to the display controller, then the most significant part of handling the GPU is done in user-space libraries implementing OpenGL. In the open-source world, the de-facto standard OpenGL implementation is Mesa3D, which has support for a number of different GPUs. From a GPU driver perspective, the kernel mainly serves as a way for the user-space OpenGL library to allocate buffers and send commands to the GPU.

The STM32MP15 has both a display controller and a GPU, but in this blog post, we are only going to make use of the display controller. The Linux kernel DRM driver used for the display controller of the STM32MP15 is in drivers/gpu/drm/stm. The driver can be enabled using the CONFIG_DRM_STM kernel configuration option.

In addition, the display panel used on the STM32MP15 Discovery Kit is connected to the SoC using the MIPI DSI interface. The STM32MP15 SoC contains a DSI encoder hardware block from Synopsys, and a glue driver for the DSI encoder is available in drivers/gpu/drm/stm/dw_mipi_dsi-stm.c and can be enabled using the CONFIG_DRM_STM_DSI option.

It turns out that the kernel configuration file we’re using since our initial blog post does have both of these options enabled, as well as the option providing support for the specific DSI display panel used on the Discovery board:

CONFIG_DRM=y
CONFIG_DRM_STM=y
CONFIG_DRM_STM_DSI=y
CONFIG_DRM_PANEL_ORISETECH_OTM8009A=y

The user-space interface of DRM is detailed in its documentation. Typical applications do not use directly this interface, and instead rely on a display stack in user-space such as X.org or Wayland, or directly on a graphical toolkit like Qt.

In addition, the DRM subsystem implements a compatibility layer that emulates the Linux framebuffer user-space interface, as documented in framebuffer.txt. This allows to support older applications/libraries that don’t use DRM directly.

In our case, we are going to use the Qt graphical toolkit, and on embedded Linux systems, it has four main display backends: eglfs (which requires an OpenGL/EGL graphics stack), linuxfb (which uses a simple legacy framebuffer interface), wayland (for Wayland, obviously) and xcb (for X.org). To keep things simple for this series and blog post, and because we don’t require OpenGL support, we will use the linuxfb backend.

Touch panel support in the Linux kernel

The Linux kernel has a subsystem called input for all input devices, such as keyboards, mice, touchscreens, joysticks and more. Its user-space interface is described in details in the kernel documentation, and this interface is used by most display stacks in user-space. For example, Qt supports it through the backend called evdev.

In terms of hardware, the Discovery Kit display panel integrates a touch panel that uses a Focaltech FT6236 controller, connected over I2C. The Device Tree describes this touch panel device as follows:

&i2c1 {
        touchscreen@2a {
                compatible = "focaltech,ft6236";
                reg = <0x2a>;
                interrupts = <2 2>;
                interrupt-parent = <&gpiof>;
                interrupt-controller;
                touchscreen-size-x = <480>;
                touchscreen-size-y = <800>;
                status = "okay";
        };
};

The corresponding driver in the Linux kernel is drivers/input/touchscreen/edt-ft5x06.c, which can be enabled using the CONFIG_TOUCHSCREEN_EDT_FT5X06 kernel configuration option.

The input subsystem, its evdev interface, and the specific driver for our touch panel are all already enabled in the kernel configuration file we are using since our first blog post:

CONFIG_INPUT_EVDEV=y
CONFIG_INPUT_TOUCHSCREEN=y
CONFIG_TOUCHSCREEN_EDT_FT5X06=y

Basic testing of the display and touch panel

To test the touch panel and display, we can start with very simple tools instead of using directly a complex graphical stack. The tools we recommend to use are:

  • The evtest program from the project of the same name. It allows to dump input events from any input device.
  • The modetest program that comes from the libdrm project. It allows to test a display by showing some pre-defined pictures.

Let’s add those two software components in our Buildroot configuration: go to menuconfig, and enable the BR2_PACKAGE_EVTEST, BR2_PACKAGE_LIBDRM and BR2_PACKAGE_LIBDRM_INSTALL_TESTS options. Restart the build of the Buildroot system by running make, and once the build has completed, write the new SD card image to your SD card.

On the system, let’s have a look at available input device (output edited to fit in the blog post):

# ls -l /sys/class/input/
event0 -> ...platform/soc/40012000.i2c/i2c-0/0-002a/input/input0/event0
event1 -> ...platform/soc/5c002000.i2c/i2c-2/2-0033/5c002000.i2c:stpmic@33:onkey/input/input2/event1
input0 -> ...platform/soc/40012000.i2c/i2c-0/0-002a/input/input0
input2 -> ...platform/soc/5c002000.i2c/i2c-2/2-0033/5c002000.i2c:stpmic@33:onkey/input/input2

So input0 is our touch panel (connected on I2C bus 0, at address 0x2A), and event0 is its evdev user-space interface. This evdev interface is accessible through the /dev/input/event0 device file:

# ls -l /dev/input/event0 
crw-------    1 root     root       13,  64 Jan  1  1970 /dev/input/event0

Now we can run evtest. With no arguments, it shows the list of available input devices, their name, and allows use to chose the one we would like to test:

# evtest 
No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0:	generic ft5x06 (11)
/dev/input/event1:	pmic_onkey
Select the device event number [0-1]: 0

Once evtest is running, press the touch panel, and you will see events reported like this:

Event: time 946685148.736402, type 3 (EV_ABS), code 57 (ABS_MT_TRACKING_ID), value 0
Event: time 946685148.736402, type 3 (EV_ABS), code 53 (ABS_MT_POSITION_X), value 259
Event: time 946685148.736402, type 3 (EV_ABS), code 54 (ABS_MT_POSITION_Y), value 428
Event: time 946685148.736402, type 1 (EV_KEY), code 330 (BTN_TOUCH), value 1
Event: time 946685148.736402, type 3 (EV_ABS), code 0 (ABS_X), value 259
Event: time 946685148.736402, type 3 (EV_ABS), code 1 (ABS_Y), value 428

These events show the coordinates of the press, and the actual press event (BTN_TOUCH).

With the touch panel working, let’s move on to the display. In fact, you know the display is already working: the fbcon kernel driver provides a console over the framebuffer, which is why you are seeing the Linux kernel messages on the screen. Nevertheless, let’s use the modetest program from libdrm. Without any argument, it just prints out some details about the available display hardware. First the encoders:

Encoders:
id	crtc	type	possible crtcs	possible clones	
28	0	DPI	0x00000001	0x00000000
30	33	DSI	0x00000001	0x00000000

So the display controller has two encoders: one with a DPI interface (i.e parallel RGB interface) and one with a (MIPI) DSI interface.

Then, we have the list of connectors:

Connectors:
id	encoder	status		name		size (mm)	modes	encoders
29	0	disconnected	HDMI-A-1       	0x0		0	28
[..]
31	30	connected	DSI-1          	52x86		1	30
  modes:
	name refresh (Hz) hdisp hss hse htot vdisp vss vse vtot)
  480x800 50 480 578 610 708 800 815 825 839 29700 flags: ; type: preferred, driver

We have a HDMI connector, which can be used with the encoder of id 28, that is the DPI encoder. Indeed, the RGB parallel interface of the STM32 processor is fed on the board into an HDMI transceiver, that goes out with HDMI signals on connector CN9. So from the point of view of the SoC, it is a parallel RGB interface, but thanks to the HDMI transceiver on the Discovery board, it is in fact usable as an HDMI connector.

The second connector is the DSI connector, which can be used with encoder of id 30, i.e the DSI encoder, which makes sense.

So let’s ask modetest to display its test picture on the DSI connector, which has id 31. The DSI panel resolution is 480×800, so we’ll use the following command:

# modetest -s 31:480x800

And voilà:

modetest on the STM32MP15 Discovery board

Now if we plug an HDMI screen to the HDMI connector, the modetest output about connector 29 changes as the connector is no longer disconnected:

Connectors:
id	encoder	status		name		size (mm)	modes	encoders
29	0	connected	HDMI-A-1       	520x290		11	28
  modes:
	name refresh (Hz) hdisp hss hse htot vdisp vss vse vtot)
  1280x720 60 1280 1390 1430 1650 720 725 730 750 74250 flags: phsync, pvsync; type: driver
  1280x720 60 1280 1390 1430 1650 720 725 730 750 74250 flags: phsync, pvsync; type: driver
  1280x720 50 1280 1720 1760 1980 720 725 730 750 74250 flags: phsync, pvsync; type: driver
  [...]

So we can ask modetest to display a 1280×720 picture on the HDMI screen:

# modetest -s 29:1280x720

Enabling Qt5 support in Buildroot

Now that we have successfully tested the display and touchscreen, it is time to move on and use a powerful graphical toolkit for embedded Linux systems: Qt. Qt is already packaged in Buildroot, and therefore very easy to add to our system, including some examples. Simply enable the following options in your Buildroot configuration:

  • BR2_PACKAGE_QT5, which enables Qt as a whole, and automatically selects the core Qt module called qt5base
  • BR2_PACKAGE_QT5BASE_GUI, which enables GUI support in qt5base. The linuxfb backend is automatically selected, but provided the appropriate dependencies are enabled, other display backends can be enabled as well. In our case, we’ll use the linuxfb backend so the default selection will work for us.
  • BR2_PACKAGE_QT5BASE_WIDGETS, which enables the Qt5 Widget library, which allows to easily write graphical applications with buttons, text boxes, drop down lists and other familiar graphical widgets
  • BR2_PACKAGE_QT5BASE_EXAMPLES, to enable the example Qt5 applications
  • BR2_PACKAGE_QT5BASE_FONTCONFIG, to enable the fontconfig support in Qt. This allows Qt to discover the fonts available on our system to render text.
  • BR2_PACKAGE_DEJAVU, which will provide one font to render text. Without this, Qt applications would run, but no text would be rendered

Once these options are enabled, restart the build with make and rewrite the new image to your SD card.

Test Qt5 application

All the Qt examples are installed in /usr/lib/qt/examples/, so you can try all of them. Let’s start with the analogclock for example:

# /usr/lib/qt/examples/gui/analogclock/analogclock -platform linuxfb

Qt Analog Clock

Then, another one which allows to test the touch panel:

# /usr/lib/qt/examples/widgets/scroller/graphicsview/graphicsview -platform linuxfb

Qt Graphics View

And a more “complete” and useful application, which as you can see does not fit very well on a 480×800 screen in portrait mode, but also allows to use the touchscreen:

# /usr/lib/qt/examples/widgets/widgets/calculator/calculator -platform linuxfb

Qt Calculator

Filesystem size

To conclude this article, let’s have a look at the size of our filesystem size. After a completely clean build (make clean all), Buildroot can generate a nice graph of the filesystem size using make graph-size. In our case, it generates the following graph:

Filesystem size graph

So the overall filesystem size is 58.7 MB, on which 34.4 MB come from Qt. But our Qt is compiled with examples, and the examples take up 16.7 MB (this is not visible on the graph, it was calculated by looking at the size of /usr/lib/qt/examples/) on the target). Also, the dejavu font package is quite large, with 9.6 MB.

Conclusion

You can find the exact Buildroot source code used to reproduce the system used in this article in the branch at 2019.02/stm32mp157-dk-blog-3.

In this article, we learned about how Linux manages display and input devices and how to test them with simple applications such as modetest and evtest. Then we looked at how to add the Qt library to our Buildroot configuration, and verify it is working using Qt example applications.

Our next blog post will cover how to build a real Qt application!

New training course: displaying and rendering graphics with Linux

Training – Displaying and rendering graphics with LinuxOver the past years, Bootlin engineers have accumulated significant knowledge and experience on the topic of display and graphics support in the Linux kernel, and based on this knowledge and experience, we have created a new training course: Displaying and rendering graphics with Linux.

This course targets engineers who need a detailed level of understanding of graphics concepts, graphics hardware and how the graphics stack is organized with Linux. The main topics covered are:

  • Image and color representation
  • Basic drawing
  • Basic and advanced operations
  • Hardware aspects overview
  • Hardware for display
  • Hardware for rendering
  • Memory aspects
  • Performance aspects
  • Software aspects overview
  • Kernel components in Linux
  • Userspace components with Linux

See the detailed agenda for all the details.

This course has a duration of two days, and is composed of lectures and demonstrations made by the trainer. It does not include practical labs, unlike all our other training courses. The development of the training materials is in progress, and they will be released under the same Creative Commons license that we use for all our training materials, once the first course has taken place (September 2019).

We can deliver this course on-site, anywhere in the world, please contact us for more details.

Feedback from the SiFive Tech Symposium in Grenoble

SiFive LogoSiFive is a semi-conductor company that produces chips based on the RISC-V architecture. On May 15th, they organized a Technical Symposium in Grenoble on May 15th and we took the opportunity to attend, as the agenda looked interesting.

It was especially nice having Krste Asanovic present many of the topics, wearing different hats (RISC-V Foundation Chairman of the Board and SiFive Co-Founder and Chief Architect). The RISC-V architecture and its history and use cases were presented. One of the main benefit of having a brand new ISA (instruction set architecture), Asanovic said, is that it doesn’t have to handle legacy instructions and compatibility. Moreover, RISC-V is a frozen ISA, the base instructions are frozen and optional extensions which have been approved are also frozen. Finally, the ISA is open and anybody can implement a CPU core. During the presentation, the RISC-V ISA was (obviously) favorably compared to competing ISAs, mainly ARM.

Another interesting topic was the presentation of SiFive’s business model. They want anyone, including small companies to be able to design an SoC fitting their particular product, instead of having to choose from a set of more general purpose SoC. This can be done by using an existing SiFive RISC-V core or by customizing one. SiFive then offers a library of IPs that can be added on the SoC and third party IPs are available through their Designshare program. They handle NDA, contract and licensing and will collect non recurring engineering costs and royalties once the SoC is mass produced but not during the prototyping phase. They first provide virtualized chips and then sample chips. For the core, they also provide RTL that can run on FPGAs. For mass production, SiFive partnered with TSMC and their customers can benefit from their process (down to 7nm).

The most relevant topic for us was the software ecosystem. There is a very nice will to get code upstream and this is the case for GCC, binutils, newlib, gdb, glibc, qemu. Clang/LLVM is coming up. Regarding the Linux kernel port, it still requires some work as the core architecture support is there but no devices drivers or device tree support yet. There is however a fully working vendor tree. FreeBSD seems to be in the same state.

Most of the remaining time was focused on the design and customization tool available here.
SiFive Chip Designer

SiFive also Sponsored Linus Sebastian (from Linus Tech Tips) for a video:

To conclude, it was an very interesting day. At Bootlin, we are delighted to see architecture designers and silicon vendors actively pushing software support upstream and we are looking forward to work on RISC-V platforms.

Building a Linux system for the STM32MP1: connecting an I2C sensor

After showing how to build and run a minimal Linux system for the STM32MP157 Discovery board in a previous blog post, we are now going to see how to connect an I2C sensor, adjust the Device Tree to enable the I2C bus and I2C device, and how to adjust the kernel configuration to enable the appropriate kernel driver.

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

Choosing an I2C sensor

BME280 breakout boardFor this project, we wanted an I2C sensor that was at least capable of measuring the temperature, so we simply started by searching i2c temperature sensor on Amazon. After a bit of research, we found that the BME280 sensor from Bosch was available on several inexpensive break-out boards, and it already had a device driver in the upstream Linux kernel. When choosing hardware, it is always important to check whether it is already supported or not in the upstream Linux kernel. Having a driver already integrated in the upstream Linux kernel has a number of advantages:

  • The driver is readily available, you don’t have to integrate a vendor-provided driver, with all the possible integration issues
  • The driver has been reviewed by the Linux kernel maintainers, so you can be pretty confident of the code quality
  • The driver is using standard Linux interfaces, and not some vendor-specific one
  • The driver will be maintained in the long run by the kernel community, so you can continue to update your Linux kernel to benefit from security updates, bug fixes and new features

In addition, it also turns out that the BME280 sensor not only provides temperature sensing, but also pressure and humidity, which makes it even more interesting.

Among the numerous inexpensive BME280 break-out boards, we have chosen specifically this one, but plenty of others are available. The following details will work with any other BME280-based break-out board.

Connecting the I2C sensor

From a connectivity point of view, our I2C sensor is pretty simple: a VIN signal for power, a GND signal for ground, a SCL for the I2C clock and a SDA for the I2C data.

To understand how to connect this sensor to the Discovery board, we need to start with the board user manual.

The Discovery board has two main expansion connectors: CN2 and the Arduino connectors.

Connector CN2

Connector CN2 is a 40-pin male header on the front side of the board:

CN2 connector

Section 7.17 of the board user manual documents the pin-out of this connector. There is one I2C bus available, through the I2C1_SDA (pin 27) and I2C1_SCL (pin 28) signals.

CN2 I2C1

Arduino connectors

Connectors CN13, CN14, CN16, CN17 are female connectors on the back side of the board. They are compatible in pin-out and form-factor with the Arduino connector:

Arduino connectors

Section 7.16 of the board user manual documents the pin-out for these connectors. There is one I2C bus available as well in CN13, through the I2C5_SDA (pin 9) and I2C5_SCL (pin 10) signals.

CN13

Choosing the connector

According to the block diagram in Figure 3 of the board user manual, the I2C1 bus is already used to connect the touchscreen, the USB hub, the audio codec and the HDMI transceiver. However, I2C5 doesn’t seem to be used at all. In addition, with the screen mounted on the Discovery board, the CN2 connector is beneath the screen, which makes it a bit more difficult to use than the Arduino connectors on the back side.

We will therefore use the I2C5 bus, through the Arduino connector CN13. Pin 9 will be used to connect the data signal of our sensor, and pin 10 will be used to connect the clock signal of our sensor.

Finalizing the connectivity

We still have to find out how to connect the VIN and GND pins. According to the BME280 datasheet, VDDmain supply voltage range: 1.71V to 3.6V. The Arduino connector CN16 provides either 3.3V or 5V, so we’ll chose 3.3V (pin 4). And this connector also has multiple ground pins, among which we will chose pin 6.

Overall, this gives us the following connections:

Sensor signal Arduino connector Pin
VIN CN16 pin 4
GND CN16 pin 6
SDA CN13 pin 9
SCL CN13 pin 10

Here are a few pictures of the setup. First, on the sensor side, we have a purple wire for VIN, a grey wire for GND, a white wire for SCL and a black wire for SDA:

I2C sensor connection

On the board side, we can see the purple wire (VIN) going to pin 4 of CN16, the grey wire (GND) going to pin 6 of CN16, the white wire (SCL) going to pin 10 of CN13 and the black wire (SDA) going to pin 9 of CN13.

I2C sensor connected to the board

With this we’re now all set in terms of hardware setup, let’s move on to enabling the I2C bus in Linux!

Enabling the I2C bus

An introduction to the Device Tree

In order to enable the I2C bus, we’ll need to modify the Device Tree, so we’ll first need to give a few details about what Device Tree is. If you read again our previous blog post in this series, we already mentioned the Device Tree. As part of the Buildroot build process, a file called stm32mp157c-dk2.dtb is produced, and this file is used at boot time by the Linux kernel: it is the Device Tree.

On most embedded architectures, devices are connected using buses that do not provide any dynamic enumeration capabilities. While buses like USB or PCI provide such capabilities, popular buses used on embedded architectures like memory-mapped buses, I2C, SPI and several others do not allow the operating system to ask the hardware: what peripherals are connected ? what are their characteristics ?. The operating system needs to know which devices are available and what their characteristics are. This is where the Device Tree comes into play: it is a data structure that describes in the form of a tree all the devices that we have in our hardware platform, so that the Linux kernel knows the topology of the hardware.

On ARM platforms, each particular board is described by its own Device Tree file. In our case, the STM32MP157 Discovery Kit 2 is described by the Device Tree file arch/arm/boot/dts/stm32mp157c-dk2.dts in the Linux kernel source code. This human-readable source file, with a .dts extension, is compiled during the Linux kernel build process into a machine-readable binary file, with a .dtb extension.

This stm32mp157c-dk2.dts describes the hardware of our Discovery Kit 2 platform. In fact, it only describes what is specific to the Discovery Kit 2: the display panel, the touchscreen, the WiFi and Bluetooth chip. Everything else is common with the Discovery Kit 1 platform, which is why the stm32mp157c-dk2.dts file includes the arm/boot/dts/stm32mp157a-dk1.dts file. Indeed, stm32mp157a-dk1.dts describes the hardware on the Discovery Kit 1, which is the same as the Discovery Kit 2, without the display, touchscreen and WiFi/Bluetooth chip.

In turn, the stm32mp157a-dk1.dts includes three other Device Tree files:

At this point, we won’t give much more generic details about the Device Tree, as it’s an entire topic on its own. For additional details, you could check the Device Tree for Dummies presentation from your author (slides, video) or the devicetree.org web site.

I2C controllers in the Device Tree

Zooming in to the topic of I2C, we can see that arm/boot/dts/stm32mp157c.dtsi describes 6 I2C controllers through six different nodes in the Device Tree:

  • i2c1: i2c@40012000
  • i2c2: i2c@40013000
  • i2c3: i2c@40014000
  • i2c4: i2c@5c002000
  • i2c5: i2c@40015000
  • i2c6: i2c@5c009000

This list of six I2C controllers nice matches the list of I2C controllers in the STM32MP157 datasheet, and their base address in the memory map, section 2.5.2:

I2C1I2C2I2C3I2C4I2C5I2C6

In the file arm/boot/dts/stm32mp157a-dk1.dts, we can see that the I2C1 bus is enabled, and that a cs42l51 audio codec (I2C address 0x4a) and a sii9022 HDMI transceiver (I2C address 0x39) are connected to it:

&i2c1 {
	status = "okay";

	cs42l51: cs42l51@4a {
		compatible = "cirrus,cs42l51";
		reg = <0x4a>;
	};

	hdmi-transmitter@39 {
		compatible = "sil,sii9022";
		reg = <0x39>;
	};
};

Also, on the I2C4 bus, we can see the USB-C controller (I2C address 0x28) and the PMIC (I2C address 0x33):

&i2c4 {
	status = "okay";

	typec: stusb1600@28 {
		compatible = "st,stusb1600";
		reg = <0x28>;
	};

	pmic: stpmic@33 {
		compatible = "st,stpmic1";
		reg = <0x33>;
	};
};

So, to enable our I2C5 bus, we will simply need to add:

&i2c5 {
	status = "okay";
	clock-frequency = <100000>;
	pinctrl-names = "default", "sleep";
	pinctrl-0 = <&i2c5_pins_a>;
	pinctrl-1 = <&i2c5_pins_sleep_a>;
};

to enable the bus. This piece of code adds the following Device Tree properties to the I2C5 Device Tree node:

  • status = "okay" which simply tells the Linux kernel: I really intend to use this device, so please enable whatever driver is needed to use this device
  • clock-frequency = <100000> tells Linux at which frequency we want to operate the I2C bus: in this case, 100 kHz
  • The pinctrl properties configure the pin muxing, so that the pins are configured in the I2C function when the system is running (the default state) and into a different state to preserve power when the system is in suspend to RAM (sleep state). Both i2c5_pins_a and i2c5_pins_sleep_a are already defined in arch/arm/boot/dts/stm32mp157-pinctrl.dtsi.

For now, this doesn’t describe any device on the bus, but should be sufficient to have the bus enabled in Linux. The question now is how to make this modification in our Device Tree in the proper way ?

Changing the Linux kernel source code

When Buildroot builds each package, it extracts its source code in output/build/<package>-<version>, so the source code of our Linux kernel has been extracted in output/build/linux-custom/. One could therefore be tempted to make his code changes directory in output/build/linux-custom/, but this has a number of major drawbacks:

  1. output/build/linux-custom/ is not under version control: it is not part of a Linux kernel Git repository, so you can’t version control your changes, which is really not great
  2. output/build/linux-custom/ is a temporary folder: if you do a make clean in Buildroot, this folder will be entirely removed, and re-created during the next Buildroot build

So, while doing a change directly in output/build/linux-custom/ is perfectly fine for quick/temporary changes, it’s not a good option to make changes that will be permanent.

To do this in a proper way, we will use a feature of Buildroot called pkg_OVERRIDE_SRCDIR, which is documented in section 8.12.6 Using Buildroot during development of the Buildroot manual. This feature allows to tell Buildroot: for a given package, please don’t download it from the usual location, but instead take the source code from a specific location on my system. This specific location will of course be under version control, and located outside of Buildroot, which allows to solve the two issues mentioned above.

So, let’s get set this up for the Linux kernel source code:

  1. Start in the parent folder of Buildroot, so that the Linux kernel source code ends up being side-by-side with Buildroot
  2. Clone the official upstream Linux kernel repository. Even though we could directly clone the STMicro Linux kernel repository, your author always finds it nicer to have the origin Git remote set up to the official upstream Git repository.
    git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
    
  3. Move inside this Git repository
    cd linux/
    
  4. Add the STMicro Linux kernel repository as a remote:
    git remote add stmicro https://github.com/STMicroelectronics/linux.git
    
  5. Fetch all the changes from the STMicro Linux kernel repository:
    git fetch stmicro
    
  6. Create a new branch, called bme280, based on the tag v4.19-stm32mp-r1.2. This tag is the one used by our Buildroot configuration as the version of the Linux kernel. The following command also moves to this new branch as the same time:
    git checkout -b bme280 v4.19-stm32mp-r1.2
    
  7. At this point, our linux/ folder contains the exact same source code as what Buildroot has retrieved. It is time to make our Device Tree change by editing arch/arm/boot/dts/stm32mp157c-dk2.dts and at the end of it, add:

    &i2c5 {
    	status = "okay";
    	clock-frequency = <100000>;
    	pinctrl-names = "default", "sleep";
    	pinctrl-0 = <&i2c5_pins_a>;
    	pinctrl-1 = <&i2c5_pins_sleep_a>;
    };
    

    Once done, we need to tell Buildroot to use our kernel source code, using the pkg_OVERRIDE_SRCDIR mechanism. To this, create a file called local.mk, in the top-level Buildroot source directory, which contains:

    LINUX_OVERRIDE_SRCDIR = $(TOPDIR)/../linux
    

    This tells Buildroot to pick the Linux kernel source from $(TOPDIR)/../linux. We’ll now ask Buildroot to wipe out its Linux kernel build, and do a build again:

    $ make linux-dirclean
    $ make
    

    If you look closely at what Buildroot will do, it will do a rsync of the Linux kernel source code from your linux/ Git repository to output/build/linux-custom in Buildroot, and then do the build. You can check output/build/linux-custom/arch/arm/boot/dts/stm32mp157c-dk2.dts to make sure that your I2C5 change is there!

    If that is the case, then reflash output/images/sdcard.img on your SD card, and run the new system on the board. It’s now time to test the I2C bus!

    Testing the I2C bus

    After booting the new system on your Discovery board and logging in as root, let’s have a look at all I2C related devices:

    # ls -l /sys/bus/i2c/devices/
    total 0
    lrwxrwxrwx    0-002a -> ../../../devices/platform/soc/40012000.i2c/i2c-0/0-002a
    lrwxrwxrwx    0-0038 -> ../../../devices/platform/soc/40012000.i2c/i2c-0/0-0038
    lrwxrwxrwx    0-0039 -> ../../../devices/platform/soc/40012000.i2c/i2c-0/0-0039
    lrwxrwxrwx    0-004a -> ../../../devices/platform/soc/40012000.i2c/i2c-0/0-004a
    lrwxrwxrwx    2-0028 -> ../../../devices/platform/soc/5c002000.i2c/i2c-2/2-0028
    lrwxrwxrwx    2-0033 -> ../../../devices/platform/soc/5c002000.i2c/i2c-2/2-0033
    lrwxrwxrwx    i2c-0 -> ../../../devices/platform/soc/40012000.i2c/i2c-0
    lrwxrwxrwx    i2c-1 -> ../../../devices/platform/soc/40015000.i2c/i2c-1
    lrwxrwxrwx    i2c-2 -> ../../../devices/platform/soc/5c002000.i2c/i2c-2
    lrwxrwxrwx    i2c-3 -> ../../../devices/platform/soc/40012000.i2c/i2c-0/i2c-3
    

    This folder is part of the sysfs filesystem, which is used by the Linux kernel to expose to user-space applications all sort of details about the hardware devices connected to the system. More specifically, in this folder, we have symbolic links for two types of devices:

    • The I2C busses: i2c-0, i2c-1, i2c-2 and i2c-3. It is worth mentioning that the bus numbers do not match the datasheet: they are simply numbered from 0 to N. However, the i2c-0 symbolic link shows it’s the I2C controller at base address 0x40012000, so it’s I2C1 in the datasheet, i2c-1 is at base address 0x40015000 so it’s I2C5 in the datasheet, and i2c-2 at base address 0x5c002000 is I2C4 in the datasheet. i2c-3 is special as it’s not an I2C bus provided by the SoC itself, but the I2C bus provided by the HDMI transmitter to talk with the remote HDMI device (since this is unrelated to our discussion, we won’t go into more details on this).
    • The I2C devices: 0-002a, 0-0038, 0-0039, 0-004a, 2-0028, 2-033. These entries have the form B-SSSS where B is the bus number and SSSS is the I2C address of the device. So you can see that for example 0-004a corresponds to the cs42l51 audio codec we mentioned earlier.

    In our case, we are interested by I2C5, which is known by Linux as i2c-1. We will use the i2cdetect utility, provided by Busybox, to probe the different devices on this bus:

    # i2cdetect -y 1
         0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
    00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
    10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    70: -- -- -- -- -- -- 76 --                         
    

    Interesting, we have a device at address 0x76! Try to disconnect VIN of your I2C sensor, and repeat the command:

    # i2cdetect -y 1
         0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
    00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
    10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
    70: -- -- -- -- -- -- -- --                         
    

    The device at 0x76 has disappeared, so it looks like our sensor is at I2C address 0x76. To confirm this, let’s have a look at what the BME280 datasheet says about the I2C address of the device, in section 6.2 I2C Interface:

    BME280 I2C address

    So, the I2C address is indeed 0x76 when the SDO pin of the sensor is connected to GND, which is probably what our BME280 break-out board is doing. It matches the address we have detected with i2cdetect!

    Now, let’s talk to our device. According to section 5.4 Register description of the datasheet, there is a Chip ID register, at offset 0xD0 that is supposed to contain 0x60:

    BME280 Chip ID

    We can read this register using the i2cget command:

    # i2cget -y 1 0x76 0xd0
    0x60
    

    Good, this matches the expected value according to the BME280 datasheet, so it seems like communication with our I2C device is working, let’s move on to enabling the BME280 sensor driver.

    Enabling the sensor driver

    As discussed earlier, this BME280 sensor already has a driver in the upstream Linux kernel, in the IIO subsystem. IIO stands for Industrial Input/Output, and this subsystems contains a lot of drivers for various ADCs, sensors and other types of measurement/acquisition devices. In order to use this driver for our BME280 device, we will essentially have to do two things:

    1. Enable the driver in our Linux kernel configuration, so that the driver code gets built as part of our kernel image
    2. Describe the BME280 device in our Device Tree so that the Linux kernel knows we have one such device, and how it is connected to the system

    Adjusting the kernel configuration

    In the previous blog post, we explained that the Linux kernel configuration used to build the kernel for the STM32 Discovery board was located at board/stmicroelectronics/stm32mp157-dk/linux.config. Obviously, we are not going to edit this file manually: we need to run the standard Linux kernel configuration tools.

    It turns out that Buildroot has convenient shortcuts to manipulate the Linux kernel configuration. We can run the Linux kernel menuconfig configuration tool by running:

    $ make linux-menuconfig
    

    At this point, it is really important to not be confused by the fact that both Buildroot and the Linux kernel use the same configuration utility, but each have its own configuration. The Buildroot configuration describes your overall system (target architecture, which software components you want, which type of filesystem you want, etc.) while the Linux kernel configuration describes the kernel configuration itself (which drivers you want, which kernel features you need, etc.). So make sure to not confuse the menuconfig of Buildroot with the menuconfig of the Linux kernel!

    Once you have run make linux-menuconfig, the menuconfig of the Linux kernel will show up. You will then enable the following option:

    Device Drivers
    +- Industrial I/O support
       +- Pressure sensors
          +- Bosch Sensortec BMP180/BMP280 pressure sensor I2C driver
    

    Make sure to enable this option with a star <*> so that the driver is compiled inside the kernel image itself and not as a separate kernel module. You can then exit the menuconfig utility, and confirm that you want to save the configuration.

    At this point, the Linux kernel configuration file in output/build/linux-custom/.config has been changed. You can confirm it by running:

    $ grep CONFIG_BMP280 output/build/linux-custom/.config
    CONFIG_BMP280=y
    CONFIG_BMP280_I2C=y
    CONFIG_BMP280_SPI=y
    

    However, as we explained earlier, the output/build/linux-custom/ folder is temporary: it would be removed when doing a Buildroot make clean. We would like to permanently keep our Linux kernel configuration. Once again, Buildroot provides a nice shortcut to do this:

    $ make linux-update-defconfig
    

    After running this command, the kernel configuration file board/stmicroelectronics/stm32mp157-dk/linux.config has been updated, and this file is not temporary, and is under version control. If you run git diff, you can see the change on this file:

    $ git diff
    [...]
    index 878a0c39f1..12f3e22647 100644
    --- a/board/stmicroelectronics/stm32mp157-dk/linux.config
    +++ b/board/stmicroelectronics/stm32mp157-dk/linux.config
    @@ -169,6 +169,7 @@ CONFIG_STM32_LPTIMER_CNT=y
     CONFIG_STM32_DAC=y
     CONFIG_IIO_HRTIMER_TRIGGER=y
     CONFIG_IIO_STM32_LPTIMER_TRIGGER=y
    +CONFIG_BMP280=y
     CONFIG_PWM=y
     CONFIG_PWM_STM32=y
     CONFIG_PWM_STM32_LP=y
    

    We’re all set for the kernel configuration!

    Describing the BME280 in the Device Tree

    We now need to tell the Linux kernel that we have a BME280 sensor and how it is connected to the system, which is done by adding more details into our Device Tree. We have already enabled the I2C5 bus, and we now need to describe one device connected to it: this gets done by creating a child node of the I2C controller node.

    How do we know what to write in the Device Tree node describing the BME280 ? Using Device Tree bindings. Those bindings are specification documents that describe how a given device should be represented in the Device Tree: which properties are available, what are their possible values, etc. All Device Tree bindings supported by the Linux kernel are documented in Documentation/devicetree/bindings in the Linux kernel source code. For our BME280 device, the binding is at Documentation/devicetree/bindings/iio/pressure/bmp085.txt.

    This document tells us that we have one required property, the compatible property, with the range of possible values. Since we have a BME280 sensor, we’ll use bosch,bme280. The other properties are optional, so we’ll ignore them for now. However, what this binding does explicitly mention is the fact that a reg property is also mandatory, to tell the Linux kernel the I2C address of the device. This reg property is however visible in the example given in this binding document.

    So, we’ll go back to our linux/ directory outside of Buildroot, where we cloned the Linux kernel repository, and we’ll adjust our Device Tree file arch/arm/boot/dts/stm32mp157c-dk2.dts so that it contains:

    &i2c5 {
    	status = "okay";
    	clock-frequency = <100000>;
    	pinctrl-names = "default", "sleep";
    	pinctrl-0 = <&i2c5_pins_a>;
    	pinctrl-1 = <&i2c5_pins_sleep_a>;
    
    	pressure@76 {
    		compatible = "bosch,bme280";
    		reg = <0x76>;
    	};
    };
    

    Re-building the kernel

    Let’s now ask Buildroot to rebuild the Linux kernel, with our Device Tree change and kernel configuration change. Instead of rebuilding from scratch, we’ll just ask Buildroot to restart the build of the Linux kernel, which will be much faster:

    $ make linux-rebuild
    

    As part of this, Buildroot will re-run rsync from our linux/ kernel Git repository to output/build/linux-custom/, so that we really build the latest version of our code, which includes our Device Tree change.

    However, this just rebuilds the Linux kernel, and not the complete SD card image, so also run:

    $ make
    

    To regenerate the SD card image, write it on your SD card, and boot your system.

    Testing the sensor

    After booting the system, if we check /sys/bus/i2c/devices, a new entry has appeared:

    lrwxrwxrwx    1-0076 -> ../../../devices/platform/soc/40015000.i2c/i2c-1/1-0076
    

    If we following this symbolic link, we can see a number of interesting information:

    # ls -l /sys/bus/i2c/devices/1-0076/
    total 0
    lrwxrwxrwx    driver -> ../../../../../../bus/i2c/drivers/bmp280
    drwxr-xr-x    iio:device2
    -r--r--r--    modalias
    -r--r--r--    name
    lrwxrwxrwx    of_node -> ../../../../../../firmware/devicetree/base/soc/i2c@40015000/pressure@76
    drwxr-xr-x    power
    lrwxrwxrwx    subsystem -> ../../../../../../bus/i2c
    -rw-r--r--    uevent
    

    Here we can see that this device is bound with the device driver named bmp280, and that its Device Tree node is base/soc/i2c@40015000/pressure@76.

    Now, to actually use the sensor, we need to understand what is the user-space interface provided by IIO devices. The kernel documentation gives some hints:

    There are two ways for a user space application to interact with an IIO driver.

    • /sys/bus/iio/iio:deviceX/, this represents a hardware sensor and groups together the data channels of the same chip.
    • /dev/iio:deviceX, character device node interface used for buffered data transfer and for events information retrieval.

    So, we’ll try to explore the /sys/bus/iio/ option:

    # ls -l /sys/bus/iio/devices/
    total 0
    lrwxrwxrwx    iio:device0 -> ../../../devices/platform/soc/48003000.adc/48003000.adc:adc@0/iio:device0
    lrwxrwxrwx    iio:device1 -> ../../../devices/platform/soc/48003000.adc/48003000.adc:adc@100/iio:device1
    lrwxrwxrwx    iio:device2 -> ../../../devices/platform/soc/40015000.i2c/i2c-1/1-0076/iio:device2
    lrwxrwxrwx    iio:device3 -> ../../../devices/platform/soc/48003000.adc/48003000.adc:temp/iio:device3
    lrwxrwxrwx    trigger0 -> ../../../devices/platform/soc/40004000.timer/trigger0
    

    Here we can see a number of IIO devices: our IIO device is iio:device2, as can be seen by looking at the target of the symbolic links. The other ones are IIO devices related to the ADC on the STM32 processor. Let’s check what we have inside /sys/bus/iio/devices/iio:device2/:

    # ls -l /sys/bus/iio/devices/iio\:device2/
    total 0
    -r--r--r--    dev
    -rw-r--r--    in_humidityrelative_input
    -rw-r--r--    in_humidityrelative_oversampling_ratio
    -rw-r--r--    in_pressure_input
    -rw-r--r--    in_pressure_oversampling_ratio
    -r--r--r--    in_pressure_oversampling_ratio_available
    -rw-r--r--    in_temp_input
    -rw-r--r--    in_temp_oversampling_ratio
    -r--r--r--    in_temp_oversampling_ratio_available
    -r--r--r--    name
    lrwxrwxrwx    of_node -> ../../../../../../../firmware/devicetree/base/soc/i2c@40015000/pressure@76
    drwxr-xr-x    power
    lrwxrwxrwx    subsystem -> ../../../../../../../bus/iio
    -rw-r--r--    uevent
    

    This is becoming interesting! We have a number of files that we can read to get the humidity, pressure, and temperature:

    # cat /sys/bus/iio/devices/iio\:device2/in_humidityrelative_input 
    49147
    # cat /sys/bus/iio/devices/iio\:device2/in_pressure_input 
    101.567167968
    # cat /sys/bus/iio/devices/iio\:device2/in_temp_input 
    24380
    

    Now, let’s check the kernel documentation at Documentation/ABI/testing/sysfs-bus-iio to understand the units used in these files:

    What:		/sys/bus/iio/devices/iio:deviceX/in_tempX_input
    Description:
    		Scaled temperature measurement in milli degrees Celsius.
    
    What:		/sys/bus/iio/devices/iio:deviceX/in_pressure_input
    Description:
    		Scaled pressure measurement from channel Y, in kilopascal.
    
    What:		/sys/bus/iio/devices/iio:deviceX/in_humidityrelative_input
    Description:
    		Scaled humidity measurement in milli percent.
    

    So here we are: we are able to read the data from our sensor, and the Linux kernel driver does all the conversion work to convert the raw values from the sensors into usable values in meaningful units.

    Turning our kernel change into a patch

    Our Device Tree change is for now only located in our local Linux kernel Git repository: if another person builds our Buildroot configuration, he won’t have access to this Linux kernel Git repository, which Buildroot knows about thanks to the LINUX_OVERRIDE_SRCDIR variable. So what we’ll do now is to generate a Linux kernel patch that contains our Device Tree change, add it to Buildroot, and ask Buildroot to apply it when building the Linux kernel. Let’s get started.

    First, go in your Linux kernel Git repository in linux/, review your Device Tree change with git diff, and if everything is alright, make a commit out of it:

    $ git commit -as -m "ARM: dts: add support for BME280 sensor on STM32MP157 DK2"
    

    Then, generate a patch out of this commit:

    $ git format-patch HEAD^
    

    This will create a file called 0001-ARM-dts-add-support-for-BME280-sensor-on-STM32MP157-.patch that contains our Device Tree change.

    Now, back in Buildroot in the buildroot/ folder, create the board/stmicroelectronics/stm32mp157-dk/patches/ folder and a sub-directory board/stmicroelectronics/stm32mp157-dk/patches/linux. Copy the patch into this folder, so that the file hierarchy looks like this:

    $ tree board/stmicroelectronics/stm32mp157-dk/
    board/stmicroelectronics/stm32mp157-dk/
    ├── genimage.cfg
    ├── linux.config
    ├── overlay
    │   └── boot
    │       └── extlinux
    │           └── extlinux.conf
    ├── patches
    │   └── linux
    │       └── 0001-ARM-dts-add-support-for-BME280-sensor-on-STM32MP157-.patch
    ├── readme.txt
    └── uboot-fragment.config
    

    Now, run Buildroot’s menuconfig:

    $ make menuconfig
    

    And in Build options, set global patch directories to the value board/stmicroelectronics/stm32mp157-dk/patches/. This tells Buildroot to apply patches located in this folder whenever building packages. This way, when the linux package will be built, our patch in board/stmicroelectronics/stm32mp157-dk/patches/linux/ will be applied.

    We can now remove the local.mk file to disable the pkg_OVERRIDE_SRCDIR mechanism, and ask Buildroot to rebuild the Linux kernel:

    $ rm local.mk
    $ make linux-dirclean
    $ make
    

    If you pay attention to the Linux kernel build process, you will see that during the Patching step, our Device Tree patch gets applied:

    >>> linux custom Patching
    
    Applying 0001-ARM-dts-add-support-for-BME280-sensor-on-STM32MP157-.patch using patch: 
    patching file arch/arm/boot/dts/stm32mp157c-dk2.dts
    

    You can of course reflash the SD card at the end of the build, and verify that everything still works as expected.

    Let’s save our Buildroot configuration change:

    $ make savedefconfig
    

    And commit our Buildroot changes:

    $ git add board/stmicroelectronics/stm32mp157-dk/linux.config
    $ git add board/stmicroelectronics/stm32mp157-dk/patches/
    $ git add configs/stm32mp157_dk_defconfig
    $ git commit -s -m "configs/stm32mp157_dk: enable support for BME280 sensor"
    

    We can now share our Buildroot change with others: they can build our improved system which has support for the BME280 sensor.

    Conclusion

    You can find the exact Buildroot source code used to reproduce the system used in this article in the branch at 2019.02/stm32mp157-dk-blog-2.

    In this article, we have learned a lot of things:

    • How to connect an I2C sensor to the Discovery board
    • What is the Device Tree, and how it is used to describe devices
    • How to use Buildroot’s pkg_OVERRIDE_SRCDIR mechanism
    • How to enable the I2C bus in the Device Tree and test its operation using i2cdetect and i2cget
    • How to change the Linux kernel configuration to enable a new driver
    • How to interact using sysfs with a sensor supported by the IIO subsystem
    • How to generate a Linux kernel patch, and add it into Buildroot

    In our next article, we’ll look at adding support for the Qt5 graphical library into our system, as a preparation to developing a Qt5 application that will display our sensor measurements on the Discovery board screen.