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

Qt Quick Ultralite supports following hardware:

Hardware boardMCUArchitectureCompilerSupported FreeRTOS
EK-RA6M3GR7FA6M3AH3CFCARM Cortex-M4GNU Arm GCC 10.3-2021.10, IAR Build Tools for Arm V9.40FreeRTOS V10.4.6
NXP IMXRT1050-EVKBMIMXRT1052DVL6AARM Cortex-M7GNU Arm GCC 10.3-2021.10, IAR Build Tools for Arm V9.40FreeRTOS V10.0.1
NXP IMXRT1064-EVKMIMXRT1064DVL6AARM Cortex-M7GNU Arm GCC 10.3-2021.10, IAR Build Tools for Arm V9.40FreeRTOS V10.0.1
STM32F769I-DISCOVERYSTM32F769NIARM Cortex-M7GNU Arm GCC 10.3-2021.10, IAR Build Tools for Arm V9.40FreeRTOS V10.0.1
NXP IMXRT1170-EVKBMIMXRT1176DVMAAARM Cortex-M7 and ARM Cortex-M4GNU Arm GCC 10.3-2021.10, IAR Build Tools for Arm V9.40FreeRTOS V10.0.1

These reference boards are supported under the Qt Standard Support.

Note: If you are using the precompiled platform library for a given hardware platform then part of the FreeRTOS sources are already compiled into it. If you would like to change the FreeRTOS version then the platform library has to be rebuilt.

Development environment requirements

Prerequisites

In order to build Qt Quick Ultralite for FreeRTOS, you'll need following things:

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:

-DFREERTOS_DIR=< FreeRTOS directory path >

When running cmake, use the FreeRTOS suffix for the platform name to generate FreeRTOS project build files.

Example:

cmake .. -G "Ninja" -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_TOOLCHAIN_FILE=<Qul install path>/lib/cmake/Qul/toolchain/armgcc.cmake -DQUL_PLATFORM=<target platform>-freertos -DFREERTOS_DIR=<FreeRTOS directory path>
cmake .. -G "Ninja" -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_TOOLCHAIN_FILE=<Qul install path>\lib\cmake\Qul\toolchain\armgcc.cmake -DQUL_PLATFORM=<target platform>-freertos -DFREERTOS_DIR=<FreeRTOS directory path>

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:

  1. 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.

    See Changing heap policies for information about different memory allocation implementations in FreeRTOS and how to use them in Qt Quick Ultralite projects.

  2. 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 Running Qt Quick Ultralite in applications.

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 QUL_FREERTOS_HEAP_POLICY target property in your project. See Changing heap policies for more information.

The memory allocators provided with FreeRTOS implement pvPortMalloc and vPortFree. These allocators are used internally by the Qt Quick Ultralite platform ports and in most cases they should be used by application code as well. It is possible to configure separate heap areas for the standard library and the FreeRTOS allocators in the linker configuration.

One way of using FreeRTOS heap allocators in your application is to overload the default implementation of malloc, free, and realloc. If the new and delete C++ keywords are using malloc and free internally, your application need not provide separate overloads.

Example C code for memory allocation functions including realloc:

#include <FreeRTOS.h>
#include <portable.h>
#if defined(__ICCARM__)
#include <string.h>
#else
#include <memory.h>
#endif

extern void *pvPortMalloc(size_t xWantedSize);
extern void vPortFree(void *pv);

void *malloc(size_t sz)
{
    void *ptr = pvPortMalloc(sizeof(size_t) + sz);

    if (ptr == NULL) {
        return NULL;
    }

    *((size_t *) ptr) = sz;
    return ((char *) ptr) + sizeof(size_t);
}

void free(void *p)
{
    if (p != NULL) {
        vPortFree(((char *) p) - sizeof(size_t));
    }
}

void *realloc(void *ptr, size_t sz)
{
    if (ptr == NULL)
        return malloc(sz);

    size_t oldSize = *(size_t *) ((unsigned long) ptr - sizeof(size_t));

    if (sz == oldSize)
        return ptr;

    void *newPtr = NULL;

    if (sz > 0) {
        newPtr = malloc(sz);
        memcpy(newPtr, ptr, (sz > oldSize) ? oldSize : sz);
    }
    free(ptr);
    return newPtr;
}

Qt Quick Ultralite is providing a default allocator override that can be enabled for your application in CMakeLists.txt by adding a property to your application:

qul_add_target(my_application …)
set_target_properties(my_application PROPERTIES FREERTOS_PROVIDE_MEMORY_ALLOCATOR_OVERLOADS TRUE)

The property needs to be set before calling app_target_setup_os. It adds C and C++ allocator overloads to the executable.

Note: The provided allocator overloads by Qt Quick Ultralite are not compatible with the FreeRTOS heap_3.c because its pvPortMalloc function is using malloc internally.

Note: Due to internal malloc calls, some functions such as printf, may allocate memory that might have already been allocated by FreeRTOS. This leads to unexpected behaviour, which can be avoided by enabling overloads in app_common.

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 <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) {
        configASSERT(false); // Task creation failed
    }

    vTaskStartScheduler();

    configASSERT(false);
}

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.

See FreeRTOS application build process for more information regarding app_common.

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. The application has three tasks:

  • Qul_Thread runs the Qt Quick Ultralite exec() loop for the QML application instance and notifies other tasks about touch events using HardwareControl methods.
  • Led_Thread performs the blinking of LED and updates the QML application(Qt Quick Ultralite thread) with the latest LED blink count.
  • FanControl_Thread recalculates the rotation period for the fan animation and updates the QML application(Qt Quick Ultralite thread).

HardwareControl Class

HardwareControl is a Qul::Singleton class that provides an interface for the QML application to interact with other threads. The class handles communication with Led_Thread (using FreeRTOS task notifications) and FanControl_Thread (using FreeRTOS queues).

class HardwareControl : public Qul::Singleton<HardwareControl>
{
public:
    HardwareControl();
    Qul::Property<int> fanSpeed;
    Qul::Property<int> ledCycleCount;
    Qul::Signal<void(int rotationPeriod)> fanRotationPeriodChanged;
    void updateSpeed(int newSpeed);

private:
    void updateLedSpeed();
    void updateFanSpeed();
...

Here, we first set a new value to speed property. After that we call updateFanSpeed and updateLedSpeed() to update the task about the change in speed on onPressed events from the QML application.

void HardwareControl::updateSpeed(int newSpeed)
{
    fanSpeed.setValue(newSpeed);
    updateFanSpeed();
    updateLedSpeed();
}

The LED blinking speed is updated using the updateSpeed() method:

void HardwareControl::updateLedSpeed()
{
    xTaskNotify(LedTask, fanSpeed.value(), eSetValueWithOverwrite);
}

The QML application requests the new fan rotation period with the call to updateFanSpeed():

void HardwareControl::updateFanSpeed()
{
    xQueueSend(getFanControlQueueHandle(), (void *) &(fanSpeed.value()), portMAX_DELAY);
}

qul_thread.cpp

The Qt Quick Ultralite thread creates the application instance and runs the exec() loop.

...
void Qul_Thread(void *argument)
{
    (void) argument;
    Qul::Application app;
    static freertos_multitask item;
    app.setRootItem(&item);
    app.exec();
}

The qul_thread.cpp source file also implements the function postEventsToUI(). This function is used by other threads to to send events to modify the Qul::Property fanSpeed and Qul::Property ledCycleCount QML properties.

void postEventsToUI(HardwareEvent &event)
{
    static HardwareControlEventQueue eventQueue;
    eventQueue.postEvent(event);
}

These events are processed by the onEvent() callback as follows:

void HardwareControlEventQueue::onEvent(const HardwareEvent &event)
{
    if (event.id == HardwareEventId::LedCycleCount)
        HardwareControl::instance().ledCycleCount.setValue(event.data);
    else if (event.id == HardwareEventId::FanRotationPeriod)
        HardwareControl::instance().fanRotationPeriodChanged(event.data);
}

Note: It is not thread-safe to update the Qul property values directly from other threads. Instead, use Qul::EventQueue as shown in the code snippets above.

led_thread.cpp

On startup, the LED thread waits on a FreeRTOS task notification indefinitely. On receiving touch event from the Qt Quick Ultralite thread, it unblocks and updates the LED blinking speed. After the first event, it blinks the LED with the newly calculated speed value.

void Led_Thread(void *argument)
{
...
    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 LED thread also calculates the number of blink counts and sends this information to the QML application using the function postEventsToUI() based on Qul::EventQueue. This count is updated on the screen by the QML application.

        ledEvent.id = HardwareEventId::LedCycleCount;
        ledEvent.data = ledCycleCount;
        postEventsToUI(ledEvent);
        taskYIELD();
    }

fan_thread.cpp

The fan control thread waits on an FreeRTOS event queue to update the fan speed on touch events from the Qt Quick Ultralite thread. It recalculates the rotationPeriod for the fan animation and sends this value to UI with the help of postEventsToUI() function. The UI updates the animation speed based on the rotationPeriod value.

...
void FanControl_Thread(void *argument)
{
    (void) argument;
    int newSpeed;
    HardwareEvent fanEvent;

    while (true) {
        if (xQueueReceive(fanControlQueue, &newSpeed, portMAX_DELAY) == pdTRUE) {
            int rotationPeriod = newSpeed == 0 ? 0 : 5000 / (newSpeed * 3);
            fanEvent.id = HardwareEventId::FanRotationPeriod;
            fanEvent.data = rotationPeriod;
            postEventsToUI(fanEvent);
        }

freertos_multitask.qml

freertos_multitask.qml declares the UI which is shown on the device's screen.

import QtQuick 2.15

Rectangle {
    id: root

    Image {
        id: background
        source: "images/background-dark.png"
        anchors.fill: root
    }

    Column {
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.top: parent.top
        anchors.topMargin: 10

        Text {
            anchors.horizontalCenter: parent.horizontalCenter
            horizontalAlignment: Text.AlignHCenter
            color: "white"
            text: "Speed: " + HardwareControl.fanSpeed
            font.pixelSize: 34
        }

        Text {
            topPadding: 10
            anchors.horizontalCenter: parent.horizontalCenter
            horizontalAlignment: Text.AlignHCenter
            color: "silver"
            text: "LED cycle count:"
            font.pixelSize: 17
        }

        Text {
            anchors.horizontalCenter: parent.horizontalCenter
            horizontalAlignment: Text.AlignHCenter
            color: "white"
            text: HardwareControl.ledCycleCount
            font.pixelSize: 17
            font.bold: true
        }
    }

    Text {
        horizontalAlignment: Text.AlignHCenter
        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: "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 {
                id: imageRotation
                loops: Animation.Infinite
                from: 0
                to: 360
                duration: 0
                running: false
            }
        }
    }

    MouseArea {
        id: ta
        anchors.fill: parent
        onPressed: {
            HardwareControl.updateSpeed((HardwareControl.fanSpeed + 1) % 6);
        }
    }

    HardwareControl.onFanRotationPeriodChanged: {
        imageRotation.duration = rotationPeriod
        imageRotation.running = rotationPeriod > 0
    }

    Component.onCompleted: { HardwareControl.updateSpeed(HardwareControl.fanSpeed) }
}

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 and creation of Qt Quick Ultralite, LED and fan control threads.

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

    initFanControlQueue();

    if (xTaskCreate(Qul_Thread, "QulExec", QUL_STACK_SIZE, 0, 4, &QulTask) != pdPASS) {
        Qul::PlatformInterface::log("Task creation failed!.\r\n");
        configASSERT(false);
    }

    if (xTaskCreate(Led_Thread, "LedToggle", configMINIMAL_STACK_SIZE, 0, 4, &LedTask) != pdPASS) {
        Qul::PlatformInterface::log("LED task creation failed!.\r\n");
        configASSERT(false);
    }

    if (xTaskCreate(FanControl_Thread, "FanControl", configMINIMAL_STACK_SIZE, 0, 4, &FanControlTask) != pdPASS) {
        Qul::PlatformInterface::log("Fan control task creation failed!.\r\n");
        configASSERT(false);
    }

    vTaskStartScheduler();

    // Should not reach this point
    return 1;
}

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) {
        Qul::PlatformInterface::log("Task creation failed!.\r\n");
        configASSERT(false);
    }

    if (xTaskCreate(Led_Thread, "LedToggle", configMINIMAL_STACK_SIZE, 0, 4, &LedTask) != pdPASS) {
        Qul::PlatformInterface::log("LED task creation failed!.\r\n");
        configASSERT(false);
    }

    if (xTaskCreate(FanControl_Thread, "FanControl", configMINIMAL_STACK_SIZE, 0, 4, &FanControlTask) != pdPASS) {
        Qul::PlatformInterface::log("Fan control task creation failed!.\r\n");
        configASSERT(false);
    }

We use the function xTaskCreate() to create thread for Qt Quick Ultralite main loop, fan control thread and LED blinking thread. All of these threads uses priority 4. The stack size of the Qt Quick Ultralite thread is QUL_STACK_SIZE which is defined in FreeRTOSConfig.h as 32*1024 words. Led thread 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.

Available under certain Qt licenses.
Find out more.