C
Qt Quick ウルトラライトの使用FreeRTOS
FreeRTOS は、組み込み機器やマイクロコントローラプラットフォーム向けに設計されたリアルタイムOSカーネルです。スレッド(FreeRTOS のタスク)、ミューテックス、セマフォ、ソフトウェアタイマーを提供します。
このガイドでは、FreeRTOS 、Qt Quick Ultralite +FreeRTOS の背景情報を使ってQt Quick Ultralite の開発を始めるために必要なことを説明します。
サポートされるアーキテクチャ、プラットフォーム、およびFreeRTOS バージョン
Qt Quick Ultraliteは以下のハードウェアをサポートしています:
| ハードウェアボード | マイコン | アーキテクチャ | コンパイラ | サポートFreeRTOS |
|---|---|---|---|---|
| NXP IMXRT1050-EVKB | IMXRT1052DVL6A | ARM Cortex-M7 | GNU Arm GCC 12.3.rel1、IAR Build Tools for Arm V9.40 | FreeRTOS V10.0.1 |
| NXP IMXRT1064-EVK | IMXRT1064DVL6A | ARM Cortex-M7 | GNU Arm GCC 12.3.rel1、IAR Build Tools for Arm V9.40 | FreeRTOS V10.0.1 |
| STM32F769I ディスカバリ | STM32F769NI | ARM Cortex-M7 | GNU Arm GCC 12.3.rel1、IAR Build Tools for Arm V9.40 | FreeRTOS V10.0.1 |
| NXP IMXRT1170-EVKB | IMXRT1176DVMAA | ARM Cortex-M7およびARM Cortex-M4 | GNU Arm GCC 12.3.rel1、IAR Build Tools for Arm V9.40 | FreeRTOS V10.0.1 |
| Renesas EK-RA8D1 | R7FA8D1BHECBD | ARM Cortex-M85 | GNUアームGCC 12.3.rel1 | FreeRTOS V10.6.1 |
| エスプレシフESP32-S3-BOX-3 | ESP32-S3 MCU | デュアルコアXtensa LX7 | GNU Xtensa GCC 13.2.0 | FreeRTOS V10.5.1 |
| Infineon PSOC™ Edge E84評価キット | PS846GPS2DBZC4 | ARM Cortex-M55 | GNU Arm GCC 14.2.1、LLVM Embedded Toolchain for Arm 19.1.5 | FreeRTOS V10.6.202 |
これらのリファレンスボードはQt Standard Support でサポートされています。
注意: 特定のハードウェアプラットフォーム用にプリコンパイルされたプラットフォームライブラリを使用している場合、FreeRTOS のソースの一部はすでにコンパイルされています。FreeRTOS のバージョンを変更したい場合は、プラットフォーム・ライブラリを再構築する必要があります。
開発環境要件
前提条件
FreeRTOS 用のQt Quick Ultralite をビルドするには、以下のものが必要です:
- Qt Quick UltraliteFreeRTOS パッケージがインストールされていること。 Qt Quick Ultralite forFreeRTOS の入手を参照してください。
- FreeRTOS ソース。FreeRTOS ウェブサイトから FreeRTOS を入手できます。
- Qt Quick Ultraliteとターゲット・プラットフォームの前提条件(サポートされるターゲット・ボードと開発ホストを参照)。
環境のセットアップ
使用しているボードに基づき、 NXP (BareMetal およびFreeRTOS) および 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 バージョンのハードウェアボードのリンクを参照してください。
Qt Quick Ultraliteの使用方法FreeRTOS
Qt Quick Ultraliteの入手FreeRTOS
サポートされているプラットフォームでは、Qt Quick Ultraliteのインストー ルにFreeRTOS サポートが付属しています。FreeRTOS 用のサンプルをコンパイルするには、 app_common を 使用してください。
Qt Quick Ultraliteスレッドの開始
Qt Quick Ultraliteをターゲット上で実行するには、2つの関数を呼び出す必要があります:
Qul::initPlatform();この関数はプラットフォームのハードウェアとオペレーティング・システムを初期化します。この関数はできるだけ早く、遅くとも
Qul::appMain()が実行される前、またはデバイス固有の関数がアクセスされる前に呼び出す必要があります。FreeRTOS のさまざまなメモリ割り当て実装と、Qt Quick Ultralite プロジェクトでの使用方法については、ヒープポリシーの変更を参照してください。
Qul::appMain();この関数は初期化し、Qt Quick Ultraliteのメインループとしても動作します。FreeRTOS では、タスクから実行する必要があります。
カスタムエントリポイントの設定に関する詳細は、アプリケーションでQt Quick Ultraliteを実行するを参照してください。
FreeRTOS タスクでQt Quick Ultraliteを実行する例main.cpp も参照してください。
メモリアロケータの提供
FreeRTOS はデフォルトで5つの異なるメモリアロケータ実装を提供します。これらはFreeRTOS' MemMang ディレクトリにあり、ヒープ管理に異なる戦略を提供します。異なる実装の詳細についてはFreeRTOS developer docs, Memory managementを参照してください。
プロジェクトでapp_common を使用している場合は、QUL_FREERTOS_HEAP_POLICY ターゲット・プロパティを設定することで、使用する実装を変更できます。詳細については、ヒープ・ポリシーの変更を参照してください。
FreeRTOS で提供されるメモリ・アロケータは、pvPortMalloc とvPortFree を実装しています。これらのアロケータはQt Quick Ultraliteプラットフォーム・ポートによって内部的に使用され、ほとんどの場合、アプリケーション・コードによっても使用されるべきです。リンカ設定で、標準ライブラリ用とFreeRTOS アロケータ用に別々のヒープ領域を設定することができます。
アプリケーションでFreeRTOS ヒープ・アロケータを使用する方法の 1 つは、malloc 、free 、realloc のデフォルト実装をオーバーロードすることです。new とdelete C++ キーワードが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 プラットフォームは、24 KiB に相当する 6 キロワードのスレッドスタックサイズを使用します。
注: FreeRTOS はスタック・サイズをバイトではなくワードで定義しています。
FreeRTOS タスクでQt Quick Ultraliteを実行する例main.cpp
次のコードは、Qt Quick Ultralite用の基本的なFreeRTOS スレッドを作成し、それを実行する方法を示している。
#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 を使って複数のタスクを実行する。

この例では、相互に作用する複数のタスクの作成方法を示します。このアプリケーションには3つのタスクがあります:
Qul_ThreadQML アプリケーションインスタンスのQt Quick Ultraliteexec()ループを実行し、 HardwareControl メソッドを使用してタッチイベントを他のタスクに通知します。Led_ThreadLED の点滅を実行し、最新の LED 点滅回数で QML アプリケーション(Qt Quick Ultralite スレッド)を更新する。FanControl_Threadファンアニメーションの回転周期を再計算し、QML アプリケーション(Qt Quick Ultralite スレッド)を更新します。
HardwareControl クラス
HardwareControl はQul::Singleton クラスで、QML アプリケーションが他のスレッドとやりとりするための インターフェイスを提供します。このクラスはLed_Thread (FreeRTOS タスク通知を使用) やFanControl_Thread (FreeRTOS キューを使用) との通信を処理します。
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();
}LED点滅速度はupdateSpeed() :
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 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 プロパティの値を更新することはスレッドセーフではありません。代わりに、上記のコードスニペットに示すように、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
ファン制御スレッドはFreeRTOS イベントキューで待機し、Qt Quick Ultraliteスレッドからのタッチイベントでファン速度を更新します。このスレッドはファンアニメーションのrotationPeriod を再計算し、postEventsToUI() 関数の助けを借りてこの値をUIに送信します。UIはrotationPeriod の値に基づいてアニメーションの速度を更新します。
...
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);
}multitask.qml
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 BoardUtilsmain.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 Reference の xTaskCreate を参照。
vTaskStartScheduler();これを呼び出すと、FreeRTOS スケジューラが起動し、以前に作成されたスレッドがスケジューリングされます。FreeRTOS API リファレンス、vTaskStartScheduler を参照。
特定の Qt ライセンスの下で利用可能です。
詳細はこちら。