C

使用 Qt Quick Ultralite 与 FreeRTOS

FreeRTOS 是为嵌入式设备和微控制器平台设计的实时操作系统内核。它提供了线程(FreeRTOS 中的任务)、互斥锁、信号量和软件计时器。

本指南告诉您开始使用 FreeRTOS 开发 Qt Quick Ultralite 所需的内容,以及关于 FreeRTOS 的相关信息。

支持的架构、平台和 FreeRTOS 版本

Qt Quick Ultralite 支持以下硬件

硬件板MCU架构编译器支持的 FreeRTOS
EK-RA6M3GR7FA6M3AH3CFCARM Cortex-M4GNU Arm GCC 12.3.rel1, IAR Build Tools for Arm V9.40FreeRTOS V10.6.1
NXP IMXRT1050-EVKBMIMXRT1052DVL6AARM Cortex-M7GNU Arm GCC 12.3.rel1, IAR Build Tools for Arm V9.40FreeRTOS V10.0.1
NXP IMXRT1064-EVKMIMXRT1064DVL6AARM Cortex-M7GNU Arm GCC 12.3.rel1, IAR Build Tools for Arm V9.40FreeRTOS V10.0.1
STM32F769I-DISCOVERYSTM32F769NIARM Cortex-M7GNU Arm GCC 12.3.rel1, IAR Build Tools for Arm V9.40FreeRTOS V10.0.1
NXP IMXRT1170-EVKBMIMXRT1176DVMAAARM Cortex-M7 和 ARM Cortex-M4GNU Arm GCC 12.3.rel1, IAR Build Tools for Arm V9.40FreeRTOS V10.0.1

这些参考板在 Qt 标准支持 之下得到支持。

注意:如果您正在使用给定硬件平台的预编译平台库,那么 FreeRTOS 源代码的某些部分已经编译到其中。如果您想更改 FreeRTOS 版本,则必须重新构建平台库。

开发环境要求

先决条件

要为 FreeRTOS 构建 Qt Quick Ultralite,您需要以下内容

环境设置

根据您使用的板子,设置在 在 NXP 上入门在 STM 上入门 中定义的平台特定环境变量。

如果你正在使用app_common,那么你还需要设置FreeRTOS源文件的路径

-DFREERTOS_DIR=< FreeRTOS directory path >

运行cmake时,使用平台名的FreeRTOS后缀来生成FreeRTOS项目构建文件。

示例

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>

关于在受支持的平台上进行每设备环境设置,请参阅受支持的架构、平台和FreeRTOS版本中的硬件板链接。

如何在FreeRTOS上使用Qt Quick Ultralite

获取FreeRTOS上的Qt Quick Ultralite

对于受支持的平台,Qt Quick Ultralite的安装附带了对FreeRTOS的支持。若要在FreeRTOS上编译示例,请参阅使用app_common

启动Qt Quick Ultralite线程

你需要调用两个函数以在目标上运行Qt Quick Ultralite

  1. Qul::initPlatform();

    此函数初始化平台硬件和操作系统。这应该在尽快的时候调用,但最迟不应该在Qul::appMain()运行或访问任何特定于设备的函数之前。

    有关FreeRTOS中不同内存分配实现以及如何在Qt Quick Ultralite项目中使用它们的信息,请参阅更改堆策略

  2. Qul::appMain();

    此函数初始化,同时也充当Qt Quick Ultralite的主循环。在FreeRTOS上,这应该在任务中运行。

有关设置自定义入口点的更多信息,请参阅在应用程序中运行Qt Quick Ultralite

请参阅使用FreeRTOS任务运行Qt Quick Ultralite的示例代码main.cpp

提供内存分配器

FreeRTOS默认提供了五种不同的内存分配器实现。它们位于FreeRTOS的MemMang目录中,并为堆管理提供了不同的策略。有关不同实现的信息,请参阅FreeRTOS开发文档,内存管理

如果你在项目中使用app_common,你可以通过在项目中将目标属性QUL_FREERTOS_HEAP_POLICY设置为来更改使用的实现。有关更多信息,请参阅更改堆策略

FreeRTOS提供的内存分配器实现了pvPortMallocvPortFree。这些分配器由Qt Quick Ultralite平台端口内部使用,并且在大多数情况下应该由应用程序代码使用。可以在链接器配置中为标准库和FreeRTOS分配器配置独立的堆区域。

在应用程序中使用FreeRTOS堆分配器的方法之一是重载默认的mallocfreerealloc实现。如果C++中的newdelete关键字内部使用mallocfree,则不需要为应用程序提供单独的重载。

包括realloc在内的内存分配函数的示例C代码

#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提供了默认的分配器重载,可以在CMakeLists.txt中通过向应用程序添加属性来启用,方法是在你的应用程序中添加属性

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

安装属性需要在调用app_target_setup_os之前设置。它向可执行文件添加C和C++分配器重载。

注意:提供的 Qt Quick Ultralite 分配器通过重载与 FreeRTOS 的 heap_3.c 不兼容,因为其内部的 pvPortMalloc 函数正在使用 malloc

注意:由于内部调用 malloc,一些函数如 printf 可能分配内存,这些内存已被 FreeRTOS 分配。这可能导致意外行为,可以通过在 app_common 中启用重载来避免。

线程堆栈大小

在 FreeRTOS 中,每个单独的线程(或任务)都有其自己的堆栈。Qt Quick Ultralite 需要的堆栈量主要取决于项目的复杂性。默认情况下,Qt Quick Ultralite 参考FreeRTOS平台使用 6 千字节的线程堆栈大小,相当于 24 KiB。

注意:FreeRTOS 使用字而不是字节来定义堆栈大小。

在 FreeRTOS 任务中运行 Qt Quick Ultralite 的 main.cpp 示例

以下代码展示了如何创建基本 FreeRTOS 线程用于 Qt Quick Ultralite 并运行它。

#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();
}

从其他应用程序与其他 Qt Quick Ultralite 交互

请参阅 FreeRTOS 多任务示例将 C++ 代码与 QML 集成

构建 FreeRTOS

请参阅 FreeRTOS 应用程序构建过程

有关对 app_common 的更多信息,请参阅 FreeRTOS 应用程序构建过程

FreeRTOS 多任务示例

使用 FreeRTOS 和 Qt Quick Ultralite 运行多个任务。

本例演示了如何创建相互交互的多个任务。应用程序有三个任务

  • Qul_Thread 运行 QML 应用实例的 Qt Quick Ultralite exec() 循环,并通过硬件控制方法通知其他任务触摸事件。
  • Led_Thread 演示 LED 闪烁并更新 QML 应用(Qt Quick Ultralite 线程)的最近 LED 闪烁计数。
  • FanControl_Thread 重新计算风扇动画的旋转周期,并更新 QML 应用(Qt Quick Ultralite 线程)。

HardwareControl

HardwareControl 是一个 Qul::Singleton 类,为 QML 应用提供与其他线程交互的接口。该类通过 FreeRTOS 任务通知处理与 Led_Thread 的通信,并使用 FreeRTOS 队列与 FanControl_Thread 进行通信。

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

在此,我们首先将新的值设置给 speed 属性。之后,我们调用 updateFanSpeedupdateLedSpeed() 来更新任务关于 QML 应用在 onPressed 事件中速度变化的信息。

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

使用 updateSpeed() 方法更新 LED 闪烁速度

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

QML 应用通过调用 updateFanSpeed() 请求新的风扇旋转周期

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

qul_thread.cpp

Qt Quick Ultralite 线程创建应用程序实例并执行 exec() 循环。

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

qul_thread.cpp 源文件还实现了 postEventsToUI() 函数。此函数由其他线程使用,将事件发送到修改 Qul::Property fanSpeedQul::Property ledCycleCount QML 属性。

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

以下是如何处理这些事件的 onEvent() 回调:

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);
}

注意:直接从其他线程更新Qul属性值可能会导致线程不安全。相反,请按照上面代码片段所示使用Qul::EventQueue

led_thread.cpp

在启动时,LED线程无限期地等待FreeRTOS任务通知。收到Qt Quick Ultralite线程的触摸事件后,它解除阻塞并更新LED闪烁速度。在第一个事件后,它以重新计算的速度值闪烁LED。

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

LED线程还会计算闪烁次数,并根据Qul::EventQueue使用postEventsToUI()函数将此信息发送到QML应用程序。此计数由QML应用程序在屏幕上更新。

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

fan_thread.cpp

风扇控制线程在Qt Quick Ultralite线程的触摸事件发生时等待FreeRTOS事件队列,以更新风扇的速度。它重新计算风扇动画的rotationPeriod,并通过postEventsToUI()函数将该值发送到UI。根据rotationPeriod值,UI更新动画速度。

...
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声明了在设备屏幕上显示的UI。

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是一个命名空间,其中我们声明初始化和用于控制目标板LED的函数。

namespace BoardUtils {
void initLED();
void toggleLED();
} // namespace BoardUtils

这些函数的定义因设备而异。以下示例来自STM32F769I-DISCOVERY的实现。

namespace BoardUtils {
void initLED()
{
    BSP_LED_Init(LED1);
}

void toggleLED()
{
    BSP_LED_Toggle(LED1);
}
} // namespace BoardUtils

main.cpp

main.cpp包含硬件初始化和创建Qt Quick Ultralite、LED和风扇控制线程。

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;
}

第一次调用是用于硬件初始化

    Qul::initPlatform();
    BoardUtils::initLED();

我们首先使用Qul::initPlatform()初始化板。使用BoardUtils::initLED()函数初始化用于闪烁的特定于板的LED。

    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);
    }

我们使用xTaskCreate()函数创建用于Qt Quick Ultralite主循环、风扇控制线程和LED闪烁线程的线程。所有这些线程都使用优先级4。Qt Quick Ultralite线程的堆栈大小为QUL_STACK_SIZE,该值在FreeRTOSConfig.h中定义为32*1024个单词。配置LED线程的堆栈大小为configMINIMAL_STACK_SIZE,该值也配置在FreeRTOSConfig.h中,在STM32F769I-DISCOVERY的案例中为128个单词。有关xTaskCreate()函数的详细信息,请参阅FreeRTOS API参考,xTaskCreate

    vTaskStartScheduler();

调用此函数启动FreeRTOS调度器,然后调度先前创建的线程。请参阅FreeRTOS API参考,vTaskStartScheduler

在特定的Qt许可下适用。
了解更多信息。