0

MCU からデータを取得し、ファイルに保存してプロットしようとしています。コードはしばらくの間は正常に機能しますが、ランダムにハングアップします (1 秒後、1 分後に ...!)。また、シリアルポートのタイムアウトも考慮されていません。つまり、タイムアウト例外が発生していません。FTDI232RL チップを使用しています。タイムアウト例外が発生するのは、プログラムの実行中にプラグを抜いたときだけです。

コード:

private: System::Void START_Click(System::Object^  sender, System::EventArgs^  e) {
                 seconds=0;
                 minutes=0;
                 hours=0;
                 days=0;
                 t=0;


                 if((this->comboBox4->Text == String::Empty)||(this->textBox2->Text == String::Empty)||(this->textBox3->Text == String::Empty)){
                     this->textBox1->Text="please select port, save file directory and logging interval";
                     timer1->Enabled=false;

                 }





                 else{ // start assigning

                     w=Convert::ToDouble(this->textBox3->Text);
                     double q=fmod(w*1000,10);
                     if(q!=0){
                         MessageBox::Show("The logging interval must be a multiple of 0.01s");
                     }
                     else{
                         period=static_cast<int>(w*1000);
                         this->interval->Interval = period;
                         try{ // first make sure port isn't busy/open
                             if(!this->serialPort1->IsOpen){
                                 // select the port whose name is in comboBox4 (select port)
                                 this->serialPort1->PortName=this->comboBox4->Text;


                                 //open the port
                                 this->serialPort1->Open();


                                 this->serialPort1->ReadTimeout = period+1;
                                 this->serialPort1->WriteTimeout = period+1;

                                 String^ name_ = this->serialPort1->PortName;
                                 START=gcnew String("S");

                                 this->textBox1->Text="Logging started";
                                 timer1->Enabled=true;
                                 interval->Enabled=true;

                                 myStream=new ofstream(directory,ios::out);
                                 *myStream<<"time(ms);ADC1;ADC2;ADC3;ADC4;ADC5;ADC6;ADC7;ADC8;";
                                 *myStream<<endl;
                                 chart1->Series["ADC1"]->Points->Clear();
                                 chart1->Series["ADC2"]->Points->Clear();
                                 chart1->Series["ADC3"]->Points->Clear();
                                 chart1->Series["ADC4"]->Points->Clear();
                                 chart1->Series["ADC5"]->Points->Clear();
                                 chart1->Series["ADC6"]->Points->Clear();
                                 chart1->Series["ADC7"]->Points->Clear();
                                 chart1->Series["ADC8"]->Points->Clear();

                                 backgroundWorker1->RunWorkerAsync();

                             }
                             else
                             {
                                 this->textBox1->Text="Warning: port is busy or isn't open";
                                 timer1->Enabled=false;
                                 interval->Enabled=false;
                             }
                         }
                         catch(UnauthorizedAccessException^)
                         {
                             this->textBox1->Text="Unauthorized access";
                             timer1->Enabled=false;
                             interval->Enabled=false;
                         }
                     }

                 }
             }



private: System::Void backgroundWorker1_DoWork(System::Object^  sender, System::ComponentModel::DoWorkEventArgs^  e) {

                 while(!backgroundWorker1->CancellationPending){
                     if(backgroundWorker1->CancellationPending){
                         e->Cancel=true;
                         return;
                     }
                     t+=period;
                     if(t<10*period){
                         this->chart1->ChartAreas["ChartArea1"]->AxisX->Minimum=0;
                         this->chart1->ChartAreas["ChartArea1"]->AxisX->Maximum=t+10*period;
                     }
                     else {
                         this->chart1->ChartAreas["ChartArea1"]->AxisX->Minimum=t-10*period;
                         this->chart1->ChartAreas["ChartArea1"]->AxisX->Maximum=t+10*period;
                     }
                     *myStream<<t<<";";


                     for (int n=0;n<8;n++){
                         adc_array[n]= this->serialPort1->ReadByte();

                     }

                     Array::Copy(adc_array,ADC,8);

                     for(int f=0; f<8; f++){
                         *myStream<<ADC[f]<<";";
                     }

                     *myStream<<endl;

                     backgroundWorker1->ReportProgress(t);

                 }
             }


private: System::Void backgroundWorker1_ProgressChanged(System::Object^  sender, System::ComponentModel::ProgressChangedEventArgs^  e) {
                 chart1->Series["ADC1"]->Points->AddXY(t,ADC[0]);
                 chart1->Series["ADC2"]->Points->AddXY(t,ADC[1]);
                 chart1->Series["ADC3"]->Points->AddXY(t,ADC[2]);
                 chart1->Series["ADC4"]->Points->AddXY(t,ADC[3]);
                 chart1->Series["ADC5"]->Points->AddXY(t,ADC[4]);
                 chart1->Series["ADC6"]->Points->AddXY(t,ADC[5]);
                 chart1->Series["ADC7"]->Points->AddXY(t,ADC[6]);
                 chart1->Series["ADC8"]->Points->AddXY(t,ADC[7]);
         }

ユーザーは、データ取得の間隔を秒単位で定義できます (コードでは、この間隔は double に変換された後の w です)。この場合、プログラムは新しいデータ送信を要求するパルスを MCU に送信します。これまでのところ、これを 1 秒間隔でテストしてきました (各間隔で、MCU はそれぞれが ADC を表す 8 つのフレームを送信することに注意してください)。ただし、ある時点でこれを 10ms 間隔で実行する必要があります。これは可能でしょうか?冒頭で述べたいくつかの問題を解決する方法について何か考えはありますか?

前もって感謝します

アップデート

何が起こっているのかを理解してもらうために、チャートの部分にコメントを付けて、読み取り間隔を 1 秒にして、プログラムを約 5 分間実行しました。そのため、出力ファイルで約 5x60=300 の値が得られると予想していましたが、39 しか得られませんでした (つまり、1 秒から 39 秒まで)。プログラムはまだ実行されていましたが、データは保存されていませんでした。テストは、デバッグ モードではなくリリース モードで行われました。デバッグ モードで、serialport->readbyte() の下にブレーク ポイントを設定しても、問題は再現されません。私の推測では、プログラムと MCU の間のタイミングの問題です。

4

1 に答える 1

1

あなたはいくつかの標準的な間違いを犯しています。まず、ポートが開いているときにケーブルを抜かないでください。多くの USB エミュレーターはそれに対処する方法を知りません。FTDI ドライバーは特に悪名高いです。使用中にポートが消えるだけです。これにより、ポートを使用するコードは常に深刻な心臓発作を引き起こします。キャッチできない例外はよくあることです。

次に、ワーカー スレッドでスレッド セーフではないクラスのプロパティにアクセスしています。Chart コントロールは UI スレッドでのみ使用されるように作成されており、ワーカーで ChartAreas プロパティにアクセスすると、多くの悲惨な事態が発生します。スレッド要件に違反した場合に InvalidOperationException が発生するのは非常に一般的ですが、一貫して実装されているわけではありません。厄介さには、ランダムな AccessViolationExceptions、破損したデータ、およびデッドロックが含まれます。

第三に、あなたは完全に非現実的な目標を設定しています。10 ミリ秒ごとに更新を続けるのは無意味です。人間の目はそれを認識できません。50 ミリ秒を超えると、ぼやけてしまいます。映画館で映画を見るときに利用されるもので、毎秒 24 フレームで表示されます。その失敗モードも同様に不快です。最終的には、UI スレッド (または Chart コントロール) が処理しきれないほどの更新を大量に処理するところまで到達します。副作用として、UI 自体の描画が停止し、大量の呼び出し要求に追いつくのに忙しすぎます。そして、プログラムが消費するメモリの量が増加し続け、更新キューが際限なく増大します。最終的には OOM 例外で終了しますが、2 ジガバイトを消費するには時間がかかります。これが起こらないようにする必要があります。呼び出すレートを調整する必要があります。単純なスレッドセーフ カウンターでそれを処理できます。

4 つ目は、スレッド セーフを考慮せずに、複数のスレッドで収集したデータにアクセスしていることです。UI スレッドが読み取っている間に、ADC 配列の内容がワーカーによって変更されています。それによるさまざまな悲惨さ、最低でも悪いデータ。簡単な回避策は、データのコピーを ReportProgress メソッドに渡すことです。一般に、プッシュではなくプルを使用して、この種のスレッドの問題に対処します。UI スレッドを維持しようとするのではなく、UI スレッドに要求のペースを合わせさせることで、消防ホースの問題を取り除きます。

于 2013-02-13T14:28:12.127 に答える