電子コントローラとシリアル通信で通信するWindowsアプリを作成する場合、弊方では動作が軽く、細かくコントロールしたいため大体Win32APIを直接使います。
Windows用製シリアル通信コントロールは、自主トライや既存コードが使っていた場合に使用しています。その際に気づいた事項の覚書です。
.NET SerialPort の場合
.NET の標準コントロール SerialPort は以下の特徴があるようです。
- 当然ですがMFC や VB6 では使用できない。
- Windowsレジストリ登録は不要。
- 受信イベントは画面と別スレッドで動作する。
- 特に多機能ではない。
以下留意点です。
留意点1: 受信イベント内でUI操作を行う場合はデリケートが必要。
受信イベントは別スレッドで動作するためデリゲート( 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処理 ....
:
:
留意点2: 受信イベントを高周期で発生させるとフリーズ状態に至る。
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秒周期程度で、キャッシュした表示内容をまとめて反映させるようにします。これで下図くらい改善されます。

秒オーダーの応答性でよい場合以外は、受信インベントは使わない方が無難なようです。
MS Commコントロールの場合
2000年以前から存在しているVB6標準のコントロール、まだ使用されている場合かあるようです。弊方では最近まで使ったことはありませんでした。以下の特徴があるようです。
- たぶん MFC か VB6 でないと使用できない。
- 実行するPCにて、Windowsレジストリ登録が必要。
- 通信用スレッドは立てないので、通信周期によっては画面処理の応答性が落ちる。
- 使い方がシンプル。
- ナゾが多い。
以下留意点です。
注意1: 60byteづつしか受信できない
普通に受信させると下図のようになってしまいます。

これによる具体的弊害は、
- 60byte超えるパケットを読む場合、ロジックでバッファリングする必要がある。
- シリアルポートOPEN直後、60byte受信バッファに溜まるのでアプリが応答しなくなってしまう。
その半面メリットも考えられます。
- 受信イベントが多発しないため、.NETの様に大量受信時にフリーズ状態になりにくい。
ActiveComm の場合
ActiveCommはかつて販売されていたサードパーティのコントロールです。これもかなり古いものです。以下の特徴があるようです。
- たぶんMFC か VB6 でないと使用できない。
- 実行するPCにて、Windowsレジストリ登録が必要。
- 通信用スレッドは立てないので、通信周期によっては画面処理の応答性が落ちる。
- MS Comm よりは普通に使える。
昔々使った印象ですと、購入して使うほどメリットはないと感じていましたが、MS Comm の奇妙さを目のあたりにすると有用性はあったのかと思います。
jSerialComm
java には、jSerialComm というものがあるようですね。今度また。
補足情報
あまり知られた情報ではないかもしれませんが、リアルRS232C、USBシリアル変換器の種類によって、単位時間あたりの ReadFile API で読めるサイズが変わります。1フレームを受信長で十分なタイムアウト時間で受信ストリームバッファを活用してRead待ちしている場合は関係ないですが、1~数byteづづ受信都度読みをしている場合はこの影響をうけます。アプリ側に作りによっては、RS232Cの種類によって動作がかわってしまいます。この影響か、上記コントロールにどこまで影響を与えているかは分っていません。このことが「USBシリアル変換器の相性問題」の要因かもしれません。
以上、Windows用シリアル通信コントロール新旧の留意点です。何にしても用途に応じてWin32APIを直接使い 内部Thread動作する.dllコントロールを作成するのがベストです。組込み機器との通信となると、どうしても応答性が求められてしまいます。 この主のWindowsアプリケーションで、不安定、遅い、応答性悪いが、対策時間ない/対策する自信がないなどお困りの場合は、こちらまでご相談してみてださい。アプリは本体は作るが、.dll 側だけ頼みたい場合でも製作したします。