みなさん、おはこんにちばんは!
D言語でマイコンとかUSB機器とか計測機器と通信したりとかそういうお話をします。
誰得だって?C言語でやれって?
俺得ライブラリだよ!!D言語でやりたいんだよ!!
あと、タイトルはラノベからインスパイアされました。
というわけで、diodeというのちょっとずつ作っています。
diodeはdioをベースとして、COMポートとかUSBとかGPIBなどなどを
D言語から簡単に叩くためのライブラリです。
dioは現在のところはshooさんのリポジトリにあるdioを使用してます。
例を挙げておきましょう。
以下のコードはCOM6から1byte取得してそのまま出力するプログラムです。
今回はこのコードを中心にしてdiodeの説明をしていきます。
import core.thread;
import std.range;
import dio.core; //diodeはdioベース
import dio.port;
import diode.visa; //VISAライブラリを使用
void main(){
auto serial = Serial("COM6");
with(serial){
timeout = dur!"msecs"(10); //10ms
dataBits = 8; //8bit
baudRate = 9600; //9600
stopBits = Stop.one; //ストップビット1
parity = Parity.none; //パリティーなし
}
auto serialBuf = serial.buffered;
auto rng = serialBuf.ranged;
put(rng, rng.take(1));
}
残念なことに、diodeの開発速度はかなり遅く、
今はまだWin32APIとVISAというライブラリを通してしかSerialやGPIBを叩けませんので、
今回はVISAライブラリのラッパーとしてのdiodeの使い方を説明します。
VISA(Virtual Instrument Software Architecture)とは、
本当は計測器などとGPIBとかで通信するためのライブラリなのですが、
普通にCOMポートのマイコンともやり取りできます。
私の環境ではNI(National Instruments)のVISA実装であるNI-VISAを使用しています。
その他に菊水電子が実装したKI-VISAとかもあったりしますが、
インターフェースは統一されているのでリンクするライブラリ変更すれば大丈夫なはずです。
また、NI-VISAだとWindows, Linux, Macで使えるようです。
###importと宣言部
次のようにSerialオブジェクトを宣言すればいいだけです。
import core.thread;
import std.range;
import dio.core; //diodeはdioベース
import dio.port;
import diode.visa; //VISAライブラリを使用
void main(){
auto serial = Serial("COM6");
//以下からはserialを使って通信できる
}
###ポートの設定とか
今回は
・タイムアウトは10ms、
・データビット数は8bit、
・ボーレート9600、
・ストップビット1、
・パリティーはなし
で設定しておきます。
この設定項目もWin32APIとVISAの2つで同じインターフェースとしています。
with(serial){
timeout = dur!"msecs"(10); //10ms
dataBits = 8; //8bit
baudRate = 9600; //9600
stopBits = Stop.one; //ストップビット1
parity = Parity.none; //パリティーなし
}
###通信を行う -その1:ubyteが基本-
diodeはそもそもがdioと一緒に使われることを想定して設計しています。
serialはdioで定義されているもっとも低い入出力を備えたオブジェクトです。
ですから、serialは次のようなコードで入出力が行えます
ubyte[] buf = new ubyte[1];
ubyte[] _b = buf;
while(serial.pull(_b)){} //1byte入力
while(serial.push(buf)){} //そのまま出力
しかし、個人的にpull, pushはめんどくさいので、range化してしまいます。
rangeにするにはまずbufferedを通してからrangedでrange化します。
作られるrangeはInput-RangeかつOutput-Rangeです。
range化してしまえば、Phobosの恩恵を受けれます。
auto serialBuf = serial.buffered;
auto binRng = serialBuf.ranged;
put(binRng, binRng.take(1));
###通信を行う -その2:ubyte以外-
例では入力-出力共にubyte型で行いましたが、
普通は複数バイト連続して意味のあるバイト列が送られてくるはずです。
今回は次のような構造体がリトルエンディアンで送られてくることを仮定します。
align(1):
struct D{
ulong v8;
ubyte v1;
}
では、このようにすればよろしいのでしょうか?
size_t n = 1;
D[] data = cast(D[])(binRng.take(D.sizeof * n).array());
put(binRng, cast(ubyte[])(data));
これはcastとか出てきて非常にめんどくさそうです。
dio.coercedの出番みたいです。
//マイコンからの情報をData型に変換
auto rngD = serialBuf.coerced!D.ranged;
put(rngD, rngD.take(1));
###VISA以外は?COMポート以外は?
先程までの例はVISAライブラリを用いた場合の説明ですが、Win32APIも宣言部が違うだけでほかは全く同一です。
ただ、GPIBで計測機器を制御する場合には文字列を使ってやり取りするのが違いますが。
(下のコードはコンパイルは通るものの、GPIB機器が身近に無いため動くかどうかわかりません)
auto dev = Gpib("GPIB0::1::INSTR").textPort;
dev.writeln("*IDN?");
dev.lines.front.writeln();
###まとめ
COMポートとかUSBとか計測機器(GPIB)とか扱うひとにとっては有用なライブラリになると思います。
そのうちlibusbとかも扱えるようにしようと思ってます。
ただ、大学編入試験の勉強とか卒研とかで忙しくなりそうなので、かなりゆっくり進んでいくと思います。
明日、16日目は@kyubunsさんです。
##2012年12月22日追記
###エンコーダとデコーダ
diode.coreにデコーダやエンコーダを扱うcodedを追加しました。
まず、T型に対するエンコーダはstd.Base64のように次のコードが通るものです。
T input;
auto encoded = Encoder.encode(input)
デコーダは、std.Base64とは違い、
T[] input;
size_t n = slice(input);
auto decoded = Decoder.decode(input);
sliceはref T[] inputを受け取り、inputの先頭からデータの固まりを切りだします。
戻り値は先頭にある不要な要素の個数です。
例を示すと、たとえば英数字以外を区切り文字とし、英数字をデコード処理する場合には
char[] input = "\n\r\tabcdefg\nabc\na".dup;
size_t n = slice(input);
writeln(n); //3, \n\r\tの先頭3文字は区切り文字または不正な要素
writeln(input); //"abcdefg", \tから\nに囲まれている要素
というようになります。
###エンコードとデコード
エンコードとデコードをするにはcodedを使います。
auto cd = serial.buffered.coded!MyCoder;
MyCoderはエンコーダとデコーダの両方を併せ持つか、2つのうちのどちらか一方のみをもつものです。
結果のcdはレンジとなっており、この場合にはinput-output-rangeです。
このようなエンコーダとデコーダにより、より使いやすくなったかと思います。
(coercedはcodedにより置き換え可能になりましたし、coercedの問題であるデータ開始点の不明確性がcoded.sliceによりなくなりました)