Tutorial Qt für den Mikrocontroller, Teil 1: Minimalismus

Seite 2: Qt ist nicht mehr gleich Qt

Inhaltsverzeichnis

In der Vergangenheit galt, dass die "grundlegende" Architektur von Qt über alle Systeme gleich blieb, unabhängig davon, ob es sich um Qt für Linux, für Symbian oder für Windows CE handelte: Die im Qt Core enthaltene Grundlagen wie QObjekt und die dazu gehörende Parent-Child Garbage Collection standen auf allen Zielsystemen gleichermaßen zur Verfügung.

Obwohl das für das Beispiel verwendete Evaluationsboard 16 MByte RAM und einige Dutzend Megabyte ROM mitbringt, entschied sich die Qt Company im Mikrocontroller-Bereich für eine andere Strategie, die sie in einem Dokument folgendermaßen zusammenfasst:

For example, instead of using QObject or QAbstractItemModel, Qt Quick Ultralite provides a simple C++ API to expose objects and models.

Eine auf der im Mikrocontrollerbereich zu verwendenden Qt-Variante Qt Quick Ultralite basierende Applikation muss auf die in Modulen wie Qt Core und Qt GUI bereitgestellten Hilfselemente verzichten und stattdessen eine einfache C++-API nutzen. Für die Entwicklung steht lediglich eine QML-Runtime zur Verfügung, die entweder direkt auf der Hardwareabstraktionsschicht des Zielsystems oder auf einem Echtzeitbetriebssystem wie FreeRTOS basiert.

Diese Designentscheidung der Qt Company hat gravierende Konsequenzen für das Portieren von Qt-Anwendungen auf Mikrocontroller: Wer QObject oder ähnliche Hilfsklassen nutzt, muss auf C++-Konstrukte umsteigen.

Die erste Änderung, die mit der neuen Qt-Bibliothek einhergeht, ist die konsequente Verwendung des Buildsystems CMake. Für Qt for MCU 1.9 ergibt sich eine CMakeLists-Datei folgender Art:

cmake_minimum_required (VERSION 3.15)
project(HeiseMCU1 VERSION 0.0.1 LANGUAGES C CXX ASM)
find_package(Qul)

Nach der Deklaration der Mindestversionen dient der Aufruf von find_package zum Bereitstellen des Qt-Quick-Ultralite-Pakets.

Seit Version 1.7 von Qt for MCUs steht mit qul_add_target ein Komfortmakro zur Verfügung, mit dem sich eine kompilierfertige Anwendung mit einem einzigen Aufruf erstellen lässt.

Qt Creator generiert im Interesse der Abwärtskompatibilität zusätzlich einen else-Zweig, der die beiden für Qt Quick Ultralite benötigten Bibliotheken explizit einbindet:

if(Qul_VERSION VERSION_GREATER_EQUAL "1.7")
  qul_add_target(HeiseMCU1)
else()
  add_executable(HeiseMCU1)
  target_link_libraries(HeiseMCU1
    Qul::QuickUltralite
    Qul::QuickUltralitePlatform)
endif()

Zum Schluss finden sich drei Makro-Aufrufe, die ein QML-basiertes Projekt als Ganzes ins Leben rufen:

qul_target_qml_sources(HeiseMCU1 HeiseMCU1.qml)
app_target_setup_os(HeiseMCU1)
app_target_default_main(HeiseMCU1 HeiseMCU1)

Der nächste Schritt öffnet die Datei HeiseMCU1.qmlproject. In Qt-for-MCUs-Projekten findet sich keine gewöhnliche .pro-Datei, sondern die Konfiguration der Projektbestandteile erfolgt ebenfalls in QML.

Aus technischer Sicht enthält die Datei die Befehle zum Einbinden der in den festgelegten Verzeichnissen befindlichen Ressourcen in den Build-Prozess:

import QmlProject 1.1
Project {
  mainFile: "HeiseMCU1.qml"
  qtForMCUs: true
  /* Include .qml, .js, and image files from
     current directory and subdirectories */
  QmlFiles {
    directory: "."
  }
  JavaScriptFiles {
    directory: "."
  }
  ImageFiles {
    directory: "."
  }
}

Schließlich setzt die Datei HeiseMCU1.qml ein QML-Fenster um, das die vom Desktop und aus dem Mobilbereich bekannten Text- und Rectangle-Objekte verwendet:

import QtQuick 2.0
Rectangle {
  width: 480
  height: 272
  Text {
    anchors.centerIn: parent
    color: "salmon"
    text: "Hello World!"
    font.pixelSize: 14
  }
}

Das Kompilieren der Applikation generiert die Datei HeiseMCU1_main.cpp, die als Einsprungspunkt ins Hauptprojekt dient. Qt Creator gibt dabei Warnungen wie

Use of function app_target_default_main in target HeiseMCU1
is deprecated.
Please use app_target_default_entrypoint instead of this one

aus, die aber für das Beispielprojekt irrelevant sind.

Der Start der generierten C++-Datei kümmert sich um das Einbinden einiger QML-Bibliotheken:

#include "HeiseMCU1.h"
#include <qul/application.h>
#include <qul/qul.h>

Die Initialisierung erfolgt anders als in herkömmlichen QML-Applikation. Nach dem Initialisieren der Qt-Quick-Ultralite-Plattform erhält diese ein QML-Elternelement:

int main()
{
  Qul::initPlatform();
  Qul::Application _qul_app;
  static struct ::HeiseMCU1 _qul_item;
  _qul_app.setRootItem(&_qul_item);
#ifdef APP_DEFAULT_UILANGUAGE
  _qul_app.setUiLanguage(APP_DEFAULT_UILANGUAGE);
#endif
  _qul_app.exec();
  return 0;
}

Zum Abschluss stößt der Aufruf von exec das Ausführen des QML-Threads an. Analog zu klassischen Qt-Application-Klassen gilt, dass die Methode erst nach dem Beenden des GUI zurückkehrt.

Ein Klick auf das Play-Symbol führt die Anwendung aus (s. Abb. 4).

Qt Quick Ultralite funktioniert auch unter Windows (Abb. 4).