C

Using Qt Quick Ultralite with Zephyr

Zephyr is an RTOS for embedded systems. It provides a fully preemptible tickless kernel, both cooperative and preemptive threading, semaphores and queues.

Supported architectures, platforms and Zephyr versions

Qt Quick Ultralite supports the following hardware:

Hardware boardMCUArchitectureCompilerSupported Zephyr version
NXP IMXRT1060-EVKBMIMXRT1062DVL6BARM Cortex-M7GNU Arm GCC 12.3.rel13.6.0
NXP IMXRT1064-EVKMIMXRT1064DVL6AARM Cortex-M7GNU Arm GCC 12.3.rel13.6.0

Porting Qt Quick Ultralite to a Zephyr platform

You can port Qt Quick Ultralite to a Zephyr platform by following the Qt Quick Ultralite Platform Porting Guide. In the reference ports, the following parts of have been implemented using Zephyr APIs:

  • currentTimestamp() calls k_uptime_get().
  • Touch support is done using Keyboard Scan APIs.
  • Qt Quick Ultralite Queues use Zephyr message queues.
  • Semaphores are used to suspend and resume Qt Quick Ultralite thread operation.
  • Display interrupt is set up and enabled using Zephyr:
    void setInterruptPriorities()
    {
        IRQ_CONNECT(LCDIF_IRQn, 3, displayIRQHandler, NULL, 0);
        irq_enable(LCDIF_IRQn);
    }
  • LCD screen pinmux is initialized using Zephyr pin control API
    #define LCDIF_DEV DT_NODELABEL(lcdif)
    
    void initializeLcdPinmux()
    {
        PINCTRL_DT_DEFINE(LCDIF_DEV);
    
        pinctrl_apply_state(PINCTRL_DT_DEV_CONFIG_GET(LCDIF_DEV), PINCTRL_STATE_DEFAULT);
    }

Note: If you have installed any of the supported reference platform port(s), the reference Zephyr port source code is in the <QT_INSTALL>/QtMCUs/2.9.0/platform/boards/nxp/common/zephyr directory.

Zephyr KConfig options

When configuring the Zephyr project to use Qt Quick Ultralite, include the following options:

  • CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
  • CONFIG_REQUIRES_FULL_LIBC=y
  • CONFIG_NEWLIB_LIBC=y
  • CONFIG_COMMON_LIBC_MALLOC=y
  • CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=2097152
  • CONFIG_NEWLIB_LIBC_NANO=y
  • CONFIG_NEWLIB_LIBC_MIN_REQUIRED_HEAP_SIZE=8192
  • CONFIG_CPP=y
  • CONFIG_STD_CPP14=y
  • CONFIG_EXTERNAL_LIBCPP=y
  • CONFIG_FORTIFY_SOURCE_NONE=y

Note: If you are using a custom allocation system in the platform port, you don't need to set the malloc related options.

If you want to run the Qt Quick Ultralite application from the main thread, increase the stack size of the main thread. For example, the reference platform ports use CONFIG_MAIN_STACK_SIZE=12288, which is sufficient to run thermo demo and all of the supported examples.

If you are using prebuilt Qt Quick Ultralite libraries, add the following options:

  • CONFIG_FPU=y
  • CONFIG_LTO=y
  • CONFIG_ISR_TABLES_LOCAL_DECLARATION=y

Workflow

Qt Quick Ultralite offers tools which enable easy integration with a pre-existing Zephyr project.

The overall workflow for creating and compiling a Qt Quick Ultralite application is as follows:

Creating a new Qt Quick Ultralite application

To create a Qt Quick Ultralite application, see Qt Design Studio on MCUs documentation.

Exporting the application as a CMake project

The Generating CMake project page contains a list of flags you can use with qmlprojectexporter to generate a CMake project. In case you don't need to export platform sources, add --no-export-platform to the qmlprojectexporter call. See integrating exported projects without a platform for a reference qmlprojectexporter call without a platform export.

Adding the exported CMake project to a Zephyr project

You can add the exported CMake project by following the CMake project file creation instructions with the following differences:

  • You don't need to specify a new project inside CMakeLists.txt. However, make sure your project specifies LANGUAGES C CXX ASM.
  • Place the find_package and all the target_* calls after find_package(Zephyr).
  • When specifying target_*, use app as the target to make sure the Qt Quick Ultralite application is built to your Zephyr project.

Adding Qt Quick Ultralite-specific sections to the linker script

Qt Quick Ultralite requires linker scripts to contain several sections where all the Qt Quick Ultralite application related data can be stored. Descriptions of these sections can be found here.

However, unless a custom linker script is used, Zephyr generates the script automatically. To add sections required by Qt Quick Ultralite to the automatically created script, you will need to use Zephyr-specific section description notation in the linker scripts and include them using zephyr_linker_sources(SECTION_NAME "path/to/linkerscript.ld"). For more information about zephyr_linker_sources and how to use the notation, see ZEPHYR_DIR/zephyr/cmake/modules/extensions.cmake.

Qt Quick Ultralite reference platform ports define the linker sections in three files:

  • qul_data_sections.ld
    SECTION_PROLOGUE (QulModuleResourceData,,SUBALIGN(4)) {
        __ModuleResourceDataCacheStart = .;
        *(QulModuleResourceData)
        __ModuleResourceDataCacheEnd = .;
    } GROUP_LINK_IN(RAMABLE_REGION) AT > ROMABLE_REGION
    
    __ModuleResourceDataStart = LOADADDR(QulModuleResourceData);
  • qul_ram_sections.ld
    SECTION_PROLOGUE (QulPreloadData,,SUBALIGN(4)) {
        __preloadRamStart = .;
        *(QulPreloadData)
        . += 10M;
        __preloadRamEnd = .;
    } GROUP_LINK_IN(RAMABLE_REGION)
  • qul_rom_sections.ld
    SECTION_PROLOGUE (QulFontResourceData,,) {
        . = ALIGN(4);
        *(QulFontResourceData)
        . = ALIGN(4);
    } GROUP_LINK_IN(ROMABLE_REGION)
    
    SECTION_PROLOGUE (QulResourceData,,SUBALIGN(4)) {
        *(QulResourceData)
    } GROUP_LINK_IN(ROMABLE_REGION)

These files are then added to the Zephyr project with zephyr-linker-sources.cmake:

zephyr_linker_sources(DATA_SECTIONS "${CMAKE_CURRENT_LIST_DIR}/cmake/armgcc/qul_data_sections.ld")
zephyr_linker_sources(RAM_SECTIONS "${CMAKE_CURRENT_LIST_DIR}/cmake/armgcc/qul_ram_sections.ld")
zephyr_linker_sources(SECTIONS "${CMAKE_CURRENT_LIST_DIR}/cmake/armgcc/qul_rom_sections.ld")

Iterative builds of the Qt Quick Ultralite application

When you change the contents of the Qt Quick Ultralite application, you only need to rebuild the Zephyr project. When the qmlprojectexporter is run, it reads the changes, re-exports the Qt Quick Ultralite application sources, and updates the QulExport package, making the changes available to the Zephyr project.

Creating a Qt Quick Ultralite thread

If --generate-entrypoint is used when exporting the Qt Quick Ultralite application, the main thread is used as an entrypoint to the application. If you want to have more control over the thread creation, you can also spawn a Zephyr thread that runs the Qt Quick Ultralite application. To do this, omit the --generate-entrypoint argument from the qmlprojectexporter call.

In the Zephyr project, first declare the Qt Quick Ultralite application thread and assign a stack area for it:

k_tid_t QulThread;
static K_THREAD_STACK_DEFINE(qul_stack_area, 12288);

Note: The stack size depends on the complexity of the Qt Quick Ultralite application. The example value here is sufficient for the Qt Quick Ultralite Thermostat Demo.

Next, define the Qt Quick Ultralite application thread:

void Qul_Thread(void *arg1, void *arg2, void *arg3)
{
    (void) arg1;
    (void) arg2;
    (void) arg3;

    Qul::Application app;
    static MainScreen item;
    app.setRootItem(&item);
    while (true)
        app.update();
}

Note: MainScreen assumes the main qml file in your Qt Quick Ultralite application is MainScreen.qml. Change the item name to match the main qml filename in your application.

After this, create the Qt Quick Ultralite application thread:

struct k_thread qul_thread_data;

QulThread = k_thread_create(&qul_thread_data,
                            qul_stack_area,
                            K_THREAD_STACK_SIZEOF(qul_stack_area),
                            Qul_Thread,
                            NULL,
                            NULL,
                            NULL,
                            4,
                            0,
                            K_NO_WAIT);

Note: Ensure you have initialized the hardware before running the Qt Quick Ultralite application.

A complete Zephyr project main.cpp spawning a Qt Quick Ultralite application thread can, for example, look like this:

#include "MainScreen.h"

#include <zephyr/kernel.h>

k_tid_t QulTask;
static K_THREAD_STACK_DEFINE(qul_stack_area, 12288);

static void Qul_Thread(void *arg1, void *arg2, void *arg3);

int main()
{
    Qul::initHardware();
    Qul::initPlatform();

    struct k_thread qul_thread_data;

    QulThread = k_thread_create(&qul_thread_data,
                                qul_stack_area,
                                K_THREAD_STACK_SIZEOF(qul_stack_area),
                                Qul_Thread,
                                NULL,
                                NULL,
                                NULL,
                                4,
                                0,
                                K_NO_WAIT);

    return 0;
}

static void Qul_Thread(void *arg1, void *arg2, void *arg3)
{
    (void) arg1;
    (void) arg2;
    (void) arg3;

    Qul::Application app;
    static MainScreen item;
    app.setRootItem(&item);
    while (true)
        app.update();
}

Using a custom-built Qt Quick Ultralite with a Zephyr platform

To build Qt Quick Ultralite from sources, follow the instructions in Building Qt Quick Ultralite from sources.

mkdir -p <builddir>/.cmake/api/v1/query && touch <builddir>/.cmake/api/v1/query/codemodel-v2
cmake
    -S $QUL_ROOT
    -B <builddir>
    -DQul_ROOT=$QUL_ROOT
    -DQUL_TARGET_TOOLCHAIN_DIR=<toolchaindir>
    -DQUL_GENERATORS=$QUL_ROOT/lib/cmake/Qul/QulGenerators.cmake
    -DCMAKE_TOOLCHAIN_FILE=$QUL_ROOT/lib/cmake/Qul/toolchain/armgcc.cmake
    -DQUL_PLATFORM_ARCHITECTURE=cortex-m7-hf-fpv5-d16
    -DQUL_BUILD_PLATFORM=OFF
    -DCMAKE_INSTALL_PREFIX=<installdir>
    -DQUL_PLATFORM_ARCHITECTURE_FILE=$QUL_ROOT/platform/architecture/cortex-m7-hf-fpv5-d16/armgcc/architecture.cmake
make -C <builddir> install

When the build is finished, you can export the Qt Quick Ultralite application with the built libraries:

$QUL_ROOT/bin/qmlprojectexporter
    $QUL_ROOT/examples/chess/mcu_chess.qmlproject
    --boarddefaults=<path/to/>BoardDefaults_24bpp_default.qmlprojectconfig
    --toolchain GNU
    --cxx-standard=c++17
    --platform <platform_name>
    --outdir <target_dir>
    --qul-source-dir <installdir>
    --project-type cmake
    --no-export-platform
    --generate-entrypoint
    --platform-metadata <installdir>/lib/cortex-m7-hf-fpv5-d16-export.json

After the export, add the exported CMake project to the Zephyr project.

Available under certain Qt licenses.
Find out more.