2016-07-30 15 views
1

現在、Raspberry Pi 3(Linux Ubuntu)上で実行されているCプログラムで、組み込みシステムでネットワーキングを設定するためのWebページインターフェイスを提供しています。USBシリアルポートのプログラミングに「悲惨な結果」があります

コードは、Code :: BlocksをGDBデバッガで使用して開発されています。私はウェブサーバーにmicrohttpdを使用していますが、それに加えてさまざまなWebページもすばらしく働いています。私は現在、「シリアルプログラミングガイドfor POSIXオペレーティングシステム」の情報を使用して、組み込みシステムへのUSBシリアルリンクを作成中です。

以下のコードは、ターゲットシステムへのUSBシリアルリンクを開く責任があり、正常に動作するようです。私がプログラムを閉じて、それを再起動すると(コマンドラインやCode :: Blocksのスタンドアローンのいずれかで)、microhttpdが2度目に閉じられます。ブラウザウィンドウはもう接続されません。さらに、Code :: Blocks内からデバッガもホースされています。プログラムが起動されると、デバッガは一時停止または停止できません。唯一の方法は、プロジェクトを閉じることによってそれを終了させることです。

問題は明らかに関数内にあります。私はその呼び出しをコメントすることができ、以前と同じようにすべて機能します。残念ながら、問題が発生したら、唯一の解決策はPiを再起動することです。

私はスクリプト言語(Tcl)を使用する前にこのようなことをしましたが、今回はPiが高帯域幅データロギングプログラムを実行しているため、非解釈言語からパフォーマンスを向上させたいと考えています。同様のUSBシリアルインターフェイスを介して。

コードを以下に示します。

/******************************************************************************/ 
/* This function scans through the list of USB Serial ports and tries to  */ 
/* establish communication with the target system.       */ 
/******************************************************************************/ 

void tapCommInit(void) { 
    char line[128]; 
    char port[15]; // this is always of the form "/dev/TTYACMn" 
    char *ptr; 
    FILE *ifd; 
    struct termios options; 
    uint8_t msgOut[3], msgIn[4]; 

    msgOut[0] = REQ_ID;      // now prepare the message to send 
    msgOut[1] = 0;         // no data so length is zero 
    msgOut[2] = 0; 


    /**************************************************************************/ 
    /* First, get the list of USB Serial ports.        */ 
    /**************************************************************************/ 

    system("ls -l /dev/serial/by-path > usbSerial\n"); // get current port list 
    ifd = fopen("usbSerial", "r"); 
    logIt(fprintf(lfd, "serial ports: \n")); 


    /**************************************************************************/ 
    /* The main loop iterates through the file looking for lines containing */ 
    /* "tty" which should be a valid USB Serial port. The port is configured */ 
    /* in raw mode as 8N1 and an ID request command is sent, which has no  */ 
    /* data. If a response is received it's checked to see if the returned */ 
    /* ID is a match. If not, the port is closed and we keep looking. If a */ 
    /* match is found, tapState is set to "UP" and the function returns. If */ 
    /* no match is found, tapState is left in the initial "DOWN" state.  */ 
    /**************************************************************************/ 

    while(1) { 
     if (fgets(line, 127, ifd) == NULL) {      // end of file? 
      break;         // yes - break out and return 
     } 
     ptr = strstr(line, "tty"); // make sure the line contains a valid entry 
     if (ptr == NULL) { 
      continue;       // nothing to process on this line 
     } 
     strcpy(port, "/dev/");      // create a correct pathname 
     strcat(port, ptr);    // append the "ttyACMn" part of the line 
     port[strlen(port)-1] = 0; // the last character is a newline - remove it 
     logIt(fprintf(lfd," %s\n", port));  // we have a port to process now 
     cfd = open(port, O_RDWR | O_NOCTTY | O_NDELAY); // cfd is a global int 
     if (cfd == -1) { 
      logIt(fprintf(lfd, "Could not open port: %s\n", port)); 
      continue;     // keep going with the next one (if any) 
     } 
     fcntl(cfd, F_SETFL, 0);         // blocking mode 
     tcgetattr(cfd, &options);    // get the current port settings 
     options.c_cflag |= (CLOCAL | CREAD); // ena receiver, ignore modem lines 
     options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);  // raw, no echo 
     options.c_oflag &= ~OPOST;    // no special output processing 
     options.c_cc[VMIN] = 0;   // minimum number of raw read characters 
     options.c_cc[VTIME] = 10; // timeout in deciseconds (1 second timeout) 
     tcsetattr(cfd, TCSANOW, &options);    // set options right now 
     cfsetispeed(&options, B115200);      // input baud rate 
     cfsetospeed(&options, B115200);      // output baud rate 
     options.c_cflag &= ~(CSIZE | PARENB |  // clear size bits, no parity 
      CSTOPB | CRTSCTS);     // 1 stop bit, no hw flow control 
     options.c_cflag |= CS8;    // now set size: 8-bit characters 
     options.c_cflag &= ~(IXON | IXOFF | IXANY);  // no sw flow control 

     if (write(cfd, msgOut, 3) < 3) { 
      logIt(fprintf(lfd, "Sending of output message failed\n")); 
      close(cfd); 
      continue; 
     } 
     if (read(cfd, msgIn, 4) != 4) { 
      logIt(fprintf(lfd, "Didn't get expected amount of return data\n")); 
      close(cfd); 
      continue; 
     } 
     if (msgIn[3] != HOST_ID) { 
      logIt(fprintf(lfd, "Got the wrong HOST_ID response\n")); 
      close(cfd); 
      continue; 
     } 
     logIt(fprintf(lfd, "Port found - communication established\n")); 
     tapState = UP; 
     break;        // we're done - break out of the loop 
    } 
    fclose(ifd);       // close and remove the file we created 
    remove("usbSerial"); 
} 
+0

あなたのこのプログラム...それはどのようにWebサーバーに接続しますか? CGI? FastCGI?そのリンクが関連していない場合、Webサーバーがまったく関わっていないのはなぜですか? –

+0

libmicrohttpdパッケージは、接続ごとに1つのスレッドプールを使用し、プログラムの別の部分でコールバックを使用してWebページ情報を提供するCライブラリです。唯一の関連性は、私が上記の関数で行っていることがlibmicrohttpd操作を強制終了しているように見えるという事実です。これはLinuxでのC言語での私の最初の関与です。私は組み込みのハードウェアの方ですから、この問題を解決する方法については現在迷っています。 –

+0

グローバル変数(つまり、cfd)とスレッドは混在しません。あなたは競争状態のために自分自身を設定しているか、ここで悪化しています。それ以外に、[スレッドは悪い](https://www2.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-1.pdf)。私はあなたがGo、Node、または実際にはスレッドより上の抽象レベルを上げる何かを書く方が良いかもしれないと思う。 –

答えて

2

from within Code::Blocks the debugger is also hosed - once the program is started it cannot be paused or stopped

それはあなたがkillできないプログラムを作成しているよりも、あなたのツールを理解していないことをはるかに可能性があります。

これを理解するのは簡単です:分割して征服してください。ここには無関係のコンポーネントがたくさんあります。それらを分離して、どの作品が孤立してうまく動作し、他の作品から切り離されてもうまく動作し続けるかを確認します。それからあなたはあなたの犯人を持つでしょう。

具体的には、IDE経由でプログラムを実行してから、IDE経由でGDBの代わりにコマンドラインgdbを実行してみてください。

また、Webサーバーの部分を起動せずにプログラムを実行して、アプリケーションのシリアル部分を単独で実行できるようにする必要があります。これは、混乱する変数を最小限に抑えることによってデバッグするのに適しているだけでなく、疎結合のプログラム設計を奨励します。これは、それ自体が良いことです。

最後に、プログラムの停止を止めるのは、Webフレームワーク、Code :: Blocks、またはGDBがCode :: BlocksのPiで動作する方法です。 USBからシリアルアダプタ。あなたのプログラムがまだバックグラウンドで実行されている場合は、同じUSBポートを開こうとした場合

once the problem happens the only solution seems to be to reboot the Pi

は、その後、もちろん、あなたの次のインスタンスが失敗します。

$ sudo lsof | grep ttyACM 

か::推測しないでください

、見つける(。あなたのシステムがpidofを持っていない場合pgrepを代替)

$ lsof -p $(pidof myprogram) 

I've done things like this before using a scripting language (Tcl) but this time around I'm looking for a performance boost from a non-interpreted language

あなたのシリアルをポートは115,200 bpsで動作しています。それを10で割って停止ビットと開始ビットを考慮し、次に小数点をフリップして1バイトあたり秒を得ると、1バイトあたり87マイクロ秒になります。また、シリアルポートがフラットアウトしているときには、1秒あたり11,500バイトを送受信しているだけです。 Tclが87マイクロ秒で解釈できるコードの行数を推測しますか? Tclは超高速ではありませんが、Tclの土地でも87マイクロ秒が永遠です。

接続の反対側には、HTTPと[LAN]があり、1トランザクションあたり100ミリ秒程度の遅延が追加される可能性があります。

スピードの必要性は幻想です。

これらのうち100個を非同期に話す必要があるときにもう一度私に話しかけると、かもしれません。 TclよりCを正当化することができます。

(そして、私はその日の仕事シリアルおよびネットワークI/Oの多くを行う大規模なC++プログラムを維持する必要としてこれを言う。)

は今、このコードには多くの問題に取得することができます:

system("ls -l /dev/serial/by-path > usbSerial\n"); // get current port list 
ifd = fopen("usbSerial", "r"); 

パイプで十分な一時的な場所は使用しないでください。代わりにpopen()を使用してください。

while(1) { 

これは単に間違っています。ここではwhile (!feof(ifd)) {と答えてください。そうしないと、ファイルの最後を読み取ろうとします。

これは、次のエラーに加えて、あなたの主要な症状の鍵となりそうです。

if (fgets(line, 127, ifd) == NULL) { 
    break; 

いくつかの問題がここにあります

  1. あなたはドキュメントから従わない戻り値の意味についての事を想定しています。 The Linux fopen(3) man pageはこれについては非常に明確ではありません。 BSD versionが優れている:fgets()は、標準Cで、Linux-またはBSD固有ではないので

    The fgets() and gets() functions do not distinguish between end-of-file and error, and callers must use feof(3) and ferror(3) to determine which occurred.

    、他のシステムのマニュアルページを参照してくださいすることが一般的に安全です。さらに良い例は、Harbison & Steeleのような一般的なCリファレンスを参照してください。 (私はC++よりも、純粋なCをしていたとき、私はそれがはるかに便利なK &バックRよりも発見しました。)

    ボトムラインを、単にあなたがここに知る必要があるすべてを教えてくれないNULLをチェック。

  2. 二次的に、ハードコードされた127定数は、lineバッファのサイズを縮小すると、コードの爆弾が出るのを待っています。ここにsizeof(line)と言ってください。

    (いいえ、ありませんsizeof(line) - 1:。。慎重に、再びRTFMを読むときfgets()は、末尾のヌル文字のためのスペースを残して)

  3. breakも問題ですが、私たちはさらに下で取得する必要があります理由を確認するコード。

に移動:あなたは盲目的strlen(ptr) <= sizeof(port) - 6と仮定している

  1. :ここ

    strcat(port, ptr);    // append the "ttyACMn" part of the line 
    

    2つの問題。代わりにstrncat(3)を使用してください。

    (リテラル文字列をコピーしているので、前の行のstrcpy()は(strncpy()と対照的に)正当化されるので、あなたがバッファをオーバーランしていない、しかし、あなたがいることをふりをするのが習慣に取得すべきであることがわかります長さをチェックしない古いC文字列関数は存在しません。警告レベルを上げると、コンパイラによって実際に警告が出されます。

    またはCの文字列をあきらめて、 std::stringを使用して開始します。私はあなたがCに固執しようとしているのを見ることができますが、C++の自動メモリ管理機能(ほとんどstringだけでなく、auto_ptr/unique_ptr以上)を使用していても、このカテゴリに入る

    さらに、C++文字列はTcl文字列のように動作するので、おそらくもっと快適になります。

  2. コメント内の事実アサーションは、常にである必要があります。そうでないと、後で誤解を招く可能性があります。あなたの特定のUSB toシリアルアダプタは、/dev/ttyACMxを使用するかもしれませんが、すべてではありません。いくつかのシリアル-USBアダプタによって使用されるもう1つの共通のUSB device classが、Linuxの下にttyUSBxとして表示されます。より一般的には、将来の変更によってデバイス名が変更されることがあります。例えば、BSDに移植すると、今度はUSBへのシリアルデバイスは/dev/cu.usbserialと呼ばれ、15バイトのportバッファを吹き飛ばします。 想定しないでください。

    BSDケースを除いても、portバッファは、後者を前者に連結しているため、lineバッファより小さくすることはできません。場合によっては、sizeof(port)sizeof(line) + strlen("/dev/")になるはずです。それが過剰であると思われるのは、ラインバッファの128バイトが不必要に大きいからです。 (ない私はそれを変更するためにあなたの腕をねじるしようとしているRAMが安くなっている、プログラマのデバッグ時間が高価である。。)次

fcntl(cfd, F_SETFL, 0);         // blocking mode 

ファイルハンドルは、UNIXではデフォルトでブロックされています。には、ブロックされていないファイルハンドルとしてを入力する必要があります。とにかく、すべての旗を爆破するのは悪いスタイルです。あなたは何を知りませんフラグここで変更しています。適切なスタイルはずっとあなたがtcsetattr()でやっている方法のように、取得、変更、そして設定することです:

tcsetattr(cfd, TCSANOW, &options); 

int flags; 
fcntl(cfd, F_GETFL, &flags); 
flags &= ~O_NONBLOCK; 
fcntl(cfd, F_SETFL, flags); 

さて、あなたは正しくtcsetattr()を使用しての種類です。 .. tcsetattr()への2回目の呼び出しなしでoptionsへの更なる修正が行われた。おっとっと!

options構造の変更がシリアルポートにすぐに影響するという印象はありませんでしたか?ここで間違ったの

if (write(cfd, msgOut, 3) < 3) { 
    logIt(fprintf(lfd, "Sending of output message failed\n")); 
    close(cfd); 
    continue; 
} 

杭:

  1. あなたは短い書き込みとエラーケースが崩壊しています。それらを別々に扱う:

    int bytes = write(cfd, msgOut, 3); 
    if (bytes == 0) { 
        // can't happen with USB, but you may later change to a 
        // serial-to-Ethernet bridge (e.g. Digi One SP), and then 
        // it *can* happen under TCP. 
        // 
        // complain, close, etc. 
    } 
    else if (bytes < 0) { 
        // plain failure case; could collapse this with the == 0 case 
        // close, etc 
    } 
    else if (bytes < 3) { 
        // short write case 
    } 
    else { 
        // success case 
    } 
    
  2. あなたがエラーを取得するときに、あなたがどのエラー知ることができません、errnoまたはその対応する文字列をログに記録されていません(!):

    logIt(fprintf(lfd, "Sending of output message failed: %s (code %d)\n", 
          strerror(errno), errno)); 
    

    を好みに合わせて修正する。ほとんどの他のUnixシステムコールのように、write(2)には、考えられるエラーコードがたくさんあります。あなたはおそらく同じようにそれらのすべてを扱いたくありません。 (例:EINTR

  3. FDを閉じた後、有効なFD値に設定したままにしておくと、1行を読み込んだ後にEOFで有効なFD値を残します。 (これは上記の breakの問題です:閉じたFDを呼び出し側に暗黙的に返すことができます)。 close(cfd)コールごとに cfd = -1と言います。

についてwrite()の上に書かれたすべてのものは、次のread()コールに適用されますが、また:

if (read(cfd, msgIn, 4) != 4) { 

シリアルデバイスが4つのバイトを送信する場合は、すべて取得することことを示していますPOSIXで何もありません単一のread()の4バイト、ブロックされたFDの場合でも。シリアルポートと比較してプログラムが瞬時に高速であるため、遅いシリアルポートを持つread()ごとに1バイト以上を取得することは特にありません。ここでループ内でread()に電話する必要があります。エラーまたは完了時にのみ終了します。

そして、念のために、それは明らかにされていません。

remove("usbSerial"); 

あなたはpopen()に切り替える場合は、上記のことをする必要はありません。一時的な作業ファイルを、パイプが実行するファイルシステムに散らばらないようにしてください。

+0

非常に長い応答を提供するために時間を割いていただきありがとうございます。それを完全に理解することができます(私はこれでかなり新しいです)。しかし、Web上で私はすでに "sudo lsof | grep/dev/ttyACM"を見つけていましたが、(失敗した後に)それを実行したときに出力が全く生成されませんでした。なぜ私はTclに比べてCでこれをやっているのですか?2つの理由があります。まず、データロガーがどれくらいのシステム負荷を掛けるのか分かりませんが、それは学習体験です。私はLinuxでCでできることについてもっと学びたいと思っています。 –

+0

完了しましたが、私は十分な "評判"がまだカウントされていません。義務的な言葉:「正しいことをすることは経験から生まれ、経験は間違ったことから生まれる」。上記のコードからわかるように、今私は明らかに経験集めのモードになっています。 –

+0

@DavidHarper:Re経験、上記の批判のいずれかを値引きしないでください。それは厳しいように見えるかもしれませんが、私はこの種のプログラムを書いて何十年もあります。上の批判にはそれを裏付ける戦いの傷跡があります。 –

関連する問題