3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C# では、SerialPort クラスを用いてシリアルポートでの通信を行うことができる。
しかし、このクラスには、扱うポートの名前にこだわりがあり、ポートが気に入らない名前だというだけで開かずに例外を吐くという仕様がある。
そこで、この記事では、そのようなこだわりによって生じる害を少し軽減する方法を紹介する。

システムに存在するポートを列挙する

SerialPort.GetPortNames() 関数を用いると、システムに存在するシリアルポートを列挙できる。

ListPorts.cs
using System;
using System.IO.Ports;

class ListPorts
{
	public static void Main(String[] args)
	{
		string[] ports = SerialPort.GetPortNames();
		Console.WriteLine(string.Join("\n", ports));
	}
}
実行例
YUKI.N>ListPorts
CNCA0
CNCB0
COM5
COM6

ここで表示されている CNCA0 および CNCB0 は、Null-modem emulator (com0com) で作成した仮想シリアルポートである。

CNCA0 と CNCB0 の設定

たとえば、RS232Cテストツール を用いると、これらのポートで通信ができる。

RS232Cテストツールを用いた通信の例

シリアルポートで通信を行う

SerialPort クラスを用いた通信の基本的な流れは、以下のようになる。

  1. SerialPort クラスのオブジェクトを作成する
  2. 必要に応じて、パラメータ (フロー制御の方式など) を設定する
  3. Open() メソッドでポートを開く
  4. データの送受信を行う
  5. Close() メソッドでポートを閉じる

例えば、以下のコードで通信ができる。

PingPort.cs
using System;
using System.IO.Ports;

class PingPort
{
	public static void Main(string[] args)
	{
		string portName = args.Length > 0 ? args[0] : "COM1";
		int baudRate = 115200;
		Console.WriteLine("opening port \"" + portName + "\"");
		SerialPort port = new SerialPort(portName, baudRate);
		port.Open();

		string messageToSend = "ping";
		Console.WriteLine("sending \"" + messageToSend + "\"");
		port.WriteLine(messageToSend);
		string messageReceived = port.ReadLine();
		Console.WriteLine("received \"" + messageReceived + "\"");

		port.Close();
	}
}

以下が実行例であり、LF で終わるテキストの送受信ができていることがわかる。

実行例

SerialPort が持つ名前こだわり

同じプログラムで、先ほど使えることを確認したポート CNCA0 での通信をしようとすると、エラーになってしまう。

実行結果
YUKI.N>PingPort CNCA0
opening port "CNCA0"

ハンドルされていない例外: System.ArgumentException: 指定されたポート名の先頭が COM/com でないか、または有効なシリアル ポートではありません。
パラメーター名:portName
   場所 System.IO.Ports.SerialStream..ctor(String portName, Int32 baudRate, Parity parity, Int32 dataBits, StopBits stopBits, Int32 readTimeout, Int32 writeTimeout, Handshake handshake, Boolean dtrEnable, Boolean rtsEnable, Boolean discardNull, Byte parityReplace)
   場所 System.IO.Ports.SerialPort.Open()
   場所 PingPort.Main(String[] args)

ポートは有効であるにもかかわらず、「ポート名の先頭が COM/com でない」というだけの理由で、合理性なくエラーになってしまうのである。
なお、「ポート名の先頭が COM でないと例外を吐く」というのは、以下のように Open() のドキュメントに明示されている。

ArgumentException
The port name does not begin with "COM".

-or-

The file type of the port is not supported.

検証のため、名前が COM で始まるポートを追加してみる。

com0comでポートを追加

すると、このポートでは普通に通信することができた。

通信に成功

ということは、use Ports class がオンになっていないと通信できないなどの技術的な理由ではなく、やはり本当に単にポートの名前が気に入らないというだけの理由で蹴っているようである。

SerialPort が持つ名前こだわりの害を軽減する

C# の SerialPort では、合理性のない名前こだわりのため、有効なポートであっても、ポート名が COM で始まらないというだけで開こうとすると例外が出てしまって開くことができない。
しかし、そのような名前が気に入らないポートであっても、有効なポートであるため、SerialPort.GetPortNames() では普通に列挙対象に含まれる。
そのため、この列挙結果をそのまま選択肢としてユーザーに提示してしまうと、使えないポートが選択肢に含まれてしまうことがあり、不親切になってしまう。
(そもそも、本当に不親切なのは、有効なポートでも名前が気に入らないだけで例外を吐きやがる SerialPort の方なのだが)

SerialPort を使わず、Windows API を用いて通信することで、SerialPort のこだわりに振り回されるのを回避できるかもしれない。
しかし、そうすると Windows に依存することになるし、DLL の読み込みの手間も生じる。
そこで、今回は列挙結果から COM で始まらないポートを外すことで、SerialPort で使えるポートのみを選択肢としてユーザーに提示できるようにする。

「列挙結果から COM で始まらないポートを外す」処理は、以下の処理からなる。

  • 文字列が COM で始まるかを判定する
  • 配列の要素のうち指定した条件を満たす要素のみを取り出す

前者は String.StartsWith メソッドで、後者は Array.FindAll メソッドで実現できる。
大文字の COM だけでなく小文字の com も通すため、StringComparison.CurrentCultureIgnoreCase を指定する。

ListPorts2.cs
using System;
using System.IO.Ports;

class ListPorts2
{
	public static void Main(String[] args)
	{
		string[] ports = SerialPort.GetPortNames();
		string[] portsFiltered = Array.FindAll(
			ports,
			str => str.StartsWith("COM", StringComparison.CurrentCultureIgnoreCase)
		);
		Console.WriteLine(string.Join("\n", portsFiltered));
	}
}
実行結果
YUKI.N>ListPorts
CNCA0
CNCB0
COM5
COM6
COMFORT
TEST

YUKI.N>ListPorts2
COM5
COM6
COMFORT

COM で始まるポート名のみを出力できた。

まとめ

C# でシリアルポート経由の通信を行う SerialPort クラスには合理性のない名前こだわりがあり、有効なポートであってもポート名が COM で始まっていないというだけで開けない仕様であることを確認した。
そして、このこだわりのせいで SerialPort で開けないポートを選択肢から外すため、文字列の配列から COM で始まる要素だけを抽出する方法を紹介した。

3
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?