Skip to content

Commit

Permalink
Merge pull request qmlbook#148 from qmlbook/new-chapter-qtformcu
Browse files Browse the repository at this point in the history
New chapter qtformcu
  • Loading branch information
e8johan authored Feb 3, 2022
2 parents ac14426 + da70b23 commit 53c7e5e
Show file tree
Hide file tree
Showing 33 changed files with 915 additions and 0 deletions.
17 changes: 17 additions & 0 deletions docs/.vuepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,27 @@ module.exports = {
ch16Sidebar(),
ch17Sidebar(),
ch18Sidebar(),
ch19Sidebar(),
],
},
}

function ch19Sidebar() {
return {
title: "Qt for MCUs",
path: '/ch19-qtformcu/qtformcu',
collapsable: false,
children: [
'/ch19-qtformcu/qtformcu',
'/ch19-qtformcu/setup',
'/ch19-qtformcu/helloworld',
'/ch19-qtformcu/cpp',
'/ch19-qtformcu/models',
'/ch19-qtformcu/summary',
]
}
}

function ch18Sidebar() {
return {
title: "Qt for Python",
Expand Down
Binary file added docs/ch19-qtformcu/assets/counter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/ch19-qtformcu/assets/create-project-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/ch19-qtformcu/assets/create-project-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/ch19-qtformcu/assets/create-project-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/ch19-qtformcu/assets/devices.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/ch19-qtformcu/assets/hello-world-orange.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/ch19-qtformcu/assets/hello-world-red.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/ch19-qtformcu/assets/installer-mcu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/ch19-qtformcu/assets/kits.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/ch19-qtformcu/assets/model-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/ch19-qtformcu/assets/model-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/ch19-qtformcu/assets/model-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/ch19-qtformcu/assets/qul-hello-world.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
115 changes: 115 additions & 0 deletions docs/ch19-qtformcu/cpp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Integrating with C++

## The C++

In order to demonstrate the connection between C++ and QML in Qt for MCUs, we will create a simple ``Counter`` singleton holding an integer value. Notice that we start from a ``struct`` and not a ``class``. This is common practice in Qt Quick Ultralite.

The singleton will be used from a small UI as shown below.

![](./assets/counter.png)

The ``Counter`` struct provides a property, ``value``, as well as methods for changing the value, ``increase`` and ``decrease``, as well as a ``reset`` method. It also provides a signal, ``hasBeenReset``.

<<< @/docs/ch19-qtformcu/src/cppintegration/counter.h#global

Coming from Qt, this looks odd. This is where Qt for MCUs shows the main differences. There is no ``QObject`` base class or ``Q_OBJECT`` macro, instead a new set of classes from the ``Qul`` is used. In this particular case, the base class is the ``Qul::Singleton`` class, creating a globally accessible singleton in the QML world. We also use the ``Qul::Signal`` class to create a signal and the ``Qul::Property`` class to create a property. All public, non-overloaded member functions are exposed to QML automatically.

::: tip
To create an element that can be instatiated from QML, instead of a singleton, use the ``Qul::Object`` base class.
:::

The struct is then exposed to QML using the CMake macro ``qul_target_generate_interfaces``. Below you can see the ``CMakeLists.txt``, based on the file generated by Qt Creator, with the ``counter.h`` and ``counter.cpp`` files added.

<<< @/docs/ch19-qtformcu/src/cppintegration/CMakeLists.txt#generate_interfaces

Now, let's continue with the implementation of the ``Counter`` struct. First up, for the ``value`` property, we use the ``value`` and ``setValue`` functions to access and modify the actual value. In our case, the property holds and ``int``, but just as for the ordinary QML engine, types are [mapped between C++ and QML](https://doc.qt.io/QtForMCUs/qtul-integratecppqml.html#type-mapping).

This is used in the constructor, shown below, that sets the initial value to zero.

<<< @/docs/ch19-qtformcu/src/cppintegration/counter.cpp#ctor

The ``increase`` and ``decrease`` functions look similar. They use the getter and setter instead of interacting directly with the value.

<<< @/docs/ch19-qtformcu/src/cppintegration/counter.cpp#incdec

``Counter`` also has a signal. The signal is represented by the ``Qul::Signal`` instance named ``hasReset``. The signal takes a function signature as template argument, so to create a signal carrying an integer, create a ``Qul::Signal<void(int)>``. In this case, the signal does not carry any values, so it is defined as a `void(void)`. To emit the signal, we simply call it as if it was an ordiary function as shown in the ``reset`` function below.

<<< @/docs/ch19-qtformcu/src/cppintegration/counter.cpp#reset

## The QML

The QML code produces the simple user interface shown below.

![](./assets/counter.png)

We will look at the UI in three parts. First, the basic structure, and bindings to ``Counter.value``:

```
import QtQuick
Rectangle {
width: 480
height: 272
Column {
// Left buttons goes here
}
Column {
// Right buttons goes here
}
Text {
anchors.centerIn: parent
text: Counter.value;
}
}
```

As you can tell, the ``Text`` element's ``text`` property is bound to the ``Counter.value`` as in all QML.

Now, let's look at the left side buttons. These are used to invoke the C++ methods provided via the ``Counter`` singleton. The ``PlainButton`` is a QML element that we use to create these simple buttons. It let's you set the text, background color and a handler for the ``clicked`` signal. As you can tell, each button calls the corresponding method on the ``Counter`` singleton.

<<< @/docs/ch19-qtformcu/src/cppintegration/cppintegration.qml#left

The buttons on the right modify the ``Counter.value`` directly from QML. This is possible to do, but invisible to C++. There is no simple way for C++ to monitor if a property has changed, so if a C++ reaction is needed, it is recommended to use a setter method, rather than directly modifying the property value.

<<< @/docs/ch19-qtformcu/src/cppintegration/cppintegration.qml#right

This shows how to provide a singleton from C++ and how to make function calls, emit signals, and share state (properties) between C++ and QML.

## Revisiting the CMake file

The ``CMakeLists.txt`` file may look familiar to you, but there are some tips and tricks that we need to discuss.

First of all, in order to expose a C++ class to QML, use the ``qul_target_generate_interfaces``, e.g:

```
qul_target_generate_interfaces(cppintegration counter.h)
```

The other half, the QML files, are added using the ``qul_target_qml_sources`` macro. If you have multiple QML files, simply list them one by one as shown below:

```
qul_target_qml_sources(cppintegration cppintegration.qml PlainButton.qml)
```

Another interesting aspect is that we are building a C++ project without writing a ``main`` function. This is taken care of by the ``app_target_default_main`` macro that adds a reference main implementation to the project. You can of course replace this with a custom ``main`` function if you need more control.

```
app_target_default_main(cppintegration cppintegration)
```

Finally, the libraries linked to are not the standard Qt ones, but the ``Qul::`` ones, e.g:

```
target_link_libraries(cppintegration
Qul::QuickUltralite
Qul::QuickUltralitePlatform)
```

::: tip Links
Further reading at qt.io:
* [Integrate C++ and QML](https://doc.qt.io/QtForMCUs/qtul-integratecppqml.html)
:::
103 changes: 103 additions & 0 deletions docs/ch19-qtformcu/helloworld.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Hello World - for MCUs

As the setup of Qt for MCU can be a bit tricky, we will start with a _Hello World_ like example to ensure that the toolchain works, and so that we can discuss the basic differences between Qt Quick Ultralite and standard Qt Quick.

First up, we need to start by creating a Qt for MCUs project in Qt Creator to get a C++ entry point into the system. When working with Qt Quick Ultralite, we cannot use a common runtime such as ``qml``. This is because Qt Quick Ultralite is translated into C++ together with optimized versions of all the assets. These are then built into a target executable. This means that there is no support for dynamic loading of QML and such - as there is no interpreter running on the target.

![](./assets/create-project-1.png)

I call the project ``helloworld``. Feel free to pick a name of your own. The only thing changing is the name of the entry-point QML-file of the project.

![](./assets/create-project-2.png)

Also, make sure to pick the Qt for MCUs kits when creating your project.

![](./assets/create-project-3.png)

After a few more configuration pages, you will end up with a project as shown below.

![](./assets/qtcreator-with-project.png)

Once the basic project is setup, run the project on your desktop and ensure that you get a window like the one shown below.

![](./assets/qul-hello-world.png)

Now that we know that the installation works, replace the QML in ``helloworld.qml`` with the code shown below. We will walk through this example line by line below, but first, build and run it for your _Qt for MCU Desktop_ target. This should result in a window looking like the screenshot below the code.

<<< @/docs/ch19-qtformcu/src/helloworld/helloworld.qml#global

![](./assets/hello-world-orange.png)

Click the orange rectangle, and it fades to red. Click it again and it fades back to orange.

![](./assets/hello-world-red.png)

Now, let's have a look at the source code from a Qt Quick perspective and compare.

First up, Qt Quick Ultralight ignores the version numbers after import statements. This is supported in Qt Quick since Qt 6 too by leaving out the version number, so if you can manage without it and need compatibility, make sure to leave out the version number.

```qml
import QtQuick
import QtQuickUltralite.Extras
```

In the root of our scene, we place a ``Rectangle``. This is because Qt Quick Ultralite does not provide a default, white, background. By using a ``Rectangle`` as root, we ensure that we control the background color of the scene.

```qml
Rectangle {
width: 480
height: 272
```

The next part, the clickable ``Rectangle``, is straight forward QML, with some Javascript bound to the ``onClicked`` event. Qt for MCUs has limited support for Javascript, so ensure to keep such scripts simple. You can read more about the specific limitations in the links at the end of this section.

```qml
Rectangle {
id: rect
anchors.fill: parent
anchors.margins: 60
color: "orange"
Behavior on color {
ColorAnimation { duration: 400 }
}
MouseArea {
anchors.fill: parent
onClicked: {
if (rect.color == "red")
rect.color = "orange";
else
rect.color = "red";
}
}
}
```

Finally, the text is rendered using a ``StaticText`` element, which is a version of the ``Text`` element for static texts. This means that the text can be rendered once, or even pre-rendered, which can save a lot of resources on a small, MCU-based, system.

```qml
StaticText {
anchors.centerIn: parent
color: "black"
text: "Hello World!"
font.pixelSize: 52
}
}
```

In Qt Creator, you will notice that the you get warnings around the ``StaticText`` element. This is because Qt Creator assumes that you are working with Qt Quick. To make Qt Creator aware of Qt Quick Ultralite, you need to set the ``QML_IMPORT_PATH`` to the path of your Qt for MCUs compatibility module. You can do this in your CMakeLists.txt file, or in your project settings. The project settings for a standard Windows 10 install is shown below.

![](./assets/qtcreator-qml-import-path.png)

In addition to what has been stated above, there are more differences. For instance, the Qt Quick Ultralite ``Item`` class, and hence the ``Rectangle`` class, lacks a lot of properties that could be found in Qt Quick. For instance, the ``scale`` and ``rotation`` properties are missing. These are only available for specific elements such as ``Image``, and there it is made available through the ``Rotation`` and ``Scale`` types instead of properties.

Going beyond the eaxmple above, there are fewer QML elements in general in Qt Quick Ultralite, but the supported types is continously growing. The intention is that the provided types cover the use-cases of the intended target devices. You can read more about this and general compatibility issues in the links provided below.

::: tip Links
Further reading at qt.io:
* [Qt Quick Ultralite vs Qt Quick](https://doc.qt.io/QtForMCUs/qtul-qtquick-differences.html)
* [Differences between Qt Quick Ultralite Controls and Qt Quick Controls](https://doc.qt.io/QtForMCUs/qtul-qtquick-controls-api-differences.html)
* [Known Issues and Limitations](https://doc.qt.io/QtForMCUs/qtul-known-issues.html)
:::
59 changes: 59 additions & 0 deletions docs/ch19-qtformcu/models.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Working with Models

In Qt Quick Ultralite, it is possible to create models in QML using the ``ListModel`` element. It is also possible, and a bit more interesting, to create models from C++. This lets you expose lists of data from C++ to QML and to instantiate user interface elements for each item of the list. The setup is very similar to the ordinary Qt Quick, but the base classes and interfaces are more limited.

In this chapter we will create a list of cities in Europe, listing the name of the city and the country in which the city is located. The cities will be shown in a ``ListView`` as shown below:

![](./assets/model-1.png)

## The C++

To create a model in Qt Quick Ultralite, the first thing we need to do is to define a ``struct`` with the data of each list item. For this struct, we also need to provide a ``==`` operator. This is what we do with the ``CityData`` struct shown below. Notice that we use ``std::string`` rather than ``QString`` in Qt Quick Ultralite. The assumption is that UTF-8 encoding is used.

<<< @/docs/ch19-qtformcu/src/cppmodel/citymodel.h#data

Once the data type has been prepared, we declare the ``CityModel`` struct, inheriting from ``Qul::ListModel``. This lets us define a model that can be accessed from QML. We must implement the ``count`` and ``data`` methods, which are similar, but not identical to, the corresponding methods from the ``QAbstractListModel`` class. We also use the `CMake macro ``qul_target_generate_interfaces`` to make the types available to QML.

<<< @/docs/ch19-qtformcu/src/cppmodel/citymodel.h#model

We also implement a constructor for the ``CityModel`` struct that populates the ``m_data`` vector with data.

<<< @/docs/ch19-qtformcu/src/cppmodel/citymodel.cpp#global

## The QML

In the example, we show the model as a scrollable list, as shown below.

![](./assets/model-2.png)

The QML code is shown in its entirety below:

<<< @/docs/ch19-qtformcu/src/cppmodel/cppmodel.qml#global

The example starts by instantiating the ``cityModel``. As the model is not a singleton, it has to be instantiated from QML.

Then the delegate, ``cityDelegate`` is implemented as a ``Component``. This means that it can be instantiated multiple times from QML. The model data is accessed via the ``model.name`` and ``model.country`` attached properties.

Finally, the ``ListView`` element joins the model and the delegate, resulting in the list shown in the screenshots in this chapter.

![](./assets/model-3.png)

An interesting aspect of the QML is how the font of the ``Text`` elements is configured. The ``unicodeCoverage`` property lets us tell the Qt Quick Ultralite compiler what characters we would like to be able to render. When specifying fixed strings, the Qt Quick Ultralite tooling generates minimal fonts containing exactly the glyphs that we intend to use. However, since the model will provide us with dynamic data, we need to tell the font what characters we expect to use.

When rendering a complete font, sometimes you encounter the following style of warnings:

```
[2/7 8.8/sec] Generating CMakeFiles/cppmodel.dir/qul_font_engines.cpp, CMakeFiles/cppmodel.dir/qul_font_data.cpp
Warning: Glyph not found for character "\u0000"
Warning: Glyph not found for character "\u0001"
Warning: Glyph not found for character "\u0002"
Warning: Glyph not found for character "\u0003"
Warning: Glyph not found for character "\u0004"
Warning: Glyph not found for character "\u0005"
Warning: Glyph not found for character "\u0006"
Warning: Glyph not found for character "\u0007"
...
```

These can safely be disregarded, unless you expect to show the character in question.

9 changes: 9 additions & 0 deletions docs/ch19-qtformcu/qtformcu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Qt for MCUs

::: tip Notice
Qt for MCUs is not a part of the open source Qt distribution, but as a commercial add-on.
:::

Qt for MCUs is a Qt version takes Qt for platforms that are too small to run Linux. Instead, Qt for MCUs can run on top of FreeRTOS, or even on the bare metal, i.e. without any operating system involved. As this book focuses on QML, we will have a deeper look at Qt Quick Ultralite and compare it to the full-size Qt offering.

Using Qt for MCUs you can build beautiful, fluid graphical user interfaces for your micro controller-based systems. Qt for MCUs is focused on the graphical front-end, so instead of the traditional Qt modules, common C++ types are used. This means that some interfaces change. Most notably how models are exposed to QML. In this chapter we will dive into this, and more.
25 changes: 25 additions & 0 deletions docs/ch19-qtformcu/setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Setup

[Qt for MCUs](https://doc.qt.io/QtForMCUs/index.html) comes with support for a range of evaluation boards from companies such as NCP, Renesas, ST, and Infinion/Cypress. These are good for getting started and helps you try out the integration to the specific MCU. In the end, you will most likely end up tuning a specific platform definition to your specific hardware, e.g. to configure the amount of RAM, FLASH and screen configuration.

![](./assets/installer-mcu.png)

In addition to supporting multiple MCUs out of the box, Qt for MCUs also support running either on FreeRTOS or directly on the bare metal, i.e. without an operating system. As Qt for MCUs focuses on the graphical front-end part of things, there are no classes for filesystems and such. All this has to come from the underlaying system. Hence, if you need support for more complex feature, FreeRTOS is one option.

When it comes to the development environment, various boards come with various compilers, so the Qt for MCUs setup will look a bit different depending on which MCU you target, as well as which compiler you choose. For instance, for the boards from ST, both GCC and IAR are supported, while for some other boards Green Hills MULTI Compiler is used. The officially supported development hosts from Qt's point of view are Linux (Ubuntu 20.04 LTS on x86_64) or Windows (Windows 10 on x86_64). For Windows, please notice that the MSVC compilers supported are the 2017 and 2019 editions - not the very latest. Make sure to follow the latest [setup instructions on qt.io](https://doc.qt.io/QtForMCUs/qtul-setup-development-host.html) to get a working environment.

Once you have setup your environment, you can find the supported boards as _Kits_ as well as under _Devices - MCU_ under the _Tools - Options..._ menu item in Qt Creator.

![](./assets/kits.png)
![](./assets/devices.png)

::: tip
If you do not find the MCUs tab under Tools, make sure that the Qt for MCUs plugins (McuSupport and BaremetalSupport) are available and activated under _Help - About Plugins..._.
:::

::: tip Links
Further reading at qt.io:

* [Supported boards](https://doc.qt.io/QtForMCUs/qtul-supported-platforms.html)
* [Platform porting guide](https://doc.qt.io/QtForMCUs/platform-porting-guide-introduction.html)
:::
22 changes: 22 additions & 0 deletions docs/ch19-qtformcu/src/cppintegration/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
cmake_minimum_required (VERSION 3.15)

project(cppintegration VERSION 0.0.1 LANGUAGES C CXX ASM)

find_package(Qul)

if(Qul_VERSION VERSION_GREATER_EQUAL "1.7")
qul_add_target(cppintegration counter.cpp)
else()
add_executable(cppintegration counter.cpp)
target_link_libraries(cppintegration
Qul::QuickUltralite
Qul::QuickUltralitePlatform)
endif()

#region generate_interfaces
qul_target_generate_interfaces(cppintegration counter.h)
#endregion generate_interfaces
qul_target_qml_sources(cppintegration cppintegration.qml PlainButton.qml)

app_target_setup_os(cppintegration)
app_target_default_main(cppintegration cppintegration)
Loading

0 comments on commit 53c7e5e

Please sign in to comment.