C

Qt Quick Ultralite multitask Example

Shows how the communication between Qt Quick Ultralite and the background RTOS thread can be implemented.

Overview

The multitask example demonstrates how multiple RTOS threads can communicate with each other. It shows how events from QML can be used to control other threads' activities and how non-GUI threads can trigger updates to the QML properties. There are three independent threads running: one is responsible for running Qt Quick Ultralite, one for blinking the onboard LED, and one for calculating the fan rotation period on the request of the QML application. It has a simple QML UI where a fan image is spinning at the center of the screen while the onboard LED is blinking. The screen also displays the LED blink count (the total number of times the LED has blinked). By tapping the screen, the user can change the fan speed rotation and LED blinking frequency.

Target platforms

Project structure

The project structure is divided into directories:

  • board_utils - static library that implements LED control routines for a given platform.
  • freertos - static library that provides FreeRTOS kernel for a given platform.
  • images - graphical resources used by the project.
  • src - C++ source code used by the application.
    • desktop - C++ source code for the desktop version of the application.
    • freertos - C++ source code of the FreeRTOS port.
    • zephyr - C++ source code of the Zephyr® port.

Code overview

CMake project file

The main CMake file checks whether an example is built for one of the supported platforms by including a freertos directory. A freertos CMake target is defined only for supported platforms. When building for a desktop backend, a simplified multitask_desktop application is built.

Note: The build process for Zephyr® does not use the CMake project file here. See Using Qt Quick Ultralite with Zephyr®.

...
add_subdirectory(freertos)

if(TARGET freertos_kernel) # FreeRTOS support implemented for this platform
    add_subdirectory(board_utils)

    qul_add_target(multitask
         src/freertos/main.cpp
         src/freertos/hardwarecontrol.cpp
         src/freertos/threads/led_thread.cpp
         src/freertos/threads/qul_thread.cpp
         src/freertos/threads/fan_thread.cpp
         QML_PROJECT
         mcu_multitask.qmlproject
    )

    target_compile_definitions(multitask PRIVATE FREERTOS)
    target_include_directories(multitask PRIVATE src src/freertos/threads)
...
elseif(NOT CMAKE_CROSSCOMPILING) # No FreeRTOS here - fallback for building on desktop platform
    qul_add_target(multitask_desktop
        src/desktop/hardwarecontrol.cpp
        QML_PROJECT
        mcu_multitask.qmlproject
        GENERATE_ENTRYPOINT
    )
    target_compile_definitions(multitask_desktop PRIVATE DESKTOP)
    target_include_directories(multitask_desktop PRIVATE src)
...
else()
    message(STATUS "Skipping generating target: multitask")
endif()
BoardUtils library

This library provides the most basic, hardware-specific implementation of the LED control. It ships a simplistic API to initialize and toggle the LED on a supported board. The board_utils/include/board_utils/led.h contains the API of the library.

...
namespace BoardUtils {
void initLED();
void toggleLED();
} // namespace BoardUtils
Application entry point

The main.cpp source file is used only in FreeRTOS and Zephyr® ports (it's not used when building for desktop).

The main() function initializes the Qt Quick Ultralite platform, the hardware LED for the LED control thread, and the FreeRTOS queue for the fan control thread.

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

    initFanControlQueue();
...

Next, the FreeRTOS tasks are created for LED control, Qt Quick Ultralite engine and fan control.

    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();
    ...

The main() function initializes the Qt Quick Ultralite platform, the hardware LED for the LED control thread, and the Zephyr® queues for the fan and the LED control threads.

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

    initFanControlQueue();
    initLedControlQueue();
...

Next, the Zephyr® threads are created for Qt Quick Ultralite engine, LED control and fan control. After this, the main thread exits as it is not used anymore.

    struct k_thread QulTaskData, LedTaskData, FanControlTaskData;

    QulTask = k_thread_create(&QulTaskData,
                              qul_stack_area,
                              K_THREAD_STACK_SIZEOF(qul_stack_area),
                              Qul_Thread,
                              NULL,
                              NULL,
                              NULL,
                              4,
                              0,
                              K_NO_WAIT);
    LedTask = k_thread_create(&LedTaskData,
                              led_stack_area,
                              K_THREAD_STACK_SIZEOF(led_stack_area),
                              Led_Thread,
                              NULL,
                              NULL,
                              NULL,
                              4,
                              0,
                              K_NO_WAIT);
    FanControlTask = k_thread_create(&FanControlTaskData,
                                     fancontrol_stack_area,
                                     K_THREAD_STACK_SIZEOF(fancontrol_stack_area),
                                     FanControl_Thread,
                                     NULL,
                                     NULL,
                                     NULL,
                                     4,
                                     0,
                                     K_NO_WAIT);

    // Exit the main thread because it is not needed anymore.
    return 0;
    ...
Qt Quick Ultralite thread

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 multitask item;
    app.setRootItem(&item);
    app.exec();
}
...
void Qul_Thread(void *arg1, void *arg2, void *arg3)
{
    (void) arg1;
    (void) arg2;
    (void) arg3;

    Qul::Application app;
    static 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 send events to modify the Qul::Property fanSpeed and Qul::Property ledCycleCount QML properties.

void postEventsToUI(HardwareEvent &event)
{
    static HardwareControlEventQueue eventQueue;
    eventQueue.postEvent(event);
}
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);
}
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);
}

The HardwareControlEventQueue is derived from Qul::EventQueue as shown in the snippet below:

class HardwareControlEventQueue : public Qul::EventQueue<HardwareEvent>
{
    void onEvent(const HardwareEvent &event) override;
};

Note: It is not thread-safe to use the Qul property setValue() method directly from other threads. Instead, use Qul::EventQueue to post an event to QML interface object as shown in the code snippets above. Qul::EventQueue is implemented in the platform layer and it utilizes operating system-specific queues. For the provided Qt Quick Ultralite reference ports, Qul::EventQueue is implemented using native thread-safe RTOS queues.

LED thread

On startup, the LED thread waits on a FreeRTOS task notification. When the Qt Quick Ultralite thread processes a touch event, it sends a new speed value to the queue, which unblocks the LED thread and updates the blinking speed. After this, the LED thread blinks the LED according to the new 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();
...

On startup, the LED thread waits for a new LED speed value from a Zephyr® queue. When the Qt Quick Ultralite thread processes a touch event, it sends a new speed value to the queue, which unblocks the LED thread and updates the blinking speed. After this, the LED thread blinks the LED according to the new speed value.

void Led_Thread(void *arg1, void *arg2, void *arg3)
{
...
    while (true) {
        const k_timeout_t ticks = speed > 0 ? K_MSEC(350 / speed) : K_FOREVER;
        if (k_msgq_get(&ledControlQueue, &newSpeed, ticks) == 0) {
            speed = newSpeed;
        }
        BoardUtils::toggleLED();
...

The LED thread also calculates the number of blink counts and sends this information to the QML application using the postEventsToUI() function. The QML application updates this count on the screen.

...
        ledEvent.id = HardwareEventId::LedCycleCount;
        ledEvent.data = ledCycleCount;
        postEventsToUI(ledEvent);
        taskYIELD();
    }
}
...
        ledEvent.id = HardwareEventId::LedCycleCount;
        ledEvent.data = ledCycleCount;
        postEventsToUI(ledEvent);
        k_yield();
    }
}
Fan control thread

The fan control thread waits on an RTOS event queue to update the fan speed on touch events from the QML application. It recalculates the rotationPeriod for the fan animation and uses the postEventsToUI() function to send this value back to the QML application. The QML application updates the animation speed based on the rotationPeriod value.

...
void FanControl_Thread(void *argument)
{
...
    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);
        }
    }
}
...
void FanControl_Thread(void *arg1, void *arg2, void *arg3)
{
...
    while (true) {
        if (k_msgq_get(&fanControlQueue, &newSpeed, K_FOREVER) == 0) {
            int rotationPeriod = newSpeed == 0 ? 0 : 5000 / (newSpeed * 3);
            fanEvent.id = HardwareEventId::FanRotationPeriod;
            fanEvent.data = rotationPeriod;
            postEventsToUI(fanEvent);
        }
    }
}

Data flow diagram

The sequence diagram below summarizes how an event from the QML appöication affects the background thread that is in charge of the hardware.

Files:

Images:

See also Zephyr® application build process.

Available under certain Qt licenses.
Find out more.