Windows IoT Coreで赤外線リモコンの信号を受信する(上級編)

Receive infrared signal by Windows IoT Core (Advanced)
前回では低レベルデバイスアクセスによる赤外線リモコンの信号を受信する方法を紹介しました。今回はさらに速度を追求したい人のための制御方法を紹介します。

Windows Iot Core Templatesを導入すると、新規Visual C++プロジェクトの一覧に「Arduino Wiring Application for Windows IoT Core」なるものが表示されるようになります。
名前を見るとRaspberry Piなどの母艦デバイスとArduinoデバイスをピンでつないでデータのやりとりをするようにするための機能を提供しているように見えますが、実はこれはRaspberry Piをはじめとした対応機種単体で、Arduinoの機能をシミュレートしてしまうという代物なのです。

このテンプレートを使うメリットはArduino言語を記述したinoファイルをそのままプロジェクトに組み込め、また、delay()やopenPin()などのArduino標準APIはもちろん、SPIなどのArduinoの公式が提供するライブラリも一部実装済みのため、Arduinoの資産を少ない労力で移植することができる点にあります。

また、C#による低レベルデバイスの制御では1.45MHzの周波数(1秒に約145万回のGPIOのオン・オフができる)できるのにくらべ、Arduinoアプリとして開発されたプログラムでは6.05MHzと驚異的な計測結果が出ています(マイクロソフト社の検証による)。

ただし、注意すべき点もあります。Arduinoアプリはバックグラウンドのみで動作し、かつデバイス上ではVisual C++のUWPアプリとして動作するため、GUIなどのフォアグラウンドアプリとの連携など、UWPに関する制御を実装しようとすると、マイクロソフトによる独自言語である「C++/CX」の知識が必要となり、プログラミングの難易度が一気に跳ね上がります。また、「intは32bit整数」「longは64bit整数」「booleanはunsinged charに置き換えられるため、boolean値をtrueやfalseで比較しようとすると警告が出る」など、変数の型が一般的なArduinoと若干異なっている点にも気をつけましょう。

以上を踏まえて、Arduino Wiredアプリを作ってみることにしましょう。プロジェクトを作成したら、それに含まれるinoファイルを開き、以下のように入力してみてください。このArduinoアプリは低レベルデバイスが大前提のため、appxmanifestファイルにはすでに宣言が組み込み済みなので、C#プロジェクトのようにマニフェストの変更は必要ありません。

これは前回と同じGPIO 17に接続された赤外線受信モジュールの信号を解析するプログラムです。こちらもおもしろまじめ電子工作にあったサンプルコードが元となっています。サンプルコードを丸ごとコピペすることももちろん可能ですが、著作権を考慮して大部分に手を加えています。

Visual Studioでは「_WIN_IOT」プリプロセッサが自動で加わるので、C++/CXとArduino言語との差異をここで埋めています。これは余談ですが、Raspberry Piで動かすプログラムなので、数MBくらいメモリーを消費してもなんら問題なく動作するのもいいですね。
remocon.c
private const long timeoutMicros = 5000000;
private const long waitMicros = 1000000;

private long micros()
{
    return (long)(sw.Elapsed.TotalMilliseconds * 1000.0);
}

private bool waitChange(GpioPinValue status, bool began)
{
    long waitStartMicros = micros();
    long waits = (began == true) ? timeoutMicros : waitMicros;
    while (irpin_in.Read() == status) {
        if (micros() - waitStartMicros > waits) return true;
    }
    return false;
}

private void OnClickReceive(object sender, RoutedEventArgs e)
{
    SetButtonEnable(false);

    Task.Run(async () => {
        GpioPinValue moduleStatus;
        long now, lastMicros;
        sw.Restart();

        now = micros();
        lastMicros = micros();

        bool began = false;
        int stcount = 0;

        moduleStatus = irpin_in.Read();

        while (stcount < ST_MAX) {
            bool waitStatus = waitChange(moduleStatus, began);
            if (waitStatus == true) {
                if (began == true) {
                    break;
                } else {
                    continue;
                }
            } else {
                Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
                    txtSignal.Text = "Analyzing...";
                });
                began = true;
            }

            now = micros();
            signalTimes[stcount++] = (int)((now - lastMicros) / 10);
            lastMicros = now;

            moduleStatus = (moduleStatus == GpioPinValue.High) ? GpioPinValue.Low : GpioPinValue.High;
        }

        await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
            StringBuilder sb = new StringBuilder();
            for(int i = 0; i < stcount; i++) {
                sb.AppendFormat("{0},", signalTimes[i]);
            }
            txtSignal.Text = sb.ToString();

            SetButtonEnable(true);
        });
    });
}
ちなみにVisual C++プロジェクトでリモートデバッグを実行する場合は、プロジェクトファイルのメニューより「構成プロパティ」ダイアログを開き、「デバッグ」の項目より対象のコンピューターを指定します。
バックグラウンドアプリなので、デバッガを実行してもモニターの画面は変わりませんが、受信モジュールにリモコンの信号を送ると、Serial.print()の内容がVisual Studioの「出力」に表示されると思います。ただ、周波数が高くて感度が高すぎるためか、何もリモコンを操作していないのに結果が表示されることがあるので、まだ修正の余地があるかもしれません。

Visual Studio 2015のフルプロジェクトはこちらからダウンロードできます。
2016/11/02