Windows用シリアル通信コントロール新旧の留意点

電子コントローラとシリアル通信で通信するWindowsアプリを作成する場合、弊方では動作が軽く、細かくコントロールしたいため大体Win32APIを直接使います。

Windows用製シリアル通信コントロールは、自主トライや既存コードが使っていた場合に使用しています。その際に気づいた事項の覚書です。


.NET の標準コントロール SerialPort は以下の特徴があるようです。

  • 当然ですがMFC や VB6 では使用できない。
  • Windowsレジストリ登録は不要。
  • 受信イベントは画面と別スレッドで動作する。
  • 特に多機能ではない。

以下留意点です。

受信イベントは別スレッドで動作するためデリゲート( Swiftのデリゲートとは異なる物です )が必要になります。 そのままだとbuildエラーか実行時にエラーが出ます。デリケートは .NET の特有のややとっつきにくい部分、理解してもたまに使う程度なのでよく忘れてしまいます。以下コード例です。

private:
delegate void formatReceiveData_Delegate(void);	    // デリゲートの型宣言
formatReceiveData_Delegate^ formatReceiveData_Obj;  // デリゲートのインスタンス宣言	
    :
    :

private: System::Void Form1_Load(
   System::Object^  sender, 
   System::EventArgs^  e
) {
    :
    :
   // UI操作を行うmethodのデリゲートを生成、生成は一度でOK
   formatReceiveData_Obj	= gcnew formatReceiveData_Delegate(this, &Form1::formatReceiveData);
    :
    :
}

private: System::Void serialPort1_DataReceived(
   System::Object^ sender, 
   System::IO::Ports::SerialDataReceivedEventArgs^ e
) {
	if ( dgrdReceive->InvokeRequired ) {		// thread排他が必要か、対象UIで判別。

		// デリゲートを排他無しで呼び出す
		this->BeginInvoke( this->formatReceiveData_Obj );
	}
	else {
		formatReceiveData();			// 受信値表示
	}

	procReceivedAfter( serialPort1 );  // 受信後処理
}

private: System::Void formatReceiveData(void) {
    :
    :
    UI処理 ....
    :
    :

ASCIIコード通信など不定長フレーム通信にて、msecオーダーで受信イベントが発生すると、忙しすぎて画面操作イベントが受付けられずフリーズ状態に至ってしまいます。 .NETでもこんなもんなんですね。PC は core i7 です。

対策するには、受信専用Thread立てます。以下コード例です。

private:
		Thread^	recvTheard = nullptr;
		static	System::IO::Ports::SerialPort^  _serialPort1;

private: System::Void btnOpen_Click(
   System::Object^ sender, 
   System::EventArgs^ e
) {
  	_serialPort1	= serialPort1;  // Thread内で参照用にstaticにインスタンス退避
		serialPort1->Open();
		recvTheard = gcnew Thread( gcnew ThreadStart( this->RecvThreadMain ) );
		recvTheard->Start( );
}

public: static System::Void RecvThreadMain( 
	void
) {
	char rcvBytes[8192];

	while( 1 ) {
			Byte	rcvByte;
			Int32	rcvLen;
			try {
			  // 不定長の場合はその時々で受信した分を読む
 				 rcvByte		= _serialPort1->ReadByte();
				 _serialPort1->Read( rcvBytes, rcvByte );  
			}
			catch (System::TimeoutException^ exp) {
				// タイムアウトはスルー
			}
	}

受信の都度、UIに表示するとマトモに見えないので、表示内容は変数にキャッシュしておいて、Timerコントロールで0.5秒周期程度で、キャッシュした表示内容をまとめて反映させるようにします。これで下図くらい改善されます。

秒オーダーの応答性でよい場合以外は、受信インベントは使わない方が無難なようです。


2000年以前から存在しているVB6標準のコントロール、まだ使用されている場合かあるようです。弊方では最近まで使ったことはありませんでした。以下の特徴があるようです。

  • たぶん MFC か VB6 でないと使用できない。
  • 実行するPCにて、Windowsレジストリ登録が必要。
  • 通信用スレッドは立てないので、通信周期によっては画面処理の応答性が落ちる。
  • 使い方がシンプル。
  • ナゾが多い。

以下留意点です。

普通に受信させると下図のようになってしまいます。

これによる具体的弊害は、

  • 60byte超えるパケットを読む場合、ロジックでバッファリングする必要がある。
  • シリアルポートOPEN直後、60byte受信バッファに溜まるのでアプリが応答しなくなってしまう。

その半面メリットも考えられます。

  • 受信イベントが多発しないため、.NETの様に大量受信時にフリーズ状態になりにくい。


ActiveCommはかつて販売されていたサードパーティのコントロールです。これもかなり古いものです。以下の特徴があるようです。

  • たぶんMFC か VB6 でないと使用できない。
  • 実行するPCにて、Windowsレジストリ登録が必要。
  • 通信用スレッドは立てないので、通信周期によっては画面処理の応答性が落ちる。
  • MS Comm よりは普通に使える。

昔々使った印象ですと、購入して使うほどメリットはないと感じていましたが、MS Comm の奇妙さを目のあたりにすると有用性はあったのかと思います。


java には、jSerialComm というものがあるようですね。今度また。


あまり知られた情報ではないかもしれませんが、リアルRS232C、USBシリアル変換器の種類によって、単位時間あたりの ReadFile API で読めるサイズが変わります。1フレームを受信長で十分なタイムアウト時間で受信ストリームバッファを活用してRead待ちしている場合は関係ないですが、1~数byteづづ受信都度読みをしている場合はこの影響をうけます。アプリ側に作りによっては、RS232Cの種類によって動作がかわってしまいます。この影響か、上記コントロールにどこまで影響を与えているかは分っていません。このことが「USBシリアル変換器の相性問題」の要因かもしれません。


以上、Windows用シリアル通信コントロール新旧の留意点です。何にしても用途に応じてWin32APIを直接使い 内部Thread動作する.dllコントロールを作成するのがベストです。組込み機器との通信となると、どうしても応答性が求められてしまいます。 この主のWindowsアプリケーションで、不安定、遅い、応答性悪いが、対策時間ない/対策する自信がないなどお困りの場合は、こちらまでご相談してみてださい。アプリは本体は作るが、.dll 側だけ頼みたい場合でも製作したします。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です