C

Qt Quick Ultralite freertos_multitask Example

Shows how the communication between Qt Quick Ultralite and the background FreeRTOS task can be implemented.

Overview

The freertos_multitask demonstrates how multiple FreeRTOS tasks can communicate with each other. It shows how events from QML can be used to control other tasks' activities and how non-GUI tasks can trigger updates to the QML properties. There are three independent tasks 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 fan speed rotation and LED blinking frequency.

Target platforms

Code overview

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.
    • mcu - C++ source code for MCU targets.

Code overview

CMake project file

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

...
add_subdirectory(freertos)

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

    qul_add_target(freertos_multitask
         src/mcu/main.cpp
         src/mcu/hardwarecontrol.cpp
         src/mcu/threads/led_thread.cpp
         src/mcu/threads/qul_thread.cpp
         src/mcu/threads/fan_thread.cpp
         QML_PROJECT
         mcu_freertos_multitask.qmlproject
    )

    target_compile_definitions(freertos_multitask PRIVATE FREERTOS)
    target_include_directories(freertos_multitask PRIVATE src src/mcu/threads)
...
elseif(NOT CMAKE_CROSSCOMPILING) # No FreeRTOS here - fallback for building on desktop platform
    qul_add_target(freertos_multitask_desktop
        src/desktop/hardwarecontrol.cpp
        QML_PROJECT
        mcu_freertos_multitask.qmlproject
        GENERATE_ENTRYPOINT
    )
    target_compile_definitions(freertos_multitask_desktop PRIVATE DESKTOP)
    target_include_directories(freertos_multitask_desktop PRIVATE src)
...
else()
    message(STATUS "Skipping generating target: freertos_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 for freertos_multitask target (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();
    ...
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 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 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);
}

The HardwarControlEventQueue 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 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 FreeRTOS, Qul::EventQueue is based on FreeRTOS queues, which makes it thread-safe.

LED thread

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 postEventsToUI() function. This count is updated on the screen by QML application.

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

The fan control thread waits on a FreeRTOS 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);
        }
    }
}

Data flow diagram

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

Files:

Images:

See also FreeRTOS application build process.

Available under certain Qt licenses.
Find out more.