« 前ページへ
前回では「Arduino Wiring Application for Windows IoT Core」を使ったラズパイでの高速GPIO制御のお話をしました。先述の通りこのプロジェクトはバックグラウンドアプリのため、単体ではGUIが使えません。と、いうわけでバックグラウンドとフォアグラウンドを連携するプログラミングのお話をしたいと思います。今回は手始めとしてすべてC#でプログラムを組んでいます。

一番手っ取り早いのは、バックグラウンドアプリをアプリサービスとして動作させ、必要に応じて登録されたフォアグラウンドアプリにイベントを送信する方法です。バックグラウンドタスクとは違い、アプリサービスではストレージを介さずに直接データをやりとりすることができるのも、これを利用する理由の一つです。

それではプログラミングを始めましょう。まずはソリューションを作成して、フォアグラウンドアプリ(空白のアプリ)プロジェクトを作成し、次にバックグラウンドアプリ(Background Application (IoT))プロジェクトを作成して同じソリューションに追加します。続いてソリューションのプロパティーを開き、「プロジェクトの依存関係」でフォアグラウンドアプリがバックグラウンドアプリに依存していることを指定することで、デバッグ時に両方のアプリが動作するようにします。


続いてバックグラウンドアプリをサービスとして動作させるように、マニフェストを変更します。一覧にある「宣言」にある「バックグラウンドタスク」を削除して、かわりに「App Service」を追加します。プロパティの「名前」と「エントリポイント」は「バックグラウンドタスク」での定義と同じにします。


LEDとタクトスイッチを配置しただけの至極単純なものですが、今回のプログラムにおける配線図を掲載しておきます。


次にコード例を紹介します。フォアグラウンドアプリではバックグラウンドアプリの「App Service」で指定した名前を連携先として登録し、GUI上のLEDボタンが押されたらバックグラウンドアプリにGPIO 17の値を切り替えるメッセージを送信しています。逆にタクトスイッチが押されてGPIO 18の値が変われば、登録されたフォアグラウンドアプリにその旨のメッセージが送信されます。


なお、LightningProviderについては過去の記事をご覧ください。

// UiApp.MainPage in UiApp
using System;
using Windows.ApplicationModel.AppService;
using Windows.Foundation.Collections;
using Windows.UI;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

namespace UiApp
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
        }

        private static readonly string ServiceName = "BgApp";
        private AppServiceConnection service = null;

        protected override async void OnNavigatedTo(NavigationEventArgs e)
        {
            base.OnNavigatedTo(e);

            var listing = await AppServiceCatalog.FindAppServiceProvidersAsync(ServiceName);
            if (listing.Count != 1) return;

            service = new AppServiceConnection();
            service.AppServiceName = ServiceName;
            service.PackageFamilyName = listing[0].PackageFamilyName;

            var res = await service.OpenAsync();
            if (res != AppServiceConnectionStatus.Success) {
                txtStatus.Text = "Can't connect to " + res.ToString();
            } else {
                service.RequestReceived += OnServiceRequestReceived;
                txtStatus.Text = "Ready";
            }
        }

        private SolidColorBrush brush_on = new SolidColorBrush(Color.FromArgb(255, 209, 236, 255));
        private SolidColorBrush brush_off = new SolidColorBrush(Color.FromArgb(255, 255, 170, 204));

        private async void OnServiceRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () => {
                var d = args.GetDeferral();

                ValueSet res = new ValueSet();

                string lblmsg = "";
                SolidColorBrush bgbrush = new SolidColorBrush();
                try {
                    bool pin = (bool)args.Request.Message["pin"];
                    if(pin == true) {
                        lblmsg = "Switch On";
                        bgbrush = brush_on;
                    } else {
                        lblmsg = "Switch Off";
                        bgbrush = brush_off;
                    }
                    res.Add("message", "ok");
                } catch (Exception ex) {
                    res.Add("message", ex.Message);
                }
                await args.Request.SendResponseAsync(res);

                lblSwitch.Text = lblmsg;
                pnlSwitch.Background = bgbrush;

                d.Complete();
            });
        }

        private bool led_on = false;

        private async void OnLedButtonClick(object sender, Windows.UI.Xaml.RoutedEventArgs e)
        {
            SolidColorBrush fgbrush = new SolidColorBrush(Colors.Black);

            led_on = !led_on;

            ValueSet message = new ValueSet();
            message.Add("led", led_on);

            var resp = await service.SendMessageAsync(message);
            string msg;
            if (resp.Status == AppServiceResponseStatus.Success) {
                msg = resp.Message["message"].ToString();
            } else {
                msg = "An error has occured : " + resp.Status;
                fgbrush = new SolidColorBrush(Colors.Red);
            }

            await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => {
                btnLED.Content = string.Format("LED {0}", (led_on == true) ? "On" : "Off");
                btnLED.Background = (led_on == true) ? brush_on : brush_off;
                txtStatus.Text = msg;
                txtStatus.Foreground = fgbrush;
            });
        }
    }
}


// StartupTask.cs in BgApp
using System;
using Windows.ApplicationModel.AppService;
using Windows.ApplicationModel.Background;
using Windows.Devices;
using Windows.Devices.Gpio;
using Windows.Foundation.Collections;
using Microsoft.IoT.Lightning.Providers;

namespace BgApp
{
    public sealed class StartupTask : IBackgroundTask
    {
        private BackgroundTaskDeferral deferral = null;
        private AppServiceConnection appcon = null;

        private GpioPin pin17 = null, pin18 = null;

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            // Keep the background task
            deferral = taskInstance.GetDeferral();
            taskInstance.Canceled += OnTaskCanceled;

            var triggerDetails = taskInstance.TriggerDetails as AppServiceTriggerDetails;
            appcon = triggerDetails.AppServiceConnection;
            appcon.RequestReceived += OnConnectionReceived;

            // Initialize the pins by direct memory mapping.
            if (LightningProvider.IsLightningEnabled) {
                LowLevelDevicesController.DefaultProvider = LightningProvider.GetAggregateProvider();
            }

            GpioController gc = GpioController.GetDefault();

            pin17 = gc.OpenPin(17);
            pin17.SetDriveMode(GpioPinDriveMode.Output);

            pin18 = gc.OpenPin(18);
            pin18.SetDriveMode(GpioPinDriveMode.Input);
            pin18.ValueChanged += OnPin18Changed;
        }

        private async void OnPin18Changed(GpioPin sender, GpioPinValueChangedEventArgs args)
        {
            var messages = new ValueSet();
            if (pin18.Read() == GpioPinValue.High) {
                messages.Add("pin", true);
            } else {
                messages.Add("pin", false);
            }
            await appcon.SendMessageAsync(messages);
        }

        private void OnTaskCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
        {
            if (deferral != null) {
                deferral.Complete();
                deferral = null;
            }
        }

        private async void OnConnectionReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
        {
            var deferral = args.GetDeferral();

            ValueSet resp = new ValueSet();
            try {
                bool led = (bool)args.Request.Message["led"];
                if (pin17 != null) {
                    pin17.Write(led ? GpioPinValue.High : GpioPinValue.Low);
                    resp.Add("message", "ok");
                }else {
                    resp.Add("message", "pin17 is not initialized.");
                }
            } catch (Exception ex) {
                resp.Add("message", ex.Message);
            }
            await args.Request.SendResponseAsync(resp);

            deferral.Complete();
        }
    }
}


XAMLファイルによるUIレイアウトを含めたフルプロジェクトはこちらよりダウンロードできます。
, | 2016年11月3日
前回では低レベルデバイスアクセスによる赤外線リモコンの信号を受信する方法を紹介しました。今回はさらに速度を追求したい人のための制御方法を紹介します。

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くらいメモリーを消費してもなんら問題なく動作するのもいいですね。

#ifdef _WIN_IOT
// For communicating to C++/CX
#undef boolean
#define boolean bool
#endif

#define MAX_LEN 512
unsigned int data[MAX_LEN];
int dataPos;

// --------------
#define SERIAL_SPEED 115200

const int modulePin = GPIO_17;
const long waitMicros = 1000000;

unsigned long now, lastMicros;
int moduleStatus, totalMS;

void setup() {
    Serial.begin(SERIAL_SPEED);

    pinMode(modulePin, INPUT);

    now = micros();
    lastMicros = micros();
    moduleStatus = HIGH;

    dataPos = 0;
    totalMS = 0;
}

boolean waitSignal()
{
    unsigned long waitStartMicros = micros();
    while (digitalRead(modulePin) == moduleStatus) {
        if (micros() - waitStartMicros > waitMicros) return true;
        delayMicroseconds(100);
    }
    return false;
}

void loop() {
    if (dataPos >= MAX_LEN || waitSignal() == true) {
        if (totalMS > 4000) {    //45msec未満はリモコン信号の規格に該当しないので、ノイズと見なして無視
            for (int i = 0; i < dataPos; i++) {
                Serial.print(data[i], DEC);
                Serial.print(",");
            }
            Serial.println();

            dataPos = 0;
            totalMS = 0;
        }
        return;
    }

    now = micros();

    int sc = (now - lastMicros) / 10;
    if (dataPos > 0) totalMS += sc;
    data[dataPos++] = sc;

    lastMicros = now;

    moduleStatus = !moduleStatus;
}


ちなみにVisual C++プロジェクトでリモートデバッグを実行する場合は、プロジェクトファイルのメニューより「構成プロパティ」ダイアログを開き、「デバッグ」の項目より対象のコンピューターを指定します。


バックグラウンドアプリなので、デバッガを実行してもモニターの画面は変わりませんが、受信モジュールにリモコンの信号を送ると、Serial.print()の内容がVisual Studioの「出力」に表示されると思います。ただ、周波数が高くて感度が高すぎるためか、何もリモコンを操作していないのに結果が表示されることがあるので、まだ修正の余地があるかもしれません。

Visual Studio 2015のフルプロジェクトはこちらからダウンロードできます。
,, | 2016年11月2日
Windows IoT開発者向けフォーラムでは「WindowsはリアルタイムOSではないので、ArduinoやRaspbianみたいに赤外線信号を受信することは無理」とドヤ顔で回答する人がいます。確かに、単純にユニバーサルアプリを作っただけでは受信精度が低く、満足いく結果が得られないのは事実です。が、そもそもWindows IoT Coreはその名の通り、IoTデバイスで利用されるために開発されたOSです。マイクロソフトはラズパイでWindowsアプリを買わせるためだけに、このOSをわざわざ作ったのでしょうか?

知ったかぶりの回答者に対する嫌みはここまでにして、本題に入ることにします。Windows 10 IoT Coreには、OSを介さず、ハードウェアへ直接命令を渡す低レベルデバイスアクセスが用意されており、これを使うことでGPIOの処理速度を劇的に上げることができます。マイクロソフトによるオシロスコープを使った検証では、ネイティブコンパイルを行ったC#プログラムでRaspberry Pi 2に搭載されているGPIOのオン・オフが約1.45MHzによる周期で計測されたとしています。平たくいえば、1秒間にLEDを100万回は優に点滅させることができるようになるということになります。

低レベルデバイスを有効にするにはOSとアプリの両方で準備が必要になります。まずはパソコンより「Windows 10 IoT Core Dashboard」を実行して、「自分のデバイス」に表示されている機器より選択するか、ブラウザに直接IPアドレスを入力してデバイスポータルを開きます。ログインするには初期状態ではユーザー名は「Administrator」、パスワードは「p@ssw0rd」もしくはインストール時にユーザーが定義したものになります。


ポータルの「Devices」より「Default Contoller Driver」を「Inbox Driver」から「Direct Memory Mapped Driver」に変更してRaspberry Piを再起動します。もし、この選択肢が表示されていないようであればWindows IoTのバージョンを「10.0.10586(TH2に相当。ちなみにAnniversary Updateのバージョンは10.0.14393)」以降にアップデートしましょう。OSでの設定はこれだけです。


次はアプリの作成です。いつものようにユニーバーサルアプリプロジェクト(※JavaScriptやPythonなどのネイティブコンパイルができない言語では効果がほとんど発揮されないのでC#を推奨)を作成したら、Visual Studioの「ツール→NuGetパッケージマネージャー→ソリューションのNuGetパッケージの管理」を選択して表示されるページより、「Microsoft.IoT.Lightning」を参照、導入します。


プロジェクトのappxmanifestファイルをコードエディタで開き、低レベルデバイスの使用に関する宣言を追記します。

BeforeAfter
<?xml version="1.0" encoding="utf-8"?>

<Package
  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
  xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
  IgnorableNamespaces="uap mp">
:
:
:
  <Capabilities>
    <Capability Name="internetClient" />
  </Capabilities>
</Package>
<?xml version="1.0" encoding="utf-8"?>

<Package
  xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
  xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest"
  xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
  xmlns:iot="http://schemas.microsoft.com/appx/manifest/iot/windows10"
  IgnorableNamespaces="uap mp iot">
:
:
:
  <Capabilities>
    <Capability Name="internetClient" />
    <iot:Capability Name="lowLevelDevices" />
    <DeviceCapability Name="109b86ad-f53d-4b76-aa5f-821e2ddf2141"/>
  </Capabilities>
</Package>
MainPageのコンストラクタなど、GpioControllerを取得する前のコードでLightningProviderを有効にします。「using Microsoft.IoT.Lightning.Providers;」も忘れずに。
if (LightningProvider.IsLightningEnabled)
{
    LowLevelDevicesController.DefaultProvider = LightningProvider.GetAggregateProvider();
}

下準備は以上です。赤外線受信モジュールを設置しただけの非常にシンプルなものですが、ブレッドボードへの配線例を掲載しておきます。


リモコンから出力される赤外線は電灯などの光線との混同を防ぐため、信号がOnの状態でも点灯ではなく38kHzで常に点滅しているのが特徴です。ですが、リモコン用の受信モジュールであれば中にICが入っており、この点滅をひとつの信号と見なしてくれるので、特別なプログラムを組む必要はありません。電子パーツ店で赤外線受信モジュールといえば、まずリモコン用ですが、購入前に型番などを確認しておくとより確実でしょう。また、モジュールのメーカーによってはピンの配置が異なってくるので適時修正してください。

以下はプログラム例です(Visual Studioのフルプロジェクトはこちらからダウンロードできます)。XAMLで配置したボタンをクリックしてから5秒以内にリモコンのボタンを押して受信モジュールに信号を送ると、解析結果をテキストボックスに表示します。なお、受信結果の比較を容易にするため、あえておもしろまじめ電子工作という技術本のArduinoコードと同じ結果をテキストボックスに出力するようにしていますので、Arduinoをお持ちの方はそちらの参考書で書いたプログラムとの結果を見比べてみてください。

ちなみに、Arduino Unoは普通に買うと3000円前後ほどしますが、Arduinoは設計図が公開されていて、商標を使わない限りは互換品を自由に作ってもよいため、AmazonでArduino 互換と検索すると、Arduinoによく似た名前の同等の機能を持つ互換機をわずか数百円で購入することができます。

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);
        });
    });
}


結果は正しく取得できたでしょうか。精度のばらつきが大きいようであれば、受信モジュールにノイズが乗っかっている可能性もあるので、そのときはコンデンサーや抵抗を組み合わせるなどして、ハードウェア面でのノイズの除去も試してみるとよいでしょう。


なお、Windows 10 IoT Coreにはこれよりもさらに精度の高いGPIOを制御する方法が存在しますが、それについては次回に解説。
, | 2016年11月1日
Raspberry Piユーザーは周知の通り、電気信号を送るためのピンがあらかじめ用意されています。Raspberry Pi 2以降のモデルは基本的に40ピンで、定義もほぼ同一です。入門書を開くと、それぞれのピンがどのような役割を果たすかの一覧表がまず載っていますが、個人的には次の問題点がありました。

  • SPIやI2C通信ができるピンでもGPIO番号しか載っていない、またはその逆のケースがある
  • 電源投入直後で信号が通っている状態のピンがあるが、それに触れていないことがある(参考)
  • GPIOケーブルで延長したとき、外部ケースのためやむを得ず配線の仕方を変更するとちょっと混乱する

以上の問題点をすべて解決してくれる一覧表が見つからなかったので、自分で作成することにしました。画像の左側が基盤のロゴの向きに対応した通常の配置、右側が延長ケーブルを使って配置の上下が入れ替わったときの一覧となります。ちなみに赤色の丸は電源投入直後では信号がHighになっていることを示しています。


また、SVG形式もダウンロードできるようにしています。サイズを一般的なフラットケーブルの幅に合わせているので、InkscapeなどでL判用紙に印刷して切り取れば、GPIO延長ケーブルにそのまま貼り付けて使用できます。


これらの画像やデータはTNKソフトウェアブログからの引用であることを紹介していただけるのであれば、自由に再配布していただいてかまいません。

2016/11/08追記:SPI0のラベルに誤りがあったので修正しました。
 | 2016年10月24日
前回ではWindows IoT Coreが用意してくれているSPIをやりとりするAPIを使ったプログラミングを紹介しました。電子パーツのお店で売っているLCDはI2CやSPI規格に則ったものが2,3千円くらいで売られています(表示できる内容や規模にもよります)が、ワゴンに無造作に入れられたような数百円で買える激安LCDは独自のシリアル通信を使っていたりします。今回はそういった信号のための学習として、GPIOだけによるSPI通信を試してみました。

配線図は前回とほとんど同じですが、Raspberry Pi側のピンをすべてSPIに対応していない箇所に移しています。データは書き込みのみで、読み取りは行わないので、この線は省略しています。


まずはどのようにしてSPI通信が行われているかを知る必要があります。MCP23S08のデータシートやウィキペディア英語版によると、CS信号を流していない間に、総ビット分のオン・オフをSCKで持続的におこない、SCKがオン(またはオフ)になるタイミングでSI(SO)に信号があるかないかで、シリアルデータが決定されるようです。

ここでWindows特有の問題が発生します。Windows IoT CoreはUniversal Windows Appベースのため、待機状態にするには精度の低い「await Task.Delay()」しかプログラム命令がない上に、Arduinoでのdelay、Linuxでのusleepのようにミリ秒未満を指定することができません(追記:先日発売されたトランジスタ技術2016年11月号の記事によると、Raspberry Pi自体がマイクロ秒のカウントに対応していないようです)。

こちらのブログによると、マイクロ秒単位での待機には、精度の高いStopwatchクラスのカウンターが一定数に達するまで無限ループでぶん回すという力技しか今のところ方法は無いようです。

以上の点を踏まえたプログラムコードがこちらです。つないだ各種ピンをGPIOとして宣言したうえで、信号を送るときはCSピンをオフにし、入力ピンのスイッチを切り替え、パルス信号を送信する手順を行っています。

using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.ApplicationModel.Background;
using Windows.Devices.Gpio;

namespace SpiGpio
{
    public sealed class StartupTask : IBackgroundTask
    {
        private BackgroundTaskDeferral deferral;

        private const int GPIO_CS_PIN = 2;  // External chip select input 
        private const int GPIO_SI_PIN = 3;  // Serial data input
        private const int GPIO_CSK_PIN = 4; // Serial clock input
        private const int GPIO_SO_PIN = 17; // Serial data output
        private GpioPin cskpin, sipin, sopin, cspin;

        //Refer from : http://uepon.hatenadiary.com/entry/2016/08/19/234838
        private Stopwatch sw = new Stopwatch();
        private const long WAIT_SEC = 10;
        private void msDelay(long microSeconds)
        {
            long ticks = microSeconds * Stopwatch.Frequency / 1000000L;
            sw.Restart();
            while (sw.ElapsedTicks < ticks) ;
        }

        private GpioPin OpenPin(int pinno, bool input, bool high = false)
        {
            var pin = GpioController.GetDefault().OpenPin(pinno);
            if (input == false) {
                pin.SetDriveMode(GpioPinDriveMode.Output);
                pin.Write((high == true) ? GpioPinValue.High : GpioPinValue.Low);
            } else {
                pin.SetDriveMode(GpioPinDriveMode.Input);
            }
            return pin;
        }

        private void SendByte(byte bits)
        {
            for (int i = 0; i < 8; i++) {
                var pv = (((bits >> (7 - i)) & 0x1) != 0) ? GpioPinValue.High : GpioPinValue.Low;
                sipin.Write(pv);
                msDelay(WAIT_SEC / 2);
                cskpin.Write(GpioPinValue.High);
                msDelay(WAIT_SEC);
                cskpin.Write(GpioPinValue.Low);
                msDelay(WAIT_SEC / 2);
            }
        }

        private void SendCommand(params byte[] bytes)
        {
            cspin.Write(GpioPinValue.Low);
            msDelay(WAIT_SEC);

            foreach(byte b in bytes) {
                SendByte(b);
            }

            msDelay(WAIT_SEC);
            cspin.Write(GpioPinValue.High);
        }

        private async void Start()
        {
            cskpin = OpenPin(GPIO_CSK_PIN, false);
            sipin = OpenPin(GPIO_SI_PIN, false);
            sopin = OpenPin(GPIO_SO_PIN, true);
            cspin = OpenPin(GPIO_CS_PIN, false, true);

            SendCommand(0x40, 0x00, 0x00);

            Random r = new Random();
            while (true) {
                byte bits = 0x0;
                for(int i = 0; i < 4; i++) {
                    if (r.Next(0, 2) == 1) bits |= (byte)(1 << i);
                }
                SendCommand(0x40, 0x09, bits);

                await Task.Delay(500);
            }
        }

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            deferral = taskInstance.GetDeferral();

            Start();
        }
    }
}

このプログラムで実行される内容は前回のLチカと全く同じなので、実行例は写真を掲載するだけにとどめておきます。

, | 2016年10月7日
前回はI2C方式のIOエキスパンダーのWindows IoT Coreプログラミングを紹介しましたが、今回はSPI方式のIOエキスパンダー(MCP23S08)の使用例を紹介したいと思います。

ブレッドボードによる配線図と、写真によるイメージはこちらの通りです。Raspberry PiのSPI0ピンから出力される信号によって、上半分の右8ピンで入出力を拡張することができます。

 

続いてプログラミング。Windows IoT CoreにはSPI通信のためのAPIも用意されているので、これを使ってみます。このICではGPIO出力は3バイト単位で信号を送信することで操作が行えます。はじめの1バイトは操作コード(書き込みモードなら0x40、読み込みモードなら0x41。いずれもハードウェア操作の信号はなし)、次のバイトはレジスタアドレス(GPIOなら0x09)、最後のバイトはオン・オフのビット配列となります。今回は単純なGPIO操作なので、はじめに「0x40, 0x00, 0x00」の信号を送信することで、すべてのピンを出力モードとして初期化しています。

以上を踏まえたソースコードがこちらになります。今回も同様にバックグラウンドで動作し、4つのLEDがランダムで点灯します。
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Windows.ApplicationModel.Background;
using Windows.Devices.Enumeration;
using Windows.Devices.Spi;

namespace SpiApiTest
{
    public sealed class StartupTask : IBackgroundTask
    {
        private BackgroundTaskDeferral deferral;

        private SpiDevice SPIAccel;

        private byte[] sendbytes = new byte[] { 0x40, 0x09, 0x00 };
        private void SendGpio(byte bits)
        {
            sendbytes[2] = bits;
            SPIAccel.Write(sendbytes);
        }

        private void SendCommand(params byte[] args)
        {
            SPIAccel.Write(args);
        }

        private async void Start()
        {
            try {
                var settings = new SpiConnectionSettings(0);
                settings.ClockFrequency = 5000000;
                settings.Mode = SpiMode.Mode0;

                string aqs = SpiDevice.GetDeviceSelector("SPI0");
                var dis = await DeviceInformation.FindAllAsync(aqs);
                SPIAccel = await SpiDevice.FromIdAsync(dis[0].Id, settings);
                if (SPIAccel == null) {
                    Debug.WriteLine(string.Format(
                        "SPI Controller {0} is currently in use by " +
                        "another application. Please ensure that no other applications are using SPI.",
                        dis[0].Id));
                    return;
                }

                SendCommand(0x40, 0x00, 0x00);

                Random r = new Random();
                while (true) {
                    byte bits = 0x0;
                    for (int i = 0; i < 4; i++) {
                        if (r.Next(0, 2) == 1) bits |= (byte)(1 << i);
                    }
                    SendGpio(bits);

                    await Task.Delay(500);
                }
            } catch (Exception ex) {
                Debug.WriteLine("SPI Initialization failed. Exception: " + ex.Message);
            }
        }

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            deferral = taskInstance.GetDeferral();

            Start();
        }
    }
}
, | 2016年10月6日
Raspberry PiやArduinoでLEDドットマトリクスなどを使うと、あっという間にGPIOピンを使い尽くしてしまい、複数の機器をどうすれば同時に動作させられるのかと思うのが電子工作の初心者あるある。今回はRaspberry Pi 2とWindows 10 IoT CoreによるGPIOを増やす方法を紹介します。

用意するのはIOエクステンダーとよばれる200円ほどのIC。これとWindowsのAPIによってプログラミングすることで、4ピン(うち2ピンは電源とGND)だけでより多くのGPIOを用意することができます。エクステンダーは通信方法で大きく分けるとSPIとI2Cがありますが、今回はI2Cを採用している「MCP23017」を使いました。

このチップでは切りかけのある左側上下の16ピンでデータの入出力ができます。上側(GPA)と下側(GPB)ではプログラミングの方法が若干異なってくるので、それを交えた解説をしていきます。

まずは配線図。


写真だとこのような感じになります。

続いてプログラミング。Hackster.ioにあった投稿をベースにしていますが、手を加えているので、独自にカスタマイズした点だけを簡単に記述します。
  1. GPBへの出力を有効にするにはあらかじめ[0x01, 0x00]の2バイトを書き込みます。
  2. GPBバスの番号は0x13です。
  3. GPAとGPBのポート番号は上下で逆(GPAは左から7~0、GPBは左から0~7)になっているので、誤って同じ値を両方に適用しないようにしましょう。

以上を踏まえたソースコードがこちらになります。バックグラウンドで動作し、4つのLEDがランダムで点灯します。
using System;
using System.Threading.Tasks;
using Windows.ApplicationModel.Background;
using Windows.Devices.Enumeration;
using Windows.Devices.I2c;

namespace I2CApiTest
{
    public sealed class StartupTask : IBackgroundTask
    {
        private BackgroundTaskDeferral deferral;

        private const byte PORT_EXPANDER_I2C_ADDRESS = 0x20;

        private const byte IODIR_A_REGISTER = 0x00;
        private const byte MCP23017_BUS_A = 0x12;

        private const byte IODIR_B_REGISTER = 0x01;
        private const byte MCP23017_BUS_B = 0x13;

        private I2cDevice mcp23017;

        private async void Start()
        {
            var settings = new I2cConnectionSettings(PORT_EXPANDER_I2C_ADDRESS);
            var ds = I2cDevice.GetDeviceSelector("I2C1");
            var dis = await DeviceInformation.FindAllAsync(ds);
            mcp23017 = await I2cDevice.FromIdAsync(dis[0].Id, settings);

            try {
                mcp23017.Write(new byte[] { IODIR_A_REGISTER, 0x00 });
                mcp23017.Write(new byte[] { IODIR_B_REGISTER, 0x00 });

                var r = new Random();
                var wbuf = new byte[2];
                var m = new int[2,2]{{7, 6}, {0, 1}};

                while (true) {
                    for (int i = 0; i < 2; i++) {
                        wbuf[0] = (i == 0) ? MCP23017_BUS_A : MCP23017_BUS_B;
                        wbuf[1] = 0x0;
                        for (int j = 0; j < 2; j++) {
                            if(r.Next(0, 2) == 1) wbuf[1] |= (byte)(1 << m[i,j]);
                        }
                        mcp23017.Write(wbuf);
                    }

                    await Task.Delay(500);
                }
            } catch (Exception e) {
                System.Diagnostics.Debug.WriteLine("Exception: " + e.Message);
            }
        }

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            deferral = taskInstance.GetDeferral();
            Start();
        }
    }
}
, | 2016年10月5日
Raspberry Pi 2とWindows 10 IoT coreによる電子工作に挑戦しようとしているのですが、なかなか思うように行きません。まずひっかかったのは何もアプリを導入していないにもかかわらず、本体が再起動を繰り返すトラブル。調べてみたところ、主な原因としてSDカードとの相性に問題(200円で買ったワゴンのSDカードを使っていた)があるとのことでした。有志によって作成された互換性一覧によると、サンディスクやサムスンでさえも外れがあるみたいなので、結果、東芝製のカード(EXCERIA M301[輸入品])を買い直すことにしました。

ではここからが本題。Windows 10 IoT Coreによる開発は日本語による資料がほとんどないため、まずは基礎固めとして、ホビープログラミング入門という日経ソフトウェアのムックで紹介されていたRaspbian & Pythonによるスイッチプログラムの移植にチャレンジしてみました。

Windows 10 iot coreの導入方法は探せばすぐに見つかるので、ここでは割愛。まずは、スイッチと抵抗をブレッドボード経由で配線します。ブレッドボード中央の青い線の上に1KΩの抵抗がささっている点に気をつけてください。


続いてVisual Studio 2015のセットアップです。標準ではGUIのあるアプリしか作れませんが、こちらのページよりテンプレートを導入することで、GUIのないバックグラウンドアプリを作成できるようになります。インストールが終わると、Visual Studio 2015の新しいプロジェクトに「Background Application(IoT)」が追加されていると思います。


それではプログラムを組んでみましょう。バックグラウンドアプリはRunメソッドから呼び出されます。アプリを半永久的に起動させ続けるには、ここでGetDeferral()を呼び出す必要があります。今回はGPIO4に電気が入って来るかどうかの回路なので、4番ピンを入力モードにしています。あとはタイマーを起動させ、入力値が変更されたらVisual Studioのデバッグ出力に結果を表示させます。

// StartupTask.cs
using System;
using System.Diagnostics;
using Windows.System.Threading;
using Windows.ApplicationModel.Background;
using Windows.Devices.Gpio;

namespace Switch
{
    public sealed class StartupTask : IBackgroundTask
    {
        private BackgroundTaskDeferral deferral;

        private const int GPIO_PIN = 4;
        private GpioPin pin;

        private ThreadPoolTimer timer;
        private GpioPinValue gpv = GpioPinValue.Low;

        public void Run(IBackgroundTaskInstance taskInstance)
        {
            // これがないとRunメソッドが終わるとアプリが終了してしまう
            // また、deferral.Complete(); で明示的に終了させることができる
            deferral = taskInstance.GetDeferral();

            // 4番ピンの入力を有効にする
            pin = GpioController.GetDefault().OpenPin(GPIO_PIN);
            pin.SetDriveMode(GpioPinDriveMode.Input);
            gpv = pin.Read();

            // 500msごとに判定する
            timer = ThreadPoolTimer.CreatePeriodicTimer(OnTickTimer, TimeSpan.FromMilliseconds(500));
        }

        private void OnTickTimer(ThreadPoolTimer timer)
        {
            var v = pin.Read();
            if (gpv != v) {
                Debug.WriteLine(v.ToString());
                gpv = v;
            }
        }
    }
}


続いてデバッガの設定です。対象のアーキテクチャを「ARM」にし、ターゲットは「リモートコンピューター」に。C#プロジェクトのプロパティの「Debug」に、対象のRaspberry Piが接続されているIPアドレスを入力します。アドレスは「Windows 10 IoT Core Dashboard」の「自分のデバイス」より確認できます。


初回起動時は追加コンポーネントなどの導入のため、やや時間がかかりますが、デバッガが起動すると、ブレッドボードのスイッチを押したり離したりするたびに、「出力」ウィンドウに結果が表示されます。


ちなみに、スイッチを押しているときはGNDピンへと電気が通じてGPIO4ピンには流れないため、GPIO4の結果は「Low」となり、離すとGPIO4に電気が流れるようになるため、結果が「High」となります。
, | 2016年9月21日
« 前ページへ