画像ファイル
画像ファイル 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プロセス間通信(受信側)


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


MSDNサンプルコードは問題なく動作するのか?
MSDNの下のサイトにRS-232Cのサンプルソフトが掲載されている。
http://www.microsoft.com/japan/msdn/vbasic/migration/tips/SeriaPort/
VB.NETのRS-232Cで悩まれた方はここにたどり着く可能性が高い。
はたしてこのコードは上手く動くのであろうか? さっそく試してみた。
先ず2台のPCをRS-232Cケーブルで接続して、ダウンロードしたコードを走らせ、
「abc」を送信してみた。
以下の画像が受信結果である。
画像ファイル

正常に受信している様に見える。
次に「あいうえお」を送信して受信を行なった、結果は下の画像で有る。
画像ファイル

「あいうえお」が表示されない、漢字を送っても同じである。
そこで登場するのが RS-232C受信モニター である。
先ず「abc」の場合、
画像ファイル

「61 62 63」は16進の「abc」である、改行は「0A」だけで有る。
続いて「あいうえお」の送受信結果である。
画像ファイル

驚いたことに「 あいうえお」は全て「3F」に変わっている、ちなみに「3F」はASKIIコードの「?」である。
要するに漢字は送れないのである。(下の注意を参照)
サンプルコードの送信は
SerialPort1.WriteLine(TextBox2.Text)
となっている、要するにSerialPortクラスのWriteLineメソドは使えないのである。
そこでこのコードを書き直して漢字も、改行も送る事が出来る仕様にする。

(注意)
その後の調査でサンプルコードのSerialPort1クラスのEncodingにエンコードクラスを設定すると
正しく送受信がされる事がわかりました。(ただしオリジナルコードでは送りません)
具体的には次のコードを書き加えます。
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
'Shift-Jisの場合
SerialPort1.Encoding = System.Text.Encoding.GetEncoding("Shift_JIS")
'Unicodeの場合
'SerialPort1.Encoding = System.Text.Encoding.GetEncoding("utf-16")
End Sub



サンプルコードの書き換えと仕様
仕様に関しては、上記コードとほぼ同じであるが、
送信は全てバイト配列を使用し、ユニコードをShift-jisに変換して送信する。
Shift-jisで送信する理由はPC対PCの場合はユニコードでも構わないのだが、
外部機器はShift-jisの場合が多いことと、Unicode(utf-16)やutf-8に比較して
Shift-jisの方がデータ量が少なくなることが多いからです。
ただしFrameWorkは全て内部はUnicodeなので受信したデータを再びSift-jisから
Unicodeに変換する必要が有る。
UnicodeやShift-jisに関しては 「Unicode入門」 を参照されたし。

外観
外観は大体こんなこんなものである。
RS-232c画像


送信
送信ボタンを押すとデータを送信するがその際データの最後に改行が無い場合は 自動で改行を付加する。
送信はUnicodeからSift-JisのByte配列に変換して送信する。

受信
受信のバイトデータ保持にはListクラスを使う。
本来はByte配列を使用するのだが、配列には長さが固定されていると言う昔からの問題が有る。
Listクラスは必要に応じて実行中に長さが変更出来る配列である。
Listクラスは以下の様に宣言する。
C#のコード
List<byte> lByte = new List<byte>();
VB2005のコード
Dim lByte As List(Of Byte) = New List(Of Byte)
ちょっと見慣れないコードであるが、Listクラスはかなり便利なクラスで有る。
この場合はByteデータを格納しているが、Objectであれば殆ど何でも保持できる。
Add()として要素を追加すれば、大きさはList自体が自分で面倒を見てくれるし、
取り出しも配列と同じ方法で取り出せるのである。
先ず受信バッファにデータが読み込まれると、イベントが起きるからイベントの中で、
この受信バッファのデータを改行が来るまで、1バイトずつListに格納する。
改行が来たら、一旦Listから、別のByte配列に移し、Listはクリアーする。
移されたByte配列には送られて来たShift-Jisの文字列が改行まで入るから、
これをUnicodeのByte配列に変換して、更にUnicodeのByte配列からUnicodeの文字列に変換する。
このUnicodeの文字列をデリゲートとInvokeを使用して、メインスレッドのテキストボックスに書き込む。
もし改行が来なかったらどうなるだろうかと思う人がいるかも知れないが、改行が入らない文字列では
文字の区切りが判断できない。
従って、2バイト以上の文字を送った場合は一文字の途中でデータが泣き別れになる可能性が有る。
この様な場合は何か特別な方法で文字列の終わりを判断しなくてはいけない。
もちろんANK文字だけの送受信では文字の長さが決まっているので問題は起こらない。

C#のコード
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace rs232cmsdn
{
public partial class Form1 : Form
{
//'unicodeのEcodingクラスに作成
Encoding encUni = Encoding.GetEncoding("utf-16");
//'s-jisのEncodingクラスの作成
Encoding encSjis = Encoding.GetEncoding("shift-jis");

public Form1()
{
InitializeComponent();
}
//ポートのオープン&クローズ
private void Button1_Click(object sender, EventArgs e)
{
try{
//ポート名のセット
serialPort1.PortName = textBox1.Text;

//開いていれば一旦閉じる
if (serialPort1.IsOpen == true)
serialPort1.Close();

//オープン
serialPort1.Open();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error");
}
}

//ポートクローズボタン
private void Button2_Click(object sender, EventArgs e)
{
if (serialPort1.IsOpen == true)
serialPort1.Close();
}

//データ送信
private void Button3_Click(object sender, EventArgs e)
{
string strSend;
if (textBox2.Text.Length == 0)
{
MessageBox.Show("送信文字列を入力してください", "Error");
textBox2.Focus();
}
//送信文字をセット
strSend = textBox2.Text;
if (!strSend.EndsWith("\r\n"))
strSend += "\r\n";

try
{

//'送信文字をShift-jisに変化してをByte配列に格納
Byte[] byteArry = encSjis.GetBytes(strSend);
//送信実行
serialPort1.Write(byteArry, 0, byteArry.Length);
}

//送信失敗
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error");
}
}


delegate void dlgAddText(string str);
private void AddText(string strGetText)
{
//'受信データーをテキストボックスに追加する
textBox3.AppendText(strGetText);
//'キャレットを文字の最後に設定す
textBox3.ScrollToCaret();
}


//'---------------------------------
//'セリアルポートの受信イベント
//'---------------------------------
//受信データ保持用のList
List<byte> lByte = new List<byte>();
private void SerialPort1_DataReceived(object sender,
System.IO.Ports.SerialDataReceivedEventArgs e)
{
//データ受信用のバイト配列
byte[] inByte = new byte[serialPort1.BytesToRead];
//データの読み込み
serialPort1.Read(inByte, 0, serialPort1.BytesToRead);
int dataLength = serialPort1.BytesToRead;
serialPort1.Read(inByte, 0, dataLength);
byte[] inByte = new byte[dataLength];

//読み込んだデータを改行を確認しながらListに移す
for (int i = 0; i < inByte.Length; i++)
{
lByte.Add(inByte[i]); //Listに足しこむ
//改行が有った
if (lByte.Count > 2 && lByte[lByte.Count - 2] == 13 &&
lByte[lByte.Count - 1] == 10)
{
//ListからByte配列に移す
byte[] tmpByte = new byte[lByte.Count];
for (int j = 0; j < lByte.Count; j++)
tmpByte[j] = lByte[j];

lByte.Clear();   //Listをクリアー

//Shift-Jisからユニコードに変換
byte[] uniByte = Encoding.Convert(encSjis, encUni, tmpByte);

//Unicodeの配列から文字列に変換
string uniString = encUni.GetString(uniByte);

//メインスレッドのテキストに追加する
this.Invoke(new dlgAddText(AddText), new object[] { uniString });
 //上は下の記述の短縮形
 // dlgAddText dlgT = new dlgAddText(AddText);
 // this.Invoke(dlgT, uniString)
}
}
}

//テキストボックスクリアー
private void Button4_Click(object sender, EventArgs e)
{
textBox3.Clear();
}

}

}

    
VB2005のコード
Imports System.Text
Public Class Form1

'unicodeのEcodingクラスに作成
Dim encUni As Encoding = Encoding.GetEncoding("utf-16")
'下のコードでも良い
'Dim ecUni As Encoding = Encoding.Unicode

's-jisのEncodingクラスの作成
Dim encSjis As Encoding = Encoding.GetEncoding("shift-jis")

'ポートのオープン
Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles Button1.Click
Try
'ポート名のセット
SerialPort1.PortName = TextBox1.Text

'開いていれば一旦閉じる
If SerialPort1.IsOpen = True Then
SerialPort1.Close()
End If
Call SerialPort1.Open()
Catch ex As Exception
MessageBox.Show(ex.Message, "Error")
End Try
End Sub

'ポートクローズボタン
Private Sub Button2_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button2.Click
If SerialPort1.IsOpen = True Then
Call SerialPort1.Close()
End If
End Sub

'データ送信
Private Sub Button3_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button3.Click
Dim strSend As String
If TextBox2.Text.Length = 0 Then
MessageBox.Show("送信文字列を入力してください", "Error")
TextBox2.Focus()
Exit Sub
End If

strSend = TextBox2.Text
'送信文字をセット
strSend = TextBox2.Text
If Not strSend.EndsWith(Chr(13) + Chr(10)) Then
strSend += Chr(13) + Chr(10)
End If

Try
'送信文字ををByte配列に格納
Dim byteArray() As Byte = encSjis.GetBytes(strSend)

SerialPort1.Write(byteArray, 0, byteArray.Length)
Catch ex As Exception
MessageBox.Show(ex.Message, "Error")
End Try
End Sub


Delegate Sub dlgAddText(ByVal str As String)
Private Sub AddText(ByVal strGetText As String)
'受信データーをテキストボックスに追加する
TextBox3.AppendText(strGetText)
'キャレットを文字の最後に設定す
TextBox3.ScrollToCaret()
End Sub


'---------------------------------
'セリアルポートの受信イベント
'---------------------------------
Dim lByte As List(Of Byte) = New List(Of Byte)
Private Sub SerialPort1_DataReceived(ByVal sender As Object, _
ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) _
Handles SerialPort1.DataReceived
'データ受信用のバイト配列
Dim inByte(SerialPort1.BytesToRead - 1) As Byte
'データの読み込み
SerialPort1.Read(inByte, 0, SerialPort1.BytesToRead)
    Dim dataLength as Integer = SerialPort1.BytesToRead
Dim inByte(dataLength - 1) As Byte
SerialPort1.Read(inByte, 0, dataLength)

'読み込んだデータを改行を確認しながらListに移す
For i As Integer = 0 To inByte.Length - 1

lByte.Add(inByte(i)) 'Listに足しこむ

'改行が有った

If lByte.Count > 2 AndAlso
lByte(lByte.Count - 2) = 13 AndAlso _
lByte(lByte.Count - 1) = 10 Then

'ListからByte配列に移す
Dim tmpByte(lByte.Count - 1) As Byte
For j As Integer = 0 To lByte.Count - 1
tmpByte(j) = lByte(j)
Next

lByte.Clear() 'Listをクリアー

'Shift-Jisからユニコードに変換
Dim uniByte() As Byte = Encoding.Convert(encSjis, encUni, tmpByte)
'Unicodeの配列から文字列に変換
Dim uniString As String = encUni.GetString(uniByte)

'メインスレッドのテキストに追加する
Me.Invoke(New dlgAddText(AddressOf AddText), New _
Object() {uniString})
'上は下の記述の短縮形
' dlgAddText dlgT = new dlgAddText(AddressOf AddText);
' Me.Invoke(dlgT, uniString)
End If
Next
End Sub

'受信ボックスのクリア
Private Sub Button4_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button4.Click
TextBox3.Clear()
End Sub

End Class

注意
上のコードの中でC#、VB2005ともにSerialPort1_DataReceivedイベントの中で
2回SerialPort1.BytesToReadを使って受信データーの長さを取得していましたが、
この方法はバグを生む可能性があります。
変数を設定して長さの読み込みは1回として下さい。
上のコードは取り消し線で訂正しています。
RS-232Cや他の通信の時に間違いやすいポイントなのでブログに書きました。
RS-232Cの性質をよく表わしていますので一読の価値はあると思います。
ブログはこちら
をご覧ください。


画像 上記C#コードのダウンロード
画像 上記VB2005のコードのダウンロード

ここに上げたコードは、MSDNの解説と合わせてお読みください。