« 2015年11月 | トップページ | 2016年1月 »

2015年12月の記事

Windows 10 IoT CoreでI2Cを使う

Windows 10 IoT CoreでI2Cを動かしてみました。Windows 10 IoT Coreは執筆時点の最新版であるBuild 10586を使用しています。Windows 10 IoT CoreではRaspberry Piのような組み込み系ボードを使う場合でも、アプリ作成はUniversal Windows Platform (UWP)の作法に従う必要があります。コードは参考文献のものを流用していますが、当方が初めて見る非同期処理などが使われており、最初はコードの意味がわかりませんでした。

当方、Windowsのプログラムは、VS2008の時代に.Net Frameworkを使ったWindows Formプログラムを少々かじりましたが、確かC# 2.0が出た頃でラムダ式はありましたが、非同期プログラミングの概念はまだなかったと思います。ですので、UWPの流儀はWindowsプログラミングの経験的にも初めての要素が多かったです。(注:.NET 1.0の時代からDelegateを使った非同期プログラミングの概念はあったとのコメントをいただきました。ただし大変煩雑だったようです)

非同期プログラミングは、WindowsストアアプリやiOSアプリを作っている方には当たり前の処理だと思うので今更ではありますが、サンプルコードから私が理解したことを記載します(間違っていたらごめんなさい)。シングルスレッドのmbed/Arduino用プログラムを見慣れた(というか、これしか知らない)私には結構新鮮でした。

サンプルコード

TMP102温度センサーを使用して、測定値を画面に表示します。画面はTextBlockを一つ表示するだけの単純なものです。

using System;
using System.Diagnostics;
using System.Threading;
using Windows.UI.Xaml.Controls;
using Windows.Devices.I2c;
using Windows.Devices.Enumeration;
using Windows.ApplicationModel.Core;

namespace TMP102
{
    /// <summary>
    /// TMP102を使用した温度測定
    /// </summary>
    public sealed partial class MainPage : Page
    {
        private I2cDevice TMP102;
        private Timer periodicTimer;

        public MainPage()
        {
            this.InitializeComponent();
            Unloaded += MainPage_Unload;

            InitTMP102();
        }

        private void MainPage_Unload(object sender, object args)
        {
            TMP102.Dispose();
        }

        private async void InitTMP102()
        {
            try
            {
                // Get a selector string for bus "I2C1"
                string aqs = I2cDevice.GetDeviceSelector("I2C1");

                // Find the I2C bus controller with our selector string
                var dis = await DeviceInformation.FindAllAsync(aqs);
                if (dis.Count == 0)
                {
                    Debug.WriteLine("No I2C bus found");
                    CoreApplication.Exit();
                }
                string deviceID = dis[0].Id;
                var setting = new I2cConnectionSettings(0x48);
                setting.BusSpeed = I2cBusSpeed.FastMode;

                // Create an I2cDevice with our selected bus controller and I2C settings
                TMP102 = await I2cDevice.FromIdAsync(deviceID, setting);
            }
            catch(Exception error)
            {
                Debug.Write("TMP102 Instantiation Error: " + error.Message);
                CoreApplication.Exit();
            }

            periodicTimer = new Timer(this.TimerCallback, null, 0, 1000);
        }

        // スレッドプールにキューキングされ、Timerクラスをインスタンス化した
        // スレッド(UIスレッド)とは異なるスレッドで実行される
        private void TimerCallback(object state)
        {
            byte[] readBuf = new byte[2];

            try
            {
                TMP102.Read(readBuf);
            }
            catch(Exception error)
            {
                Debug.Write("I2C Read Error: " + error.Message);
                CoreApplication.Exit();
            }

            float temperature = CalcTemperature((int)readBuf[1], (int)readBuf[0]);
            string temperatureText = String.Format("Temperature : {0:F2}℃", temperature);

            // 直接 textBlock.Text = temperatureText; と書くと例外が発生する
            // UIスレッドを操作するためには以下のように記述する
            var task = this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
            {
                textBlock.Text = temperatureText;
            });

            Debug.WriteLine("Temperature:{0:F2}℃", temperature);
        }

        private float CalcTemperature(int valh, int vall)
        {
            int res = (vall << 4 | valh >> 4);
            float temperature = (float)res * 0.0625f;

            return temperature;
        }
    }
}

async/awaitによる非同期処理

Windows 10 IoT CoreもWindowsのファミリーなので、MainPageクラスはメインスレッドで動き、UI操作などのイベントが配信されます。メインスレッド内で重たい処理を実行してしまうと、処理が完了するまでの間他のイベントを処理できなくなり、UIが固まってしまいます。そのため、メインスレッドが長時間ブロックされるのを防ぐために、重たい処理を別スレッドで実行する非同期処理を使います。Windows 10 IoT Coreのデバイス操作でも非同期処理が多用されるようです。非同期処理の概要を以下に記載します。

40行目で「await演算子」がDeviceInformation.FindAllAsync(aqs)の呼び出しに付与されています。awaitを指定することによって、このメソッドを別スレッドで非同期に実行します。メソッド内に非同期処理が含まれる場合は、メソッド名(この場合はInitTMP102())に「async修飾子」をつけます。

40行目のawait DeviceInformation.FindAllAsync(aqs);を実行すると、InitTMP102()メソッドは一旦終了し(処理を呼び出し元に戻し)、他のイベントを受け取れるようにします。DeviceInformation.FindAllAsync(aqs)の処理が終了すると、41行目以降の処理を自動的に再開します。従来の非同期プログラムスタイルだと、非同期処理終了後の後処理(41〜51行目に相当する部分)を別のブロック(終了時に呼び出されるコールバックやクロージャー)に記述したりしますが、C# 5.0では一連の処理として記述できるためコードの見通しがよく大変便利です。

周期タイマー処理

このサンプルでは、Timerクラスを使って、64行目のTimerCallback()メソッドを1000ms周期で実行します。タイマ・メソッドは.NET Frameworkが管理するスレッド・プールにキューイングされて実行されるため、TimerCallback()メソッドはTimerクラスをインスタンス化したメインスレッドとは異なるスレッドで実行されます。

TimerCallback()メソッド内で測定した温度を画面に表示していますが、ここで注意が必要です。上記に示した通り、タイマー処理は別スレッドで動いているため、メインスレッドで処理すべきUIの操作ができません。例えば、このタイマースレッド内で、textBlock.Text = temperatureText;を実行すると例外が発生します。

タイマースレッド内でUIの操作依頼を行うために、83行目のDispatcher.RunAsyncによって、クロージャーとして指定した処理ブロックをUIスレッドにキューイングしています。

メインスレッド内で動くDispatcherTimerクラスを使えば上記のDispatcher.RunAsync処理は不要で、タイマー処理の中で直接UIを操作できますが、重たい処理を記述するとUIが固まってしまうため注意が必要です。Windows 10 IoT Coreを使った組み込み系のプログラムでは複雑なUIを使うことはないため、DispatcherTimerクラスでもよいような気がします。

参考情報


Windows 10 IoT Core Build 10586を使ってみる

最近Raspberry Pi用のWindows 10 IoT Coreを触っています。当初はBuild 10556を使っていましたが、10586が最近リリースされました。Build 10586になってインストールの方法やデバック時の設定が変わっているため、以下の通り整理してみます。

Windows 10 IoT Coreのインストール

Windows 10が動くPCを用意します。まず、リンクのサイトにアクセスし、「Get the Windows 10 IoT Core Dashboard」をクリクしてWindows 10 IoT Core DashboardをPCにインストールします。

SetupWindowsIoT_Device.PNG

Build 10556ではISOファイルをダウンロードしてインストールを行いましたが、Build 10586ではIoT Core DashbordがイメージのダウンロードとSDカードへの書き込みを行ってくれます。Windows 10 IoT Coreのイメージを書き込むSDカードをPCにセットしてWindows 10 IoT Core Dashboardを起動すると以下の画面が表示されますので、デバイスの種類(Raspberry Pi 2)とSDカードのドライブを指定して「ダウンロードとインストール」ボタンを押します。

Windows10_IoT_Install.PNG

SDカードへの書き込みが始まると、以下の画面が表示されます。

Windows10_IoT_ImageFlush.PNG

イメージの書き込みが完了したら、SDカードをRaspberry Piにセットして起動します。Raspberry PiにはディスプレーとEthernetケーブルをつないでおきます。

初回の起動には結構時間がかかるので気長に待ちます。Build 10556では起動中に画面がブラックアウトした時間が長く、固まってしまったのかと思ったことがありましたが、Build 10586は画面がブラックアウトすることはなく処理中の表示が回っているので改善が見られます。

Windows 10 IoT Coreが立ち上がったら、ディスプレーにDHCPで割り当てられたIPアドレスが表示されているので、Webブラウザーから<IPアドレス>:8080にアクセスすると管理Web画面が表示されます(ユーザー名:Administrator、初期パスワード:p@ssw0rdでログイン)。ログインできたら、パスワードやデバイス名の変更が管理Webの画面から行えます。

Lチカプログラムの実行

お約束のLチカをやってみます。開発環境のVisual Studio 2015はこのリンクなどの手順に従ってセットアップされているとします。Build 10586のWindows 10 IoT Coreで開発を行う際は、以下の追加インストールが必要です:

  • Visual Studio 2015 Update 1のインストール:ここからプログラムをダウンロードしてインストール。Update 1をインストールしないとBuild 10586では、ターゲット(Raspberry Pi)へのプログラムのアップロードが行えませんので必ずUpdateが必要です
  • Windows IoT Core Project Templatesを最新版に更新:ここからテンプレートをダウンロード。すでに旧版がインストール済みの場合は、VS2015から一旦旧版を削除して再度インストールします

VS2015を起動したら以下の設定を行います

  • 新しいプロジェクトの作成を行い、テンプレートに「ユニバーサル」「空白のアプリ」を選択して、適当な名前をつける
  • 参照設定に「Windows IoT Extensions for the UWP」を追加

プロジェクトが作成できたら、MainPage.xaml.csに以下のコードを入力。

using System.Threading.Tasks;
using Windows.UI.Xaml.Controls;
using Windows.Devices.Gpio;

// 空白ページのアイテム テンプレートについては、http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409 を参照してください

namespace LED
{
    /// <summary>
    /// Lチカサンプル
    /// <summary>
    public sealed partial class MainPage : Page
    {
        private const int LED_PIN = 5;
        private GpioPin pin;

        public MainPage()
        {
            this.InitializeComponent();

            // GPIOの初期化メソッドを呼び出します
            InitGPIO();

            // LEDの ON / OFFのためのループ処理を呼び出します
            loop();
        }

        private void InitGPIO()
        {
            var gpio = GpioController.GetDefault();

            // GPIOコントローラーがない場合
            if (gpio == null)
            {
                pin = null;
                return;
            }
            // GPIOの5番ピンを開きます
            pin = gpio.OpenPin(LED_PIN);

            // 5番ピンをHighに設定します
            pin.Write(GpioPinValue.High);

            // 5番ピンを出力として使うよう設定します
            pin.SetDriveMode(GpioPinDriveMode.Output);
        }

        // 1秒おきでLEDをON/OFFさせるループの処理
        private async void loop()
        {
            while(true)
            {
                pin.Write(GpioPinValue.Low);
                await Task.Delay(500);
                pin.Write(GpioPinValue.High);
                await Task.Delay(500);
            }
        }
    }
}

ビルドができたら、以下の手順でデバックを開始し、Raspberry Piへプログラムをアップロードします
  • デバックセッションの接続先として「リモートコンピューター」を選択
  • Raspberry PiのIPアドレスを入力
  • 認証モードは「ユニバーサル」を選択。Build 10586では認証モードを「ユニバーサル」にしないとターゲット(Raspberry Pi)へのプログラムのアップロードが行えません
  • プロジェクトのプロパティーを開いて、デバックオプションの認証モードが「Universal」になっていることを確認(もしくはここで、Universalを選択)。

VS2015_DebugSetting.PNG

参考情報



« 2015年11月 | トップページ | 2016年1月 »

2018年10月
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      
無料ブログはココログ