none
SerialPort送信バッファ RRS feed

  • 質問

  • WindowsXP Embedded SP2を搭載した組み込みボード上でアプリケーションの作成を行っています。
    アプリケーションでは、40msに一度、処理を行い、処理結果として12byteのデータを送出しています。
    開発言語はC++/CLIで、System.IO.Ports.SerialPortクラスのWrite関数を使用してデータ送出を行っています。

    現在、"24byteのデータが送出されることがある"という問題があります。

    この"24byte"は、受信側で、数マイクロ秒間連続してデータを受信し続けている、という意味です。

    12byteずつ送出しているデータが2つ分、まとめられて送出されているのではないかと考えています。

    しかし、Write関数は、必ず"12"バイトを固定値で指定して呼び出しており、24バイトを指定することはなく、
    可能性があるとすれば、Write関数を呼び出す間隔が、シリアル送信のバッファリング時間に比べて短いのではないか、
    と考えています。

    質問としては、以下になります。
    (1)シリアル送信のバッファリング時間は、何ミリ秒(もしくは何マイクロ秒)でしょうか?
    (2)バッファリング以外の可能性のあることはありますでしょうか?

    よろしくお願いします。

    2010年10月14日 1:15

回答

  • >Write関数は、必ず"12"バイトを固定値で指定して呼び出しており、24バイトを指定することはなく、
    Flush してますか? それで状況が改善したとしても、その動作を「確実なもの」としてあてにするのも微妙なところですが。
    >(1)シリアル送信のバッファリング時間は、何ミリ秒(もしくは何マイクロ秒)でしょうか?
    規定はありません。
    >(2)バッファリング以外の可能性のあることはありますでしょうか?
    ドライバの実装にも影響を受けますが、それらすべてコミコミで「仕様」です。
    調歩同期の単純なシリアルポートで、送受信データの「切れ目」を時間で管理しようとするところに無理があります。
    データの開始を示す(と必要なら終端を示す)文字を追加することは出来ないのでしょうか?
    • 回答としてマーク yossy.tkb 2010年11月4日 5:49
    2010年10月14日 2:50

すべての返信

  • >Write関数は、必ず"12"バイトを固定値で指定して呼び出しており、24バイトを指定することはなく、
    Flush してますか? それで状況が改善したとしても、その動作を「確実なもの」としてあてにするのも微妙なところですが。
    >(1)シリアル送信のバッファリング時間は、何ミリ秒(もしくは何マイクロ秒)でしょうか?
    規定はありません。
    >(2)バッファリング以外の可能性のあることはありますでしょうか?
    ドライバの実装にも影響を受けますが、それらすべてコミコミで「仕様」です。
    調歩同期の単純なシリアルポートで、送受信データの「切れ目」を時間で管理しようとするところに無理があります。
    データの開始を示す(と必要なら終端を示す)文字を追加することは出来ないのでしょうか?
    • 回答としてマーク yossy.tkb 2010年11月4日 5:49
    2010年10月14日 2:50
  • ご回答ありがとうございます。
    >Write関数は、必ず"12"バイトを固定値で指定して呼び出しており、24バイトを指定することはなく、
    Flush してますか? それで状況が改善したとしても、その動作を「確実なもの」としてあてにするのも微妙なところですが。
    System.IO.Ports.SerialPortクラスには、Flushを行うメソッドがないため、行っていません。
    Flushする方法がありますでしょうか?

    >(1)シリアル送信のバッファリング時間は、何ミリ秒(もしくは何マイクロ秒)でしょうか?
    規定はありません。
    >(2)バッファリング以外の可能性のあることはありますでしょうか?
    ドライバの実装にも影響を受けますが、それらすべてコミコミで「仕様」です。
    調歩同期の単純なシリアルポートで、送受信データの「切れ目」を時間で管理しようとするところに無理があります。
    データの開始を示す(と必要なら終端を示す)文字を追加することは出来ないのでしょうか?
    ご指摘の通りです。データの開始を示すコードも存在するのですが、対向側の取りかた上、どうしようもない(理由は分からないのですが・・・)ということで、
    何とかする方法を探っています。

    ボードメーカーにバッファリングの時間を問い合わせているのですが、最悪、その分スリープする、という方法で
    回避できるでしょうか。

    2010年10月14日 6:31
  • >System.IO.Ports.SerialPortクラスには、Flushを行うメソッドがないため、行っていません。
     
    無いでしたっけ?
    であれば最悪、SerialPort クラスの使用をあきらめて、Windows API で書くとかですかね。
     
    それでも、所望の動作が得られるかどうか、すべての動作環境(PC 側のシリアルポートデバイスやデバイスドライバの実装が異なれば、動作も異なる可能性があります)で諸物憂動作が得られるかどうかはなんの保証もありませんが。
     
    >ボードメーカーにバッファリングの時間を問い合わせているのですが、
     
    この場合の「ボード」は PC 側のシリアルポートを実装する拡張カードなどではなく、対向側の装置のことですか?
    2010年10月17日 4:28
  • ご返信ありがとうございます。 また、気づかず申し訳ございません。
    >System.IO.Ports.SerialPortクラスには、Flushを行うメソッドがないため、行っていません。
     
    無いでしたっけ?
    であれば最悪、SerialPort クラスの使用をあきらめて、Windows API で書くとかですかね。
    はい、結局、そうしました。 しかし、「FlushFileBuffers」というAPIを使用することでFlushが可能、という情報があり、試したところ、Flushできませんでした。 Windowsのバッファから、UARTの送信バッファに配置するのみのようです。 この辺り、関数「FlushFileBuffers」の仕様につきまして、正確なソースをご存知ありませんでしょうか?
     
    それでも、所望の動作が得られるかどうか、すべての動作環境(PC 側のシリアルポートデバイスやデバイスドライバの実装が異なれば、動作も異なる可能性があります)で諸物憂動作が得られるかどうかはなんの保証もありませんが。
    仰る通りです。そもそも、受信側が考えて処理すべきなんですが・・・。
     
    >ボードメーカーにバッファリングの時間を問い合わせているのですが、
     
    この場合の「ボード」は PC 側のシリアルポートを実装する拡張カードなどではなく、対向側の装置のことですか?


    いえ、対向側ではありません。 こちら(送信側)が、PCではなく、RS-232Cに対応した組み込みボードを使用しています。

    回答があったのですが、Windowsの標準のデバイスドライバを使っている、とのことでした。

     

    2010年10月28日 9:30
  • 横から失礼します。
    (この質問、前から気になっていいたのですが、誰も返信されないようなので、参考になるかわかりませんけどとりあえず。。。)

    ご質問されている現象ですが、もしかしたらタイムスライス (クォンタム) の制限に起因しているのかも。。。

    確かWindows では、CPU での実行が割り当てられたスレッドは、2クロックサイクルを基準としたタイムスライスで動作していたと思います。
    1クロックの間隔は、OS や CPU にも依存して変化するのですが、XP / Vista / 7 などの x86 マルチプロセッサ環境であれば、
    デフォルトで 15.6ms になるはずだと思います。
     (この点に関する詳細な仕様に関しては、「インサイド Windows 上巻 第6章」に記載されていますので、もしこの本をお持ちでしたらご確認ください。)
    つまり、自身のスレッドに CPU が割り当てられると、その間に H/W 割り込みが無いことを前提とすると2クロック、
    即ち (x86 マルチプロセッサ環境の場合) およそ 31.2ms の動作時間を与えられる訳ですが、アプリケーション側でのポーリング間隔が
    40ms である場合当然端数が出てくるので、yossy.tkb さんがご推測されているような現象が発生してくる可能性はあると思います。

    ですので、もしポーリング間隔を変更しても差支えが無いのであれば、その間隔を2システム クロックの倍数に合わせこめば、
    ご指摘されている現象が改善される可能性があると思います。
    (もっとも Windows 自体が RTOS ではないので、ポーリング間隔を変更したとしても、「完全に」この現象を防げる訳ではないとも思いますが。。。)

    システム クロック割り込み間隔の情報取得に関しては、下記 API を使うと良いと思います。
    -----------------------------------------------------
    GetSystemTimeAdjustment
    http://msdn.microsoft.com/ja-jp/library/cc429815.aspx
    -----------------------------------------------------

    あと気になったことは。。。
    FlushFileBuffers() API ですが、これはファイルに対してのみ有効だと思います。
    FlushFileBuffers() API コールをデバッガでトレースしていくと ntdll.dll 内の ntdll!ZwFlushBuffersFile ルーチンを経由するのですが、
    この Native API はカーネル モード側の nt!ZwFlushBuffersFile ルーチンと対になっているはずです。
    で、カーネル サービス関数 ZwFlushBuffersFile() のドキュメントを確認すると以下の一文があります。

    "to send a flush request for a given file to the file system"

    なので "デバイス ハンドル" を指定した場合、この関数をコールしても意味を持たない。。。と言うことだと思います。

    -----------------------------------------------------
    ZwFlushBuffersFile
    http://msdn.microsoft.com/en-us/library/ff566454(VS.85).aspx
    -----------------------------------------------------

    参考になりましたら幸いです。

    • 回答としてマーク yossy.tkb 2010年11月4日 4:43
    • 回答としてマークされていない yossy.tkb 2010年11月4日 5:49
    2010年10月29日 6:51
  • ご回答ありがとうございます。 またも拝見するのが遅くなってしまいました。。申し訳ございません。

    横から失礼します。
    (この質問、前から気になっていいたのですが、誰も返信されないようなので、参考になるかわかりませんけどとりあえず。。。)

    ご質問されている現象ですが、もしかしたらタイムスライス (クォンタム) の制限に起因しているのかも。。。

    確かWindows では、CPU での実行が割り当てられたスレッドは、2クロックサイクルを基準としたタイムスライスで動作していたと思います。
    1クロックの間隔は、OS や CPU にも依存して変化するのですが、XP / Vista / 7 などの x86 マルチプロセッサ環境であれば、
    デフォルトで 15.6ms になるはずだと思います。
     (この点に関する詳細な仕様に関しては、「インサイド Windows 上巻 第6章」に記載されていますので、もしこの本をお持ちでしたらご確認ください。)
    つまり、自身のスレッドに CPU が割り当てられると、その間に H/W 割り込みが無いことを前提とすると2クロック、
    即ち (x86 マルチプロセッサ環境の場合) およそ 31.2ms の動作時間を与えられる訳ですが、アプリケーション側でのポーリング間隔が
    40ms である場合当然端数が出てくるので、yossy.tkb さんがご推測されているような現象が発生してくる可能性はあると思います。

    ですので、もしポーリング間隔を変更しても差支えが無いのであれば、その間隔を2システム クロックの倍数に合わせこめば、
    ご指摘されている現象が改善される可能性があると思います。
    (もっとも Windows 自体が RTOS ではないので、ポーリング間隔を変更したとしても、「完全に」この現象を防げる訳ではないとも思いますが。。。)

    システム クロック割り込み間隔の情報取得に関しては、下記 API を使うと良いと思います。
    -----------------------------------------------------
    GetSystemTimeAdjustment
    http://msdn.microsoft.com/ja-jp/library/cc429815.aspx
    -----------------------------------------------------

    Windows上の実行タイミングが一緒になってしまっている、という可能性は考えていませんでした。
    ただ、現状、timeBeginPeriod()を使用して、タイマ分解能を1msにしています。そうしますと、スレッドの切り替わりタイミングは、2ms程度ということになりますでしょうか。
    シリアル通信を行うスレッドは自身で40msのタイマーをもって処理しているのではなく、他のスレッドから必要があった時のみ起床されます。
    そして、シリアルデータ(12byte)の送信のみ行うので、そこでスレッドの切り替えが発生することは考えずらいです。

    あと気になったことは。。。
    FlushFileBuffers() API ですが、これはファイルに対してのみ有効だと思います。
    FlushFileBuffers() API コールをデバッガでトレースしていくと ntdll.dll 内の ntdll!ZwFlushBuffersFile ルーチンを経由するのですが、
    この Native API はカーネル モード側の nt!ZwFlushBuffersFile ルーチンと対になっているはずです。
    で、カーネル サービス関数 ZwFlushBuffersFile() のドキュメントを確認すると以下の一文があります。

    "to send a flush request for a given file to the file system"

    なので "デバイス ハンドル" を指定した場合、この関数をコールしても意味を持たない。。。と言うことだと思います。

    -----------------------------------------------------
    ZwFlushBuffersFile
    http://msdn.microsoft.com/en-us/library/ff566454(VS.85).aspx
    -----------------------------------------------------

    参考になりましたら幸いです。

    渡しているのがデバイスハンドルだと、何も行われないのですね。
    そのためにFlushされない、ということが分かりました。ありがとうございました。

    いろいろと勉強になりました。どうもありがとうございました。

    2010年11月4日 4:42
  • <捕捉>

    一旦、パーマン2号さんの回答を"回答としてマーク"しましたが、よく考えると質問に対する回答は

    最初の渋木様のご回答かと思い、変更させて頂きました。

    その後の追加の質問などにご回答頂き、ありがとうございました。大変勉強になりました。

    ※ フォーラムにあまり慣れていません。このあたりの使い方に誤りがあればご指摘ください。

     

    2010年11月4日 5:55