このページでは

C

Qt Quick ウルトラライトの使用FreeRTOS

FreeRTOS は、組み込み機器やマイクロコントローラプラットフォーム向けに設計されたリアルタイムOSカーネルです。スレッド(FreeRTOS のタスク)、ミューテックス、セマフォ、ソフトウェアタイマーを提供します。

このガイドでは、FreeRTOSQt Quick Ultralite +FreeRTOS の背景情報を使ってQt Quick Ultralite の開発を始めるために必要なことを説明します。

サポートされるアーキテクチャ、プラットフォーム、およびFreeRTOS バージョン

Qt Quick Ultraliteは以下のハードウェアをサポートしています:

ハードウェアボードマイコンアーキテクチャコンパイラサポートFreeRTOS
NXP IMXRT1050-EVKBIMXRT1052DVL6AARM Cortex-M7GNU Arm GCC 12.3.rel1、IAR Build Tools for Arm V9.40FreeRTOS V10.0.1
NXP IMXRT1064-EVKIMXRT1064DVL6AARM Cortex-M7GNU Arm GCC 12.3.rel1、IAR Build Tools for Arm V9.40FreeRTOS V10.0.1
STM32F769I ディスカバリSTM32F769NIARM Cortex-M7GNU Arm GCC 12.3.rel1、IAR Build Tools for Arm V9.40FreeRTOS V10.0.1
NXP IMXRT1170-EVKBIMXRT1176DVMAAARM Cortex-M7およびARM Cortex-M4GNU Arm GCC 12.3.rel1、IAR Build Tools for Arm V9.40FreeRTOS V10.0.1
Renesas EK-RA8D1R7FA8D1BHECBDARM Cortex-M85GNUアームGCC 12.3.rel1FreeRTOS V10.6.1
エスプレシフESP32-S3-BOX-3ESP32-S3 MCUデュアルコアXtensa LX7XtensaGCCtoolsFreeRTOS V10.5.1

これらのリファレンスボードはQt Standard Supportでサポートされています。

注意: 特定のハードウェアプラットフォーム用にプリコンパイルされたプラットフォームライブラリを使用している場合、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つの関数を呼び出す必要があります:

  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 はデフォルトで5つの異なるメモリアロケータ実装を提供します。これらはFreeRTOS' MemMang ディレクトリにあり、ヒープ管理に異なる戦略を提供します。異なる実装についての詳細はFreeRTOS developer docs, Memory managementを参照してください。

プロジェクトでapp_common を使用している場合は、QUL_FREERTOS_HEAP_POLICY ターゲット・プロパティを設定することで、使用する実装を変更できます。詳しくは、ヒープ・ポリシーの変更を参照してください。

FreeRTOS で提供されるメモリ・アロケータは、pvPortMallocvPortFree を実装しています。これらのアロケータはQt Quick Ultraliteプラットフォーム・ポートによって内部的に使用され、ほとんどの場合、アプリケーション・コードによっても使用されるべきです。リンカ設定で、標準ライブラリとFreeRTOS アロケータ用に別々のヒープ領域を設定することができます。

アプリケーションでFreeRTOS ヒープ・アロケータを使用する方法の 1 つは、mallocfreerealloc のデフォルト実装をオーバーロードすることです。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 はスタック・サイズをバイトではなくワードで定義しています。

main.cpp Qt Quick Ultraliteを タスクで実行する例。FreeRTOS

次のコードは、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 マルチタスクの例

FreeRTOSQt Quick Ultralite を使って複数のタスクを実行する。

STM32F769I-DISCOVERY上で動作するマルチタスク・サンプル・アプリケーション。

この例では、相互に作用する複数のタスクを作成する方法を示します。このアプリケーションには3つのタスクがあります:

  • Qul_Thread QML アプリケーションインスタンスのQt Quick Ultraliteexec() ループを実行し、 HardwareControl メソッドを使ってタッチイベントを他のタスクに通知する。
  • Led_Thread LED の点滅を実行し、最新の LED 点滅回数で QML アプリケーション(Qt Quick Ultralite スレッド)を更新する。
  • FanControl_Thread ファンアニメーションの回転周期を再計算し、QML アプリケーション(Qt Quick Ultralite スレッド)を更新します。

HardwareControl クラス

HardwareControlQul::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 プロパティに新しい値を設定します。その後、updateFanSpeedupdateLedSpeed() を呼び出し、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 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

ファン制御スレッドは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 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 Reference の xTaskCreate を参照。

    vTaskStartScheduler();

これを呼び出すと、FreeRTOS スケジューラが起動し、以前に作成されたスレッドがスケジューリングされます。FreeRTOS API リファレンス、vTaskStartScheduler を参照。

特定の Qt ライセンスの下で利用可能です。
詳細はこちら。