C
Qt Quick Ultralite マルチタスクの例
Qt Quick UltraliteとバックグラウンドのRTOSスレッド間の通信をどのように実装できるかを示します。
概要
multitask のサンプルでは、複数のRTOSスレッドが互いに通信する方法を示しています。QMLからのイベントを使用して他のスレッドの動作を制御する方法や、GUI以外のスレッドがQMLプロパティの更新をトリガーする方法を示しています。 3つの独立したスレッドが実行されています。1つはQt Quick Ultraliteの実行を担当し、1つはオンボードLEDの点滅を担当し、もう1つはQMLアプリケーションの要求に応じてファンの回転周期を計算します。シンプルなQML UIを備えており、画面中央でファンの画像が回転している間、オンボードLEDが点滅しています。 画面には、LEDの点滅回数(LEDが点滅した総回数)も表示されます。ユーザーは画面をタップすることで、ファンの回転速度やLEDの点滅頻度を変更できます。

対応プラットフォーム
プロジェクト構成
プロジェクトの構成は、以下のディレクトリに分かれています:
board_utils- 特定のプラットフォーム向けのLED制御ルーチンを実装する静的ライブラリ。freertos- 特定のプラットフォーム向けのFreeRTOS カーネルを提供する静的ライブラリ。images- プロジェクトで使用されるグラフィックリソース。src- アプリケーションで使用されるC++ソースコード。desktop- アプリケーションのデスクトップ版用のC++ソースコード。freertos- 「FreeRTOS 」移植版のC++ソースコード。zephyr-Zephyr 移植版の C++ ソースコード。
コードの概要
CMake プロジェクトファイル
メインのCMakeファイルは、freertos ディレクトリをインクルードすることで、サンプルがサポートされているプラットフォームのいずれか向けにビルドされるかどうかを確認します。freertos というCMakeターゲットは、サポートされているプラットフォームに対してのみ定義されています。デスクトップバックエンド向けにビルドする場合、簡略化されたmultitask_desktop アプリケーションがビルドされます。
注: Zephyr のビルドプロセスでは 、このCMakeプロジェクトファイルは使用されません。「 Qt Quick Ultralite とZephyr の併用」を参照してください。
...
add_subdirectory(freertos)
if(TARGET freertos_kernel) # FreeRTOS support implemented for this platform
add_subdirectory(board_utils)
qul_add_target(multitask
src/freertos/main.cpp
src/freertos/hardwarecontrol.cpp
src/freertos/threads/led_thread.cpp
src/freertos/threads/qul_thread.cpp
src/freertos/threads/fan_thread.cpp
QML_PROJECT
mcu_multitask.qmlproject
)
target_compile_definitions(multitask PRIVATE FREERTOS)
target_include_directories(multitask PRIVATE src src/freertos/threads)
...
elseif(NOT CMAKE_CROSSCOMPILING) # No FreeRTOS here - fallback for building on desktop platform
qul_add_target(multitask_desktop
src/desktop/hardwarecontrol.cpp
QML_PROJECT
mcu_multitask.qmlproject
GENERATE_ENTRYPOINT
)
target_compile_definitions(multitask_desktop PRIVATE DESKTOP)
target_include_directories(multitask_desktop PRIVATE src)
...
else()
message(STATUS "Skipping generating target: multitask")
endif()BoardUtils ライブラリ
このライブラリは、LED制御の最も基本的な、ハードウェア固有の実装を提供します。サポートされているボード上でLEDを初期化および点灯・消灯するためのシンプルなAPIが提供されています。board_utils/include/board_utils/led.hには、このライブラリのAPIが記述されています。
... namespace BoardUtils { void initLED(); void toggleLED(); } // namespace BoardUtils
アプリケーションのエントリポイント
main.cppソースファイルは、FreeRTOS およびZephyr 向けのポートでのみ使用されます(デスクトップ向けにビルドする際は使用されません)。
main() 関数は、Qt Quick Ultraliteプラットフォーム、LED制御スレッド用のハードウェアLED、およびファン制御スレッド用のFreeRTOS キューを初期化します。
... int main() { Qul::initHardware(); Qul::initPlatform(); BoardUtils::initLED(); initFanControlQueue(); ...
次に、LED制御、Qt Quick Ultraliteエンジン、およびファン制御用のFreeRTOS タスクが作成されます。
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(); ...
main() 関数は、Qt Quick Ultralite プラットフォーム、LED 制御スレッド用のハードウェア LED、およびファンと LED 制御スレッド用のZephyr キューを初期化します。
... int main() { Qul::initHardware(); Qul::initPlatform(); BoardUtils::initLED(); initFanControlQueue(); initLedControlQueue(); ...
次に、Qt Quick Ultralite エンジン、LED制御、およびファン制御用のZephyr スレッドが作成されます。その後、メインスレッドは不要となるため終了します。
struct k_thread QulTaskData, LedTaskData, FanControlTaskData; QulTask = k_thread_create(&QulTaskData, qul_stack_area, K_THREAD_STACK_SIZEOF(qul_stack_area), Qul_Thread, NULL, NULL, NULL, 4, 0, K_NO_WAIT); LedTask = k_thread_create(&LedTaskData, led_stack_area, K_THREAD_STACK_SIZEOF(led_stack_area), Led_Thread, NULL, NULL, NULL, 4, 0, K_NO_WAIT); FanControlTask = k_thread_create(&FanControlTaskData, fancontrol_stack_area, K_THREAD_STACK_SIZEOF(fancontrol_stack_area), FanControl_Thread, NULL, NULL, NULL, 4, 0, K_NO_WAIT); // Exit the main thread because it is not needed anymore. return 0; ...
Qt Quick Ultraliteスレッド
Qt Quick のUltraliteスレッドは、アプリケーションインスタンスを作成し、exec() ループを実行します。
... void Qul_Thread(void *argument) { (void) argument; Qul::Application app; static multitask item; app.setRootItem(&item); app.exec(); }
... void Qul_Thread(void *arg1, void *arg2, void *arg3) { (void) arg1; (void) arg2; (void) arg3; 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); }
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); }
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); }
HardwareControlEventQueue は、以下のスニペットに示すように、Qul::EventQueue から派生しています:
class HardwareControlEventQueue : public Qul::EventQueue<HardwareEvent> { void onEvent(const HardwareEvent &event) override; };
注: 他のスレッドから直接 Qul プロパティの `setValue() ` メソッドを使用することは、スレッドセーフではありません 。 代わりに、上記のコードスニペットに示されているように、Qul::EventQueue を使用して QML インターフェースオブジェクトにイベントを送信してください。Qul::EventQueue はプラットフォーム層で実装されており、OS固有のキューを利用しています。提供されているQt Quick Ultralite リファレンスポートでは、Qul::EventQueue はネイティブのスレッドセーフな RTOS キューを使用して実装されています。
LEDスレッド
起動時、LEDスレッドはFreeRTOS タスクからの通知を待機します。Qt Quick のUltraliteスレッドがタッチイベントを処理すると、新しい速度値をキューに送信します。これにより、LEDスレッドのブロックが解除され、点滅速度が更新されます。その後、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スレッドはZephyr キューからの新しいLED速度値を待機します。Qt Quick Ultralite スレッドがタッチイベントを処理すると、新しい速度値をキューに送信します。これにより、LEDスレッドのブロックが解除され、点滅速度が更新されます。その後、LEDスレッドは新しい速度値に従ってLEDを点滅させます。
void Led_Thread(void *arg1, void *arg2, void *arg3) { ... while (true) { const k_timeout_t ticks = speed > 0 ? K_MSEC(350 / speed) : K_FOREVER; if (k_msgq_get(&ledControlQueue, &newSpeed, ticks) == 0) { speed = newSpeed; } BoardUtils::toggleLED(); ...
また、LEDスレッドは点滅回数を計算し、postEventsToUI() 関数を使用してこの情報をQMLアプリケーションに送信します。QMLアプリケーションは、画面上でこのカウント値を更新します。
...
ledEvent.id = HardwareEventId::LedCycleCount;
ledEvent.data = ledCycleCount;
postEventsToUI(ledEvent);
taskYIELD();
}
}...
ledEvent.id = HardwareEventId::LedCycleCount;
ledEvent.data = ledCycleCount;
postEventsToUI(ledEvent);
k_yield();
}
}ファン制御スレッド
ファン制御スレッドは、RTOSのイベントキューを待機し、QMLアプリケーションからのタッチイベントに応じてファンの回転速度を更新します。このスレッドは、ファンアニメーションのrotationPeriod を再計算し、postEventsToUI() 関数を使用してこの値をQMLアプリケーションに返します。QMLアプリケーションは、rotationPeriod の値に基づいてアニメーションの速度を更新します。
... 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); } } }
... void FanControl_Thread(void *arg1, void *arg2, void *arg3) { ... while (true) { if (k_msgq_get(&fanControlQueue, &newSpeed, K_FOREVER) == 0) { int rotationPeriod = newSpeed == 0 ? 0 : 5000 / (newSpeed * 3); fanEvent.id = HardwareEventId::FanRotationPeriod; fanEvent.data = rotationPeriod; postEventsToUI(fanEvent); } } }
データフロー図
以下のシーケンス図は、QMLアプリケーションからのイベントが、ハードウェアを制御するバックグラウンドスレッドにどのように影響するかをまとめたものです。


ファイル:
- multitask/CMakeLists.txt
- multitask/board_utils/CMakeLists.txt
- multitask/board_utils/include/board_utils/led.h
- multitask/board_utils/src/ek-ra8d1-freertos/led.cpp
- multitask/board_utils/src/esp32s3box3/led.cpp
- multitask/board_utils/src/mimxrt1050-evk-freertos/led.cpp
- multitask/board_utils/src/mimxrt1064-evk-freertos/led.cpp
- multitask/board_utils/src/mimxrt1170-evkb-freertos/led.cpp
- multitask/board_utils/src/stm32f769i-discovery-freertos/led.cpp
- multitask/board_utils/src/zephyr/led.cpp
- multitask/freertos/CMakeLists.txt
- multitask/mcu_multitask.qmlproject
- multitask/multitask.qml
- multitask/src/desktop/hardwarecontrol.cpp
- multitask/src/freertos/hardwarecontrol.cpp
- multitask/src/freertos/main.cpp
- multitask/src/freertos/threads/fan_thread.cpp
- multitask/src/freertos/threads/fan_thread.h
- multitask/src/freertos/threads/led_thread.cpp
- multitask/src/freertos/threads/led_thread.h
- multitask/src/freertos/threads/qul_thread.cpp
- multitask/src/freertos/threads/qul_thread.h
- multitask/src/hardwarecontrol.h
- multitask/src/zephyr/hardwarecontrol.cpp
- multitask/src/zephyr/threads/fan_thread.cpp
- multitask/src/zephyr/threads/fan_thread.h
- multitask/src/zephyr/threads/led_thread.cpp
- multitask/src/zephyr/threads/led_thread.h
- multitask/src/zephyr/threads/qul_thread.cpp
- multitask/src/zephyr/threads/qul_thread.h
画像:
「Zephyr 」アプリケーションのビルドプロセスも参照してください 。
特定のQtライセンスの下で利用可能です。
詳細はこちら。