使用 Qt Quick Ultralite 采用 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.

支持的体系结构、平台及 FreeRTOS 版本

注意: FreeRTOS 支持目前是技术预览。

Qt Quick Ultralite 支持下列硬件:

硬件板 MCU 体系结构 编译器 支持的 FreeRTOS
NXP IMXRT1050-EVKB MIMXRT1052DVL6A ARM Cortex-M7 用于 Arm V8.50 的 GNU Arm 8,IAR 构建工具 FreeRTOS V10.0.1 (技术预览)
NXP IMXRT1064-EVK MIMXRT1064DVL6A ARM Cortex-M7 用于 Arm V8.50 的 GNU Arm 8,IAR 构建工具 FreeRTOS V10.0.1 (技术预览)
STM32F769I-DISCOVERY STM32F769NI ARM Cortex-M7 用于 Arm V8.50 的 GNU Arm 8,IAR 构建工具 FreeRTOS V10.0.1 (技术预览)
STM32F7508-DISCOVERY STM32F7508 ARM Cortex-M7 用于 Arm V8.50 的 GNU Arm 8,IAR 构建工具 FreeRTOS V10.0.1 (技术预览)

这些参考板是支持的在 Qt Standard Support .

开发环境要求

先决条件

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

环境设置

Based on the board you are using, set the platform-specific environment variables as defined in NXP (恩智浦) 快速入门 and 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 the FreeRTOS suffix for the platform name to generate FreeRTOS project build files.

范例:

cmake .. -G "Ninja" -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=%Qul_DIR%\lib\cmake\Qul\toolchain\armgcc.cmake -DQUL_PLATFORM=<target platform>-freertos
							

For per device environment setups on supported platforms, see hardware board links in 支持的体系结构、平台及 FreeRTOS 版本 .

如何在 FreeRTOS 中使用 Qt Quick Ultralite

获取用于 FreeRTOS 的 Qt Quick Ultralite

For 支持平台 the Qt Quick Ultralite installation comes with FreeRTOS support out of the box. To compile an example for FreeRTOS, see 使用 app_common .

启动 Qt Quick Ultralite 线程

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.

    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 Qt Quick Ultralite 应用程序的入口点 .

另请参阅 范例 main.cpp 为在 FreeRTOS 任务中运行 Qt Quick Ultralite .

提供内存分配器

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 了解更多信息。

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 ,和 realloc 。若 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
#include <assert.h>
extern void *pvPortMalloc(size_t xWantedSize);
extern void vPortFree(void *pv);
void *malloc(size_t sz)
{
    void *ptr = pvPortMalloc(sizeof(size_t) + sz);
    assert(ptr != NULL);
    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.

注意: 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.

线程堆栈大小

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.

注意: FreeRTOS defines stack size in words, not bytes.

范例 main.cpp 为在 FreeRTOS 任务中运行 Qt Quick Ultralite

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);
}
static void Qul_Thread(void *argument)
{
    (void) argument;
    Qul::appMain();
}
							

其它应用程序与 Qt Quick Ultralite 交互

FreeRTOS 多任务范例 and 集成 C++ 代码采用 QML .

构建 FreeRTOS

FreeRTOS application build process .

FreeRTOS application build process for more information regarding app_common .

FreeRTOS 多任务范例

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.

import QtQuick 2.15
Rectangle {
    id: root
    Image {
        id: background
        source: "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: "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: FanControl.speed === 0 ? 0 : 5000 / (FanControl.speed * 3)
                running: FanControl.speed > 0 ? true : false
            }
        }
    }
    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

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>
#include "freertos_multitask.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);
}
static void Qul_Thread(void *argument)
{
    (void) argument;
    Qul::Application app;
    static freertos_multitask item;
    app.setRootItem(&item);
    app.exec();
}
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() 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::Application app;
    static freertos_multitask item;
    app.setRootItem(&item);
    app.exec();
}
							

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() 。见 FreeRTOS API Reference, xTaskNotifyWait .