次ページへ »
何度もこのブログで言及していますが、Windows IoT Coreで「DMAP Driver」とLightning APIを使うとGPIOの処理速度が劇的に上がります。Lightning APIのソースコードはGithubで公開されているので、このコードを読み解くことで、なぜ高速な処理ができるのかがわかります。

ざっくり解説すると、LightningライブラリーではWin32 APIでGPIOコントローラーのレジスタを取得し、その値を直接書き換えることで迅速なアクセスを実現しています。また、C++/CXよりArduino言語の方がより高速なのは、C++/CXではウィンドウズランタイムが介入しているのに対し、Arduino言語はC++言語としてコンパイルされた上で、ライブラリ内のWin32 APIに直にアクセスしているためであることが理解できます。


前回紹介したサンプルプログラムはLightning APIを使用していないため、ドライバー制御ソフトはDMAP Driverでは動作しません。そこで今回はDMAP Driver環境で動くドライバー制御ソフトのソースコードを紹介したいと思います。

DMAP Driverコントローラーの作成は、Lightning APIの移植という形で実現しています。ただし、オリジナルのソースにはWinRT関連の命令が含まれているため、命令の一部をDeviceIoControl()に置き換えるといった最適化を独自に施しています。

また、Raspberry Piでは検証済みではありますが、MinnowBoard MAXについてはソースコードの移植のみで動作確認ができていないため、このハードウェアで使う場合はいくらかの修正が必要になります。

たとえば「BcmGpioControllerClass」はその名の通り、Raspberry Piに搭載されているBroadcom社のBCMシリーズのチップを低レベルで制御するためのGPIOクラスなので、BayTrailチップでは「BtFabricGpioControllerClass」に置き換えなくてはいけません。

こちらはRaspberry PiにおいてGPIO6への入力を調べるコード例です。関数のはじめが小文字であったり、WinRT APIにはあるPullDownに相当する定数が無いなど、Arduinoを意識した構成であるのが特徴的です。

BcmGpioControllerClass bgc;
ULONG state;
bgc.setPinMode(6, INPUT);
/* "setPinMode"は以下の関数と同義
setPinFunction(6, 0); // Use as GPIO
setPinDirection(6, DIRECTION_IN);
setPinPullup(6, FALSE);
*/
bgc.getPinState(6, state);
if(state == HIGH){}


DMAP対応版サンプル制御プログラムは前回とほぼ同じ振る舞い(可変抵抗で左右に移動、タクトスイッチでタッチのシミュレート)をします。ただし、GPIOの反応が相当速くなったことに伴い、タクトスイッチの反応もかなり敏感になっているため、いわゆるチャタリングが発生しやすくなっています。そのためinterruptによるハンドリングではなく、ループ内で非常に短い間隔での切り替えを排除したスイッチのチェックを行っています。


もちろん、デバイスマネージャーで「Direct Memory Mapped Driver」を有効にしておかないと動作しません。

サンプルソースのダウンロード(IoTプログラミングのページに移動します)
, | 2017年5月10日
前回は独自に開発したドライバーをWindows IoT Coreにインストールする方法を紹介しました。今回はそのドライバーを制御するためのソフトを作るテクニックを独学した成果をご紹介します。

デスクトップに比べ、OSへのドライバー導入は敷居が低いですが、UWPからのアクセスは至難の業です。そこで今回はCUIベースのアプリでGPIOからのデータを受け取り、ドライバーを動かすことで結果としてUWPに反映させたいと思います。


[コンパイルのための下地作り]
Windows IoT Core Project Templatesを導入すると、Visual C++の新規プロジェクトに「Blank Windows IoT Core Console Application」が追加されるので、このプロジェクトを選択します。


続いてWinRT APIを使えるようにします。C++プロジェクトのプロパティーで「Windowsランタイム拡張機能の利用」を「はい(/ZW)」に、「最小リビルドを有効にする」を「いいえ(/Gm-)」にします。次にWinRT APIが定義されている「Platfowm.winmd」と「Windows.winmd」を含めるため「追加の#usingディレクトリ」にそれぞれのファイルが含まれるフォルダー「$(VC_ReferencesPath_VC_x86)\store\references;$(UniversalCRTSdkDir)UnionMetadata(※開発環境によってパスが異なる場合があります)」を指定します。

こちらは任意ですが、「pch.h」ファイルが不要であれば、「プリコンパイル済みヘッダー」「プリコンパイル済みヘッダーファイル」の2カ所を削除します。


Inbox DriverによるGPIO制御はC#でのプログラムとほぼ同じです。もちろん、C++/CXとの言語の壁はありますので、こちらについては「C#と比較するC++/CX基礎知識」の項目をご覧ください。
auto gpio = GpioController::GetDefault();
auto pin = gpio->OpenPin(6);
pin->SetDriveMode(GpioPinDriveMode::InputPullDown);
auto pv = pin->Read();

インストールしたドライバーはCM_Get_Device_Interface_List()で対象のGUIDを保有するドライバーを検索し、取得したIDをCreateFile()で開き、そのハンドルを取得することで操作することができるようになります。

ここで気をつけておきたいのは、デバイス列挙APIが含まれる「cfgmgr32.h(およびcfgmgr32.lib)」はARM向けのWindows SDKには含まれていないという点です。Raspberry Piで利用するのであれば、Windows Driver Kitをあらかじめインストールしておきましょう。

[スクリーンサイズを取得する]
Raspberry Piにおける開発ではもう一つ問題があります。HIDデバイスではXとY座標は画面のサイズにかかわらず、取得される数値は相対値(プロファイルにもよりますが、基本的には0~65535の間)になります。


[HIDデバイスに送る座標値例]

そのため、座標を適切な値に変換するには画面のピクセルサイズを知る必要があります。が、Raspberry Piではスクリーンサイズを取得するWin32 APIは機能しません。例えば、「GetSystemMetrics(SM_CXSCREEN)」を呼んでも常に0が返ります。

管理人はシステムファイルを解析することでこの問題を解決しています。Raspberry Pi向けWindows IoT CoreはRaspbianと同様にconfig.txtにシステムデータが格納されています。サンプルプログラムではこのファイルから読み取ったファイルにある「hdmi_group」と「hdmi_mode」の値をRPiconfigによる定義と照らし合わせてスクリーンサイズを推測しています。

[制御プログラムをGUIアプリから呼び出す]
本当はRaspberry Piの起動時に自動で実行させたかったのですが、schtasksコマンドを使ってタスクを登録しても動作せず、開発者のコミュニティーでも回答が得られなかったので、苦肉の策として、UWPから直接起動させるという方法をとりました(ほかに良い方法があればFacebookのコミュニティーで教えてください)。

もちろんUWPアプリですから、セキュリティー云々の理由で、プロセスの実行は基本的に禁止されています。が、Windows 10 IoT Coreの10.0.10586以降であれば、あらかじめ所定のレジストリに実行ファイルのパスを登録し、ホワイトリストにしておくことで、「ProcessLauncher.RunToCompletionAsync」メソッド(Windows IoT Core Extensionを参照に追加することで使用できます)により起動させることができます。

以下はデスクトップPCのPowershellにおける、レジストリへの登録例です。
reg.exe ADD "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\EmbeddedMode\ProcessLauncher" /v AllowedExecutableFilesList /t REG_MULTI_SZ /d "c:\data\tnkHidInjectTest.exe\0"

サンプルプログラムでは、制御ソフトにおいてミューテックスとイベントハンドラによる多重起動防止の機能を追加しており、また、起動時のコマンドライン引数に「e」が含まれていたら、制御プログラムのプロセスを終了させてます。これは、「kill.exe」をホワイトリストに追加する手間を省くためと、きちんとプロセスを終了させるためです。

[コンソールアプリをデバッグする]
対象の機器のIPアドレスなどを自動取得してくれるUWPデバッガーと違い、C++コンソールアプリはちょっとした手間が必要となります。

まずはブラウザーか、Windows 10 IoT Core DashboardよりWindows Device Portalを開き、「Debug」の欄で「Start Visual Studio Remote Debugger」の「Start」ボタンを押します。


正常に動作すれば、リモート先のIPアドレスが表示されます。表示されない場合はリモートデバッグツールを導入する必要がありますが、これは適当なUWPアプリを作ってデバッグを実行すると、勝手に環境を整えてくれます。

また、リモートデバッグを実行している間はGUIアプリのリモートデバッグはできません。もしリモートデバッガーを止めてもGUIのデバッグができないようであれば、デバイスポータルの「Process」を表示して「msvsmon.exe」のプロセスを止めましょう。

対象のプロジェクトのデバッグを設定します。「リモートコマンド」はビルドしたファイルのコピー先を、「作業ディレクトリ」「追加の配置ファイル」にはその実行ファイルのあるフォルダーのパスを指定します。「リモートサーバー」はデバイスポータルのリモートデバッガーで表示された成功メッセージに記載されているアドレスを、「接続」では「認証なしでリモート接続する」、「デバッガーの種類」は「ネイティブのみ」をそれぞれ指定します。


これにより、Visual StudioからF5キー一発(「ビルド構成」の「配置」へのチェックを忘れずに!)で、ブレークポイントやウォッチリストが使えるデバッグ作業が行えるようになります。

サンプルプログラムのソースでは、起動しているGUIアプリに「tnksoft」のキーボード入力を転送するとともに、タクトスイッチを押すとA/Dコンバーターの定番「MCP3002」によってSPI経由で取得した可変抵抗の抵抗値をX座標に変換した場所で、タッチ操作をシミュレートします。また、ダウンロードできるソースコードにはX軸とY軸の値の両方を使ってマウスカーソルを移動させる例も追加しています。

 

サンプルソースのダウンロード(IoTプログラミングのページに移動します)
, | 2017年5月8日
タクトスイッチをマウスクリック、タッチパネル信号を受信したらタッチ操作。電子パーツからの信号よりGUIを操作したいとは思うものの、Windows IoT CoreのGUIアプリはUWPベースのため、SendInputなどの入力操作をシミュレートするAPIはサポートされていません。マイクロソフトは何らかの対応を検討しているようですが、2017年4月の時点では代替APIは実装されていません。

現時点でマイクロソフトが提案しているのはソフトウェアドライバーを組み込むという方法です。Windows IoT CoreはGUIアプリではUWPベースのものしか作れませんが、CUIであればWin32 APIを使ったプログラムを動かすことができます。ちなみに、マイクロソフトが公開しているソースを読む限り、Arduino Wired ProjectのGPIO処理がほかの言語に比べて異様に早いのは、ウィンドウズランタイムを介さずに、I/O制御のWin32 APIを直接実行しているためのようです。

マイクロソフトはGitHubでソフトウェアドライバーのサンプルを公開しています。このプログラムにはキーボード入力、マウス操作、マルチタッチのHIDデバイスのシミュレートが一通り行え、趣味の電子工作であれば十分実用的なので、このドライバーを使った制御方法をご紹介します。

まずはドライバーのビルドから始めましょう。基本的に、Windows Driver Kitをインストールしてから、プロジェクトを読み込んでARM向けにコンパイルすれば、Visual Studio以外の開発ツールをインストールしているなどの特殊な状況でない限り、ドライバーファイルはすぐにできます。ただし、Windows 10向けのドライバーが開発できるのはVisual Studio 2015のみで、今のところVS2017はサポートしていません。これだけのために開発環境を導入するのは面倒だという方は、管理人が独自にビルドしたドライバーを用意していますので、こちらをダウンロードしてください。

次にドライバーのインストールです。デスクトップOSではセキュリティーを理由に、電子署名のないドライバーはテストモードでないと動作しないなど、導入の壁は高い一方で、趣味の電子機器制御を前提にしたWindows IoT Coreは導入の制約がかなり緩いです。

まずはデスクトップOSよりパワーシェル(PowerShell)を管理者権限で起動したら、デバイスへのアクセスの許可を与えるコマンドを実行します。一度有効にしたら次回以降はこのコマンドの入力は不要です。「192.168.0.5」はWindows IoT Coreの保有するローカルアドレスを指していますので、Windows IoT Coreの環境に応じて適時置き換えてください。
net start WinRM
Set-Item WSMan:\localhost\Client\TrustedHosts -Value 192.168.0.5


続いて、デバイスに接続します。コマンドを実行するとパスワードが要求されるので、WinIoTのログインパスワードを入力してください(初期設定では、アカウント名が「administrator」でパスワードが「p@ssw0rd」)。
Enter-PSSession -ComputerName 192.168.0.5 -Credential administrator


接続に成功すると約30秒くらいで次のコマンドの入力ができるようになります。

続いてIoTデバイスにドライバーファイルをコピーします。エクスプローラーのアドレスに「\\192.168.0.5\C$」と入力すると、IoTのディレクトリーを開くことができます。ここから「C:\Windows\System32\Drivers」に移動し、「HidInjector.inf」と「HidInjector.sys」をコピーします(Raspberry Pi2の場合)。

パワーシェルに戻り、以下のコマンドを入力してドライバーのインストールを実行します。
cd C:\Windows\System32\Drivers
devcon install HidInjector.inf root\\HidInjector

メッセージの最後に「Drivers installed successfully.」と表示されればインストールは完了です。しばらくすると自動で再起動されますが、始まらない場合は「shutdown /r /t 0」と入力して手動で再起動してください。

デバイスの確認はパワーシェルより以下のコマンドで行えます。なお、起動直後は「Enter-PSSession」の実行が再度必要です。
devcon hwids root\\HidInjector
(※結果例)
ROOT\DEVCON\0000
    Hardware IDs:
        root\\HidInjector
1 matching device(s) found.


ちなみにこちらがアンインストールするコマンドになります。
devcon -remove root\\HidInjector


もしドライバーの問題でブートができなくなってしまい、OSの再インストールをせざるを得ない際は、以下の方法でSDカードをフォーマットします(マイクロソフトのFAQサイトからの引用。通常のフォーマットではドライバーのデータが残ることがあり、問題が解決しないことがあります)。
diskpart
list disk (このリストで表示されたSDカードのディスク番号を書き留める)
select disk <number>(<number>をSDカードのディスク番号に置き換える)
clean
create partition primary
format fs=ntfs quick
, | 2017年5月3日
マイクロソフトが公開している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日
ArduinoやPICのワンチップマイコンは基本的にマルチスレッドに対応していません。そのため、割り込み処理が発生すると、その関数を実行している間はメイン関数が滞るため、2つ以上の作業を同時進行するとなると、プログラミングに結構な手間がかかります(FreeRTOSなどの組み込み機器に特化したOSを使う方法もあります)。

Windows IoT Coreはもちろんマルチスレッドに対応している(むしろUWPベースなのでマルチタスクのプログラミングが必須)ので、GPIO処理の早いArduinoプログラムで取得したデータをRaspberry Pi 2などのパワフルなコンピューターで処理する方法も簡単に実装できます。

Arduino言語で割り込み処理のある回路とプログラムを作ってみます。回路はタクトスイッチと抵抗を置いただけのシンプルなものです。Arduinoでは割り込みができるピンが限られていますが、Raspberry Piでは基本的にどのGPIOピンでも割り込み処理を受け付けるので、ここではGPIO4を使っています。


Windows IoT Coreでは割り込み設定(attachInterrupt)での割り込み番号はピン番号と同じになります。まずは、Visual Studioのデバッガーでボタンを押すたびに出力欄に「Pressed.」と表示されるか確認してください。

[MultiThreadTest.ino]
#define INT_PIN GPIO4

void onPushSwitch()
{
    Serial.println("Pressed.");
}

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

    pinMode(INT_PIN, INPUT);
    attachInterrupt(INT_PIN, onPushSwitch, FALLING);
}

void loop()
{
}


では、バックグラウンド処理を行うプログラムを作ってみましょう。Arduino Wiring ProjectはC++/CXのメインメソッドから呼び出されていますが、逆にinoファイルにランタイムクラスを入れることもできます。むろんランタイムクラスを含むプログラムはArduino Unoなどではコンパイルできませんが、そもそもマイコンを使ったArduinoではできないことを実装するため、気にしたところで意味がありません。

C++/CXによるバックグラウンド処理のプログラム例を掲載します。ここでは、命令が実行されてから5秒後にデバッガーにメッセージを表示します。「create_task」がC#における非同期処理に当たります。簡単な解説は「UWP・C#プログラマーのためのC++/CX基礎知識」をご覧ください。

[Async.h]
#pragma once

ref class Async sealed
{
public:
    Async();
    void Wait();
};

[Async.cpp]
#include <Async.h>

#include <ppltasks.h>
using namespace concurrency;

#include <thread>
using namespace std;

Async::Async()
{
}

void Async::Wait()
{
    create_task(([this]() {
        this_thread::sleep_for(chrono::seconds(5));
        OutputDebugString(L"Wait finish\n");
    }));
}


後はこのクラスをinoファイルに組み込めば完成です。
#include "Async.h"

#define INT_PIN GPIO4

Async ^a;

void onPushSwitch()
{
    Serial.println("Pressed.");
    a->Wait();
}

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

    a = ref new Async();

    pinMode(INT_PIN, INPUT);
    attachInterrupt(INT_PIN, onPushSwitch, FALLING);
}

void loop()
{
}

このプログラムを実行すると、ボタンを押してからしばらくしてからメッセージが表示されます。通常の割り込み処理と違い、重い処理をしている間にボタンを連打しても、押した数だけ割り込み処理が発生し、その分だけ終了メッセージが表示されます。



サンプルソースのダウンロード(IoTプログラミングのページに移動します)
,, | 2017年4月5日
PICマイコン関連で多数の解説書を執筆している後閑哲也氏がPICと楽しむRaspberry Pi活用ガイドブックという本を2017年4月11日に出されるということなので、パクリだといわれないように、管理人自身が勉強した成果を、取り急ぎひとつ紹介したいと思います。

今回紹介するのは見出し通り、PICマイコンに搭載されているシリアル通信モジュール・MSSPと、Windows IoT Coreを搭載したRaspberry PIもしくはArduinoとSPI形式による通信をする方法です。

以前にも紹介したように、Windows 10 IoT Coreには低レベルGPIOドライバーが搭載されており、これにはSPIの制御ルーチンも含まれています。このドライバーが最も効果を発揮するのはArduino Wiring Project(※1)なので、今回はArduino言語を用いてプログラミングしています。逆に言えば、このプログラムは簡単な修正でArduino Unoなどでも応用が可能です。

今回使用したPICマイコンは、国内の実店舗でも比較的手に入りやすい「PIC16F1827」を使用しています。ただ、PIC16F1シリーズであれば基本的な機能は同じなので、移植の手間はそこまでかからないでしょう。

Raspberry PiとPICとの通信の主従関係(マスター/スレイブ)は常にRaspberry Piが主となっています。そのため、主従関係を明確にするためSS信号を含めた4本の配線をする必要があります。この配線図ではArduinoの方式に従って、SS信号の線はCS0ではなくGPIO(5)に繋いでいます。また、PICのMISOはRaspberry Pi(Arduino)のMISOに、MOSIもMOSI同士で繋がないと動作しない点に気をつけてください。


今回使用するPIC16F1827におけるピンの役割は下図のようになっています(データシートより)。赤色で示しているのはプログラムライターの接続先です。型番の違うPICを使用するときの参考にしてください。写真では、プログラムライターとRaspberry Piの電源とを共有していますが、このような配線にする場合はMPLABプロジェクトのプロパティで「Power target circit from PICKit3」のチェックを外して、プログラムライターからの電源供給を無効にするようにしてください。

 

まずはPICマイコンにプログラム(MPLAB X IDE/XC8 Compiler Freeを使用)を書き込みます。今回はマスターから送られてきた1byte数値(8bitPICマイコンは1回につき8ビットのデータを送受信し、SSPBUFに格納する)を受け取り、マイコンに格納している値を1ずつ引いた値を返すというシンプルな内容にしました。main内ループでの処理だと同じデータを二重に取得するなどの問題があったため、割り込み処理で信号を処理するようにしています。SPI通信を有効にするにはPICピンの入出力(ここではRB1,RB4)も明確に定義するのが肝です。

初期設定の解説はコメントのみで割愛しますが、命令の意味をより詳しく知りたいのであれば、同じく後閑哲也氏の著書PIC16F1ファミリ活用ガイドブックをご一読ください。

#include <xc.h>

#pragma config FOSC = INTOSC    // 内部クロックを使用
#pragma config WDTE = OFF       // ウォッチドッグタイマ
#pragma config PWRTE = OFF      // パワーアップタイマー
#pragma config MCLRE = ON       // MCLRピンの有効・無効
#pragma config CP = OFF         // コードプロテクト
#pragma config CPD = OFF        // メモリーの保護
#pragma config BOREN = ON       // 電圧降下監視
#pragma config CLKOUTEN = OFF   // CLKOUTの有効・無効
#pragma config IESO = OFF       // 外部クロックへの切り替え
#pragma config FCMEN = OFF      // 外部クロックの監視

#pragma config WRT = OFF        // 4KWのフラッシュメモリ自己書き込み保護
#pragma config PLLEN = ON       // 4xPLLを動作
#pragma config STVREN = OFF     // スタックエラー時のリセット
#pragma config LVP = OFF        // 低電圧プログラミング

#define _XTAL_FREQ 32000000     // スリープ処理のための周波数定義

#define LOW 0
#define HIGH 1

unsigned char return_data = 0, read_data;

void interrupt OnInterSpi()
{
    if (SSP1IF == HIGH){  // SPIデータがあるか
        SSP1IF = LOW;     // SPIデータをリセット
        
        read_data = SSP1BUF;      
        if(return_data == 0) return_data = 255; else return_data--;
        
        SSP1BUF = return_data;
    }
}

void main(void)
{
    OSCCON = 0b00110100;    // 内部クロック4Mhz(4xPLLにより32MHzで動作)
    
    ANSELA = 0b00000000;    // RAはすべてデジタル
    TRISA = 0b00000001;     // RA0を入力端子に
    PORTA = 0b00000000;     // RA初期化
 
    // SPIモードの設定と初期化
    ANSELB = 0b00000000;
    TRISB = 0b00010010;     // RB1(7/SDI1)とRB4(10/SCK1)は入力
    PORTB = 0b00000000;     // RB初期化
    
    SDO1SEL = 0;             // RB2をSDOピンに設定
    SSP1CON1 = 0b00100100;    // スレーブでSS使用
    SSP1STAT = 0b01000000;    // スレーブ, クロック位相はLOW
    
    SSP1IF = 0;  // SPIの割込みフラグを初期化する
    SSP1IE = 1;  // SPIの割込みを許可する
    PEIE = 1;   // 周辺装置割込みを許可する
    GIE = 1;   // 全割込み処理を許可する
    
    while(1) ;
}

 Pickit3(*2)などでプログラムを書き込んだら、次はVisual Studioでのプログラミングに移りましょう。Arduino Wiring Appプロジェクトを作ったら、inoファイルに次のように記述します。
#include <spi.h>

#define SS_PIN 5

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

    SPI.begin();
    SPI.setBitOrder(MSBFIRST);
    SPI.setDataMode(SPI_MODE0);

    pinMode(SS_PIN, OUTPUT);
    digitalWrite(SS_PIN, HIGH);

    delay(500);
}

char msg[21];
byte i = 0, n;

void loop()
{
    digitalWrite(SS_PIN, LOW);
    n = SPI.transfer(i);
    digitalWrite(SS_PIN, HIGH);

#ifdef _WIN_IOT
    // 文字列のあふれを防ぐためのマイクロソフトの独自拡張関数
    sprintf_s(msg, 20, "%d, %d\n", i, n);
#else
    sprintf(msg, "%d\n", n);
#endif
    Serial.print(msg);

    if (i == 255) i = 0; else i++;
}

双方のプログラムが正常に動作すれば、Visual Studioのデバッガーに受信記録が列挙されていきます。今回は数値の増減が一致していますが、PICへの電源が入るタイミングによっては結果が異なることがあると思います。


また、SPI通信を使った電子パーツを扱った方はご存じかと思いますが、データの扱いが非常にややこしくなるため、1対1でデータをリアルタイムにやりとりするケースはほとんどありません。だいたいはマスターから命令番号と追加データを送り、スレーブで命令番号に対応するデータを作成して返送する(この間はダミーのデータを受信する)のが一般的です。

サンプルソースのダウンロード(IoTプログラミングのページに移動します)

※1:20MHz~30MHzで動作するといわれていますが、ドライバーが未対応のためSPI1ポートが使えないというデメリットもあります。ただ、マイクロソフトのArduino関連のヘッダーファイルにはSPI1ピンの定義があるため、将来的に対応する可能性はあるでしょう。
※2:1ヶ月もたたずにコネクターがイカれて使い物にならなくなり、安物買いの銭失いになった互換品の例。電子パーツ専門店・マルツで売っている純正品と比べてもいろいろと構造が違うことがわかります。


すぐに故障したPickit3互換品 マルツで取り扱っている純正品との比較
,,, | 2017年3月29日
今回はエプソンのLCDコントローラー「S1D13781」の評価ボード「S5U13781R01C100」のWindows 10 IoT Coreにおける具体的なプログラミング例を紹介していきます。(前回の記事はこちら)

  • 点を描画する
    ピクセルの位置に対応するメモリーアドレスにある値を直接書き換えるのが、このボードの基本的な使い方です。ビデオメモリー領域の先頭アドレスは次のコードで取得できます。
    var vramAddress = RegRead(RegIndex.REG42_MAIN_SADDR_0) | (uint)(RegRead(RegIndex.REG44_MAIN_SADDR_1) << 16);


    あとは、このアドレスをもとに座標に対応するアドレス番号を計算し、BGRの順(24bitカラーの場合)で値を書き込むと、リアルタイムでディスプレイに点が描画されます。
    private void MemWriteByte(uint memAddress, byte memValue)
    {
        CsPin.Write(GpioPinValue.Low);
    
        SPI.Write(new byte[] {
            (byte)SPIBIT.SPIWRITE_8BIT, //command
            (byte)(memAddress >> 16), //address bits 18:16
            (byte)((memAddress & 0x0000FF00) >> 8), //address bits 15:8
            (byte)(memAddress & 0x000000FF), //address bits 7:0
            memValue, //data bits 7:0
        });
    
        CsPin.Write(GpioPinValue.High);
    }
    
    public void DrawPixel(int x, int y, Color color)
    {
        if (x < 0 || y < 0 || x >= width || y >= height) return;
    
        uint offset = (uint)(vramAddress + (y * stride) + (x * bytesPerPixel));
    
        switch (imageFormat) {
        case ImageDataFormat.RGB_888:
        case ImageDataFormat.RGB_888LUT:
            //write the color components
            MemWriteByte(offset, color.B);
            MemWriteByte(offset + 1, color.G);
            MemWriteByte(offset + 2, color.R);
            break;
        }
    }


  • 線を引く、円を描く
    メーカーの提供するライブラリーのソースコードを見る限り、専用の命令は用意されていないようなので、点を連続して繋げることで線を描画することにしました。私の場合は「フルスクラッチによるグラフィックスプログラミング入門」を参考に直線を描画するプログラムを組みましたが、こちらは現在絶版なので、手に入らないようであれば、FireAlpacaの開発スタッフが監修した「2Dグラフィックスのしくみ」が参考になるでしょう。同じ理由で円も点を連続して描画することで実装することになります。

  • 矩形を塗りつぶす
    線画の四角形は上記と同じように4本の線を描画することになりますが、指定の色で塗りつぶしたいのであればソリッドフィルBitBLT命令を使うことで高速に描画できます。

    //setup the BitBLT registers
    RegWrite(RegIndex.REG82_BLT_CTRL_1, (ushort)(dataType << 2));
    RegWrite(RegIndex.REG86_BLT_CMD, 0x0002);  //Solid Fill BitBLT
    RegWrite(RegIndex.REG8C_BLT_DSADDR_0, (ushort)(vadd & 0xFFFF));
    RegWrite(RegIndex.REG8E_BLT_DSADDR_1, (ushort)((vadd >> 16) & 0x0007));
    RegWrite(RegIndex.REG90_BLT_RECTOFFSET, (ushort)(stride / bytesPerPixel)); // 781 requires stride in pixels (not bytes)
    RegWrite(RegIndex.REG92_BLT_WIDTH, width);
    RegWrite(RegIndex.REG94_BLT_HEIGHT, height);
    
    // "c" is converted color bytes from Color class
    RegWrite(RegIndex.REG9A_BLT_FGCOLOR_0, (ushort)(c & 0xFFFF));
    RegWrite(RegIndex.REG9C_BLT_FGCOLOR_1, (ushort)((c >> 16) & 0x00FF));
    
    // Start the BitBLT
    RegWrite(RegIndex.REG80_BLT_CTRL_0, 0x0001);
    
    // Wait for completion.
    PollWhileRegBitsHigh(RegIndex.REG84_BLT_STATUS, 0x0001, 0, 1000);

    サンプルコードにある、画面を消去するClear()も、このメソッドによって黒一色で画面全体を塗りつぶすことで実装しています。

  • 文字列を描く
    LCDに文字列を描くにはあらかじめ作成した文字データを転送するのが一般的な方法です。ですが、せっかくWindowsから動かしているので、OSのAPIを使ってあらゆる文字を転送する方法を考えてみました。

    マイクロソフトはUWPのグラフィックライブラリー「Win2D.uwp」を別途提供しており、NuGetよりこのライブラリーを導入することで、2Dグラフィックのプログラミングが容易になります。今回のプログラミング例では、CanvasBitmapという仮想のキャンバスを作成し、そこにアンチエイリアスのないモノクロ文字を描画し、背景色と異なる箇所のみを抜き出して転送することで、効率を高めています。
    CanvasTextFormat ctf = new CanvasTextFormat();
    ctf.FontFamily = font.Source;
    ctf.FontSize = (float)fontsize;
    ctf.HorizontalAlignment = CanvasHorizontalAlignment.Left;
    ctf.VerticalAlignment = CanvasVerticalAlignment.Top;
    ctf.WordWrapping = CanvasWordWrapping.NoWrap;
    
    var device = CanvasDevice.GetSharedDevice();
    CanvasBitmap cb = CanvasBitmap.CreateFromBytes(device,
        new byte[w * h * 4], w, h, Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized);
    
    var offscreen = new CanvasRenderTarget(device, w, h, 96);
    using (var ds = offscreen.CreateDrawingSession()) {
        ds.TextAntialiasing = Microsoft.Graphics.Canvas.Text.CanvasTextAntialiasing.Aliased;
        ds.DrawText(text, new Rect(0, 0, w, h), Colors.White, ctf);
    }
    
    byte[] buffer = offscreen.GetPixelBytes();
    for (int yy = 0; yy < h; yy++) {
        if (y + yy >= height) break;
    
        int p = yy * w * 4;
        for (int xx = 0; xx < w; xx++) {
            if (x + xx >= width) break;
            if (buffer[p] != 0x00) DrawPixel(x + xx, y + yy, color);
            p += 4;
        }
    }


  • 画像を描画する
    こちらも画素に対応する色で点の描画をひたすら繰り返すことになります。ビデオメモリーに直接アクセスする仕様上、フルカラー写真のようなデータ量が多い情報を一度に描画しようとすると、上から下へとじわじわと表示するような印象を与えることになってしまいます。


    (フルカラービットマップ描画イメージ)

    そのため、画像を使用するのであれば、スプライト程度の小さい画像に限っておくのが良いでしょう。

サンプルプロジェクトには、Windows 10 IoT Core向けのLCDコントローラーライブラリーと、任意の文字や線、リソースから読み込んだjpegファイルを任意の位置に描画できる動作確認のためのGUIアプリを収録しています。

 

ちなみに、写真にあるLCDにみられる縦筋は、液晶のリフレッシュレートと、撮影に用いたデジカメの干渉が原因なので、肉眼ではこのようなモアレがはっきりと見えることはありません。

サンプルプロジェクトのダウンロード
※Tareq Ateik氏のColorPicker Control for Universal Appsを使用しています。
※サンプル画像に使用したイラストは四月一日進氏が著作権を保有しています。

おまけ:
XAMLリソースを用いたプログラムは、そのままだとバックグラウンドアプリとしては動作しません。バックグラウンドアプリでは「class StartupTask : IBackgroundTask」の代わりに「class StartupTask : XamlRenderingBackgroundTask」を使います。
, | 2016年12月20日
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日
次ページへ »