シリアル通信で接続したデバイス相手に何度かコマンドを送るが応答なし、その後他のツールで同じポートを開くと一気に送った分の応答が返ってくる件考察

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

掲題のような現象が起きて、なんでなのかさっぱりわからずずっともやもやしている。

が、地道に色々調べてると、きっとこれじゃないか、というのが見えてきた。
それの検証のための実験アプリを作ったので、明日試してみる。

狙った通りうまくいったらうれしい。

追記

→試した結果、狙った通りになった部分もあったが、結構たくさん疑問は残った。が、ここから先は、使うシリアル通信デバイス(とかドライバ)によって動きが変わってくるデバイス依存の部分だと思ったので、すべてが解決できなくてもよいか、と思って納得した。(👇の「検証結果」のところを参照)

起きてた現象概要

自作アプリ(①と呼ぶ)と相手デバイス(②と呼ぶ)がシリアル通信するとする。

①の仕様&①②に期待する動作としては、

  • ①は、コマンドを送信した直後、受信待ちに入る。
  • ②は、①からのコマンド受信後、応答を送信する。
  • ②から送信を行うことはない。
  • ②から応答が来たら①はそれを受信する。
  • 受信が完了したら①はポートを閉じる。
  • その直後、再度①はポートを開き、次のコマンドを送る。
  • ・・・という、ポート開く→送受信→ポート閉じる、というのを何度か繰り返す

となっているのだが、
実際に動かすと、うまく動くときもあるのだが、頻繁に、

  • ①はコマンドを送るが、②からの応答を受信できない。
  • 受信できないのでリトライのために①は数回コマンドを送るが、それにも応答が来ない。
  • その後、①のアプリを終了し、検証のためにteratermで同じポートを開くと、一気にリトライ時のコマンドへの応答が来る。

てな感じになってしまう。

前提

  • ①はWindowsアプリ(C++)
    • Win32APIでシリアル通信を行う
    • VisualStudio2022で開発
  • ②は、PCに接続したとあるデバイス

現象の原因推測

この動作を私は、

  • アプリはコマンドを複数回(リトライで)送信しても、デバイス側の応答が来ないのに、
  • teratermでつないだとたん、相手側デバイスがなぜか一気に応答を送ってきた

という現象だと思っていたのだが、実は逆で、

  • teratermでつないだとたん、こっちのアプリが送るつもりだったコマンドが一気に全部送信されて、
  • 相手側デバイスはそれに応答しただけ

だったのでは?

仮説

この動作は、

  • ①が送信をしたつもりになっていたコマンドが送信バッファに入っただけで実はUARTから送出はされておらず、②に届いていなかった
  • teratermに切り替えてteratermがポートを開いた瞬間に、①が使っていた送信バッファから一気にたまっていた分がUARTから送信された
  • 結果、②に複数コマンドがそのタイミングで届いたので、②はそれに応答した

ということだったのではないか?   で、その原因は、

  • ①がポート閉→開をして再度送信したときに、
  • ②のRTSがLOW(相手の送信を不許可にする)、つまり、①側から見たCTSがLOW(相手が送信不許可だと言っている)になっていた
  • その場合、①側のドライバはコマンドを実際には送信せずバッファに入れるだけにしていた
    • しかも、その場合に送信時に使うAPI「WriteFile」は送信を正常に終わったことにしてしまい、①のアプリは実際には送れていないのに気づけなかった

のではないか。

Copilotとのやりとり

copilot.microsoft.com

検証ツール

👇に、

github.com

シリアル通信で送受信と、RTSの操作、CTSの状態の表示を行えるツールを作った。
これで明日試してみる。

検証環境&結果

環境

検証に使った環境は下記のようなもの。

試した手順

検証用ツールはこんな感じのもの。これで👇のようなことを試した。

  • 検証用アプリを2つ起動する。
  • ArvelのSUBシリアル変換につながっているシリアルポートを、検証用アプリでポートオープンする。→①とする
  • UGREENにつながっているシリアルポートを、検証用アプリでオープンする。→②とする。

これを基本の状態として、👇のようなことを試した。

①→②に送信:②のRTS=OFFにしている間に①から送信

  • ①からコマンドを送信する
    • →①から普通に送信でき、②で送信したコマンドを受信できた。
  • ②で、RTSをOFFにする。
    • →①側のCTSがOFF(0)になった。
  • その状態で、①側から5回、コマンドの送信を行う
    • →①側では、正しく送信できたことになったが、②側では受信はしなかった。
    • →(①側のcbInQueの数字は0のままで変化なし)
  • その後、②でRTSをONにする。
    • →先ほど①で送ったコマンドが、3回分、一度に②側で受信された。
    • (コマンドの長さや内容を毎回変えて試しても、必ず3回分のコマンドが、一気に受信される、という動作だった)

②→①に送信:①のRTS=OFFしている間に②から送信

上の手順と反対を試した。つまり、

  • ②からコマンドを送信する
    • →②から普通に送信でき、①で送信したコマンドを受信できた。
  • ①で、RTSをOFFにする。
    • →②側のCTSがOFF(0)になった。
  • その状態で、②側から5回、コマンドの送信を行う
    • →②側では、正しく送信できたことになったが、①側では受信はしなかった。
    • →(②側のcbInQueの数字は0のままで変化なし)
  • その後、①でRTSをONにする。
    • →①側では、なにも受信されなかった! ★ココが違った!

考察

①→②と、②→①で、大体同じ動きだったが、RTS(自分から見たCTS)をOFFにされている間に送ったコマンドが、RTSをONに戻した後に届くか届かないか、の挙動が違った。

ほぼ、実験結果からの推測と、copilotとの会話からの推測になってしまうが、ここは、デバイスのドライバやデバイス自体の仕様によって動きが異なる部分だと思う。

相手がRTSをOFFにしている間にコマンドを送ろうとしたときに、送ろうとしたコマンドをどう扱うか、が、今回使ったArvelとUGREENのUSBシリアル変換で異なったのだと思う。 (Arvelは「3回分だけ保存しておいて、RTS=ONにされたら送る」、UGREEENは「破棄する」)

USBシリアル変換を使った実験結果はそういう結果になり、デバイスの違いで動きも変わった。

で、この記事で本来もやもやしていた、とあるデバイスと接続したときの動作も、この実験結果から以下のように納得した。

  • とあるデバイスとシリアル通信したときに掲題の現象が起きたのは、デバイス側がRTS=OFFにしていた可能性が高そう
  • そのデバイスとシリアル通信するときに私のアプリとその時使ったシリアルポート(のドライバ)が、相手がRTS=OFFにしていると、送るコマンドをいくつか貯めるタイプだった
  • 現象発生時、なんらかのタイミングで、とあるデバイス側のRTSがOFFになり、検証で起きる動きと同じ動きを起こした

推測を多分に含んだままではあるが、上記のように納得した。

まだやれる手は残ってそうだが、一旦、この辺で調べを終わろうと思う。

備考

複数のコマンドを送るのに、1コマンド送るだけでポート閉じずに、全コマンドのやり取りが終わってからポート閉じろ、という話はその通りなのだが、この時は事情があってこうなった。

実際、実験的にポートを最初に一回開き、やりとり終わった後、最後に閉じる、にすると、掲題の現象は起きなかった。 そういう不自然なことをすると、変な現象が起きるのだなと実感。次から気を付ける。

備考2

まだやれるとすると、検証環境の方で、USBシリアル変換の送信pinにオシロを当てて、

相手のRTSがOFFの時に、コマンドを送信した時に、送信pinからコマンドが出たのか、その時には出ず、RTS=ONにもどったときに出たのか、を見れば、RTS=OFFの時に送信側でコマンドをためてたのか、受信側でためてたのか、は確認できそう。

また、本来の「とあるデバイス」環境でオシロをあてることができれば、 どのタイミングで通信が送出されたか確認でき、今回の推測のたしからしさがもっと確認できそう。

しかしそこまでやるのはもはや趣味になりそうなので、上の調査で納得することにする。