※旧ロリポブログ サービス終了に伴い転載しました。
※内容が古く、間違いを多く含んでいる可能性があります。
本記事は.NET FrameWork1.1限定となります
.NET FrameWork2.0以降では SerialPortクラスを使います。
http://msdn.microsoft.com/ja-jp/library/system.io.ports.serialport(v=vs.110).aspx
自分が携わったシステムの一部で予備品倉庫のドアロックを開閉したり、 出庫履歴を取るアプリケーションを作る事になったので QRコードスキャナや外部出力装置を扱う必要が出てきた。 今回使った装置はGT10Q-SU 及び、PHC-100。
この2つを取り扱うにはRS232Cポートを制御する必要があるのだが、思ったよりネット上に資料がない。スキャナの方は、受信してテキストに落とすソフトがベンダーから出ているが、それを使うのはパフォーマンス的な問題もあるし、何よりも気に入らない。
色々調べてみると、選択肢として
[1] VB6のMSCommコントロールを使用する
http://www.picfun.com/serialframe.html
[2]APIを使って自分で制御 [HOWTO]Microsoft Visual Basic .NET を使用してシリアル ポートとパラレル ポートにアクセスする方法
http://support.microsoft.com/kb/823179/ja
この2点がある事が分かった。選択するにあたって [1]はVB6無い、ActiveXコントロールの配布に一癖ありそうな予感がする、 という事で、難しそうだが[2]で挑戦する事に。
ところがこの[2]のサンプルコード、自分のようなレベルの低い人間にとっては応用しづらいものになっており、ここ理解出来るのはWrite(出力)のみ、受信に関しては疑問が多い。
疑問点は
1. 受信されたのを知るにはどうしたらいいのか(ずっと受信する必要があるのか?)
2. 受信データのバイト数を何処で知ったらいいのか
この2点が上記サンプルだけでは全く分からないのです。しかし、これらを一挙に解決する手段はここにあった。
http://members.jcom.home.ne.jp/0434383301/vc10.htm
WindowsでRS232Cを使う
VCで書いてあるが、かなり参考になった。 ここによると、データを受信する方法の一つに「イベント駆動I/O」てのがある。これは、SetCommMaskで監視したいイベントを指定し、あとはWaitCommEventでOSに監視を任せる、というものらしい。
そして、イベントをキャッチした際にClearCommErorを呼び出せば、帰ってきたCOMSTAT構造体のメンバ(cbInQue)で受信したデータのサイズを知る事が出来る訳です。
ここで具体的にどうしたか紹介してみる。
上記[2]のサンプルコードは使わず、代わりに、マネージコードとして使いやすくクラスにまとめてある
http://www.gotdotnet.com/Community/UserSamples/Details.aspx?SampleGuid=7a677255-dd17-4105-9801-949243916763
Serial comunication (RS232) using VB.Net
の中にあるCRs232.vbを利用する事にした。 このクラスは受信用のスレッドを新しくたてて、受信が完了したらクラスからイベント(DataReceived)を発生させる仕組みなのだが、受信データのサイズがあらかじめ判ってる場合のみにしか使えない。
これから作ろうとしてるのは、いろんなQRコードを読むため、受信データサイズが一定ではなく、やっぱり改造しなければならない。
以下が改造ポイント
■COMSTAT構造体の定義を追加
Public Structure COMSTAT Public fBitFields As Int32 Public cbInQue As Int32 Public cbOutQue As Int32 End Structure
■クラスのメンバ変数でCOMSTAT構造体を追加
Private muComStat As COMSTAT
■API関数の宣言を追加
(これは、イベントのシグナル状態をリセットする時に使われる)
<DllImport("kernel32.dll")>Private Shared Function ResetEvent(ByVal hEvent As Int32) As Int32 End Function
■受信監視スレッドを改造
Private Sub pHandleOverlappedRead(ByVal Bytes2Read As Int32) Dim iReadChars, iRc, iRes, iLastErr As Int32 Dim nErrorMask As Int32 muOverlapped.hEvent = CreateEvent(Nothing, 1, 0, Nothing) If muOverlapped.hEvent = 0 Then '// Can't create event Throw New ApplicationException("Error creating event for overlapped read.") End If If Me.SetCommMask(mhRS, Me.EventMasks.RxChar) = 0 Then Throw New ApplicationException("Missing in SetCommMask") End If WaitCommEvent(mhRS, Me.EventMasks.RxChar, muOverlapped) While Not ThreadExit iRes = WaitForSingleObject(muOverlapped.hEvent, miTimeout) If iRes = WAIT_OBJECT_0 Then ClearCommError(mhRS, nErrorMask, muComStat) If muComStat.cbInQue > 0 Then ResetEvent(muOverlapped.hEvent) ReDim mabtRxBuf(muComStat.cbInQue - 1) iRc = ReadFile(mhRS, mabtRxBuf, muComStat.cbInQue, iReadChars, muOverlapped) RaiseEvent DataReceived(Me, mabtRxBuf) ClearInputBuffer() End If System.Threading.Thread.Sleep(50) ' MsgBox(muComStat.cbInQue & "バイト受信") End If End While End Sub
気づいた方もおられるかも知れないが、上記コードちょっとオカシイ(これ書きながら思い出した)。
本当はWaitCommEventのところでイベントが発生するまで待つ筈なんだが、すぐに制御が帰ってきてしまう。
結局、WaitForSingleObjectでイベント監視をしているのですが、こいつはきちんとイベント発生まで待ってくれる。(Whileループは監視を続けさせるのと、アプリ終了時にスレッドを外部から終了させるため)
WaitCommEventと似たような事をしてる訳だから、要らないかなと思い、WaitCommEventを消すとWaitForSingleObjectでまったくイベントをキャッチしなくなる。 謎多い。。。結局良くわからないまま動いてるのに使っちゃってる訳ですが(内緒)、QRコードスキャナは諸事情で入荷しないし試せない状態。
まぁテスト環境が出来たら調べてみたいと思います。
これらを使うコードは以下のとおり。
Private WithEvents RS232c As New Rs232 Private Sub MyApplicationForm_Load(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles MyBase.Load With RS232c Try .Port = 3 .Timeout = 5000 .BaudRate = 9600 .WorkingMode = Rs232.Mode.Overlapped If Not .IsOpen Then .Open() End If .AsyncRead(1) Catch ex As Exception 'エラー処理を書く End Try End With End Sub受信イベント発生時の処理
Public Sub Received(ByVal Source As Rs232, _ ByVal DataBuffer() As Byte) Handles RS232c.DataReceived Try Dim retStr As String = RS232c.InputStreamString Catch ex As Exception 'エラー処理を書く End Try End Subアプリケーション終了時
Private Sub MyApplication_Closing(ByVal sender As Object, _ ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing RS232c.ThreadExit = True RS232c.Close() End Sub
[参考]
シリアル通信で受信するには?
http://homepage1.nifty.com/MADIA/vb/vb_bbs2/200405/200405_04050031.html
MSDNライブラリ SetCommMask
http://msdn.microsoft.com/ja-jp/library/cc429711.aspx
MSDNライブラリ WaitCommEvent
http://msdn.microsoft.com/ja-jp/library/cc429842.aspx
MSDNライブラリ ClearCommError
http://msdn.microsoft.com/ja-jp/library/cc429181.aspx
※2012.4.11 リンク切れにつき修正
0 件のコメント:
コメントを投稿