« 前ページへ - 次ページへ »
Google Playストアにて通常800円販売しているカード型データベース「ショートアイデアノート」の全機能を使用できるアプリ内アイテムを2016年12月25日までの期間限定で、460円にて販売いたします。グーグルからクリスマスギフトコードをプレゼントされた方は、これを機会に是非ご購入ください。

 | 2016年12月19日
S5U13781R01C100」はエプソンが開発したLCDコントローラIC「S1D13781」を搭載した評価ボードです。

このボードの特徴はLCD用の40pinコネクタと50pinコネクタを両方備えており、単一のボードで複数のメーカーのLCDに対応できることがあげられます。また、日本語のドキュメントに加え、ライブラリーのソースが充実しているため、開発の敷居が低いのも利点です。

一方で、搭載ICのビデオRAMは384KBと、480x272のフルカラーRGBがぎりぎりで格納できる容量のため、それ以上の解像度の液晶や、表示中の画面の後ろで別の画面を作成するダブルバッファ(メーカーではPIP機能と表記)を実装するには色数を下げることでメモリーを確保しなければならず、連続した写真を表示するなどのリッチな画像処理にはやや不向きです。

S5U13781R01C100は、秋月電子や共立電子ともにArduino Due用と表記したうえで販売しており、実際にArduino Dueをセットにしたキットも販売しています。シリコンハウス共立での価格は約9000円でしたが、Arduinoのない液晶とコントローラーだけのセットでは2500円と非常に安価でした。


今回はこの評価ボードをWindows IoT CoreとRaspberry Piを使って制御してみることにします。

このボードのピンは位置はArduinoにぴったりはまるような設計になっていますが、このボードとのデータのやりとりは実質的にSPI通信なので、適切な命令さえ送れば、Raspberry Piでも難なく動きます。まずはこちらの画像を参考に、電源とGPIO端子を繋げてみてください。コントローラーは裏から見た図です。LCDバックライトへの過電流を防ぐため、表面のDIPスイッチの確認(1番は電源で、2~4はスイッチの数だけ20~60mAの電流がバックライトに流れるようになる)を忘れずに。

  

データ送受信の肝となるピンのWindows IoT Coreにおける制御プログラムはこのようになります。低レベルGPIOコントローラーであるLightningProviderについては同ブログの「Windows IoT Coreで赤外線リモコンの信号を受信する」をご参照ください。あとはデータシートに沿って描画命令を送受信するだけですが、次回では、どのように画像を表示すれば良いかを具体的に解説していきたいと思います。

public async void Begin()
{
    // SPI通信の設定
    var settings = new SpiConnectionSettings(0);
    settings.ClockFrequency = 24000000;
    settings.Mode = SpiMode.Mode0;
    settings.DataBitLength = 8;

    // GPIOピンの初期化
    if (LightningProvider.IsLightningEnabled) {
        LowLevelDevicesController.DefaultProvider = LightningProvider.GetAggregateProvider();

        var cs = await SpiController.GetControllersAsync(LightningSpiProvider.GetSpiProvider());
        var controller = cs[0];
        SPI = controller.GetDevice(settings);

        var gpc = await GpioController.GetControllersAsync(LightningGpioProvider.GetGpioProvider());
        CsPin = gpc[0].OpenPin(CsPinNo);
    } else {
        string aqs = SpiDevice.GetDeviceSelector("SPI0");
        var dis = DeviceInformation.FindAllAsync(aqs).GetResults();
        if(dis.Count > 0) {
            SPI = await SpiDevice.FromIdAsync(dis[0].Id, settings);
            CsPin = GpioController.GetDefault().OpenPin(CsPinNo);
        }
    }
    if (CsPin != null) {
        CsPin.SetDriveMode(GpioPinDriveMode.Output);
        InitRegs();
    }
}

private void RegWrite(RegIndex regIndex, ushort regValue)
{
    if (CsPin == null) return;

    // 信号送信の開始を伝える
    CsPin.Write(GpioPinValue.Low);

    // SPI信号を送信
    SPI.Write(new byte[]{
        (byte)SPIBIT.SPIWRITE_8BIT, //command
        0x06, //address bits 18:16
        0x08, //address bits 15:8
        (byte)regIndex, // address bits 7:0
        (byte)(regValue & 0x00FF), //data bits 7:0
        (byte)(regValue >> 8) //data bits 15:8
    });

    // 送信の終了を伝える
    CsPin.Write(GpioPinValue.High);
}

private ushort RegRead(RegIndex regIndex)
{
    if (CsPin == null) return 0x00;

    CsPin.Write(GpioPinValue.Low);

    SPI.Write(new byte[]{
        (byte)SPIBIT.SPIREAD_8BIT, //command
        0x06, //address bits 18:16
        0x08, //address bits 15:8
        (byte)regIndex, // address bits 7:0
    });

    // 送信結果のデータは3バイトで0番目は常にダミー値が入る
    byte[] res = new byte[3];
    SPI.Read(res);

    CsPin.Write(GpioPinValue.High);

    return (ushort)(res[1] | (res[2] << 8));
}
,, | 2016年12月16日
「AM50288H」は、大阪日本橋の電子パーツ店「デジット」や共立エレショップの通販サイトで1つ200円で販売されているLCDです。14セグメント方式なので、ASCII文字を表示する程度なら、なかなか使い道のある製品です。今回はこのLCDをWindows 10 IoT Coreで制御するためのライブラリーを開発したので、この場を借りて公開したいと思います。

商品には簡易的なデータシートが同梱されているため、これを基に配線を行いました。


この図のように配線自体は簡単なのですが、LCDの出力端子が0.5mmピッチ10ピンFFC仕様なので、実際にはFFCケーブルと変換基板を用意する必要があります。共立電子ではケーブルを取り扱っていなかったため、管理人はeBayでケーブルを10本2ドルほどで個人輸入しましたが、到着まで1ヶ月ほどかかったので、そこまで待てないという方は、aitendoの通販サイトを使うと良いでしょう。


続いてライブラリーの使い方の解説です。

AM50288クラス(AM50288H.cs)
  • PinLE,PinDIN,PinSCK:割り当てられているGPIOピンの番号を取得、設定します。
  • void Start():指定したピンを開き、通信を開始します。
  • void Update():SetSegmentなどで登録した表示情報をLCDに反映させます。
  • void Clear():LCDの表示をリセットします。
  • void SetSegment(int pos, SegmentPosition spos):pos+1桁目のセグメントに表示するデータを指定します。たとえば、「SegmentPosition.Top | SegmentPosition.Bottom」で「=」のような文字が定義されます。SetSegmentAsBitsでは列挙型の代わりに直接ビット数値を指定します。
  • ushort GetAsciiSegment(char ascii):あらかじめ登録されているASCII文字のビット数列を取得します。
  • SegmentPosition GetSegmentFlag(ushort seg):セグメントのビット数列を列挙型に変換します。
  • void SetAscii(int pos, char ascii):指定の桁にASCII文字を直接登録します。
  • void SetAsciiString(string str):最大8文字のASCII文字列を直接登録します。8文字目以降は無視されます。
  • void ShiftSegmentsLeft(), ShiftSegmentsRight():セグメントを左、もしくは右にシフトします。追加されるセグメントは空白となります。
  • void SetMark(AM50288H_Mark mark, bool set):LCDに用意されている特殊なマークを登録します。setの値がtrueで表示、falseで非表示になります。

また、付属のプロジェクトファイルには動作検証用のUWPアプリが収録されています。


SetボタンやチェックボックスでLCDに表示するデータを登録することはもちろん、画面上部のセグメントイメージの箇所を直接クリックしても、表示のオン・オフが行えます。ただし、Updateボタンを押すまでLCDにデータは送信されないのでこの点には注意してください。


プログラムを開発するに当たって、セグメントの列挙型の定義はArduino用ライブラリーを参考にさせていただきました。この場を借りて開発者にお礼を申し上げます。

サンプルソースのダウンロード(IoTプログラミングのページに移動します)
, | 2016年12月13日
ほぼ毎日に渡ってお送りしてきたWindows IoT CoreによるGPIO制御プログラミング。今回は総仕上げとして、GUIベースのアプリからArduinoアプリで赤外線信号を受信するテクニックを紹介します。

ソリューションを作成し、フォアグラウンドアプリはC#のユニバーサルアプリ、バックグラウンドアプリはC++のArduino Wireアプリを追加します。具体的な作成方法についてはこちらの記事をご覧ください。

「StartupTask.cpp」を開きます。初期状態のコードを見ると、setup()を読んだ後、loop()を永遠に回すという、Arduino IDEでのコンパイル前のコード変換と同じことをしており、また、このファイルにArduinoの関数を宣言することでinoファイルの関数を呼び出せることがわかります。

そこで、loop()の直後に信号が受信されたかをチェックして、情報があればメモリーの内容を複製して、フォアグラウンドアプリに向けてイベントを発生し、最後に信号を初期化するようにしています。ちなみにValueSetクラスはC#やVBとの互換性はあるものの、継承されているインターフェイスが違うこともあり、メソッドなどの仕様が大幅に異なっている点に注意してください。

#include 
using namespace concurrency;

using namespace Platform;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::ApplicationModel::Background;
using namespace Windows::ApplicationModel::AppService;

// These functions should be defined in the sketch file
void setup();
void loop();

bool getSignal(unsigned int **sdata, int *dataLen);
void resetSignal();

namespace BgApp
{
    [Windows::Foundation::Metadata::WebHostHidden]
    public ref class StartupTask sealed : public IBackgroundTask
    {
    private:
        AppServiceConnection^ appcon = nullptr;

    public:
        virtual void Run(IBackgroundTaskInstance^ taskInstance) 
        {
            auto deferral = taskInstance->GetDeferral();

            AppServiceTriggerDetails^ triggerDetails = dynamic_cast(taskInstance->TriggerDetails);
            appcon = triggerDetails->AppServiceConnection;
            // フォアグラウンドからデータをを受け取りたいときのサンプル
            //appcon->RequestReceived += ref new TypedEventHandler
            //    (this, &StartupTask::OnConnectionReceived);

            unsigned int *data;
            int dataLen;

            setup();
            while (true)
            {
                loop();

                if (getSignal(&data, &dataLen) == true) {
                    auto sendData = ref new Array(data, dataLen);
                    auto messages = ref new ValueSet();
                    messages->Insert("infrared", sendData);
                    try {
                        create_task(appcon->SendMessageAsync(messages)).wait();
                    }catch(Exception ^ex){ }
                    resetSignal();
                }
            }
        }
    };
}

inoファイルにも手を加えます。
#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;

boolean needSend = false;

boolean getSignal(unsigned int **sdata, int *dataLen)
{
    if (needSend == true) {
        *sdata = data;
        *dataLen = dataPos;
    } else {
        *sdata = NULL;
        *dataLen = 0;
    }
    return needSend;
}

// --------------

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

unsigned long lastMicros;
int moduleStatus, totalMS;

void resetSignal()
{
    dataPos = 0;
    totalMS = 0;
    needSend = false;
    lastMicros = micros();
}

void setup() {
    pinMode(modulePin, INPUT);
    moduleStatus = HIGH;

    resetSignal();
}

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が基準
            needSend = true;
        }
        return;
    }

    unsigned long now = micros();

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

    lastMicros = now;

    moduleStatus = !moduleStatus;
}

フルプロジェクトはこちらよりダウンロードできます。ほかに必要なテクニックはこれまでの記事でさんざん語り尽くしているので、最終回は短い内容になってしまいましたが、Windows 10 IoT Coreでも電子工作のためのOSして実用的であることを理解していただけたならば幸いです。
,, | 2016年11月5日
UWPアプリはVisual C++でも開発ができますが、UWPの機能を活用するにはマイクロソフトの独自言語であるC++/CXでプログラムを組まなくてはいけません。C++/CXに頼らず、純粋なC++のみで開発するための「WindowsランタイムC++テンプレートライブラリ(WRL)」と呼ばれる手法も提供していますが、.NETの仕様を無理にC++に寄せているためか、非常に難解になっており、これを使うくらいなら、C++を.NETに寄せたC++/CXを習得した方がまだましです。

と、いうわけで、Windows IoT Coreプログラミングの総仕上げの前に、Arduino WiredアプリをGUIとして活用したい人のための、C#のこの仕様はC++/CXではどうなるのかの基本的なお約束を列挙したいと思います。

ただし、メインプログラムはC#で、C++/CXプログラムはランタイムライブラリとして最低限だけ使うことを前提としているため、一部仕様を意図的に歪曲して説明している箇所がありますので、この点にはご注意ください。

※サンプルコードの左側はC#、右側はC++/CXでの例となっています。また、説明に[C++]とあるものは、C++/CXではなく、C++本来の仕様であるか、C++が基になっていることを示しています。

1.[C++]名前空間は「::」で区切る
using System.Diagnostics;

namespace MyApp{
    public class foo{
        private void bar(){
            Debug.Write("test");
        }
    }
}
using namespace System::Diagnostics;

namespace MyApp{
    public ref class foo{
    private:
        void foo(){
            Debug::Write("test");
        }
    };
}


2.UWPクラスは「^」ポインタの変数に、ref newで作成する。ポインタなので、メンバへのアクセスはC++同様「->」を用いる。これにより作成されたクラスはガベージコレクタにより処理されるため、明示的な削除は基本的に不要。ただし、IDisposableインターフェイスから派生したクラスはdeleteで明示的に削除しないとリソースが残ってしまう可能性がある。
StreamSocket s = new StreamSocket();
s.Dispose();
StreamSocket^ s = ref new StreamSocket();
delete s;


3.[C++]null値はnullptrと記述。
s.Dispose();
s = null;
delete s;
s = nullptr;


4.[C++]コンパイル時に型が決定されるC#のvarはautoで代用可能。
var s = new StreamSocket();
auto s = ref new StreamSocket();


5.[C++]as演算子はdynamic_castである程度代用可能。
StreamSocket s = obj as StreamSocket;
if(s == null) throw new Exception();
StreamSocket^ s = dynamic_cast<StreamSocket^>(obj);
if(s == nullptr) throw ref new Exception();


6.文字列はPlatform::String^に直接代入するのであれば「L」などのUnicode宣言は不要(コンパイル時にUnicodeとして扱われる)。配列としての文字列の場合はStringクラスの新規作成で変換する。
string s = "This is a sample";
String^ s = "This is a sample";
char ss[] = "This is a sample";
s = ref new String(ss);


7.配列はArrayクラスで作成する。C言語配列からの変換も同様。Platform::ArrayReferenceクラスを使えば、配列アドレスの参照だけが登録されるため、リソースの節約になるが、オリジナルの配列が変更されると内部のデータが破壊される点に注意。
byte[] buf = new byte[100];
Array<byte>^ buf = ref new Array<byte>(100);

byte *cbuf = new byte[100];
buf = ref new Array<byte>(cbuf, 100);

DataWriter^ writer = ref new DataWriter();
writer->WriteBytes(ArrayReference<byte>(cbuf, 100));


8.[C++]ランタイムC++クラスにネイティブC++クラスを含めたり、その逆はできるが、ランタイムに準拠していないメソッドや変数は公開できないという規則があるため、publicやメソッド経由に関わらず、外部からクラス変数にアクセスすることは原則としてできない。現時点では変数をprivateにして、friendを使うことで対処できる。
ref class WinRTApp sealed
{
private:
    friend class NativeApp;
    NativeApp *napp;
};

class NativeApp : public NativeAppBase
{
private:
    friend ref class WinRTApp;
    WinRTApp^ srt;
};


9.イベントの追加は「+=」を使用する点まではC#と同じだが、メソッドは直接指定できず、引数の型を宣言したTypedEventHandlerクラスを経由する必要がある。
var amv = CoreApplication.MainView;
if (amv){
    var cw = amv.CoreWindow;
    if (cw){
        cw.KeyDown += OnKeyDown;
        cw.KeyUp += OnKeyUp;
    }
}
WinRTApp::WinRTApp()
{
    auto amv = CoreApplication::MainView;
    if (amv){
        auto cw = amv->CoreWindow;
        if (cw){
            cw->KeyDown += ref new TypedEventHandler<CoreWindow ^, KeyEventArgs^>(this, &WinRTApp::OnKeyDown);
            cw->KeyUp += ref new TypedEventHandler<CoreWindow^, KeyEventArgs^>(this, &WinRTApp::OnKeyUp);
        }
    }
}

void WinRTApp::OnKeyDown(CoreWindow ^w, KeyEventArgs ^e)
{
    e->Handled = true;
}


10.APIの非同期処理メソッドはconcurrency拡張で処理するのが一般的。非同期処理が完了すれば、create_taskで作成したタスクのthen内の関数(ここでは[C++]のラムダ式を使用)が実行される。さらにこの関数内で非同期処理が行われたときは、jQueryのようにthenをチェインすることで処理を継続させることが可能。このチェインを抜けるときは「task_canceled()」をthrowする。また、then()の代わりに、"t.wait();t.get();"を実行することでawaitと同等の処理ができる。
#include <ppltasks.h>
using namespace concurrency;

void GameInputDevice::Start()
{
    auto selector = HidDevice::GetDeviceSelector(0x1, 0x4);
    task c = create_task(DeviceInformation::FindAllAsync(selector));
    c.then([this](DeviceInformationCollection^ info){
        DeviceInformation ^di = nullptr;
        for (unsigned int i = 0; i < info->Size; i++){
            di = info->GetAt(i);
            if (wcsstr(di->Name->Data(), L"TNK Virtual")){
                continue;
            } else if(di != nullptr) {
                return HidDevice::FromIdAsync(di->Id, Windows::Storage::FileAccessMode::Read);
            }
        }

        throw task_canceled();
    }).then([this](HidDevice ^d){
        if (!d) return;
        device = d;
    });
}


管理人が習得済みであれば、情報は随時追加していきますので、「このC#のコードをC++/CXではどう書けばいいの?」という質問があれば、このツイートへのリプライかメールを送ってください。
 | 2016年11月4日
前回では「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日
Windowsのエクスプローラーをタブ化して単一のウィンドウで編集できるようになるEJIE TECHNOLOGYのCloverというユーティリティーソフトを使っています。中国の方が開発されたアプリなのでインターフェイスは基本的に中国語か英語です。一応設定から日本語を選択できるのですが、ブックマークを「栞」と表記するなど、日本語としてはちょっと不自然なので、日本語化ファイルを独自に作ってみました。開発者に翻訳ファイルの無償提供を申し出ているのですが、今のところ返事がないので、暫定的に自身のブログで配布します。


導入するにはCloverを終了している状態で「C:\Program Files (x86)\Clover\3.2\lang」フォルダーにある同名のdllファイルを差し替えるだけです。なお、付属のrcファイルは、将来バージョンアップでメッセージに変更があった場合、リソース編集ソフトを使って加工できるようにするためのファイルなので、一般の方には不要です。

Clover日本語化ファイルダウンロード

2017年3月6日追記:
導入補助ツールを同梱した日本語化ファイルを公開しました。ダウンロードはこちらの紹介ページよりどうぞ。
2016年10月27日
Raspberry Piユーザーは周知の通り、電気信号を送るためのピンがあらかじめ用意されています。Raspberry Pi 2以降のモデルは基本的に40ピンで、定義もほぼ同一です。入門書を開くと、それぞれのピンがどのような役割を果たすかの一覧表がまず載っていますが、個人的には次の問題点がありました。

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

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


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


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

2016/11/08追記:SPI0のラベルに誤りがあったので修正しました。
 | 2016年10月24日
« 前ページへ - 次ページへ »