このページでは

C

Qt Quick Ultraliteを以下のものと組み合わせて使用する場合FreeRTOS

FreeRTOS は、組み込みデバイスやマイクロコントローラプラットフォーム向けに設計されたリアルタイムオペレーティングシステムカーネルです。スレッド(FreeRTOS では「タスク」と呼ばれます)、ミューテックス、セマフォ、およびソフトウェアタイマーを提供します。

このガイドでは、FreeRTOS およびQt Quick Ultraliteを使用したQt Quick Ultraliteの開発を開始するために必要な事項と、FreeRTOS に関する背景情報を解説します。

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

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

ハードウェアボードMCUアーキテクチャコンパイラ対応FreeRTOS
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
Renesas EK-RA8D1R7FA8D1BHECBDARM Cortex-M85GNU Arm GCC 12.3.rel1FreeRTOS V10.6.1
Espressif ESP32-S3-BOX-3ESP32-S3 MCUデュアルコア Xtensa LX7GNU Xtensa GCC 13.2.0FreeRTOS V10.5.1
Infineon PSOC™ Edge E84 評価キットPSE846GPS2DBZC4ARM Cortex-M55GNU Arm GCC 14.2.1、Arm 向け LLVM 組み込みツールチェーン 19.1.5FreeRTOS V10.6.202

これらのリファレンスボードは、Qt Standard Supportの対象となっています。

注: 特定のハードウェアプラットフォーム用にプリコンパイル済みのプラットフォームライブラリを使用している場合 FreeRTOS のソースコードの一部はすでにそこにコンパイルされています。FreeRTOS のバージョンを変更したい場合は、プラットフォームライブラリを再ビルドする必要があります。

開発環境の要件

前提条件

FreeRTOS 向けのQt Quick Ultraliteをビルドするには、以下のものが必要です:

環境のセットアップ

使用しているボードに応じて、『 NXP のはじめに(BareMetal およびFreeRTOS )』およびSTM のはじめに』で定義されているプラットフォーム固有の環境変数を設定してください。

もし app_commonFreeRTOS のソースへのパスも設定する必要があります:

-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 開発者ドキュメントの「メモリ管理」を参照してください

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

FreeRTOS に付属するメモリアロケータは、pvPortMalloc およびvPortFree を実装しています。これらのアロケータは、Qt Quick Ultralite プラットフォームポートで内部的に使用されており、ほとんどの場合、アプリケーションコードでもこれらを使用する必要があります。リンカ設定で、標準ライブラリとFreeRTOS のアロケータ用に別々のヒープ領域を構成することが可能です。

アプリケーションでFreeRTOS のヒープアロケータを使用する方法の一つは、mallocfree 、および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 プラットフォームでは、スレッドのスタックサイズとして6キロワード(24 KiBに相当)が使用されます。

注: 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 を使用した複数のタスクの実行。

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

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

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

HardwareControl クラス

HardwareControl は、QMLアプリケーションが他のスレッドとやり取りするためのインターフェースを提供するQul::Singleton クラスです。このクラスは、Led_ThreadFreeRTOS タスク通知を使用)およびFanControl_ThreadFreeRTOS キューを使用)との通信を処理します。

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::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リファレンスのxTaskCreateを参照してください

    vTaskStartScheduler();

これを呼び出すと、FreeRTOS スケジューラが起動し、事前に作成されたスレッドのスケジューリングが行われます。詳細はFreeRTOS APIリファレンスのvTaskStartSchedulerを参照してください。

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