« 前ページへ - 次ページへ »
マウスカーソルのすぐそばでIMEが有効になっているかの状態を一目で確認できるユーティリティ「IMEステータス」のVer 2.13の正式版を公開しました。Windows 10の一部バージョンにおいてOSの問題によりエクスプローラーの負荷が高くなる現象が発生しているため、最新版では仮想デスクトップ間移動機能を標準で無効にしています。また、マイクロソフトによるサポートが正式に終了することに伴い、2.13からWindows Vistaへの動作保証を対象外にさせていただいております。

2017年4月8日
マイクロソフトが公開している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日
ネジを壁に差し込むのが甘かったため、シチズンの壁がけ時計を買ってすぐに落下させてしまい、結果、表面のガラスを割ってしまう羽目になりました。とりあえずガラスを外して設置し直したものの、このままではみすぼらしいし、ほこりがたまる懸念があります。しかしながら、ガラスだけを別途発注するとなると、ガラス単体で数千円+円形への加工賃が数千円で、これなら時計自体を買い直した方が安いくらいです。

と、いうわけで今回はビニール板によるDIY修理という方法に行き着いたので、その一部始終をご紹介いたします。

まずは型紙の作成。ベクタグラフィックスソフト「Inkscape」で、直径300mmの円と280mmの円を描画し、プリンターのポスター印刷機能でA4用紙2枚に分けて出力しました。


この型紙はこちらのリンクにアップロードしていますので、欲しい方や参考にしたい方はご自由にダウンロードしてください(SVG形式)。

今回ガラス板の代わりに購入したのはサンデーシート硬質塩ビ板300x300ミリの1mm厚。価格は300円ちょいです。


印刷した紙をセロハンテープで貼り合わせたら、これを塩ビ板にマスキングテープで貼り付けて固定します。メーカーは専用のカッターによる切断を推奨していますが、私は強力ばさみで大雑把に切りながら、徐々に円を狭めていきました。むろん、ゆがみやバリが出てきますが、これらは時計のフレームで隠れるのでほとんど問題ありません。

最後に時計の枠組みを精密ドライバーで外し、カットした板をはめ込みます。その結果がこちらの写真。ガラスに比べて高級感が落ちるのは否めませんが、遠目から見るとほとんど気になりません。

 | 2017年4月2日
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日
管理人が愛用していた内田洋行のオフィスチェア(中古)の車輪がすべて瓦解して、ほとんど使い物にならなくなってしまいました。と、いうわけでこれを機会に椅子を買い換えることにしたのですが、どうやらゲーミングチェアーというジャンルがあって、前屈姿勢を前提にしたオフィスチェアとは違って、体を預ける姿勢で快適に座れるようにデザインされているらしい。大阪日本橋のPCワンズでは最近ゲーミングパソコンに力を入れていて、各種ブランドのゲーミングチェアーに試乗できるコーナーが設置されているため、早速実店舗に出向いてみました。

いろいろ試した結果、個人的な見解ですが、DxRacerの標準モデルは横幅がやや狭く窮屈で、廉価モデルはメッシュ生地なので埃がたまりやすい懸念があります。AkRacingは合皮かつ幅が広いので座り心地は良好なものの、4万円弱とすこし高価です。そんななか、ドイツのゲーミングブランド「Cougar」が2月に発売を開始したというPOPがついていた「Cougar Armor(CGR-NXNB-GC1)」はAkRacingの4万円モデルと遜色のない座り心地と素材で3万円と安価なので、こちらを購入することに決めました。

そして宅配で届いたのがこれ。ゲーミングチェアーの例に漏れず、20kg近くあるため、箱がデカくて重いです。

 

椅子の車輪がフローリングを傷つけてしまわないか心配だったので、近所のホームセンターで金属パネル調のビニルシートを購入し、これを保護マットとした上で、組み立てを開始。説明書は紙切れ一枚で英文でしたが、ほとんどイラストなので組み立て自体はそこまで難しくはありませんでした。ただ、リクライニングなどの操作説明もイラストだけなので、その点に関しては少しわかりづらいです。


完成したらこんな感じになりました。もちろん試乗済みなので、今のところ座り心地に不満は特になし。特に腰の負担が少ないのがいいですね。ゆるりとゲームを楽しむ派の私はゆったりと体を預けられるので、その点でも快適です。ただ、クッションを固定するベルトがゴムバンドなので、将来的な劣化が心配ではありますが。


2017年2月20日
ブラザーのプリンターとスキャナーが一体化した複合機は、DCP-J968-Nなどの1万円台の安価なモデルでも自動紙送り機能が付属したものがあります。もちろん、数万円するドキュメントスキャナーのような高速な両面同時スキャンは無理ですが、オンラインソフトと組み合わせることで簡易的な自炊(実本の電子書籍化)が行えます。今回はそんなブラザーの製品を使った自炊のテクニックをご紹介します。

  1. 紙を裁断する
    スキャンできるのはA4サイズまでなので、冊子などは中央をカットしてA4以下に納めるようにします。私が今回自炊したのは大学ノートだったので、裁断機のようなたいそうなものは使わず、ホームセンターで打っている強力なはさみを使って中央をばっさり切りました。


  2. 15枚単位で紙をまとめる
    裁断した紙をいくつかのグループに分けておきます。20枚ぐらいであれば給紙トレイに問題なく差し込めますが、紙詰まりの原因になるので、少し余裕を持たせた方が良いでしょう。

  3. 表紙と裏表紙を普通の方法でスキャンする
    ブラザーの複合機はADFの構造上、吸い込まれた紙は一度巻かれて排出されるため、厚紙や薄すぎる紙だと紙詰まりを起こします。そのため、厚みのある表紙だけは個別にスキャンして保存しておきます。


  4. 最初のグループをスキャンする
    給紙トレイにスキャンしたい紙をセットしたら、プリンターのコントロールセンターより「スキャン→ファイル」を選択します。原稿サイズはなるべく指定した方がスキャンの効率は上がります。カラーとモノクロが混ざった雑誌などではカラー設定を「自動」にしてしまうと、淡い色しかない紙をモノクロと認識することがあるので、その場合はすべてフルカラーでスキャンしてからモノクロのページをグラフィックソフトでグレースケールに変換した方が良いでしょう。複合機のスキャナーなので、完了までには少し時間がかかるので、本を読むなどして時間をつぶしましょう。


  5. 最初のグループのファイル名を飛び番号に変更する
    スキャンされるのはすべて表面のみなので、2ページごとに出力されることになります。ここで利用するのがファイル名変換ユーティリティですが、私は「お~瑠璃ね~む」を愛用しています。ここで「連番変換」を指定し、番号を「<03+2>」のようにすると、「03.pngから2番飛び筒のファイル名に一括変換」のような処理を一挙に行えます。


  6. 最初のグループを裏返してスキャンし、リネーム
    名前の変更が済んだら、最後のページの裏面が下になるように、先ほどのグループをセットし直して再び自動スキャンを行います。出力された画像は最後のページから2ページずつ少なくなっていくので、リネームソフトで「<63-2>」のように入力し直し、一括実行を行います。ただし、変更後の番号に空きができていたり、番号が重複しているなどの矛盾が生じているようであれば、2枚同時給紙してしまったなどのスキャナー側でのミスが生じている可能性があるので、その場合はスキャン漏れの画像をチェックします。

     

  7. 同様の手順で残りのグループをスキャンしていく
    順番通りにファイルが並んでいることを確認したら、同様の手順で自動スキャンとファイル名の連番変更を行っていきます。

  8. タイムスタンプを更新する
    これは必須ではありませんが、Googleフォトなどのクラウドサービスでバックアップしたい場合は、ファイル番号とファイル日時を順番通りにすると、よりきちんと自動整理されるようになります。「お~瑠璃ね~む」で1冊分のファイルをリストアップし、「属性変更→タイムスタンプ」より、ほかの冊子のタイムスタンプと重複しないように気をつけて、画像のような設定でページ順にファイルを作成したように装います。


  9. 1つのファイルにまとめる
    非圧縮のzip形式にしておくと、エクスプローラーや漫画ビューアーなどで整理しやすくなるので、少し便利になります(PDF形式だとファイルサイズが大きくなるし、スキャン後の画像がpngやjpegなどのすでに圧縮された形式なので、さらにzip圧縮するのは非効率)。TNKソフトウェアではフォルダ以下のファイルを一挙に非圧縮zip形式に変換してくれる「漫画ビューアー用zipアーカイバ」という便利なユーティリティソフトを提供しているので、ぜひご活用ください。
2017年1月21日
TNKソフトウェアで公開しているオンラインソフトの購入方法をこのたび変更することにいたしました。

代金引換便を廃止する代わりに、ギフト券や、プリペイドカードに記載されている登録コードを代金としてお使いいただけるようになりました。原則としてVJA、JCB、JTBギフトカードと、Amazon.co.jp、nanaco、楽天、Tマネー、Google Play、iTunesコードがご利用いただけますが、開発者のアカウントの残高によって受付を一時停止するものもございますので、お使いいただけるギフトカードの確認はそれぞれの決済ページで改めてご確認ください。

なお、ストアアプリ版やAndroid版は従来通り、それぞれのストアが提供するサービスより決済してください。
2017年1月6日
カード型データベース「ショートアイデアノート」のAndroid版とWindows Store版を1.13に更新しました。最新版では、画面の小さいスマートフォンなどでテンプレートアイテム選択ダイアログが小さく表示されてしまう問題を修正しています。アプリは認証が通り次第、順次アップデート通知が送信される予定です。

Google Playでは2016年12月25日までの値下げキャンペーンを実施中です。グーグルからクリスマスギフトコードをプレゼントされた方は、ぜひショートアイデアノートの購入にご利用ください。
2016年12月21日
今回はエプソンの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日
« 前ページへ - 次ページへ »