WinIoTのArduino言語でマルチタスクはできるのか(結論:簡単にできた)

マイクロソフトが公開しているWindows 10 IoT CoreにおけるGPIO速度テストのテスト機器がRaspberry Pi 2からRaspberry Pi 3に更新されました。ハードウェア自体の性能向上に伴い、全体的に速度が若干向上していますが、ソフトウェア面における性能は相変わらず「C# < C++/CX < Arduino Wiring」であり、かつC++よりArduinoが2倍近く速度に差をつけていて、パフォーマンスを考えると、GPIO制御にはArduino言語一択という状況は変わりなさそうです。

Arduinoのオープンソースコードからもおわかりいただけますが、Arduinoの内部ではC言語と同様にmain関数からsetupを呼び出した後で無限ループ内でloopを実行しています。Visual Studioでも"StartupTask.cpp"のスタートアップメソッドからsetup()とloop()を呼び出すという、ほぼ同じ手法でプログラムが動作しています。Arduino IDEでmain.cppをいじくると他のプロジェクトにも影響するので、簡単にいじくるわけにはいきませんが、Windows IoT Core向けのArduino Wiring Projectのスタートアップファイルは独立したコードなので自由にいじれます。そこで実験がてら、C++11の標準ライブラリーを使い、3つ並べたLEDをマルチタスクによって光らせるながらタクトスイッチを操作するというRTOSっぽいことをやってみました。


C++11の標準ライブラリーで非同期処理を実行するには「<future>」をインクルードし、必要に応じてstdを定義します。非同期処理を実行する関数はその名も「async」で、第一引数には実行方法("launch::async"ならマルチスレッド対応なら即実行(※OSが未対応ならエラーになる)、"launch::deferred"ならget()が呼び出された時点で実行、"launch::async | launch::deferred"ならOSがサポートしている方が優先されて実行)、第二引数には実行したい関数のポインタを入れます。

こちらはテストプログラムです。setup()とloop()をばっさりカットし、代わりに4つのタスクを同時に実行するようにしています。

[StartupTask.cpp]
#include <future>
using namespace std;

using namespace Windows::ApplicationModel::Background;

void task1();
void task2();
void task3();
void task4();

namespace MultiTask
{
    [Windows::Foundation::Metadata::WebHostHidden]
    public ref class StartupTask sealed : public IBackgroundTask
    {
    public:
        virtual void Run(Windows::ApplicationModel::Background::IBackgroundTaskInstance^ taskInstance) 
        {
            auto deferral = taskInstance->GetDeferral();

            auto t1 = async(launch::async, task1);
            auto t2 = async(launch::async, task2);
            auto t3 = async(launch::async, task3);
            auto t4 = async(launch::async, task4);
        }
    };
}


Arduinoコード側でもタスク処理だけ記述し、それぞれのタスク内でピン別に初期化と制御を行っています。

void task1()
{
    pinMode(GPIO5, OUTPUT);
    digitalWrite(GPIO5, LOW);

    while (true) {
        digitalWrite(GPIO5, HIGH);
        delay(1000);
        digitalWrite(GPIO5, LOW);
        delay(1000);
    }
}

void task2()
{
    pinMode(GPIO6, OUTPUT);
    digitalWrite(GPIO6, LOW);

    while (true) {
        digitalWrite(GPIO6, HIGH);
        delay(2000);
        digitalWrite(GPIO6, LOW);
        delay(2000);
    }
}

void task3()
{
    pinMode(GPIO13, OUTPUT);
    digitalWrite(GPIO13, LOW);

    while (true) {
        digitalWrite(GPIO13, HIGH);
        delay(3000);
        digitalWrite(GPIO13, LOW);
        delay(3000);
    }
}

void onPressSwitch()
{
    delay(5000);
}

void task4()
{
    pinMode(GPIO12, INPUT);
    attachInterrupt(GPIO12, onPressSwitch, RISING);

    while (true);
}

こちらがプログラムの実行結果となります。マルチタスクが機能し、それぞれのLEDが独立して一定の周期で点灯しており、また、タクトスイッチを押してスリープを割り込ませても、LEDの点滅には影響がないことがおわかりいただけると思います。


サンプルプロジェクトのダウンロード
,, | 2017年4月7日