続いて実際にポートを介してデータの受信を行ってみます。今回作るソフトは
- データ受信
- 即座に画面に出力
という極めてシンプルな動作をするターミナルソフトです。
使用する SerialPort
クラス
先の記事にも載せましたが、
MSDN にある説明ページをこちらに掲載しますので、適宜ご参考にどうぞ。
実際のプログラミング
SerialPort
クラスの初期化
まずはポートの指定等が必要です。上のリファレンスでコンストラクタとして簡単にポート初期化が出来てしまう物がありますが、今回はとりあえず理解しながらということで逐一メンバ変数に値を代入していく方法を取ります。
目標としてはコンソールの実行時引数を利用して各種設定を反映させるということですが、初めての製作ということで決め打ちでやってみましょう。今回はポート名を /dev/tty.usbmodem1412
、Baud Rate を 115200
bps としたいと思います。
初期化段階でのコードはこちら。
private static SerialPort port; // SerialPortのインスタンス
public static void Main(string[] args) {
port = new SerialPort(); // インスタンス化
port.PortName = "/dev/tty.usbmodem1412"; // 通信するポート名
port.Parity = Parity.None; // パリティ
port.DataBits = 8; // 送信するデータのビット数 通常は8
port.BaudRate = 115200; // baud rate
port.StopBits = StopBits.One; // ストップビットの数
port.DtrEnable = false; // DTRを用いるか
port.RtsEnable = false; // RTSを用いるか
}
こんな感じに直接値を代入していきます。
ポートのオープン
さあ実際にポートをオープンしていきましょう。但しここで別のターミナルソフトウェアが既に目的のポートを開いていた場合等で実行時にエラーが生じることがあります。このため、例外を捕捉してやらなければなりません。こんな感じに。
try {
port.Open(); // ポートをオープン
}
catch( Exception ) { // 例外が起きたらとりあえず握り潰してプログラム終了
// ポートオープンに失敗した場合
Console.WriteLine($"Fail: Port open of {port.PortName}. Application stops.");
Environment.Exit(1); // 終了コード1を発行して終了
}
簡易的に作るため、例外はキャッチするがその詳細については捨てるようにしています。また、例外が発生した時点でその後の動作は諦め、Environment.Exit()
でプログラムを終了させています。
実際の読み取り動作(ポーリング)
event やら delegate やらは(僕がよく分かっていないので)使いません。今回は逐一値を 1byte ずつ読んでいって逐一出力させます。
port.DiscardInBuffer(); // 現段階で受信バッファに溜まっているデータをすべて破棄
while (true) { // 無限ループで順次読んでいく
char dat = (char)port.ReadByte(); // 1バイト分データを読む char型(ASCIIコード)にキャストして格納
Console.Write(dat); // 読んだデータをそのままコンソールに書き込む
// Console.Write((char)port.ReadByte()); // 上2行を縮めて書くとこんな感じ
}
最初の port.DiscardInBuffer()
は僕の趣味です。必須ではありませんが、直前まで溜め込んでいた文字列がプログラム実行時にドワっと出力されるのが個人的に余り好きではないです。
読み出される値の型は byte
型です。しかしながらこれをそのまま Console.WriteLine()
に突っ込むとナマの数値として出力されてしまうため、一旦 char
型にキャストしています。
一応できた
あとはシリアル通信用のインターフェイスを介してターゲットに接続して確認するだけ。TXD と RXD のクロス配線には気をつけてくださいね〜。僕はこれで動作を確認しました。こんなに簡単に作れるなんてすごいですね。コード全景はこちら。
using System;
using System.IO.Ports;
namespace ser {
class MainClass {
private static SerialPort port; // SerialPortのインスタンス
public static void Main(string[] args) {
port = new SerialPort(); // インスタンス化
port.PortName = "/dev/tty.usbmodem1412"; // 通信するポート名
port.Parity = Parity.None; // パリティ
port.DataBits = 8; // 送信するデータのビット数 通常は8
port.BaudRate = 115200; // baud rate
port.StopBits = StopBits.One; // ストップビットの数
port.DtrEnable = false; // DTRを用いるか
port.RtsEnable = false; // RTSを用いるか
try {
port.Open(); // ポートをオープン
}
catch ( Exception ) { // 後を書かないことで例外原因を補足する変数を捨てられる
// ポートオープンに失敗した場合
Console.WriteLine($"Fail: Port open of {port.PortName}. Application stops.");
Environment.Exit(1); // 終了コード1を発行して終了
}
port.DiscardInBuffer(); // 現段階で受信バッファに溜まっているデータをすべて破棄
while (true) {// 無限ループで順次読んでいく
char dat = (char)port.ReadByte(); // 1バイト分データを読む char型(ASCIIコード)にキャストして格納
Console.Write(dat); // 読んだデータをそのままコンソールに書き込む
// Console.Write((char)port.ReadByte()); // 上2行を縮めて書くとこんな感じ
}
}
}
}
各種値を実行時に決められるようにする
これまでは決め打ちでやってましたが、いよいよ一般に流布しているターミナルソフトウェアのように実行時のコマンドライン引数から値を持ってくるように改造します。まずはそれ用の関数 assignPortAndBaud
を作ります。
コマンドライン引数 string[] args
の仕様ですが、sting
型配列に則って以下のように格納されています。
$ mono prog.txt aaa bbb ccc # 例
# args[0] => aaa
# args[1] => bbb
# args[2] => ccc
C言語とは違って、0番目に実行ファイル名が格納されているということはなく、イキナリ引数が入っていることに注意です。また、引数の数は args.Length
で取得可能になっています。当然当てずっぽうでいきなり args[0]
とソースコードに書いても、1つも引数を当てず実行したときには例外が発生してしまいますから、事前に .Length
で分岐させておく必要があります。
今回の仕様としては、screen
コマンドのように第1引数がポート名、第2引数が baud rate とします。swich
文を用いて簡単に記述できます。
static string portName;
static int baudRate;
public static void assignPortAndBaud(string[] args) {
switch( args.Length ) {
default:
portName = "/dev/tty.usbmodem1412";
baudRate = 115200;
break;
case 1:
portName = args[0];
break;
case 2:
portName = args[0];
baudRate = Convert.ToInt32(args[1]);
break;
}
}
呼び出し源は素直に assignPortAndBaud(args)
としておけば良い感じにメインクラス内のメンバ変数に格納されます。あとは各種 SerialPort
インスタンスへの値代入を弄くれば良いのですね。
port.PortName = portName; // 通信するポート名
port.BaudRate = baudRate; // baud rate
実行はこんな感じに。当然ですが、ポート名と baud rate はターゲットに合わせてください。終了する時は Ctrl-C で SIGINT
を突っ込めばいいです。
$ mono ser.exe /dev/tty.usbmodem1422 9600
〆
とりあえず受信プログラムは出来ました。一番単純なポーリングのみによってこれがかけてしまいました。続いて目指すのは送受信両方に対応するプログラムですね。僕のコードで高速にデータ落ちがなく通信出来るのかは非常に怪しいのですが、とりあえず作っていきます。ちなみにここでキーとなるのはスレッドなのですが、これも次回にしましょう。