C
使用 Qt Quick Ultralite 与 FreeRTOS
FreeRTOS 是为嵌入式设备和微控制器平台设计的实时操作系统内核。它提供了线程(FreeRTOS 中的任务)、互斥锁、信号量和软件计时器。
本指南告诉您开始使用 FreeRTOS 开发 Qt Quick Ultralite 所需的内容,以及关于 FreeRTOS 的相关信息。
支持的架构、平台和 FreeRTOS 版本
Qt Quick Ultralite 支持以下硬件
硬件板 | MCU | 架构 | 编译器 | 支持的 FreeRTOS |
---|---|---|---|---|
EK-RA6M3G | R7FA6M3AH3CFC | ARM Cortex-M4 | GNU Arm GCC 12.3.rel1, IAR Build Tools for Arm V9.40 | FreeRTOS V10.6.1 |
NXP IMXRT1050-EVKB | MIMXRT1052DVL6A | ARM Cortex-M7 | GNU Arm GCC 12.3.rel1, IAR Build Tools for Arm V9.40 | FreeRTOS V10.0.1 |
NXP IMXRT1064-EVK | MIMXRT1064DVL6A | ARM Cortex-M7 | GNU Arm GCC 12.3.rel1, IAR Build Tools for Arm V9.40 | FreeRTOS V10.0.1 |
STM32F769I-DISCOVERY | STM32F769NI | ARM Cortex-M7 | GNU Arm GCC 12.3.rel1, IAR Build Tools for Arm V9.40 | FreeRTOS V10.0.1 |
NXP IMXRT1170-EVKB | MIMXRT1176DVMAA | ARM Cortex-M7 和 ARM Cortex-M4 | GNU Arm GCC 12.3.rel1, IAR Build Tools for Arm V9.40 | FreeRTOS V10.0.1 |
这些参考板在 Qt 标准支持 之下得到支持。
注意:如果您正在使用给定硬件平台的预编译平台库,那么 FreeRTOS 源代码的某些部分已经编译到其中。如果您想更改 FreeRTOS 版本,则必须重新构建平台库。
开发环境要求
先决条件
要为 FreeRTOS 构建 Qt Quick Ultralite,您需要以下内容
- 已安装 Qt Quick Ultralite FreeRTOS 软件包。有关详细信息,请参阅 获得 Qt Quick Ultralite for FreeRTOS。
- 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
Qul::initPlatform();
此函数初始化平台硬件和操作系统。这应该在尽快的时候调用,但最迟不应该在
Qul::appMain()
运行或访问任何特定于设备的函数之前。有关FreeRTOS中不同内存分配实现以及如何在Qt Quick Ultralite项目中使用它们的信息,请参阅更改堆策略。
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提供的内存分配器实现了pvPortMalloc
和vPortFree
。这些分配器由Qt Quick Ultralite平台端口内部使用,并且在大多数情况下应该由应用程序代码使用。可以在链接器配置中为标准库和FreeRTOS分配器配置独立的堆区域。
在应用程序中使用FreeRTOS堆分配器的方法之一是重载默认的malloc
、free
和realloc
实现。如果C++中的new
和delete
关键字内部使用malloc
和free
,则不需要为应用程序提供单独的重载。
包括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 Ultraliteexec()
循环,并通过硬件控制方法通知其他任务触摸事件。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
属性。之后,我们调用 updateFanSpeed
和 updateLedSpeed()
来更新任务关于 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 fanSpeed
和 Qul::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许可下适用。
了解更多信息。