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入門」 を参照されたし。
外観
外観は大体こんなこんなものである。
送信
送信ボタンを押すとデータを送信するがその際データの最後に改行が無い場合は
自動で改行を付加する。
送信は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の性質をよく表わしていますので一読の価値はあると思います。
ブログはこちら
をご覧ください。
|