機器との通信でMSCommコントロールを使用しています。
プログラムの内容は、機器が持っているID(値)を読み出すために
こちらからコマンドを送り、機器が返す値を受けるものです。
この値はズラズラと返って来るのですが、機器によって、桁数(文字数)や
その時間も異なります。
そのため、InBufferCountで一律に規定出来ないと思ったので、以下のように
ある程度時間をおいてから取り込むようにしています。
なお、RThreshold=1にしています。
(1)'受信またはタイムアウトまで待つ
Do While timCommOut.Enabled = True: DoEvents: Loop .....(a)
If bgTimeOut = False Then '受信なら
:
:(その後の処理)
(2)OnComイベントにて
Private Sub MSComm1_OnComm()
Select Case MSComm1.CommEvent
:
Case comEvReceive
timInterval.Enabled = True
bgTimePass = False
Do While bgTimePass = False: DoEvents: Loop .....(b)
sBuf = MSComm1.Input
sgCommGet = sgCommGet & sBuf
:
:(処理)
timCommOut.Enabled = False
bgTimeOut = False
End Select
sgCommGet = ""
End Sub
(3)各タイマー
Private Sub timCommOut_Timer()
timCommOut.Enabled = False
bgTimeOut = True
End Sub
----------
Private Sub timInterval_Timer()
timInterval.Enabled = False
bgTimePass = True
End Sub
これをVB上で走らせると、上記(b)の DoEvents で
「実行時エラー'28':スタック領域が不足しています。」
が出て止まってしまいます。
この状態で、F5を押す(継続する)と無事に最後まで行きます。
この問題を解決する方法、もしくは他の方法が良いよ、とかあればご指導、
アドバイスをいただけると助かります。
よろしくお願いします。
DoEventsを実行すると、その時点でイベントが発生している場合、そちらに処理が移ります。
comEvReceiveイベントは、Inputするまで発生し続けます。
つまり、今回の場合はMSComm1_OnCommが処理途中で何度も呼ばれ、スタック領域を食いつぶすために実行時エラー28が発生しています。
このロジックのままいくなら、受信データは内部バッファに蓄えながら(Inputして受信バッファをクリアしつつ)タイムアウトを待つ方向になるでしょうか。
ただ、普通はこのような時間で受信完了という手順は行いません。機器側の仕様に送信完了の目印(EOF等)は無いのでしょうか。普通はこの目印を1回の受信完了として処理します。
最近MSComm使用してないので、空想のレベルですが
OnCommイベントが発生する間隔を算出する様にして
データの区切りを判断してはどうだろう?
例えば100ms以上間隔が開いたらデータ区切りとか。
ただ、T★Mさんが言われている様に普通は終了を
判断する条件があるような気がしますよ。
>そのため、InBufferCountで一律に規定出来ないと思ったので、以下のように
>ある程度時間をおいてから取り込むようにしています。
なぜ? 何か勘違いがあるのでは。
>この問題を解決する方法、もしくは他の方法が良いよ、とかあればご指導、
>アドバイスをいただけると助かります。
簡単な仕様の説明が必要ですね。
たとえば機器に対してIDの送信要求を出してから応答が返るまでのタイムアウト設定
が必要とか、IDを受け取った後はただ表示するだけなのか、IDを受け取って次の
動作に移る必要があるのかなどです。
早々のアドバイスありがとうございます。
皆さんのご指摘のとおり、通信仕様の問題とも言えるのですが、
機器からの応答は、テキストで数行分です。区切りは<CR>ですが、
送信完了の目印はありません。
機器やコマンドにより、この行数や文字数が変わります。
次に本プログラムの仕様ですが、返ってきた値が正しいかどうかの
判断をするものです。これは併せて機器側のハードを検査する目的
も兼ねています。
ハードがNGの場合もあるので、レスポンス完了までにタイムアウトを
設けています。その値はある程度サンプルを採った後のおおよその値
としています。
今のプログラムは受信しつつの解析・判断ではなくて、受信完了後に
まとめて処理するようにしています。
最初の文中の(1)に書いた(その後の処理)のところで、返ってきた
文字の中から、必要な部分を抜き取りその値が正しいかどうかを
判断しています。
そのため受信完了を待つためにLoopを入れていました。
今回この部分がよろしくないようなので、他の手段を含め、皆さんに
相談させていただいた次第です。
自分でも改めて考えてみますが、再度アドバイスございましたら
よろしくお願いします。
>ハードがNGの場合もあるので、レスポンス完了までにタイムアウトを
>設けています。
正確には機器がID送信要求を受けて、IDを送信し終わるまでの時間ですね。
機器側で測定する訳にはいかない場合は、パソコン側から送信要求を出して
IDを受信するまでの時間にタイムアウトを設定することになります。
更に、受信データーの終わりがフォーマット的に判断不能ですから、
タイマーの設定時間内に正しいIDが送られて来るか否かの判断になります。
大まかのフローは、こんなところですか。
1、送信要求
2、タイムアウトのタイマースタート
3、受信
4、タイムアップ
5、受信データーの検査
まあ、機器にもよりますが、数行のIDを送り返すだけなら、タイマーの時間は
500msも有れば十分と思われます。
これであれば、受信のところにタイマーはいらないと思います。
sBuf = MSComm1.Input
sgCommGet = sgCommGet & sBuf
これだけでOKで、時間内に送られたデーターは文字列に足しこまれます。
実は今初めてまともにソースを見たんですが、(おぃ)
OnComm内のタイマを全部取っ払って下記の様にしたら
だめなのかな?※細かいエラー処理は手抜きしました。
タイマー設定(レスポンス用)
Do
レスポンスタイムアウトしたら終了
データを1バイト以上受信したら終了
Loop
タイマー停止
if データを1バイト以上受信 Then
タイマー再設定(受信終了用)
Do
タイムアウトしたら終了
Loop
タイマー停止
受信データ解析
End If
ありがとうございます。
機器からの返りは数行と書きましたが、最大27行(992文字)です。
OnComm内のタイマーが無い場合は頭の20文字分くらいしか受け取れなかった
のでタイマーを入れるようにしました。
我龍院さんの書いてくださったフローを使わせてもらうと
1、送信要求
2、タイムアウトのタイマースタート
3、全て受信またはタイムアップ
(受信ならタイムアウトタイマーも止める)
4、受信ならばデータの検査
になります。
上記3の「全て受信(1回のOnComm内で全てを受信して、
タイムアウトタイマーも止める)」に無理があるのでしょうか。
確かに皆さんのアドバイスのとおり、OnComm内の待ちTimerは無くして
タイムアウトTimerだけでやる方が、Timerコントロールも1個で済みますし
良いのかもしれません。
試してみたいと思います。
>上記3の「全て受信(1回のOnComm内で全てを受信して、
>タイムアウトタイマーも止める)」に無理があるのでしょうか。
無理があります。
RThresholdを1にしてますので、受信文字が1文字以上でComイベント
が発生します。従って、取れるデーターの数はボーレートとマシンスピード、
その他のタスク等に依存して何文字受信出来るのかは不確定です。
無理やりLoopを回して受信する方法は無くも有りませんが、
そのようにしなければならない理由が見つかりません。
普通にOnCommイベントが発生して自動的にバッファから読み取る
構造にしておいた方が柔軟性が有ります。
その辺に何か勘違いされている気がしてならないのですが。
OnComm内でループさせてしまうと、せっかくのOnComm
イベントが無意味になりません?
OnComm内でLoopさせてしまうと下記と大差ないような
気が・・・。
タイマー設定(レスポンス用)
Do
タイムアウトしたら終了
If MSComm1.InBufferCount > 0 Then
sgCommGet = sgCommGet & MSComm1.Input
End If
Loop
タイマー停止
If 受信 Then ・・・・
先にも書きましたが、当初全部の文字を受け取ることが出来なかった時に、
ブレークポイントで止めたら受けられたので、単純にTimerを追加してみた
のでした。
皆さんのアドバイスに従って、作り直してみます。
また、何かありましたら質問するかもしれませんが、その時はよろしく
お願いします。
ありがとうございました。m(__)m
ツイート | ![]() |