Arduinoでマルチタスクを簡単に【FreeRTOS】


# 概要
Arduinoプロジェクトで複数の非同期タスクを簡単に管理したい時に役立つ、シングルファイルヘッダオンリーライブラリ、`task_helper.hpp`について解説します。このライブラリを使用すると、タスクの作成、スケジューリング、終了条件の設定が容易になります。

```cpp:task_helper.hpp
#pragma once
#include <Arduino.h>
#include <functional>
#include <memory>

struct TaskParameters
{
    std::function<void()> taskFunction;
    int intervalMs;
    std::function<bool()> endCondition;
};

// 関数、インターバル時間（ms）、タスク終了条件を引数に取るタスク作成ヘルパー関数
void createTask(
    std::function<void()> taskFunction, int intervalMs, std::function<bool()> endCondition = []()
                                                        { return false; },
    int stackSize = 50000, int priority = 1)
{
    auto parameters = new TaskParameters;
    parameters->taskFunction = taskFunction;
    parameters->intervalMs = intervalMs;
    parameters->endCondition = endCondition;

    xTaskCreateUniversal(
        [](void *param)
        {
            auto parameters = static_cast<TaskParameters *>(param);

            try
            {
                while (!parameters->endCondition())
                {
                    parameters->taskFunction();
                    
                    delay(parameters->intervalMs);
                }
            }
            catch(const std::exception& e)
            {
                Serial.print(e.what());
            }

            delete parameters;
            vTaskDelete(nullptr);
        },
        "task",
        stackSize,
        parameters,
        priority,
        nullptr,
        APP_CPU_NUM);
}

// void setup()
// {
//     Serial.begin(19200);
//     Serial1.begin(19200);

//     // Serialからの入力を処理するタスク
//     createTask([]()
//                {
//     if (Serial.available()) {
//       String input = Serial.readStringUntil('\n');
//       Serial.println("Received from Serial: " + input);
//     } },
//                10); // 10msごとに確認

//     // Serial1からの入力を処理するタスク
//     createTask([]()
//                {
//     if (Serial1.available()) {
//       String input = Serial1.readStringUntil('\n');
//       Serial.println("Received from Serial1: " + input);
//     } },
//                10); // 10msごとに確認
// }

// void loop()
// {
//     // メインループは空です。全ての処理は上記のタスクで行われます。
// }
```

# はじめに
Arduinoでプロジェクトを進める際、特に初心者は複数の非同期タスクを管理するのに苦労することがあります。例えば、センサからのデータ読み取り、シリアル通信の監視、モータの制御など、それぞれ異なるタイミングで行わなければならないタスクが増えると、コードの管理が複雑化します。ここで`task_helper.hpp`ライブラリが役立ちます。このライブラリを使えば、各タスクを独立したものとして扱い、スケジューリングや終了条件の設定を容易に行うことができます。

# 技術的解説

`task_helper.hpp`ライブラリの主な機能は`createTask`関数です。この関数は以下の引数を取ります:

1. `taskFunction` - タスクとして実行する関数。
2. `intervalMs` - タスクを実行する間隔（ミリ秒）。
3. `endCondition` - タスクの終了条件を判断する関数。デフォルトでは常に`false`を返し、タスクは終了しません。
4. `stackSize` - タスクスタックのサイズ。デフォルトは50000です。
5. `priority` - タスクの優先度。デフォルトは1です。

`createTask`関数は`xTaskCreateUniversal`関数を使用して新しいタスクを作成し、そのタスクが指定した間隔で指定した関数を実行するようにスケジューリングします。


## FreeRTOSとは

FreeRTOSは組み込みシステム向けのリアルタイムオペレーティングシステム (RTOS) です。リアルタイムオペレーティングシステムは、決まった時間内に特定のタスクを完了する能力を持つオペレーティングシステムで、組み込みシステムや産業用システムなど、タイミングが重要なシステムでよく使用されます。

FreeRTOSはその名の通りフリーでオープンソースのRTOSで、小型で移植性が高いことから広く利用されています。FreeRTOSはプリエンプティブまたは協調スケジューリングを提供し、マルチタスクアプリケーションを簡単に作成できます。

## FreeRTOSの主な機能

- **マルチタスク処理**: FreeRTOSは複数のタスクを同時に実行することができます。各タスクは独立しており、それぞれが自分のスタック領域を持つため、タスク間でデータを共有する必要はありません。

- **タスクスケジューリング**: FreeRTOSはタスクの優先度を設定することができます。優先度が高いタスクは、低いタスクよりも先に実行されます。また、同じ優先度のタスクはラウンドロビンまたは優先度に基づいてスケジューリングされます。

- **同期と通信**: FreeRTOSはタスク間の同期と通信をサポートしています。これには、セマフォ、ミューテックス、キューなどのメカニズムが用意されています。

- **メモリ管理**: FreeRTOSには柔軟なメモリ管理機能があります。ユーザーは自分のニーズに合わせて、さまざまなメモリ管理スキームを選択できます。

## FreeRTOSとマイクロコントローラ

FreeRTOSは様々なマイクロコントローラで利用できます。例えばESP32やRaspberry Pi Picoなどでは、FreeRTOSを用いて複数のタスクを並行して実行することが可能です。これにより、シリアル通信の監視、センサーデータの読み取り、モーターの制御など、異なるタスクを独立に管理することができます。

`task_helper.hpp`ライブラリは、FreeRTOSの機能を簡単に利用するためのツールです。このライブラリを使用すれば、FreeRTOSの詳細な知識がなくても、マルチタスク処理を簡単に実装することができます。

# 応用例
`task_helper.hpp`ライブラリは様々な場面で利用できます。例えば、以下のようなシチュエーションで活用できます:

1. センサからのデータを定期的に読み取りながら、同時にユーザーからのシリアル入力を監視する。
2. 特定の時間が経過したら自動的にタスクを終了する、タイムアウト機能を持つタスクを作成する。
3. 複数のモータをそれぞれ異なる間隔で制御する。

# 注意点
`task_helper.hpp`ライブラリはタスクの作成と管理を容易にしますが、以下のような制約もあります:

1. ライブラリはタスクのスケジューリングを助けますが、タスク間の同期や通信のための追加のメカニズムは提供しません。必要な場合は別途実装が必要です。
2. `createTask`関数は動的メモリを使用します。したがって、利用可能なRAMが少ないマイクロコントローラでは注意が必要です。

# まとめ
`task_helper.hpp`ライブラリはArduinoプロジェクトで複数の非同期タスクを簡単に管理するための強力なツールです。タスクの作成、スケジューリング、終了条件の設定を一元管理することが可能になります。ただし、タスク間の同期や通信、メモリの管理など、その他の詳細についてはユーザーが注意深く扱う必要があります。

# 参考
https://github.com/hideakitai/TaskManager