C
Using Qt Quick Ultralite with FreeRTOS
FreeRTOS is a real-time operating system kernel designed for embedded devices and microcontroller platforms. It provides threads (tasks in FreeRTOS), mutexes, semaphores and software timers.
This guide tells you what is needed to start developing Qt Quick Ultralite with FreeRTOS, Qt Quick Ultralite + background information on FreeRTOS.
Supported architectures, platforms and FreeRTOS versions
Note: FreeRTOS support is currently in technological preview.
Qt Quick Ultralite supports following hardware:
Hardware board | MCU | Architecture | Compiler | Supported FreeRTOS |
---|---|---|---|---|
NXP IMXRT1050-EVKB | MIMXRT1052DVL6A | ARM Cortex-M7 | gcc-arm 8 | FreeRTOS V10.0.1 (technological preview) |
NXP IMXRT1064-EVK | MIMXRT1064DVL6A | ARM Cortex-M7 | gcc-arm 8 | FreeRTOS V10.0.1 (technological preview) |
STM32F769I-DISCOVERY | STM32F769NI | ARM Cortex-M7 | gcc-arm 8 | FreeRTOS V10.0.1 (technological preview) |
STM32F7508-DISCOVERY | STM32F7508 | ARM Cortex-M7 | gcc-arm 8 | FreeRTOS V10.0.1 (technological preview) |
These reference boards are supported under the Qt Standard Support.
Development environment requirements
Prerequisites
In order to build Qt Quick Ultralite for FreeRTOS, you'll need following things:
- Qt Quick Ultralite FreeRTOS packages installed. See Obtaining Qt Quick Ultralite for FreeRTOS.
- FreeRTOS sources. You can get FreeRTOS from the FreeRTOS website.
- Prerequisites for Qt Quick Ultralite and target platform (see Supported Target Boards and Development Hosts).
Environment setup
Based on the board you are using, set the platform-specific environment variables as defined in Getting started on NXP and Getting started on STM.
If you are using app_common
you'll also need to setup the path to FreeRTOS sources. Either:
STM32F7_FREERTOS_DIR=< FreeRTOS directory path >
IMXRT1050_FREERTOS_DIR=< FreeRTOS directory path >
IMXRT1064_FREERTOS_DIR=< FreeRTOS directory path >
Depending on the target platform.
When running cmake, use -DOS=FreeRTOS
to generate FreeRTOS project build files.
Example:
cmake .. -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=%Qul_DIR%\lib\cmake\Qul\toolchain\armgcc.cmake -DQUL_PLATFORM=<target platform> -DOS=FreeRTOS
For per device environment setups on supported platforms, see hardware board links in Supported architectures, platforms and FreeRTOS versions.
How to use Qt Quick Ultralite on FreeRTOS
Obtaining Qt Quick Ultralite for FreeRTOS
For supported platforms the Qt Quick Ultralite installation comes with FreeRTOS support out of the box. To compile an example for FreeRTOS, see using app_common
.
Starting Qt Quick Ultralite thread
You'll need two functions to be called to get Qt Quick Ultralite running on target:
Qul::initPlatform();
This function initializes the platform hardware and the operating system. This should be called as early as possible but at latest before
Qul::appMain()
is run or any device specific function is accessed.In FreeRTOS' case the OS initialization only loads the heap region mapping if
heap_5
is used. See Changing heap policies for information about different memory allocation implementations in FreeRTOS and how to use them in Qt Quick Ultralite projects.Qul::appMain();
This function initializes and also acts as a main loop for Qt Quick Ultralite. In FreeRTOS, this should be run from a task.
For more information regarding setting up custom entry point, see Entry point to Qt Quick Ultralite application.
See also Example main.cpp
for running Qt Quick Ultralite in a FreeRTOS task.
Providing memory allocator
FreeRTOS offers five different memory allocator implementations by default. They are located in FreeRTOS' MemMang directory and provide different strategies for heap management. See FreeRTOS developer docs, Memory management for more information about different implementations.
If you are using app_common
in your project, you can change the used implementation by setting FREERTOS_HEAP_POLICY
target property in your project. See Changing heap policies for more information.
Qt Quick Ultralite OSAL provides malloc, realloc and free overwrites for FreeRTOS. More info about OSAL can be found from OSAL description.
Thread stack size
In FreeRTOS each individual thread (or task) has its own stack, The amount of stack Qt Quick Ultralite needs is largely dependent on the complexity of the project. For example, most examples and demos use a stack size of 32 kilowords.
Note: FreeRTOS defines stack size in words, not bytes.
Example main.cpp
for running Qt Quick Ultralite in a FreeRTOS task
The following code shows how to create a basic FreeRTOS thread for Qt Quick Ultralite and run it.
#include <qul/qul.h> #include <cstdio> #include <FreeRTOS.h> #include <task.h> static void Qul_Thread(void *argument); int main() { Qul::initPlatform(); if (xTaskCreate(Qul_Thread, "QulExec", 32*1024, 0, 4, 0) != pdPASS) { std::printf("Task creation failed!.\r\n"); configASSERT(false); } vTaskStartScheduler(); configASSERT(false); return 0; } static void Qul_Thread(void *argument) { (void) argument; Qul::appMain(); }
Interacting with Qt Quick Ultralite from other applications
See FreeRTOS Multitasking Example and Integrating C++ code with QML.
Building FreeRTOS
See FreeRTOS application build process.
How to use QUL_OS
target property
QUL_OS
property is used to tell Qt Quick Ultralite runtime the targeted operating system. This determines:
- The main.cpp that is used for the application if the default entrypoint is used.
- What operating system functions Qt Quick Ultralite should use, such as initialization, SysTick interrupt content and memory management.
- In case
heap_5
policy is used, heap regions for supported targets are also defined and automatically brought in.
To use QUL_OS
, you'll need to set QUL_OS
target property in your project's CMakeLists.txt
:
set_target_properties(<target_name> PROPERTIES QUL_OS <Operating_System>)
Where
- <target_name> is the name of your compile target, usually an executable.
- <Operating_System> is the OS you are using. Possible values are BareMetal and FreeRTOS.
If QUL_OS
is not defined, Qt Quick Ultralite assumes baremetal to be the target.
If you are using app_common
, QUL_OS
is set automatically in app_target_setup_os(<project_name>)
. See FreeRTOS application build process for more information regarding app_common
.
OSAL description
Qt Quick Ultralite build system uses the supplied QUL_OS
target property to determine whether to build for bare metal or for FreeRTOS.
OS Abstraction layer contains following functionalities:
- OS kernel initialization
- SysTick interrupt handling
- OS compliant main.cpp
- Memory allocation overwrites
- Stack usage calculation
In addition, OSAL also includes heap_regions.c
file, which is responsible for providing heap regions of each supported device. These regions are used by FreeRTOS to allocate heap over different RAM regions. It is used only in FreeRTOS heap_5
implementation. See FreeRTOS Memory Management, heap_5
FreeRTOS Multitasking Example
Running multiple tasks with FreeRTOS and Qt Quick Ultralite.
This example demonstrates how to create multiple tasks that interact with each other. One task is running Qt Quick Ultralite while other is blinking LED on a device. The LED blinking speed is controlled from Qt Quick Ultralite thread.
FanControl
Class
FanControl is a Qul::Singleton
class that provides an interface for QML to interact with device's LED. The class handles LED blinking speed and communication between Qt Quick Ultralite and LED blinking tasks.
class FanControl : public Qul::Singleton<FanControl> { public: FanControl(); Qul::Property<int> speed; void updateSpeed(int newSpeed); private: void updateLedSpeed(); };
The LED blinking speed is updated when QML calls updateSpeed()
:
void FanControl::updateSpeed(int newSpeed) { speed.setValue(newSpeed); updateLedSpeed(); }
Here we first set a new value to speed
property. After that we call updateLedSpeed()
which handles notificating LED blinking task of the speed change:
void FanControl::updateLedSpeed() { #ifndef NO_LED xTaskNotify(LedTask, speed.value(), eSetValueWithOverwrite); #endif }
The communication with LED blinking task is done by simply calling FreeRTOS' xTaskNotify()
with the new speed value. The notification implementation is in main.cpp. For xTaskNotify()
documentation, see FreeRTOS API Reference, xTaskNotify.
Main.qml
Main.qml
declares the UI which is shown on device's screen.
pragma main import QtQuick Rectangle { id: root Image { id: background source: "qrc:///images/background-dark.png" anchors.fill: root } Row { anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: 10 Text { color: "white" text: "Speed: " font.pixelSize: 30; } Text { color: "white" text: FanControl.speed font.pixelSize: 30; } } Text { anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: 10 color: "gray" text: "Tap to change fan and LED speed!" } Image { id: fan source: "qrc:///images/fan-off.png" anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter transform: Rotation { origin.x: fan.width / 2 origin.y: fan.height / 2 RotationAnimation on angle { loops: Animation.Infinite from: 0 to: 360 duration: 5000 / (FanControl.speed * 3) } } } MouseArea { id: ta anchors.fill: parent onPressed: { FanControl.updateSpeed((FanControl.speed + 1) % 6); } } }
BoardUtils
BoardUtils is a namespace where we declare functions that initialize and control target board's LED:
namespace BoardUtils { void initLED(); void toggleLED(); } // namespace BoardUtils
The definitions of these functions vary per device. The following example is from the implementation for STM32F769I-DISCOVERY.
namespace BoardUtils { void initLED() { BSP_LED_Init(LED1); } void toggleLED() { BSP_LED_Toggle(LED1); } } // namespace BoardUtils
main.cpp
The main.cpp
contains hardware initialization, creation of Qt Quick Ultralite and LED blinking tasks and the main loop of the program:
#include <qul/qul.h> #include <cstdio> #include <climits> #include <FreeRTOS.h> #include <task.h> #include <board_utils/led.h> static void Qul_Thread(void *argument); static void Led_Thread(void *argument); #ifndef QUL_STACK_SIZE #error QUL_STACK_SIZE must be defined. #endif TaskHandle_t QulTask = NULL, LedTask = NULL; int main() { Qul::initPlatform(); BoardUtils::initLED(); if (xTaskCreate(Qul_Thread, "QulExec", QUL_STACK_SIZE, 0, 4, &QulTask) != pdPASS) { std::printf("Task creation failed!.\r\n"); configASSERT(false); } if (xTaskCreate(Led_Thread, "LedToggle", configMINIMAL_STACK_SIZE, 0, 4, &LedTask) != pdPASS) { std::printf("LED task creation failed!.\r\n"); configASSERT(false); } vTaskStartScheduler(); // Should not reach this point configASSERT(false); return 0; } static void Qul_Thread(void *argument) { (void) argument; Qul::appMain(); } static void Led_Thread(void *argument) { (void) argument; uint32_t speed = 1; uint32_t newSpeed = 0; while (true) { const TickType_t ticks = speed > 0 ? (350 / (portTICK_PERIOD_MS * speed)) : portMAX_DELAY; if (xTaskNotifyWait(0, ULONG_MAX, &newSpeed, ticks) == pdTRUE) { speed = newSpeed; } BoardUtils::toggleLED(); } }
The first function calls are for hardware initialization:
Qul::initPlatform(); BoardUtils::initLED();
We first initialize the board with Qul::initPlatform()
. The BoardUtils::initLED()
function is used to initialize a board specific LED for blinking.
if (xTaskCreate(Qul_Thread, "QulExec", QUL_STACK_SIZE, 0, 4, &QulTask) != pdPASS) { std::printf("Task creation failed!.\r\n"); configASSERT(false); } if (xTaskCreate(Led_Thread, "LedToggle", configMINIMAL_STACK_SIZE, 0, 4, &LedTask) != pdPASS) { std::printf("LED task creation failed!.\r\n"); configASSERT(false); }
We use function xTaskCreate()
to create tasks for Qt Quick Ultralite main loop and LED blinking loop. Both tasks are using priority 4. The stack size of Qt Quick Ultralite task is QUL_STACK_SIZE
which is defined in FreeRTOSConfig.h
as 32*1024 words. Led task is configured to have a stack size of configMINIMAL_STACK_SIZE
which is also configured in FreeRTOSConfig.h
and is in STM32F769I-DISCOVERY's case 128 words. For detailed information about xTaskCreate()
function, see FreeRTOS API Reference, xTaskCreate.
vTaskStartScheduler();
Calling this starts the FreeRTOS scheduler which then schedules previously created threads. See FreeRTOS API Reference, vTaskStartScheduler.
static void Qul_Thread(void *argument) { (void) argument; Qul::appMain(); }
Qul_Thread
function defines contents of the Qt Quick Ultralite task. Qul::appMain
initializes Qt Quick Ultralite and contains the main loop for Qt Quick Ultralite functionality.
static void Led_Thread(void *argument) { (void) argument; uint32_t speed = 1; uint32_t newSpeed = 0; while (true) { const TickType_t ticks = speed > 0 ? (350 / (portTICK_PERIOD_MS * speed)) : portMAX_DELAY; if (xTaskNotifyWait(0, ULONG_MAX, &newSpeed, ticks) == pdTRUE) { speed = newSpeed; } BoardUtils::toggleLED(); } }
Led_Thread
defines contents of the LED blinking task. After initializing speed
to 1 and newSpeed
to 0, we enter an infinite loop where LED blinking occurs. First we determine the amount of ticks
xTaskNotifyWait()
waits for a notification before the LED is toggled using BoardUtils::toggleLED()
. See FreeRTOS API Reference, xTaskNotifyWait.
Available under certain Qt licenses.
Find out more.