2005/12/17

RS232Cサンプル


2014.3.29追記

※旧ロリポブログ サービス終了に伴い転載しました。
※内容が古く、間違いを多く含んでいる可能性があります。

本記事は.NET FrameWork1.1限定となります 

.NET FrameWork2.0以降では SerialPortクラスを使います。

http://msdn.microsoft.com/ja-jp/library/system.io.ports.serialport(v=vs.110).aspx




以前のエントリー RS232Cポートを扱う では説明不足や不具合もあり
役に立たないと思われるので改めてサンプルを紹介します。
(今回はプロジェクトがダウンロードできるようにしました)

サンプルソフトの動作は、

1.スキャナでQRコードを読み取り

2.配置されたテキストボックスへ読み取りデータを表示

3.リレー出力ユニットで接点No1を0.2秒間だけONさせる

といった形になっていて、ハード構成はQRコードの読み取りに GT10Q-SU 、リレー出力にはPHC-100を使用しています。尚、前者はCOM3、後者はCOM1に接続されているものとします。

また、サンプルとして読み取るQRコードは、”1234567”とだけ書かれた物を利用します。
QRコードのbmpはプロジェクトに収めてありますので
解像度の高いプリンタで印刷してから利用してください。


 ポートの制御は以前紹介したとおり

http://www.gotdotnet.com/Community/UserSamples/Details.aspx?SampleGuid=7a677255-dd17-4105-9801-949243916763
Serial comunication (RS232) using VB.Net

の中にあるCRs232.vbを改造して利用しておりますが、改造内容の説明は省略させて頂きます。

QRコードのデータ受信にはそのままRS232クラスを使っており、アプリケーションロード時に、以下の様にして受信待機させる。
Private PATLITE As New cPatLite
    Private WithEvents RS232c As New Rs232

    Private Sub fmRS232sample_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load
        With RS232c
            .Port = 3
            .Timeout = 5000
            .BaudRate = 9600
            .WorkingMode = Rs232.Mode.Overlapped
            If Not .IsOpen Then
                .Open()
            End If
            .AsyncRead(1)
        End With
    End Sub
信号の出力にはRS232クラスにもう一つかぶせたcPatLiteクラスを作成して制御しています(自部署では、あるドアのロックを解除する為、に利用してるので、DoorOpen()というメソッド名になってます)

パトライトクラス
Public Class cPatLite

    Private myRs232 As New Rs232
    Private bThStarted As Boolean = False

    Public Sub New()
        With myRs232
            .Port = 1
            .BaudRate = 9600
            .DataBit = 8
            .Timeout = 5000
            .Parity = Rs232.DataParity.Parity_None
            .StopBit = Rs232.DataStopBit.StopBit_1
            .WorkingMode = Rs232.Mode.NonOverlapped
        End With
    End Sub

    Public Sub DoorOpen()
        Dim Th As New System.Threading.Thread( _
        AddressOf Me.OpenOplate)
        If bThStarted Then Return
        Th.Start()
    End Sub

    Private Sub OpenOplate()
        bThStarted = True

        With myRs232
            .Open()
            .Dtr = True
            .Rts = True
            .Write("@??101!")

            System.Threading.Thread.Sleep(200)

            .Write("@??001!")
            .Close()
        End With
        bThStarted = False
    End Sub

    Public Sub ALL_Reset()
        With myRs232
            .Open()
            .Dtr = True
            .Rts = True
            .Write("@??0??!")
            .Close()
        End With
    End Sub
End Class
QRコードの受信に関しては前回のエントリーで紹介した内容に不具合があった為修正しています。RS232クラスは別スレッドで受信待機し、受信があったらイベントを発生させるのですが、UIスレッドとは異なる為イベントハンドラの処理内容によっては想定外の動作をする事があります。その為、UIスレッドへマーシャリングする処理を追加しました。
'デリゲート宣言
Private Delegate Sub ReceivedDEL(ByVal Source As Rs232, _
  ByVal Databuffer() As Byte)

Private Sub Received(ByVal Source As Rs232, _
  ByVal DataBuffer() As Byte) Handles RS232c.DataReceived
    If Me.InvokeRequired Then
        Dim d As New ReceivedDEL(AddressOf Received)
        Me.Invoke(d, New Object() {Nothing, Nothing})
    Else
    
        Dim retStr As String = RS232c.InputStreamString
        TextBox1.Text = retStr

        'QRコードから読み取ったデータは末尾に改行コードがあるので削除

        retStr = retStr.Remove(retStr.Length - 1, 1)
        If retStr = "1234567" Then
            'ドア開閉処理
            PATLITE.DoorOpen()
        End If
    End If
End Sub
アプリケーション終了時は受信待機してるスレッドを停止させます
Private Sub fmRS232sample_Closing(ByVal sender As Object, _
  ByVal e As System.ComponentModel.CancelEventArgs) Handles MyBase.Closing
        RS232c.ThreadExit()
        RS232c.Close()
End Sub


■このサンプルを利用したことで発生したいかなる損害も当方は一切責任を負わないものとしますので、了承した上でダウンロードしてください。(2005.10.23)
・ダウンロード RS232sample.zip (299KB)

このサンプルを使って役に立った、又ここが違う等
感想、意見、ご指摘があればコメントいただけると嬉しいです。




2005/12/16

RS232Cポートを扱う

2014.3.29追記

※旧ロリポブログ サービス終了に伴い転載しました。
※内容が古く、間違いを多く含んでいる可能性があります。

本記事は.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 リンク切れにつき修正




ADOXで複数列に主キーを設定する

※旧ロリポブログ サービス終了に伴い転載しました。
(.NET FrameWork1.1)
Imports ADOX
Imports ADOX.DataTypeEnum

'参照設定で  Microsoft ADO Ext. 2.7 for DDL and Security を追加する事
Public Class Class1
    Public Sub CreateDB()
        'mdbファイル作成( E:¥new.mdb )
        Dim cat As New ADOX.Catalog
        cat.Create("Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "data source=e:¥new.mdb;")

        'テーブル追加
        Dim tbl As New Table
        tbl.Name = "MyTable"
        tbl.Columns.Append("ID", adInteger)
        tbl.Columns.Append("CODE", adVarWChar, 10)
        tbl.Columns.Append("NAME", adVarWChar, 10)

        cat.Tables.Append(tbl)

        '一列だけを主キーに設定するならこれでもよい
        '列「ID」を主キーに設定する場合

        'cat.Tables("MyTable").Keys.Append("keyname", KeyTypeEnum.adKeyPrimary, "ID")
        'ID列とCODE列の組み合わせの主キーを設定

        Dim MyKey As New ADOX.Key

        With MyKey
            .Name = "keyname"
            .Type = KeyTypeEnum.adKeyPrimary
            .Columns.Append("ID")
            .Columns.Append("CODE")
        End With

        cat.Tables("MyTable").Keys.Append(MyKey)
    End Sub
End Class

2005/12/01

ADOXで既存のmdbファイルを操作

※旧ロリポブログ サービス終了に伴い転載しました。
(.NET FrameWork1.1)


ADOXを使って既存のmdbファイルの構成を変更する時に

Dim cat As New ADOX.Catalog
cat.ActiveConnection="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=e:¥new.mdb"



このような方法でカタログを開こうとすると

引数が間違った型、許容範囲外、または競合しています。

といったメッセージで例外が発生する。
これを解決するには、let_ActiveConnectionメソッドを使う。

Dim cat As New ADOX.Catalog
cat.let_ActiveConnection("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=e:¥new.mdb;")
'テーブル追加など