画像ファイル
画像ファイル RS-232C ループテスト
RS-232C関係
1RS-232Cの基礎
2RS-232Cの何が変わった..
3SerialPortクラス
4Unicode(ユニコード)の壁
5マルチスレッドの壁
6RS-232C サンプルコード
7RS-232CのHEXモニタ
8RS-232C 送信モジュール
9RS-232Cのループテスト
10RS-232Cのピンチェンジ..

VB.NET C#全般
1羊の皮を着た狼 VB.NET
2Form1、Form2の相互参照
3Form1、Form2の相互参照 2
4VB.NET C# データ型の基本
5VB.NET C# 文字列
6VB.NET タイマー精度
7BackgroundWorkerの魅力1..
8BackgroundWorkerの魅力2..
9VB6のタイマー
10コントロールの配列をインデクサ..
11コントロールの配列はジェネリク..
12インデクサ(C#、VB.NET)
13インデクサでBit操作
14Unicode 入門
15デリゲート入門
16マルチスレッド入門
17イベント入門
18デリゲートとイベント
18インターフェースの基本

RichTextBox関係
1RichTextBoxの不思議
2テキスト色付け高速化計画
3VB.NET RichTextBox1
4VB.NET RichTextBox 2

Socket通信
1C#、VB2005 でSocket通信
2サーバー 複数接続

プロセス間通信
1プロセス間通信(送信側)
2プロセス間通信(受信側)


質問、意見はこちらに
画像ファイル


一台のPCによる、RS-232Cのループテスト
RS-232Cのプログラムを作成して他の機器やコンピュータに接続してテストした時に上手く動作しない事が有る。
いったいプログラムが悪いのか、ケーブルの不良なのか又は相手の機器の不具合なのか途方にくれてしまう。
こんな時はまずは自分のコンピューターには少なくても異常が無い事を確認したい。
RS-232Cにはループバックテストという方法が有る、この方法はRS-232Cのコネクタ部分又はケーブルの終端で、
RxDとTxD、DSRとDTRと言った具合に、RS-232cのINとOUTを互いに端子部でショートさ、自分で出した信号を
自分で受け取る事で機能をチェックする。
この場合は送受信が出来るプログラムを1つ立ち上げれば良い。
接続の仕方はクロスケーブルと同じなので「 RS-232Cの基礎 」を御覧頂きたい。
ループバックはかなり有力なチェック方法で有るが、ピンをショーとさせる為、ある程度ハードウェアーの知識が必要で
有る点が少々難点である。
ここでは一台のコンピューターの2つのRS-232Cのポートを使用して、プログラム及びハードウェアのチェックをする
プログラムを考えて見る。
この方法を仮に「RS-232Cのループテスト」と名づけた、これは自分のコンピュータから出した信号を自分で読み込んで
いると言う意味で、ループバックテストとは全く異なる方法であるので注意されたい。

必要なハードウェア
最近はRS-232Cのポートが無いコンピュータが増えてきた、もしお使いのコンピュータにRS-232Cのポートが1つも無かったら、
USB-RS232Cコンバータを2つ用意し、コンピュータのUSBポートに装着した後クロスケーブルで接続する。
もしコンピュータにRS-232Cのポートが1つ有る場合はUSB-RS232Cコンバータは1つで良いし、RS-232Cのポートが
2つ有る場合は当然の事ながらUSB-RS232Cは要らない。
肝心なことはいずれにしろクロスケーブルで接続することである。

仕様
下の画像を御覧頂きたい、ポートの名前を入れるテキストボックスが2つ有る。
このテキストボックスには、プログラムロード時に自動的にポート名が入るものとする。
なお自分でポート名を入力することも出来る。
各ポート名横にはOpenボタンが有り、Close時に押すとOpenにOpen時に押すとCloseになる。
その他DTR、RTSのチェックボックスが有りこれにチェックを入れると、相手のDST、CTSのラベルのバックカラーが明るい緑に
変わるものとする。
各ポートには送信と受信のテキストボックスが有り、送信テキストボックスに文字を書き込み送信ボタンを押すとデータが送られ、
相手の受信ボックスに表示される。
更にログ表示テキストボックスが有り、ログが表示出来るが、ここではエラー表示のみ表示することにする。
このテストプログラムは文字列の送信専用で有り、バイナリデータは送信出来ない。
送信文字は必ず改行が最後に付く物とし、改行が無い場合は自動的に改行を追加する。
受信側は改行を文字列の区切りとみなし、改行を受信した時に、テキストを表示する。

送信文字は全てSift-Jisで送信し受信側ではSift-JisからUnicodeに変換して表示する。
VB.NETもC#も文字操作は全てUnicode(UTF-16)で処理が行なわれる。
従って文字列をShift-Jisで送受信する場合は、送信側でUnicode - Shift-Jis変換が、
受信側でShift-Jis - Unicode変換が必要となる。
なおUnicode-Unicodeで送受信すると変換は不要となるが、現在の機器がの殆どが、
Shift-Jisでの送受信を行なっていることを考えると、一旦Shift-Jisに直して送信する事をお奨めしたい。

FrameworkのSrialPortクラスのWriteLineで送信する場合は受信側は、ReadLineで受信することになる。
この辺はMSDNにサンプルが有るのでそれを参考にされたし。

ここで注意するのは、文字列の送信と言ってもWriteLineを使用しないので、送信は
バイト配列を使用する、文字列をバイト配列に変えて送信し、受信時はバイト配列から文字列に変換する。

さて文字列の代わりにバイナリデータで送受信したい場合はどうしたらいいのか?
先ず送りたいバイナリデータを「A B F 2」等とテキストボックスに書き込み、送信字にバイトデータに変換すれば良く、
受信時もバイナリデータを「1 2 A」などのHex表示に直して表示すれば良いことになる。
今回はこのモードは実装していないが
「RS-232C 送信モジュールの作成」 にこのモードを乗せて有るので参考にされたい。

画像ファイル

コードの説明

ポート名の読み込み
先ずプログラムの起動時の自動でポート名を読み込む部分を説明する。
C#のコード
//起動時にポート名をマシンから取得し、テキストに書き出す
private void SetPortsName()
{
//ポート名を読み込みます
string[] ports = SerialPort.GetPortNames();

//利用出来るポートが2つ有る場合と、1つの場合を分ける。
if (ports.Length >= 2)
{
//ポートが2つ以上有る場合
textBoxPortName1.Text = ports[0];//ポート1をセット
textBoxPortName2.Text = ports[1];//ポート2をセット
groupBox1.Text = ports[0];
groupBox2.Text = ports[1];
}
else if (ports.Length == 1)
{
//ポートが一つの場合
textBoxPortName1.Text = ports[0];
serialPort1.PortName = ports[0];
groupBox1.Text = ports[0];
}
}

VB2005のコード
    '起動時にポート名をマシンから取得し、テキストに書き出す
Private Sub SetPortsName()
'ポート名を読み込みます
Dim ports() As String = SerialPort.GetPortNames()

'利用出来るポートが2つ有る場合と、1つの場合を分ける。
If ports.Length >= 2 Then

'ポートが2つ以上有る場合
textBoxPortName1.Text = ports(0) 'ポートをセット
textBoxPortName2.Text = ports(1) 'ポートをセット
groupBox1.Text = ports(0)
groupBox2.Text = ports(1)
ElseIf ports.Length = 1 Then
'ポートが一つの場合
textBoxPortName1.Text = ports(0)
serialPort1.PortName = ports(0)
groupBox1.Text = ports(0)
End If
End Sub


SerialPortクラスにはGetPortNames()というメソドが有り、使用可能なポートが取得出来る。
これで取得した名前をテキストに表示しているのである。
さてこの時に表示されるはずのポート名が表示されない場合はどうしたら良いのか?
「コントロールパネル」- 「コンピュータの管理」-「デバイスマネジャ」-「ポート」を確かめて頂きたい、
ここに使用可能なポートが表示されていなければ、ハードウエア的に何か不具合がある。
なお幾つかのポートが有る場合は、テキストボックスに適当な名前を入力すれば其の名前のポートが使用可能となる。
送信
送信はUnicode をSift-Jisに変換しByte配列に入れて送信する、送信する文字列の最後に改行が無い場合は
自動的に改行を挿入する。
なおencSjisクラスはコードの全体で宣言して有るので、全体のコードを見ていただきたい。
C#のコード

//送信ボタン押下
private void butSend1_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen)
{
string strSend = textBoxWrite1.Text;
//改行が無かったら付け足す
if (!strSend.EndsWith("\r\n"))
strSend += "\r\n";
//'送信文字をShift-jisに変化してをByte配列に格納
Byte[] byteArry = encSjis.GetBytes(strSend);

//送信
serialPort1.Write(byteArry, 0, byteArry.Length);
}
}

VB2005のコード
'送信ボタン押下
Private Sub butSend1_Click(ByVal sender As Object, ByVal e As EventArgs) _
Handles butSend1.Click
If serialPort1.IsOpen Then
Dim strSend As String = textBoxWrite1.Text
'改行が無かったら付け足す
If Not strSend.EndsWith(Chr(13) + Chr(10)) Then
strSend += Chr(13) + Chr(10)
''送信文字をShift-jisに変化してをByte配列に格納
Dim byteArry() As Byte = encSjis.GetBytes(strSend)

'送信
serialPort1.Write(byteArry, 0, byteArry.Length)
End If
End If
End Sub

受信
受信はSerialPortクラスのDataReceivedイベントで行なう。
このイベントはメインスレッドとは別のスレッドでイベントが発生するので注意されたい。
すなわちこのスレッドで受信した文字をメインスレッドのテキストボックスに書き出す為には、デリゲートを使いInvokeメソドで
書き込む必要が有るのだ。
デリゲートにInvokeに関しては 「デリゲート入門」 「マルチスレッド入門」 を見ていただきたい。

ここで注意すべき点はデータの一時保持にListクラスを使用している点だ。
Listクラスはobject(Object)を配列で持てる非常に便利なクラスである。
Listクラスを知らない方は、使い方はほぼListBoxと同じでStringの替わりにこの場合はIntegerが格納されると思って戴きたい。
ここでは受信したデータをIntegerとしてListクラスで保持し、改行が来た段階でByte配列に移し、其の配列を文字に変換している。
ListクラスからByte配列に移した受信したデータはSift-Jisの文字データである。
これをUnicodeのByte配列に変換し、更にこれを文字列に変換している。
もちろんListクラスを使わずにByte配列だけの処理も可能であり、たぶんその方が速度的には速いと思われるが反面、
配列の確保や開放、書き込み、読み出しのポインターの管理など面倒な操作が必要で有る。
セリアルポートの受信文字の受信バイト数はserialPort1.BytesToReadのメソドで取得出来る。
取得した長さでバイト配列を確保する。
C# コード
byte[] byteRead = new byte[serialPort1.BytesToRead];
VB2005 コード
Dim byteRead(serialPort1.BytesToRead - 1) As Byte(VB2005)

そして次のコードで読み込む。
C# コード
serialPort1.Read(byteRead, 0, serialPort1.BytesToRead);
VB2005 コード
serialPort1.Read(byteRead, 0, serialPort1.BytesToRead)

読み込んだデータを1バイトづつListクラスに追加しながら、
C# コード
lstInt1.Add(byteRead[i]);
VB2005 コード
lstInt1.Add(byteRead(i))

改行が有るか否か探す。
C# コード
if (lstInt1.Count >= 2 && lstInt1[lstInt1.Count - 2] == 13 && 
lstInt1[lstInt1.Count - 1] == 10)
VB2005 コード
If lstInt1.Count >= 2 AndAlso lstInt1(lstInt1.Count - 2) = 13 _
AndAlso lstInt1(lstInt1.Count - 1) = 10 Then

VB2005の場合はAndではなくAndAlsoを使用すること。
Andを使うとlstInt1.Countが2より小さい場合lstInt1.Count - 2でエラーになる。
AndAlsoを使うと前の条件は不成立の場合は次を評価しないので問題が起きない。
C#はもともと前の条件がfalseなら次を評価しないのだ、微妙な言語仕様の差に注意しよう。

改行が有ったらListクラスのIntegerの値をByte配列に移し、このShift-JisのByte配列をUnicodeの配列に変換して
更にUnicodeのByte配列を文字列に変換する。

メインスレッドへの書き込みはデリゲートを使ってInvokeで書き込む。
ここまで終わったらListクラスの保持している値を全てクリアーし次の文字列に備える。
C#のコード
//受信文字を保持するListクラス
//Byte配列で受信文字を読み込んだ後、Byte配列からListに1Byteづつ移し替え
//改行が現れたらSift-JisからUnicodeに変換して、デリゲートでメインのTextBox
//に書き込み、書き込み後Listをクリアして次の文字を読み込む。
List<int> lstInt1 =new List<int>() ;
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
dlgsetText dlgTxt = new dlgsetText(setReadText);
//受信文字
byte[] byteRead = new byte[serialPort1.BytesToRead];
//読み込み
serialPort1.Read(byteRead, 0, serialPort1.BytesToRead);

//読み込んだデータをlistで保持
for (int i = 0; i < byteRead.Length ; i++)
{
//Listに追加
lstInt1.Add(byteRead[i]);

//改行が出てきたらListをByte配列に移し替えてテキストボックスに書き込む
if (lstInt1.Count >= 2 && lstInt1[lstInt1.Count - 2] == 13 &&
lstInt1[lstInt1.Count - 1] == 10)
{
//Byte配列に移し替える
byte[] byteTmp = new byte[lstInt1.Count];
for (int j = 0; j < lstInt1.Count; j++)
byteTmp[j] = (byte)lstInt1[j];

//shift-jis からunicodeに変換する
byte[] byteUni = Encoding.Convert(encSjis, encUni, byteTmp);
string strUni = encUni.GetString(byteUni);

//メインスレッドのテキストボックスに書き込む
this.Invoke(new dlgsetText(setReadText), new object[]
{ textBoxRead1, strUni });
//この書き方でも良い
// dlgsetText dlgset = new dlgsetText(setReadText);
//this.Invoke(dlgset, textBoxRead1, strUni);

//Listをクリアーする
lstInt1.Clear();
}
}
}

VB2005のコード
    '受信文字を保持するリストクラス
'Byte配列で受信文字を読み込んだ後、Byte配列からListに1Byteづつ移し替え
'改行が現れたらSift-JisからUnicodeに変換して、デリゲートでメインのTextBox
'に書き込み、書き込み後Listをクリアして次の文字を読み込む。
Dim lstInt1 As List(Of Integer) = New List(Of Integer)()
Private Sub serialPort1_DataReceived(ByVal sender As Object, ByVal e As _
SerialDataReceivedEventArgs) Handles serialPort1.DataReceived

Dim dlgTxt As dlgsetText = New dlgsetText(AddressOf setReadText)
'受信文字
Dim byteRead(serialPort1.BytesToRead - 1) As Byte
'読み込み
serialPort1.Read(byteRead, 0, serialPort1.BytesToRead)

'読み込んだデータをlistで保持
For i As Integer = 0 To byteRead.Length - 1

'Listに追加
lstInt1.Add(byteRead(i))

'改行が出てきたらListをByte配列に移し替えてテキストボックスに書き込む
If lstInt1.Count >= 2 AndAlso lstInt1(lstInt1.Count - 2) = 13 _
AndAlso lstInt1(lstInt1.Count - 1) = 10 Then

'Byte配列に移し替える
Dim byteTmp(lstInt1.Count) As Byte
For j As Integer = 0 To lstInt1.Count - 1
byteTmp(j) = CByte(lstInt1(j))
Next
'shift-jis からunicodeに変換する
Dim byteUni() As Byte = Encoding.Convert(encSjis, encUni, byteTmp)
Dim strUni As String = encUni.GetString(byteUni)

'メインスレッドのテキストボックスに書き込む
Me.Invoke(New dlgsetText(AddressOf setReadText), New Object() _
{textBoxRead1, strUni})
'この書き方でも良い
'Dim dlgset As dlgsetText = New dlgsetText(AddressOf setReadText)
'Me.Invoke(dlgset, textBoxRead1, strUni)

'Listをクリアーする
lstInt1.Clear()

End If
Next
End Sub

最後に
主だったコードの説明はこれで終わるが、PicChangedイベントやDSRやCTSのラベルのバックカラーを変える説明は
「RS-232Cのピンチェンジ表示 」 で詳しく説明しているのでそちらを参考にして戴きたい。

なお今回のプログラムを実行してみて気がついたことが有る。
serialPort1_DataReceivedイベントでserialPort1.ReadByteで1バイトずつデータを読み込ませたら、
かなりの確立でデータの取りこぼしが発生した。
多分これはハードウェア依存で有ろう、 serialPort1.Readに変えてからは取りこぼしは一回も発生しなかった。


画像ファイル