VB6.0でバイナリ形式のデータをシリアル通信するツールを作成しています。
受信データは、一般的にSTX,ETXではさまれたデータではなく、コマンドデータと文字列データ(JIS)が混在した形となっていて、コマンドデータ以外は文字列データとして認識しなければなりません。また受信データは1800バイト程のデータが一気に流れてきます。
つまり、
コマンドデータ1(01,02)
コマンドデータ2(03,04)
文字列データ(2129,212A)
だった場合、
01 02 21 29 21 2A 03 04
という形でデータを受信することになります。
1バイトずつ受信してデータを受信しはじめると、「スタック領域が不足しています」とエラーが出ます。どのように対応すれば、正常に受信できるのか教えてください。
なお、受信部分のソースは下記になります。
public m_data() as byte
public m_len as long
Private Sub MSComm_OnComm()
Dim byt() As Byte
'受信データがあれば受信する
If MSComm.InBufferCount <> 0 Then
Do
byt = MSComm.Input
For i = 0 To UBound(byt)
m_data(m_len)= byt(i)
m_len = m_len + 1
Next
Loop Until MSComm.InBufferCount = 0
'受信したバイナリデータをコマンドと文字列に分解する
Call RcvProc()
m_len = 0
endif
end sub
プロパティは
InputLen=0
InputMode=1
となっています。よろしくお願い致します。
すみません、載せたソースに間違いがありました。
(誤)public m_data() as byte
(正)public m_data(15000) as byte
よろしくお願い致します。
お疲れさま。
そのプロトコルだと厳密なデータの終わりは特定できませんよね。
まあ時間的にデーターが途切れたところをデータの終わりとするしかないですね。
それとMSComm_OnComm()の中でLoopを回さないで、
byteData() = MSComm1.Input
If UBound(byteData) <= 0 Then
'データーが無い
Exit Sub
End If
という具合に、バイト配列に読み込みます。
ただこれだと、データの途中までしか読み込まないかも知れませんので、
別途バイト配列を用意してデータを足しこみます。
If UBound(byteTotalData) = 0 Then
'1番最初の処理
ReDim Preserve byteTotalData(UBound(byteTotalData) + UBound(byteData))
Else
ReDim Preserve byteTotalData(UBound(byteTotalData) + UBound(byteData) + 1)
End If
こんな具合に。
>「スタック領域が不足しています」とエラーが出ます
通常スタックオーバーフローは出ませんが、MSComm_OnComm()の中のどこかに
DoEventsが入っていませんか?
我龍院さん、回答ありがとうございます。
ちょっとわからないところがあるので、教えてください。
>ただこれだと、データの途中までしか読み込まないかも知れませんので、
>別途バイト配列を用意してデータを足しこみます。
この処理を実現するのに、
>> For i = 0 To UBound(byt)
>> m_data(m_len)= byt(i)
>> m_len = m_len + 1
>> Next
としていたのですが、教えていただいた
>If UBound(byteTotalData) = 0 Then
> '1番最初の処理
> ReDim Preserve byteTotalData(UBound(byteTotalData) + UBound(byteData))
>Else
> ReDim Preserve byteTotalData(UBound(byteTotalData) + UBound(byteData) + 1)
>End If
とすれば、データをbyteTotalData配列にコピーできるのでしょうか?
#コピー用のエリアが用意されるだけのような気がします。
> DoEventsが入っていませんか?
はい、入っています。
ループ処理の時間がかかってしまって、画面が固まるので入れています。
これが原因でしょうか?
申し訳ありませんが教えてください。
よろしくお願いします。
データは普通にコピーですね。
For n = 0 To UBound(byteData)
byteTotalData(lngPointer) = byteData(n)
lngPointer = lngPointer + 1
Next
lngPointerは次の位置を保持しますので、グローバルか、Staticで宣言してください。
>これが原因でしょうか?
です。
MSComm_OnCommの中からこのルーチンが再帰的に何度か呼び出されますので、
スタックのオーバーフローが生じます。
(実際は再帰ではないのですが)
通常外からかかってくる割り込みの中にDoEventsを入れてはいけません。
これはボーレートはどのくらいですか?
ちょっと私の回答はおかしいですね。
これじゃあ提示されたコードと殆ど同じですね。
VB.NETの場合はマルチスレッドなので問題は無いのですが、
VB6の場合で受信データが長い場合は、イベントでは無く
ポーリングでデータを受け取った方がいいですね。
何度も回答ありがとうございます。
Setting:19200,n,8,1
としています。
申し訳ありませんが、ポーリングのデータ受信をやったことがないので、やり方がよくわかりません。
#タイマでイベント発生するたびにデータを受信すればいいのでしょうか?
それともこれぐらいのボーレートだと、今のままのやり方で大丈夫でしょうか。
1800*8=14400ビットですから19200ボーだと受信は多分1秒かかりませんよね。
ポーリングは簡単ですが、今のままでやることを考えて見ましょう。
先ずどこで時間がかかっているのでしょうか?
DoEventsをどこに入れていますか?
データのコピーはそんなに時間がかかる処理とは思えないのですが、
やはりRcvProc()ですか?
それともう一度提示されたコードを良く見ると、
Do
Loop Until MSComm.InBufferCount = 0
このループはおかしいですね、このループを取ってしまって、
byt = MSComm.Input
の前で十分受信が出来る時間の(マシンスペックにもよりますが、1.5秒位かな)
TimerかけてそのTimerの中でCall RcvProc()をやったらいかがでしょうか。
1.5秒位なら経験的に、待てる時間ですよね。
我龍院さん、アドバイスありがとうございます。
>先ずどこで時間がかかっているのでしょうか?
>DoEventsをどこに入れていますか?
DoEventsは
Do〜Loop
内に入れていました(Loopの直前)。
時間がかかっているのも、この処理部分のようです。
以前の我龍院さんのアドバイスから、今はDoEventsをはずしています。
そうした場合、画面が少し固まってしまいます。
脱線してしまって申し訳ないのですが、教えていただきたいことがあります。
>このループはおかしいですね、このループを取ってしまって、
とありますが、
設定をInputLen=0としていて、InBufferCountで1500となったとしても
>byt = MSComm.Input
上記Inputでは、1バイトずつしか受信できません。
バッファにたまっているデータ全てを受信できると思っていたのですが
受信できないため、DO〜Loopでバッファにたまっているデータを
全て受信してから
> '受信したバイナリデータをコマンドと文字列に分解する
> Call RcvProc()
上記関数を読んだ方が良いかと思い、ループにしています。
Inputでバッファにたまっている全データを受信するにはどうすればいいのでしょうか?InputLenの設定だけではダメでしょうか。
MSComm1.RThreshold = 1 として受信すると、Inputバッファに1バイト以上
データが存在するとOnCommイベントが発生します。
この時InputLen=0としてbyt() = MSComm.Inputを行うとその時に
バッファに溜まっているデータが全てbyt()に読み込まれます。
送信データが大きい場合は、読み込み中にイベントが起きる為に、
送ったデータが全てバッファに溜まっているわけではなく、
データを読み込んだあと再度OnCommイベントが発生します。
つまり大きいデータの場合は何回かのOnCommイベントの中でバッファに
有るデータを読み込んで、それを足しこんで一つのデータにする
必要があります。
ループを回さずに足しこんだデータ
public m_data() as byte
の最後の2バイトがデータが終了マークの 03,04で有るか否かを
チェックしましょう。
受信データの末尾に終了コードがついてくるならやりやすいですね。
昔(というほど昔ではないけど)、こっちから送信要求コマンドを送って、終了コードの無い文字長さ固定の受信データを待っていたことがあります。
待ちうけ状態になってからタイマーイベント連発して、
MSComm.InBufferCountを既定のサイズになったら受信完了なので、
バッファの中にはそのまんまの受信データが入っていました。
このときは相手は勝手にデータを送ったりしてこなかったし、
バッファサイズ>受信データサイズでしたからこの程度でも十分でした。
当時は、連結処理しなくていいので非常にシンプル。 ъ( ゜ー^)
え?いつまでもInBufferCountが既定のサイズにならない?
そりゃ、データ抜けということで、バッファをクリアしてリトライですね。
シリアル通信はデータ抜けが怖くて・・・実際に起きるしね。
19200ボーなら大丈夫だと思うけど・・・
えと
相手からのコマンドがこっちからの応答がないのに
次のコマンドが送られてきて困っているという話では?
我龍院さんの言われるように、1バイト受信ではなく
ある程度バッファを確保してイベント発生単位で受信しながら
受信したデータのコマンドを解析して送信という流れが
効率がいいと思います
>相手からのコマンドがこっちからの応答がないのに
>次のコマンドが送られてきて困っているという話
え、そんな話でしたっけ。
ここまでのところ、1800バイト程の受信データがいわゆる「垂れ流し」なのか、応答データなのかは記載が無いので不明です。
「スタック領域が不足しています」というトラブルは、我龍院さまが明確に解決されています。
>>>通常スタックオーバーフローは出ませんが、MSComm_OnComm()の中のどこか
>>>にDoEventsが入っていませんか?
>>はい、入っています。
>>これが原因でしょうか?
>です。
あとは、OnCommイベント発生後にループ待ちでは固まっちゃいますねという問題が残りました(そもそもこれがあったからDoEventsを挿入したわけで)。
それに対して我龍院さまは複数回のOnCommイベントでデータを連結すればいいんでねーのという案を提示されました。
わたしは、条件限定なら、もっと単純なコードで受信できますよと言っただけです。
要はその機能にどの程度のコスト≒手間≒時間をかけるかと言うことだと思いますが。
まぁ、わたしの場合は、コストを下げるために多少アクロバティックなことをすることも多々ですが・・・
今回は特にアクロバティックではないですけど。
私の一番初めの寝ぼけ交じりのコメントがちょっと混乱を招いてしまいました。
日本語もヘロヘロでしたが今日は良く寝ました。
ちょっと整理してみましょう。
先ず送られるデータですが、基本的には文字データで、ヘッダーとフッタに文字コード
では出現しない、01,02と03,04が付く、データ長は不定で1800バイト程度、速度は19200ボー。
受信方法はいくつか有りますが、一番簡単な方法は、OnCommイベントで受信した
データを足しこんでデータの最後が03,04であったらデータの分離を行う方法。
この場合OnCommイベントの中から何回か抜けるので、長く画面が固まることは
無いと思われるが、もし画面が固まるようなら、OnCommイベントを止めて、
ポーリングでデータを取得することになります。
提示されたコードは
Do
Loop Until MSComm.InBufferCount = 0
でOnCommから出にくい構造なので、より画面が固まりやすい様に見られます。
紅閃光 さんの提示の方法でOnCommではデータを取らずにOnCommイベント内で
受信に十分なタイマーをかけてタイマー内でデータを取る方法も有ります。
この方法だと、データの足しこみ処理と、終了確認がいらなくなります。
送られるデータ長よりも受信バッファを長く設定する必要が有り、
MSComm1.InBufferSize=1024 *2
などとしておきます。
ただしこの方法はボーレートやマシンスペック、データ長が変わるたびにタイマの
設定を変える必要が有り、学習機能等を付加する必要が有るため、かえって
複雑になるきらいがあります。
シリアル通信のデータ抜けに関しては、DOSVの初期のコンピューターでは当たり前でしたが、
クロックの同期処理の技術が格段と進歩した現在のコンピュータでは全く心配は
いらないと思います。
データの送信コマンドに関しては、DSR/DTRで正常に動作していますが、
OnCommイベント内でデータの受信後(byt = MSComm.Input)データ足しこみ
処理が終了しない時点で、DoEventsにより次のOnCommイベントの割り込みを
許可している為、未処理のデータを保持するスタックでオーバーフローが起きました。
>また受信データは1800バイト程のデータが一気に流れてきます。
は、1800バイトのデータが次から次に流れてくると言う意味ではなく、
一回の送信で1800バイトのデータが送られると言うことで、別に珍しいことでは
無いと思います。
やはり日本語はヘロヘロです。
皆様、アドバイスありがとうございました。
我龍院さんが教えてくださった
>一番簡単な方法は、OnCommイベントで受信したデータを足しこんでデータの最後が03,04であったらデータの分離を行う方法。
をやってみたところ、受信することができました。
ありがとうございました。
申し訳ありませんが、最後にひとつ教えてください。
我龍院さんが教えてくださった下記のやり方をやろうとすると、コンパイルエラーが出てしまいます。
>public byteTotalData() as byte
>If UBound(byteTotalData) = 0 Then
> '1番最初の処理
> ReDim Preserve byteTotalData(UBound(byteTotalData) + UBound(byteData))
>Else
> ReDim Preserve byteTotalData(UBound(byteTotalData) + UBound(byteData) + 1)
>End If
どこが間違っているのか教えていただけないでしょうか。よろしくお願い致します。
Formレベル(Sub の外)で
Dim byteTotalData() As Byte 'バイトのデーター
としてください。
我龍院さん、回答ありがとうございます。
ただ、実行すると
>If UBound(byteTotalData) = 0 Then
の箇所で「インデックスが有効範囲にありません」とエラーが出ました。
そのため、判定をせずに
dim m_byteDataSize as long
ReDim Preserve m_RxData(m_byteDataSize + UBound(byteData) + 1)
m_byteDataSize = m_byteDataSize+1(←この変数は文字変換を完了させたら0にする)
にしようかと思います。問題ないでしょうか。
>問題ないでしょうか。
いずれにしろフッタでデータの終了を検索しているので
それで問題ないですね。
我龍院さん、他の皆様ありがとうございました。
またわからないことがあった時はよろしくお願い致します。
あ、とっくに解決してるんだけど、自己補完〜
>シリアル通信のデータ抜け・・・中略・・・心配はいらないと思います。
んー・・・
2年ほど前、パソコン同士のシリアル通信で文字が抜けまくって非常に困りました。
115kボーだったので19200ボーまで下げたらほぼ発生しなくはなったものの、速度が遅すぎたので結局はソケット通信に変更しました。
プロトコルアナライザで電文を監視していたのですが、文字抜けはあるものの文字化けは発生しておらず奇妙に思ってました。
ハンドシェイク無しだったのがなにか関係あったのかな・・・あるか?
このとき以降、シリアル通信にはサムチェックと再送機能は必須なのかなと少し鬱になりました。
>ただしこの方法はボーレートやマシンスペック、データ長が変わるたびにタイマの
>設定を変える必要が有り、学習機能等を付加する必要が有るため
わたしの言っていたのは、タイマーコントロールを使ってのインターバルタイマイベントによる受信データ数の監視です。
「固定データ長」ならば、長めの時間にしておけばなにも考えなくても動きます。
当然、「可変データ長」では役に立ちません。ゴミ以下です。
まぁ、スタンダードではないでしょうけど。
RS-232Cのハードウェア上のFIFOの容量より大きい量のデーターを
受信する場合はフロー制御は必須ですよね。
通常FIFOは切りの良いバイト数になっている為、オーバーフローすると文字化けより
データ欠落になる場合が多いようです。
>「固定データ長」ならば、長めの時間にしておけばなにも考えなくても動きます。
「長めの時間」がわかればそれで良いのですが.....
ツイート | ![]() |