シリアルの送信完了を待つ方法

このQ&Aのポイント
  • シリアル通信を行う際に、送信完了を待つ方法について質問しています。
  • AdvantechのPCM-9575ボードにRedHat9を入れ、COM2をRS-485に設定して、シリアル通信を行っています。
  • write()関数が送信完了まで待ってくれないため、送信完了待ちの方法について知りたいです。
回答を見る
  • ベストアンサー

シリアルの送信完了を待つ方法

AdvantechのPCM-9575ボードにRedHat9を入れてCOM2 をRS-485にして115kでシリアル通信しています。 プログラム的には"/dev/ttyS1"を使用して普通の23 2Cポートと同じように操作できますが、送信イネー ブル信号(RTS 信号がつながっているのでioctl() 関数で制御できます)を操作してやらないと送受信 できないので、送信直前にセットし、送信完了直後 にクリアしたいと考えているのですが、write() 関 数が送信完了まで待ってくれないので、タイミング を作るのに困っています。 シリアルの送信完了待ちの方法とかご存じでないし ょうか?

質問者が選んだベストアンサー

  • ベストアンサー
回答No.4

今回の問題はドライバレベル以下で起こっているのだと思います。SIOには例えば16バイトのFIFOがあって、ドライバはハードに書き込んだら、すぐにカーネルに戻ってしまうのでしょう(FIFOの目的を考えれば当然ですね)。だからプロセスレベルでBLOCKINGであろうとなかろうと短時間ではバッファリングが起きているのだろう ... という理解になりました。質問主さんは最初からお気づきだったのでしょう。 ドライバのソースを読んだところでは、closeをするとハードウェアFIFOが空になるのをちゃんと待っています。ですからwrite直後にcloseしたら完全に送信完了ということになります。またcloseでは if (!info->tty || (info->tty->termios->c_cflag & HUPCL)) info->MCR &= ~(UART_MCR_DTR|UART_MCR_RTS); serial_outp(info, UART_MCR, info->MCR); ということをしているので、RTSも一緒にクリアされるようです。 open/closeを繰り返すのはコストが高いので避けたいですけれど、そうでないなら単にcloseすることで質問主さんの目的は達せられるのではないでしょうか?私のボスと話したところでは、OSの内部をいじりたくないのであれば質問主さんのnanosleepがベストの解決だろう、という結論になりました。 その他の解決さくとしてはNo.3で書いたハードを直接見る方法(Linux流の行儀の悪いプログラム)や、ハードウェアFIFOにバッファリングをさせないためにはサイズを1バイトにしてしまう手があります。でもこれをすると山ほど割り込みが発生するのでダメかもしれません。とにかく全体のパフォーマンスは犠牲にしてもrs_writeがFIFOが空になるのを待つようなオプションをドライバ開発者に付けてもらうのがいいと思います。すでにトランスミッタが完全に送信したかどうかをチェックする関数がありますから、ちょっとの改造でできるはずです。きっと他の人の需要もあると思います。 ところで >出終わってなければ再実行 これは for (nwritten = 0;nwritten < n;nwritten += rv) { rv = write (fd, buf+nwritten, n-nwritten); if (rv < 0) break; // Error: bail out. // } こうしているということですよね?

matyrcry
質問者

お礼

ありがとうございます!! 詳しいところまで...助かります。 ドライバの深いところまではまだ読んでなかったのですが、なんとなく分かってきました。 close() は送信頻度が今は30Hzくらいですが完成予定では600Hzくらいになるので無理っぽいです。 アプリケーションレベルからの周期監視も実行時間を喰うから避けたいかなと考えてます。 (のですが、割り込みがとれなければそれしかないんでしょうね) 他の作業に追われて今は時間をかけられないので、一段落したらドライバいじりに挑戦しようと考えてます。 またそのときにはお知恵を拝借させてください。 >こうしているということですよね? だいたいそんな感じです。(プロセスがFIFOで誤動作すると実行時間を食い尽くすのでスリープで保険かけてます)

その他の回答 (3)

回答No.3

うむむ、そうでしたか.....ううむなんでだろう。すみません、ネタ切れです(^^; 格好悪いですけれども、write直後にUARTのポートを直接読みに行ってLSR内でTHRの状態を見るのはいかがでしょうか。 #今からドライバをDEBUGオプション付きで再コンパイルして眺めてみます。

回答No.2

こちらにはRS232の環境しかないのですけれど、実験してみたところ、普通にopenしてwriteしたらちゃんと待ってくれました。openするときにワザワザO_NDELAYとかO_NONBLOCKなんてセットしてないですよね....?writeが待ってくれないのはどのようにして確認されたのでしょう?writeの戻り値は送信したバイト数と同じ数になっていますか?

matyrcry
質問者

お礼

ありがとうございます。 write()の返値はあっています。(出終わってなければ再実行させています) openの引数は(O_RDWR|O_NOCTTY)です。 確認は、ソフト的にはwrite()直後にioctl()でRTSラインを操作し、信号線の電位をオシロで見ています。 開通後にtcsetattr()を引数(struct termios) c_iflag,c_oflag,c_lflag,c_cc[VTIME],c_cc[VMIN],c_cc[VEOL],c_cc[VEOF] をそれぞれ0にしてコールしています。

回答No.1

経験の無い者ですみません。思いつきでアドバイスです。 termios.hのtcdrainをwriteのあとに入れるのはいかがでしょうか?

matyrcry
質問者

お礼

ありがとうございます。早速やってみました。 プロセスを休眠させるようで、起床は次のカーネル のスケジューラ処理後になるようです。 (今カーネルは10ms周期ですが、応答が1.5 ms程度で来るので間に合いません) Linux 始めて間がないもんで、このへんの動きが さっぱり分からないので、他にも思いつきでいい ので情報いただけると嬉しいです。 とりあえず今は実測で1char=95.5usから待ち時間 を算出してnanosleep() 使って待たせてます。^^;

関連するQ&A

  • VB6でシリアルポートの制御

    VB6のコントロールまたは、VB6で使用可能なAPIを使って、シリアルポートの内、DTRとRTS信号線を常にHigh(ON)の状態にすることは可能でしょうか? RS-232CからRS-422へ変換して機器を制御するのですが、変換器がこの信号線を電源として動作するので、常にHigh(ON)の状態にしたいのです。 よろしくお願いします。

  • VMPlayerでシリアルポートの使用

    WindowsXPにVMPlayerを入れて、Debianを動かしています。 この環境で、シリアルポートを使うプログラムを書きたいんですが、 Openに失敗します。 VMPlayer、Linuxともに、初心者ですので、行き詰まっております。 Windows上では、COM1で通信はできています。 VMPlayerの起動時に Serial: 8250/16550 driver $Revision: 1.90 $ 4 ports, IRQ sharing enabled serial8250: ttyS0 at I/O 0x3f8 (irq = 4) is a 16550A serial8250: ttyS1 at I/O 0x2f8 (irq = 3) is a 16550A 00:09: ttyS0 at I/O 0x3f8 (irq = 4) is a 16550A 00:0a: ttyS1 at I/O 0x2f8 (irq = 3) is a 16550A と、なっていますが #define DEV_NAME "dev/ttyS1" int main() { int fd; //デバイスファイル オープン fd = open( DEV_NAME, O_RDWR ); if(fd<0) { //オープン失敗 printf("Open fauls \n"); exit(1); } } 抜粋、ではttyS0、でもttyS1でもオープンできません。 どこが、まずいんでしょうか? よろしく、お願いいたいます。

  • linuxのシェル上でシリアル通信したい

    linuxのシェル上でシリアル通信(RS232C)したいのですが 送信するのは、 echo "msg" /dev/tty0 で、できるのですが、 受信方法がわかりません。 知っておられる方、教えて下さい。

  • シリアル通信(送信について)

    現在VB6.0を使用してシリアル通信(RS232C)をMSCOMMを使用して行いたいのですが、受信は出来たのですが、送信がうまくいかない状態です。 MSComm.Output = 送信文字列 & vbCr とすると結果の値を受信できる予定なのですがエラーですという値が返ってきます。 同じ操作をTera Term等で行うと結果が正しく返ってきます。 何故そうなるのかわからない状態で困っております。 何かアドバイス頂けないでしょうか。

  • RS232C 制御方法

    使用しているマザーボードから出ているCOM(RS232C)を使用して RS485変換回路を考えております。 初歩的な話になり申し訳ありませんが、RS232Cは送信と受信同時にはできないですよね。 現在RTS制御を行おうと考えております。 [RTS]と[CTS]をループバックにて接続し、 [DTR]と[DSR]をループバックして接続しています。 [RTS]が"H"になるとRS485の送信側を有効にし [RTS]が"L"になるとRS485の受信側を有効にしようと考えております。 受信を受け付ける際に[CD]が"H"になっていないといけないのでしょうか。 [CD]を常に"H"に固定すると問題はあるのでしょうか。 全てにおいて初歩的なお話になり申し訳ありません。 何方かご存知のた方お願い致します。

  • シリアル通信でのread関数の戻り値

    オムロン製PLCと上位コンピュータをRS232Cで接続し、FINSコマンドを用いてシリアル通信をしようとしています。 現在作っているプログラムの流れは、 ・シリアルポート"/dev/ttyS2"のオープン   comm_fd = open("/dev/ttyS2", O_RDWR | O_NOCTTY); ・通信設定   termios構造体を設定 ・コマンドフレーム(COMMAND)を作成し、ポートに書き込む   write(comm_fd, &COMMAND, strlen(COMMAND)); ・PLCからのレスポンスを読み出す   read(comm_fd, &RESPONSE, 256); のようにしているのですが、read関数が実行されたままになってしまいます。(エラーコードも返ってこない状態です) readの戻り値が-1ならポートにアクセスできていないとわかるのですが… ためしにcomm_fdと違う値をread関数に入れてみたところ(read(6, &RESPONSE, 256)、戻り値は-1となりました。 これはどういう状態になってしまっているのでしょうか? わかりにくい質問で申し訳ありません。

  • sttyで通信速度の設定が出来ない

    今まで使用していたサーバーが壊れてしまい 新しいサーバーをセットアップしていましたが シリアルポートの速度の設定がうまくいきません root でログインし stty -F /dev/ttyS0 9600 cs8 -parenb cstopb と打ち込んでも stty -F /dev/ttyS0 -a で確認してみると speed 38400 と表示されてしまいます 他に何か設定が必要なのでしょうか? 環境 RedHat Enterprise Linux Server release 5.3 linux 2.6.18 サーバー DELL PowerEdge R200

  • VB2005でWin32APIを用いてRS-232CのRTS信号を、デ

    VB2005でWin32APIを用いてRS-232CのRTS信号を、データ送信中だけHIGHにさせるプログラムを作成中です。 色々調べて、Win32APIを使うことで、それらしい動作をさせるプログラムを作れることが分かり、色々調べてコーディングしたのですが、上手く動きません…。処理の流れは以下のようになっています。 (1) CreateFile()関数でCOMを開く。   (CreateFile()の「フラグ」には"FILE_FLAG_OVERLAPPED"を指定) (2) CreateEvent()関数にてイベントオブジェクトを作成。 (3) EscapeCommFunction()でRTS信号をHIGHに設定。 (4) WriteFile()関数でRS-232Cへデータを送信する。 ※ここまでは、オシロスコープにて正しく動作していることを確認しています。 (5) データ送信完了を待つために、SetCommEvent()関数で"EV_TXEMPTY"イベントマスクをセット。 (6) WaitCommEvent()で送信完了を待つ。   ⇒WaitCommEvent()関数が、データ送信完了を待たずに抜けてきてしまう(Falseがリターンされる)…。 (7) そこで、WaitForSingleObject()を使ってイベントハンドラが"シグナル状態"になるのを待つ。   ⇒タイムアウトに"INFINITE"を指定すると、無限待ち状態に陥ってしまう。   ⇒また、タイムアウトに"1000"を指定すると、約1秒後に"WAIT_TIMEOUT"が返ってきてしまう。 WriteFile()関数、そしてWaitCommEvent()関数の引数には、OVERLAPPED構造体へのポインタを渡しているのですが、思うように動いてくれません。 ※Win32APIの関数宣言の記述方法(データ型など)が誤って、このようになっていたりするのでしょうか… どなたかヒントでもお教えいただけると、大変助かります。 以上、よろしくお願いいたします。

  • COMポート(RS232C)の送信準備完了確認方法

    すいませんがご教授願います。 あるソフトウエアでデータの転送を行いたいのですが、 相手側(制御装置)がパソコンのCOMポートをオープンになる事を確認しています。 パソコン側で送信準備完了しているのに相手側は送信準備出来てないと認識しています。 そこでパソコン側で送信準備完了の時に本当にRS-232Cの口が開いているか確認したいのですが その様な方法がありますか? 尚、もともとWin95.OSR2では正常に通信できていたのですが、 HDDがクラッシュした為Win98SEにしてからの症状です。 説明下手ですいません。 どうぞ宜しくお願い致します。

  • C言語のシリアル通信について

    C++でプログラムを書き、シリアル通信(RS232C)を使ってデバイスを動かしたいと試みているのですがよくわからないのでアドバイスお願いします。(OSはlinux) デバイス特有の命令を送るところがよくわかりません。(命令はキャラクタではなくバイナリで送らなければなりません。) 命令はバイナリデータです。 例、 char i[]={255,20,0,10}; ↑このようなデータです。 このようなデータを送る際はfwrite関数を使うということは調べたのですがどのように表すかわからないので教えていただきたいです。 初めのシリアルポートを開く際に”wb”をつけないといけないみたいですがどのようにつければいいのでしょうか。 fd=open("/dev/ttyS0",O_RDWR|O_NOCTTY|O_NONBLOCK); if(fd<0) { printf("erroe\n"); exit(-1); } ↑自分が書いたポートを開く時のプログラムです。最初の行の最後に”wb"を付け足せば良いのですか。 上記の例のような命令を送るとすると fwrite(&i,sizeof(char),4,fd); return 0; で良いでしょうか。warningがでて動作しないのでアドバイスよろしくお願いします。 説明が下手で申し訳ありません。一番わからない点はシリアル通信で命令を送るときのポートオープンとfwriteを使った命令を送る形式です。どうぞよろしくお願いします。