VB初心者です。
Win2k VB.NETでシリアル通信のプログラムを作っています。
http://support.microsoft.com/default.aspx?scid=kb;ja;823179
を参考にしてCOMポートの開閉はできたのですが、
データの受信の方法が分かりません。
外部機器(A&D製の電子天秤EW-1500i)から送信ボタンを
押すとデータが送られてくるはずなのですが、受け取る
プログラムの作り方がわからないのです。
送信が押されてから、
Success = ReadFile(hSerialPort, Buffer, BytesWritten, BytesRead, IntPtr.Zero)
を呼べばよさそうな気がするのですが、タイミングが分かりません。
(そもそも仮定があってるかどうかも分かりません)
サンプルコードの書き込みとパラレルの部分を眠らせて実行してみたのですが、
データの読み取りから先に行かないみたいです。
ちなみに232cから変換ケーブルを使ってUSBで接続しています。
よろしくお願いします。
その後、試行錯誤してみたのですが、だめでした。
内容はタイマーでそのつど
Success = ReadFile(hSerialPort, Buffer, BytesWritten, BytesRead, IntPtr.Zero)
を呼んで、Bufferか、BytesReadのどちらかに値が入る、か、SuccessがTrueになるか、をみたのですが
外部機器から送信しても反応しませんでした。
シリアルインターフェイスのカードリーダ等でも同じだと思うので何かご存知でしたらお願いします。
前回説明不足でしたが、ReadFileは
Public Declare Auto Function ReadFile Lib "kernel32.dll" (ByVal hFile As IntPtr, _
ByVal lpBuffer As Byte(), ByVal nNumberOfBytesToRead As Int32, _
ByRef lpNumberOfBytesRead As Int32, ByVal lpOverlapped As IntPtr) As Boolean
です。
MSCommオブジェクト使うとかってだめかな?
kaiさん、ありがとうございます。
>MSCommオブジェクト使うとかってだめかな?
MSCommってVB6にあったやつですよね?環境が.NETしかないためできないのですよ。
ちなみにMSCommの場合は、常に受信バッファが開かれていて?そこにCOMからのデータをためておいて、一定のバイト数になったら変数に格納ってことでいいのでしょうか?
今度は、
Success = ReadFile(hSerialPort, Buffer, BytesWritten, BytesRead, IntPtr.Zero)
Do While BytesRead = 0
System.Windows.Forms.Application.DoEvents()
Loop
として、Loopを抜けたらメッセージボックスを出すようにしてデータを送ってみたのですが、
やはりだめでした。
別に受信バッファみたいなものを作らないとだめなのでしょうか?
また何か気づいたことなどあったらお願いします。
まず・・・
電子天秤とのRS-232C接続で、データが送信されているのかを
確認されましたか?
メーカーのサイトでは、無償でダウンロード可能なソフトが
配布されています。それではOKか否か・・・
またハイパーターミナルとかで受信してみるとか・・・
特殊なコマンドを送信しないとデータが送信されてこないと
言うようなものではないようなので、ハイパーターミナルを
受信待ち状態にしておき、電子天秤の計測を行った際にデー
タが飛び込んでくるか・・・
間違いなくデータを受信できているようであれば、後はソフ
トの問題です。
.NET でのRS-232C通信ソフトはいろいろ出ていますので、そ
れらを流用されるか、今のソフトに問題があるので、受信で
きるまでいろいろ頑張ってもらうか・・・
部分的なコードのみでは、それら受信ができないという問題
には、そうそう答えられませんので・・・
ご検討下さい。
以上。
GotDotNetのサンプルでテストできそうなのがありますヨ!
ご参考までに・・・
※ COMポートの選択を、追加すれば、USB変換アダプタでの
COM3以降のポートでも可能です。
以上。
岡田 之仁さん、ありがとうございます。
>メーカーのサイトでは、無償でダウンロード可能なソフトが配布されています。そ>れではOKか否か・・・
一応、参考用にダウンロードしてあるのですが、やはり自分で作りたいのです。
>またハイパーターミナルとかで受信してみるとか・・
ハイパーターミナルで接続状態にしておき、電子天秤から送信ボタンを押すと受信できました。
ちなみにハイパーターミナルの設定は、
ビット/秒:2400
データビット:7
パリティ:なし
ストップビット:1
フロー制御:Xon/Xoff
>部分的なコードのみでは、それら受信ができないという問題
>には、そうそう答えられませんので・・・
どうもすみませんでした。まず、受信の考え方?を理解してからコードをいじっていこうと思っていたもので・・・
とりあえず現状のコードを載せます。
Module Module1
Public Structure DCB
Public DCBlength As Int32
Public BaudRate As Int32
Public fBitFields As Int32 'Win32API.Txt のコメントを参照してください。
Public wReserved As Int16
Public XonLim As Int16
Public XoffLim As Int16
Public ByteSize As Byte
Public Parity As Byte
Public StopBits As Byte
Public XonChar As Byte
Public XoffChar As Byte
Public ErrorChar As Byte
Public EofChar As Byte
Public EvtChar As Byte
Public wReserved1 As Int16 '予約されています。使用しないでください。
End Structure
Public Structure COMMTIMEOUTS
Public ReadIntervalTimeout As Int32
Public ReadTotalTimeoutMultiplier As Int32
Public ReadTotalTimeoutConstant As Int32
Public WriteTotalTimeoutMultiplier As Int32
Public WriteTotalTimeoutConstant As Int32
End Structure
Public Const GENERIC_READ As Int32 = &H80000000
Public Const GENERIC_WRITE As Int32 = &H40000000
Public Const OPEN_EXISTING As Int32 = 3
Public Const FILE_ATTRIBUTE_NORMAL As Int32 = &H80
Public Const NOPARITY As Int32 = 0
Public Const ONESTOPBIT As Int32 = 0
Public Declare Auto Function CreateFile Lib "kernel32.dll" _
(ByVal lpFileName As String, ByVal dwDesiredAccess As Int32, _
ByVal dwShareMode As Int32, ByVal lpSecurityAttributes As IntPtr, _
ByVal dwCreationDisposition As Int32, ByVal dwFlagsAndAttributes As Int32, _
ByVal hTemplateFile As IntPtr) As IntPtr
Public Declare Auto Function GetCommState Lib "kernel32.dll" (ByVal nCid As IntPtr, _
ByRef lpDCB As DCB) As Boolean
Public Declare Auto Function SetCommState Lib "kernel32.dll" (ByVal nCid As IntPtr, _
ByRef lpDCB As DCB) As Boolean
Public Declare Auto Function GetCommTimeouts Lib "kernel32.dll" (ByVal hFile As IntPtr, _
ByRef lpCommTimeouts As COMMTIMEOUTS) As Boolean
Public Declare Auto Function SetCommTimeouts Lib "kernel32.dll" (ByVal hFile As IntPtr, _
ByRef lpCommTimeouts As COMMTIMEOUTS) As Boolean
Public Declare Auto Function WriteFile Lib "kernel32.dll" (ByVal hFile As IntPtr, _
ByVal lpBuffer As Byte(), ByVal nNumberOfBytesToWrite As Int32, _
ByRef lpNumberOfBytesWritten As Int32, ByVal lpOverlapped As IntPtr) As Boolean
Public Declare Auto Function ReadFile Lib "kernel32.dll" (ByVal hFile As IntPtr, _
ByVal lpBuffer As Byte(), ByVal nNumberOfBytesToRead As Int32, _
ByRef lpNumberOfBytesRead As Int32, ByVal lpOverlapped As IntPtr) As Boolean
Public Declare Auto Function CloseHandle Lib "kernel32.dll" (ByVal hObject As IntPtr) As Boolean
Public Class clsCashdeskCtrl
Private Success As Boolean
Private Buffer() As Byte
'Private Buffer As Object
Private hSerialPort As IntPtr
Private BytesWritten, BytesRead As Int32
Public Sub COMPortOpen2()
Dim MyDCB As DCB
Dim MyCommTimeouts As COMMTIMEOUTS
'ポートのオープン(多分・・・)
hSerialPort = CreateFile("COM3", GENERIC_READ Or GENERIC_WRITE, 0, IntPtr.Zero, _
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero)
'シリアル通信の設定
MyDCB.BaudRate = 2400
MyDCB.ByteSize = 7
MyDCB.Parity = NOPARITY
MyDCB.StopBits = ONESTOPBIT
' MyDCB のプロパティを基に COM3 を再構成します。
Success = SetCommState(hSerialPort, MyDCB)
'タイムアウトの設定
MyCommTimeouts.ReadIntervalTimeout = 0
MyCommTimeouts.ReadTotalTimeoutConstant = 0
MyCommTimeouts.ReadTotalTimeoutMultiplier = 0
MyCommTimeouts.WriteTotalTimeoutConstant = 0
MyCommTimeouts.WriteTotalTimeoutMultiplier = 0
' MyCommTimeouts のプロパティを基に、タイムアウトの設定を再構成します。
Success = SetCommTimeouts(hSerialPort, MyCommTimeouts)
BytesWritten = 100
End Sub
Public Sub receive() 'ポートからのデータの読み取り
Success = ReadFile(hSerialPort, Buffer, BytesWritten, BytesRead, IntPtr.Zero)
Do While BytesRead = 0
System.Windows.Forms.Application.DoEvents()
Loop
MsgBox("read")
End Sub
Protected Overrides Sub Finalize()
'ポートへのハンドルの解放
Success = CloseHandle(hSerialPort)
MyBase.Finalize()
End Sub
End Class
Public Class flmMain
Inherits System.Windows.Forms.Form
Private cCashdesk As clsCashdeskCtrl
Private Sub flmMain_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
cCashdesk = New clsCashdeskCtrl
Call cCashdesk.COMPortOpen2()
End Sub
Private Sub btnTest_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnTest.Click
Call cCashdesk.receive()
End Sub
の様にフォームロード時にポートを開き、ボタンを押して受信待ち受け状態にしようとしています。
お手数かけて申し訳ありませんが、何か気づいたことがあったらまたお願いします。
GotDotNetのサンプルは最初にダウンロードしてあったのですが英語のサイトだったので他を探しに行ってしまいました。これから見てみます。
すみません。失礼します。
>MSCommってVB6にあったやつですよね?環境が.NETしかないためできないのですよ。
Mscomm使えないんですか?コンポーネント追加すればOKだと思うのですが…。
↓ここにも書いてあるし…
http://www.picfun.com/serialframe.html
すみません。余計なことだったら、見なかったことにしてください。
kiyoさん、ありがとうございます。
>Mscomm使えないんですか?コンポーネント追加すればOKだと思うのですが…。
VB6がないため、コンポーネントがないのですよ。
でも、MSCommの受信を参考にしてDo〜Loopを使ってみたのですが、考え方としてあっているのでしょうか?
また何か気づいたことがあったら、よろしくお願いします。
GotDotNetのサンプルは、試してみているのですがまだうまく使えていません。
これからまた、試してみます。
>MSCommの受信を参考にしてDo〜Loopを使ってみたのですが、考え方としてあっているのでしょうか?
あまり詳しくはありませんが、ReadFileの説明で、
「hFile パラメータが、FILE_FLAG_OVERLAPPED フラグを指定せずに開かれたハンドルを指していて、
lpOverlapped パラメータで NULL を指定した場合、ファイルポインタの現在の位置からファイルの
同期読み取りが開始され、ReadFile は、読み取りが完了すると制御を返します。 」となっています。
一方 CreateFileはdwFlagsAndAttributesにFILE_ATTRIBUTE_NORMALが
設定されていますので、ReadFileは同期読み取りが開始されおり、COMポートからの読み込みが完了するまで
ここの場所でハングアップしたような感じで待機の状態になるのでは。
従って上に示された「マイクロソフト サポート技術情報」のサンプルコードの様に、ReadFileの後の
Do〜Loopはいらないと思いますが。
やってみないので違っているかも。
ねろさん、ありがとうございます。
今日は忙しくてあまり触れてないのですが、確認できただけ書きます。
>ReadFileは同期読み取りが開始されおり、COMポートからの読み込みが完了するま>でここの場所でハングアップしたような感じで待機の状態になるのでは。
確かに、
Success = ReadFile(hSerialPort, Buffer, BytesWritten, BytesRead, IntPtr.Zero)の
BytesWritten(読み取り対象のバイト数)が0の状態でステップ実行していくとReadFileの行でステップ実行が止まってしまいます。ただ、その状態でいくら送信しても次に行きません。
そこで、100と適当な数値を入れてみたら、今度はSuccessがFalseになってしまいました。
そのためDo〜Loopを置いて、COMからのデータを貯めておくバッファがあると仮定してBytesWrittenより多くなった時に、BytesWrittenの量だけBufferに格納してSuccessにTrueを返す、あるいはBytesReadに読み込んだバイト数が入るのでは、と思ったのですがなんだかダメみたいです。
サンプルコードではBytesWrittenが0なのですが、それでどう受信するかがまだ分からないのです。
GotDotNetのサンプルの方は、やっと受信ができました。
こちらは先にデータを送っておき、RXボタンを押したときに「Bytes to read」の値よりデータが少なかった場合はエラーとデータを表示し、データが多かった場合は値の分だけのデータを表示、という感じでした。ただ、どこからデータを取ってきているのかがまだ分かりません。プログラムを追っていこうとしてるのですが初心者のためなかなかうまくいきません。
こんな状態でなかなか解決できませんが、皆様よろしくお願いします。
>そこで、100と適当な数値を入れてみたら、今度はSuccessがFalseになってしまいました。
Bufferの配列の大きさが決まっていないような。
Private Buffer(200) As Byte とでもしておいたらいかがですか。
ねろさん、ありがとうございます。
>Private Buffer(200) As Byte とでもしておいたらいかがですか。
Private Buffer(200) As ByteとしBytesWrittenに適当な値を入れておき、
ポートを開いた状態で電子秤からデータを送信した後で、
Success = ReadFile(hSerialPort, Buffer, BytesWritten, BytesRead, IntPtr.Zero)
を呼んだところ、BytesWrittenで指定したバイト数以上のデータがある場合は取得することができました。
しかし、BytesWrittenで指定したバイト数以下のデータしかなかった場合に呼んでしまうと、以前のようにReadFileの行で止まってしまいます(関数から制御が返ってこない?)。
1:Commポートからデータが送られてきたことを察知
2:受信バッファに格納されているデータの量を確認して受信
という流れにしたいのです。
MSCommオブジェクトでいうところのCommEvent、InBufferCountのようなこと(実際使ったことがなく調べただけなので間違っているかもしれません)だと思います。
たびたび申し訳ありませんがよろしくお願いします。
>BytesWrittenで指定したバイト数以上のデータがある場合は取得することができました。
>BytesWrittenで指定したバイト数以下のデータしかなかった場合に呼んでしまうと、
>以前のようにReadFileの行で止まってしまいます(関数から制御が返ってこない?)。
そうなるでしょうね。
ReadFileの3番目の引数がBytesWrittenとなっているのは、サンプルコードが書き込みと同じ
長さの読み込みを期待している為で、ReadFileのレファレンスではnNumberOfBytesToRead
となっています。つまりnNumberOfBytesToRead が指定に長さになった時に読み込み完了と
なるのでは。
「A&D製の電子天秤EW-1500i」のデータ-の仕様がわかりませんので何とも言えないのですが、
データーが定型の長さで送られてくるのであれば、nNumberOfBytesToReadをその長さに
データーがデリミタで区切られているのなら、1Byteずつ読み込んでデリミタを検出するとか
そんなことになると思います。
>1:Commポートからデータが送られてきたことを察知
>2:受信バッファに格納されているデータの量を確認して受信
こちらの方は、非同期の読み取り方法でバッファをポーリングする方法になるのですかね。
ただしどこがデーターの区切りかは判りません。
ねろさん、ありがとうございます。
サンプルコードの方は同期式の読み取り方式というこなのですね。
>つまりnNumberOfBytesToRead が指定に長さになった時に読み込み完了と
なるのでは。
僕もそう思っていたので
1:先に受信ボタンを押し、ReadFileの行で待ち受け状態にしておいて、
2:秤の方からデータを送り、
3:SuccessをTrue
として次の処理に行かせたいのですが、読み込み完了と判断してくれないのかReadFileの行で止まったままなのです。
なにか他に設定等があるのでしょうか?
ちなみに、電子秤のデータはターミネータとしてのCR,LFを含めて17文字でくるようです。なので、BytesWrittenを17と設定しています。
とりあえず上記の方法と、非同期の方法を両方調べながらやってみます。
よろしくお願いします。
GotDotNetのサンプルの方ではデータがない状態で受信ボタンを押しても、lpNumberOfBytesReadには0が入って返ってきてました。そのため、ちょっと変更してDo〜LoopでlpNumberOfBytesReadをみてループを抜けて次の処理、ということができました。
<DllImport("kernel32.dll")> Private Shared Function CreateFile(<MarshalAs(UnmanagedType.LPStr)> ByVal lpFileName As String, ByVal dwDesiredAccess As Integer, ByVal dwShareMode As Integer, ByVal lpSecurityAttributes As Integer, ByVal dwCreationDisposition As Integer, ByVal dwFlagsAndAttributes As Integer, ByVal hTemplateFile As Integer) As Integer
End Function
<DllImport("kernel32.dll")> Private Shared Function ReadFile(ByVal hFile As Integer, ByVal Buffer As Byte(), ByVal nNumberOfBytesToRead As Integer, ByRef lpNumberOfBytesRead As Integer, ByRef lpOverlapped As OVERLAPPED) As Integer
End Function
<StructLayout(LayoutKind.Sequential, Pack:=1)> Public Structure OVERLAPPED
Public Internal As Int32
Public InternalHigh As Int32
Public Offset As Int32
Public OffsetHigh As Int32
Public hEvent As Int32
End Structure
これがそのCreateFile、ReadFile、OVERLAPPED構造体です。
とりあえずまねしてOVERLAPPED構造体をつくったのですが、「StructLayoutが定義されてません」といわれてしまい、よく分からなかったので、<StructLayout(LayoutKind.Sequential, Pack:=1)> をはずして作りました。
hSerialPort = CreateFile("COM3", GENERIC_READ Or GENERIC_WRITE, 0, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero)
のFILE_ATTRIBUTE_NORMALの部分はGotDotNetでは変数の中が0になっていたので直接0に置き換えてあります。
自分で気付いた相違点はこのくらいだったので実行してみましたが、やはりReadFileのところで返ってきませんでした。
非常に分かりづらいとは思いますが何か気づいたことがありましたらよろしくお願いします。
>ReadFileの行で止まったまま
>やはりReadFileのところで返ってきません
受信タイムアウトの設定がなされていないからでは?
こんな感じでどうでしょうか?
MyCommTimeouts.ReadIntervalTimeout = 500
MyCommTimeouts.ReadTotalTimeoutConstant = 0
MyCommTimeouts.ReadTotalTimeoutMultiplier = 500
MyCommTimeouts.WriteTotalTimeoutConstant = 0
MyCommTimeouts.WriteTotalTimeoutMultiplier = 500
Success = SetCommTimeouts(hSerialPort, MyCommTimeouts)
ぴろあきさん、ありがとうございます。
ご指摘のとおりでした。
つまり、条件(データの量)を満たしている場合はすぐに返ってくるが、満たしていない場合はタイムアウトで設定した時間を経過したら返ってくる、といった感じなのですね。
ただ最後にひとつ疑問なのが、いままでの「ReadFileの行で止まったまま」の状態で、データを送り続けて条件を満たしても返ってこない、という事です。
とりあえず無事解決できました。
今回質問に答えていただきました、kaiさん、岡田 之仁さん、kiyoさん、ねろさん、ぴろあきさん、本当にありがとうございました。
また質問することがあると思いますが、その時もよろしくお願いします。
うーむ...
Timeoutで抜けるのはエラー処理に近いんですが、ReadFileから抜けた時にCRLFを含めた
期待どおりのDataが得られるているのでしょうか?
設定で
MyDCB.ByteSize = 7
となっているのは電子天秤の仕様なんですかね、ちょっと珍しい気がしますが。
MyDCB.BaudRate = 9600
MyDCB.ByteSize = 8
にしてみるとか。
解決との事でなによりです。
>最後にひとつ疑問
私が答えるよりも、詳しそうな人の言葉をそのままお借りしました。
要約すれば、
同期I/Oでは、ReadFile関数はそのように動作する関数であるようです。
<以下抜粋>
同期I/O:
このモードで設定されている場合、ReadFile()を呼び出すと指定したサイズの受信が完了するまで戻りません。
従って、関数から直ちに戻る必要がある場合は、現在の受信バッファ中のデータ数を調べ、ReadFile()の引数にそれ以下を
指定するか、又はタイマー設定を、すぐ戻る設定にしなければなりません。この方式の用途は、比較的単純なデータ受信に向
きます。
CreateFile()の引数dwFlagsAndAttributesをNULLに設定すると、ReadFile()は同期I/Oとして動作します。
<以上抜粋>
それと、タイムアウトの設定ですが、
先のはただの例ですので、
実際にはボーレートから、タイムアウト値を計算してくださいね。
下記URLに、タイムアウト値の計算の方法や、
上で抜粋した記事が載っていますので、参考にしてください。
参考URL:
http://members.jcom.home.ne.jp/0434383301/vc10.htm
う。。。
上の投稿は忘れてください。
>最後の疑問
ねろさんが既に述べていますが、
CreateFileが非同期モードであるのに、
ReadFileが同期モードで呼ばれている事が問題のような気が。。。
ねろさん、ありがとうございます。
とりあえず動作したので解決としてしまいましたが、まだ問題が残っていそうなのですね。
通信設定はよく分からなかったので、電子天秤の初期設定が、
ボーレート2400、ビット長7ビットeven
だったので、それに合わせて作ってました。
変更可能なので、ボーレート9600、ビット長8ビットパリティなし
に変更して試してみました。
一応、結果はどちらも同じようです。
こんな感じでループを組んでいるのですが、
Do While BytesRead <> 17
Success = ReadFile(hSerialPort, Buffer, BytesWritten, BytesRead, Nothing)
System.Windows.Forms.Application.DoEvents()
Loop
Dim sTemp As String = System.Text.Encoding.GetEncoding(932).GetString(Buffer)
変数"sTemp"をウォッチ式で確認してみたところ、
"ST,+000000.0 g♪о"
と、一応17文字ありました。
ねろさんの言われた「ボーレート9600、ビット長8ビットパリティなし」のほうが一般的みたいなのでその設定にします。
自分では正確な判断がつけられない事が多いので、また何か気付いたことがありましたらお願いします。
>"ST,+000000.0 g♪о"
CSVで送ってきている。
"ST,+000000.0 g♪"ここまでは多分正しいんでしょう。
gの前の空白は"k"が入るスペースですかね。
"♪"はChr(13)でCR,"о"は変です、"o"はChr(9)、実際はLFでChr(10)のはずですね。
最後の文字がキャラ化けすると言うことは、てStopbitが読めないのでしょうか。
変ですね。
ぴろあきさんありがとうございます。
ねろさんへの返答時にページの更新しなかったため気付きませんでした。すみません。
>ReadFileが同期モードで呼ばれている事
って事は、転送終了を何らかの形で秤から送らなければならない、って事でそれがこないためにReadFileで止まったまま、なのでしょうか?
あとは、統一すべきですよね。
実は同期と非同期がまだよく分かっていないのです。漠然と理解している(つもり?)事は同期はackやnakを使っての応答の後にデータを送信、非同期はデータを流しっぱなし、な程度なので調べ直しているところです。
あと、参考になるホームページを教えていただきありがとうございます。昨日の午後から忙しかったため手をつけられずにいたので、これからちゃんと見たいと思います。
ねろさん、ありがとうございます。
"ST,+000000.0 g♪о"
の後ろ2文字はウォッチ式からのコピーができなかったため自分で勝手につけてしまったものです。形が似てたのですが違ったみたいです。
実際に電子秤からバイト型で受け取ったデータは「LFでChr(10)」でした。
混乱させるような誤情報で本当に申し訳ありませんでした。
gの前の空白は、1つ目が数値と単位の区切りの空白で、2つ目が、単位を切り替えることができて2文字使う単位があるためそれを使うときのためのものです。
今後は正確な情報を書くように心掛けるのでよろしくお願いします。
ツイート | ![]() |