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 board | MCU | Architecture | Compiler | Supported Zephyr version |
---|---|---|---|---|
NXP IMXRT1060-EVKB | MIMXRT1062DVL6B | ARM Cortex-M7 | GNU Arm GCC 12.3.rel1 | 3.6.0 |
NXP IMXRT1064-EVK | MIMXRT1064DVL6A | ARM Cortex-M7 | GNU Arm GCC 12.3.rel1 | 3.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 thetarget_*
calls afterfind_package(Zephyr)
. - When specifying
target_*
, useapp
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.