電子工作においてパソコンとUSBを使ったシリアル通信をするためには、専用のケーブル(USB-TTLシリアル変換ケーブル)が必要となります。まともなケーブルであれば1500円前後しますし、Amazonで200円程度で売っている激安変換ケーブルは公式のドライバーが古く、Windows 10などの最新のOSでは導入できなくはないのですが、いろいろと手間がかかってしまいます。

ところが、Arduinoがあれば、これ単体でUSBシリアルケーブルを代用することが出来ます。わざわざ単体を強調したのは、特別なスケッチを書き込む必要が一切無いからです。

一般的なArduinoにはAVRマイコンが搭載されていることは多くの方がご存じだとは思いますが、Arduino Unoを始めとしたシリアルモニターが可能なモデルには、マイコンのすぐそばに小さなIC(Arduino Uno R3であれば「ATmega16U2」)が搭載されています。

ATmega16U2

このチップにはUSBコントローラーが搭載されており、このチップを介することで、USBによるシリアル通信を実現します。つまり、このチップを単体で稼働させることができれば、ArduinoがUSB-TTLシリアルケーブルの代替品になるというわけです。

ここからは海外の情報をもとに編纂した内容であることを前提にお読みいただきたいのですが、単体で稼働させるには基本的にメインのAVRマイコン(UnoならATmega328P)を抜くだけでOKです。シリアル通信ができる機種であれば、理論上はArduino互換品でも同様の効果が得られるはずです。

とはいうものの、使うたびにチップを抜き差ししているとピンが欠けてしまうリスクもあります。そこで、より簡単な方法としてArduinoのRESETピンとGNDピンをショートさせてしまう方法があります。こうすることで、マイコンが機能しなくなり、マイコンを抜き取った状態と同じ状況を作ることが出来ます。

最小配線図
[最小配線図]


UARTを使った通信が利用されるケースとして、ワンチップマイコンのデバッグや、Raspberry Piでのドライバーモニターなど、もっぱら電子工作プログラミングのデバッグが挙げられます。ですが、ジャンク大好き人間にとって有名な活用方法があります。

それがこのチェーン店のリサイクルショップでなら捨て値で買える、某通信会社のデジタルフォトフレーム(私はブックオフで780円で買いました)。このフォトフレームの基盤にはUART端子があり、これを介してカスタマイズされたAndroidアプリをインストールすることで、B-CASカードのみで、フルセグのデジタル放送を視聴できるようになるのです。

某社フォトフレームの基盤

それでは実験をしてみましょう。やり方はほかの方のブログなどで語り尽くされているので、ググって得られた情報をもとに、基板上のTX、RX、GND端子をArduinoのRX、TX、GNDピンにそれぞれ接続します。この写真では、単線ワイヤーを半田付けしたその先に、鰐口クリップをかませています。

配線例

Arduino IDE(シリアル通信ドライバーが含まれている)をパソコンにインストールしているのを確認して、オープンソースプロジェクトの通信ソフト「Tera Term」を起動しましょう。ここでArduinoのCOMポートを接続先として指定し、転送レートを調整すれば、このようにバッチリとデータを送受信できます。

シリアル通信の成功例

もちろん、改造は自己責任で。

カスタムアプリ導入結果
 | 2017年8月7日
大阪日本橋の電子パーツショップ・デジットの200円LCD「AM50288H」。かつては段ボールいっぱいに入っているのを見かけていたのですが、最近では残りもわずかとなってきました。そんなじわじわ売れゆくLCDを見て、昔紹介したプログラミング記事を改良したい気持ちに駆られたので、今回は題名の通りのプログラミングテクニックをご紹介します。このノウハウを使って「これはWindows IoT Coreで作ったライブラリーのソースだけど、Arduinoでも手を加えることなく、すぐに使えるよ」とPRすれば、より多くの開発者の注目を集められるかも!?

まずは、「AM50288H」を制御するC#コードをArduinoライブラリーとして動作することを意識して、C++言語で書き直してみます。VC++独自拡張の「#pragma once」を使用しない、「#include <arduino.h>」を追加する二点を守れば、ArduinoでもWindows IoT Coreでも動作するコードが記述できます。以下のコード例は一部割愛しているので、すべてのコードをご覧になりたい方はこのページの最下部にあるリンクから入手してください。
// libAM50288H.h
#ifndef __AM50288H_H__
#define __AM50288H_H__

#include <arduino.h>

class AM50288H
{
private:
    enum SegmentBit
    {
    }
    const static int Digits = 8;
    const static int Segments = 14;
    const static int DinsLen = 142;
    const SegmentBit segarray[Digits][Segments] = {
        {...},
        {...}
    };

    byte ple, pdin, psck = 0xFF;
    byte dins[DinsLen];

public:
    AM50288H();
    AM50288H(byte pinLE, byte pinDIN, byte pinSCK);
    ~AM50288H();

    void setPins(byte pinLE, byte pinDIN, byte pinSCK);
    void begin();
    void update();
    void clear();
};

#endif __AM50288H_H__

以下の写真はこのC++クラスをArduino IDEのスケッチに書きこみ、実行した例です(このスケッチも"sketch_am50288h.ino"としてサンプルに含まれています)。動作確認ができたら、Windows IoT Coreに組み込んでみましょう。


ArduinoはC++言語がベースなので、Windows IoT CoreではC++/CXランタイムライブラリとしてプロジェクトを作成します。プロジェクトを作ったら、先のクラスをプロジェクトに追加し、ランタイムライブラリの仕様内に収まるようにラッパークラスを作ります。ちなみに、C言語のenumはランタイムライブラリとの互換性がないため、橋渡しのためのC++/CXとしてのenumを必要に応じて用意する必要があるのですが、これにFlags属性を加える際は「 : unsigned int」の一文がないとエラーになる点に注意しましょう。
// AM50288H.h
#pragma once

#include "libAM50288H.h"

namespace Tnksoft {
namespace AM50288H {
    public enum class Mark
    {
    };

    [Platform::Metadata::Flags]
    public enum class SegmentPosition : unsigned int
    {
    };

    public ref class AM50288H sealed
    {
    private:
        ::AM50288H _am; //Arduino向けのオリジナルクラス
    public:
        AM50288H();
        void begin(byte pinLE, byte pinDIN, byte pinSCK);
        void update();
        void clear();
    };
}
}

// AM50288H.cpp
#include "AM50288H.h"

namespace Tnksoft {
namespace AM50288H {
    AM50288H::AM50288H()
    {
    }

    void AM50288H::begin(byte pinLE, byte pinDIN, byte pinSCK)
    {
        _am.setPins(pinLE, pinDIN, pinSCK);
        _am.begin();
    }

    void AM50288H::update()
    {
        _am.update();
    }

    void AM50288H::clear()
    {
        _am.clear();
    }
}
}

今回は「Tnksoft.AM50288」という名前空間でランタイムクラスを定義したので、プロジェクトプロパティの「Windowsメタデータファイル」の項目を変更するがあり、このライブラリーの場合は「$(OutDir)Tnksoft.AM50288H.winmd」となります。

ビルドに成功したら、このランタイムライブラリをDMAPドライバーで稼働するUWPアプリで参照すれば、機器の制御が簡単かつ高速に行えるようになります。


using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml;
using Tnksoft.AM50288H;

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

            amh = new AM50288H();
            // GPIO5 = 29, GPIO6 = 31, GPIO13 = 33 on Raspberry Pi
            amh.begin(29, 31, 33);
        }
    }
}

サンプルソースのダウンロード(IoTプログラミングのページに移動します)
, | 2017年5月30日
過去に何度も取り上げているArduino Wiring on Windows 10 IoT Coreですが、ドライバーの開発における勉強の過程で、「Arduino APIは所詮C++ラッパー。別にバックグラウンドアプリでなくても使えるのではないか」と思い、UWPプロジェクトでArduino関数を使う方法を試してみました。

まずはVisual StudioでUWPアプリプロジェクトを作成します。しかし、メインアプリの開発にC++/CXを使うのは相当な困難が伴いますので、GUI部分はC#言語で開発し、C++/CXランタイムコンポーネントでGPIOを制御することにしました。

そのため、Visual C++ユニバーサルWindowsランタイムライブラリプロジェクトを追加することにします。このプロジェクトに「Windows Desktop Extensions for the UWP」と「Windows IoT Extensions for the UWP」参照を追加し、NuGetより「Microsoft.IoT.Lightning」を登録します。

コンポーネントにはマニフェストの概念がないため、低レベルアクセスのパーミッションはC#アプリのappxmanifestに追加することになります。具体的な追加方法は過去の当該記事をご覧ください。

続いてC++/CXプロジェクトでラッパークラスを作ります。Windows IoT CoreのArduino APIはMicrosoft.IoT.Lightningに含まれる「Arduino.h」をインクルードするだけで、C++/CXファイルからでも簡単に呼び出すことができます。

namespace ArduinoLibrary
{
public enum class GpioPin
{
    P2 = 3, P3 = 5, P4 = 7, ...
};

public ref class Arduino sealed
{
public:
    static void pinMode(GpioPin pin, PinMode mode);
    static PinValue digitalRead(GpioPin pin);
    static void digitalWrite(GpioPin pin, PinValue state);
};
}

#include <arduino.h>
#include "ArduinoFunctions.h"

using namespace Platform;
using namespace ArduinoLibrary;

void Arduino::pinMode(GpioPin pin, PinMode mode)
{
    ::pinMode((byte)pin, (uint8_t)mode);
}

PinValue Arduino::digitalRead(GpioPin pin)
{
    return (PinValue)::digitalRead((byte)pin);
}

void Arduino::digitalWrite(GpioPin pin, PinValue state)
{
    return ::digitalWrite((byte)pin, (uint8_t)state);
}

C#から呼び出す例です。
private async void RuntimeTest()
{
    await Task.Run(() => {
        Arduino.pinMode(GpioPin.P5, PinMode.OUTPUT);
        Arduino.pinMode(GpioPin.P6, PinMode.OUTPUT);
        Arduino.digitalWrite(GpioPin.P5, PinValue.LOW);
        Arduino.digitalWrite(GpioPin.P6, PinValue.LOW);

        while (true) {
            uint m = Arduino.millis();
            Arduino.digitalWrite(GpioPin.P5, PinValue.HIGH);
            Arduino.delay(10);

            while (Arduino.millis() - m < 10000) {
                Arduino.digitalWrite(GpioPin.P6, PinValue.HIGH);
                Arduino.delayMicroseconds(50);
                Arduino.digitalWrite(GpioPin.P6, PinValue.LOW);
            }

            Arduino.delay(10);
            Arduino.digitalWrite(GpioPin.P5, PinValue.LOW);

            Arduino.delay(500);
        }
    });
}

……と、ここまで書いておいてなんですが、この方法だとWindowsランタイムが頻繁に介入するため、実際に動かしてみると、速度やタイミングが不安定で、ダイレクトメモリーマッピングの恩恵をあまり受けられないのが実情です。そのため、以下のようにGPIOを制御するルーチンはできる限りランタイムライブラリにまとめた方が良いでしょう。
void Arduino::frequencyTest1()
{
    ::pinMode(GPIO5, DIRECTION_OUT);
    ::pinMode(GPIO6, DIRECTION_OUT);
    ::digitalWrite(GPIO5, LOW);
    ::digitalWrite(GPIO6, LOW);

    while (true) {
        unsigned long m = millis();
        ::digitalWrite(GPIO5, HIGH);
        ::delay(10);

        while (millis() - m < 10000) {
            ::digitalWrite(GPIO6, HIGH);
            ::delayMicroseconds(50);
            ::digitalWrite(GPIO6, LOW);
        }

        ::delay(10);
        ::digitalWrite(GPIO5, LOW);

        ::delay(500);
    }
}


おまけ:inoファイルをC++/CXプロジェクトに追加する方法
inoファイルをVisual C++にC言語と見なさせることで、ランタイムライブラリでもinoファイルを直接プロジェクトに追加することができます。任意の名前を持つinoファイルをプロジェクトに追加したら、ソリューションエクスプローラーより、inoファイルのプロパティを開きます。ここより「全般」の「項目の種類」に「C/C++コンパイラ」を選択し、「C/C++詳細設定」の「必ず使用されるインクルードファイル」に「Arduino.h」を追加します。

inoファイルのコンパイル方法指定 Arduino.hを自動インクルード
これにより、inoファイルに特別な定義を追加せずとも、Arduino IDE同様の記述ができるようになるため、Arduino向けのプログラムの移植がより容易になります。

// C++/CX file
void setup();
void loop();

void Arduino::frequencyTest2()
{
    setup();
    while(true) loop();
}

// test.ino
void setup()
{
    pinMode(GPIO5, OUTPUT);
    pinMode(GPIO6, OUTPUT);
    digitalWrite(GPIO5, LOW);
    digitalWrite(GPIO6, LOW);
}

void loop()
{
    unsigned long m = millis();
    digitalWrite(GPIO5, HIGH);
    delay(10);

    while (millis() - m < 10000) {
        digitalWrite(GPIO6, HIGH);
        delayMicroseconds(50);
        digitalWrite(GPIO6, LOW);
    }

    delay(10);
    digitalWrite(GPIO5, LOW);

    delay(500);
}

以前紹介したバックグラウンドアプリと通信する方法だと、リソースが不足したときにOSがバックグラウンドアプリを止めてしまう可能性があります。そのため、アプリが動いている間はGPIO制御の動作も保証されるという点を考慮すると、フォアグラウンドアプリとして埋め込む利点は十分にあると思います。

ちなみにArduino UnoとRaspberry Piを接続して簡易的な性能テストをしてみましたが、C++/CXにAPIを埋め込む方法と、inoファイルに記述した関数を呼び出す方法に目立つ性能差はありませんでしたので、どちらを採用するかは開発者の好みでよいかと思います。サンプルプログラムにはテスト内容やinoファイルまで今回紹介した内容一式がすべて含まれているので、オシロスコープなどの高度な計測ができる機器で試されたら、結果を私にも教えてください。

サンプルソースのダウンロード(IoTプログラミングのページに移動します)
, | 2017年5月19日
マイクロソフトが公開している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日
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日
ほぼ毎日に渡ってお送りしてきた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日
前回では低レベルデバイスアクセスによる赤外線リモコンの信号を受信する方法を紹介しました。今回はさらに速度を追求したい人のための制御方法を紹介します。

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日