Serial.write()がmemcpy()より遅いのはなぜですか?

Serial.write()を使用して53バイトをPCに送信します。時間測定には、micros()(書き込み関数の前後)を使用します。送信ごとに1秒の遅延があります。

Serial.write()関数の時間は、1000000ボーレートで532 us、9600ボーレートで360usです。

Serial.write()関数は、9600ボーレートで53バイトの送信が53 * 8/9600 * 1e6 = 44167 usであるため、明らかに非同期です。 (ちなみに、ボーレートが1000000の場合、関数が非同期であることはそれほど明白ではありません。)

ividの前にSerial.availableForWrite()を使用します= “7de0d4d647”>

バッファに十分なスペースがあることを確認します(これにより、毎回63が返され、バッファサイズはデフォルトで64になります)。

これらの数値がわかりません。 memcpy()で53バイトをコピーするのに32usしかかかりません。シリアルバッファへのコピーはmemcpy()関数と同じではありませんか?なぜですか?ボーレートが異なる場合、コピー時間に違いがありますか?結果によると、Serial.write()はさらに遅く、ボーレートが高くなります。バッファ中にSerial.availableForWrite()が63を返すのはなぜですか。サイズは64です(SERIAL_TX_BUFFER_SIZEによる)?

更新:すべての回答に感謝します。

シリアル通信用に別のライブラリを試しました: https://github.com/greiman/SerialPort

元の速度よりも速いようです。2時間使用していますe測定とコードの遅延。これらの3つのdeltaTimeは、ステップ全体を次のように表します。

writeTime + memcpyTime + delaymicros = computeTime!= realTime

最初の2回が測定され、 delaymicrosは理論上の遅延(900 us)であり、これらからステップ時間を計算できます。この計算されたステップ時間はリアルタイムとは異なります(測定されたステップ時間、私もステップ全体を測定します)。追加の時間は遅延にあるようです。

SerialPortライブラリ:100 + 30 + 900 = 1030!= 1350

writeReal = 100 + 1350-1030 = 430

Arduinoシリアル:570 + 30 + 900 = 1500!= 1520

writeReal = 570 + 1520-1500 = 590

次に、delaymicros時間を測定しました(理論的には900usです)、不足している時間はそこで見つけることができます。 900 usの遅延をプログラムしましたが、実際の遅延は最初のテストで約1200、2番目のテストで920でした。

書き込み関数のみを測定することはできないため、これらの測定は割り込みの存在を証明できます。すべての書き込み時間を与えます(特にダウンロードされたシリアルライブラリの場合)。ダウンロードされたライブラリはより高速に動作しますが、エラーのためにより大きなtxバッファが必要です(64ではなく256が正しく動作します)。

コードは次のとおりです。 :sendig関数でcheckSumを使用します(570-532 = 38 us)。データの受信と処理にはSimulinkを使用します。

 struct sendMsg1 { byte errorCheck;//1 unsigned long dT1;//4 unsigned long dT2;//4 unsigned long t;//4 unsigned long plus[10];//40 };//53 sendMsg1 msg1; byte copyMsg1[53]; unsigned long time1; unsigned long time2; unsigned long time3; unsigned long time4; void setup() { Serial.begin(1000000); } void loop() { sensorRead(); sendMsg1_f(); //time3 = micros(); delayMicroseconds(900); //time4 = micros(); } void sensorRead() { time3 = micros(); msg1.t = micros(); for (unsigned long i = 0; i < 1; i++) { memcpy((void*)&(copyMsg1[i*sizeof(sendMsg1)]), (void*)&msg1, sizeof(sendMsg1)); } time4 = micros(); msg1.dT2 = time4 - time3; } void sendMsg1_f() { time1 = micros(); msg1.errorCheck = 0; for (int i = 0; i < sizeof(sendMsg1) - 1; i++) { msg1.errorCheck += ((byte*)&msg1)[i]; } Serial.write((byte*)&msg1,sizeof(sendMsg1)); time2 = micros(); msg1.dT1 = time2 - time1; }  

コメント

  • これらの結果を得るために使用したコードを提供できますか?
  • リングバッファの実装により、バッファの1バイトが失われます。フルバッファの先頭のテールは、同じインデックスを指すことができません。’テールの先頭は同じインデックス上にあるため、空のバッファの場合

swer

実装を確認する場合:

 size_t HardwareSerial::write(uint8_t c) { _written = true; // If the buffer and the data register is empty, just write the byte // to the data register and be done. This shortcut helps // significantly improve the effective datarate at high (> // 500kbit/s) bitrates, where interrupt overhead becomes a slowdown. if (_tx_buffer_head == _tx_buffer_tail && bit_is_set(*_ucsra, UDRE0)) { // If TXC is cleared before writing UDR and the previous byte // completes before writing to UDR, TXC will be set but a byte // is still being transmitted causing flush() to return too soon. // So writing UDR must happen first. // Writing UDR and clearing TC must be done atomically, otherwise // interrupts might delay the TXC clear so the byte written to UDR // is transmitted (setting TXC) before clearing TXC. Then TXC will // be cleared when no bytes are left, causing flush() to hang ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { *_udr = c; #ifdef MPCM0 *_ucsra = ((*_ucsra) & ((1 << U2X0) | (1 << MPCM0))) | (1 << TXC0); #else *_ucsra = ((*_ucsra) & ((1 << U2X0) | (1 << TXC0))); #endif } return 1; } tx_buffer_index_t i = (_tx_buffer_head + 1) % SERIAL_TX_BUFFER_SIZE; // If the output buffer is full, there"s nothing for it other than to // wait for the interrupt handler to empty it a bit while (i == _tx_buffer_tail) { if (bit_is_clear(SREG, SREG_I)) { // Interrupts are disabled, so we"ll have to poll the data // register empty flag ourselves. If it is set, pretend an // interrupt has happened and call the handler to free up // space for us. if(bit_is_set(*_ucsra, UDRE0)) _tx_udr_empty_irq(); } else { // nop, the interrupt handler will free up space for us } } _tx_buffer[_tx_buffer_head] = c; // make atomic to prevent execution of ISR between setting the // head pointer and setting the interrupt flag resulting in buffer // retransmission ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { _tx_buffer_head = i; sbi(*_ucsrb, UDRIE0); } return 1; }  

基本的にすべての質問に答えます。

バッファが空で何も送信しない場合は、文字を直接送信するため、同期されます。 (ただし、文字の送信は関数コールバックのオーバーヘッドよりもはるかに高速です)

いいえ、memcpyはバッファを置き換えるだけなので使用できませんが、実際にデータレジスタの準備を開始するなど、他には何もしません。割り込みまたはヘッド/テールカウンターの適切な設定(これはラウンドバッファーであるため、バッファー外の何かを上書きするシナリオが発生する可能性があります)

そして書き込み関数は文字ごとに個別に呼び出されます(他のすべての特殊化書き込みの数はこれを使用しています)

また、バッファがいっぱいの場合は、「別の文字用のスペースができるまで待機します。

回答

シリアルバッファへのコピーは

、いいえ。

シリアルバッファは循環バッファです。そのため、バッファ内のどこに次の文字を配置するかを正確に計算するために、多くの計算が必要になります。それには時間がかかります。 memcpy()は、メモリの1つのブロックを別のブロックに直接コピーするだけです。チェックを行わず、循環バッファのようにラップアラウンドすることはできません。

ボーレートが高くなると遅く見える理由は、各文字が割り込みによってバッファから取得されるためです。ボーレートが高いほど、その割り込みがトリガーされる頻度が高くなるため、CPUがその割り込みを処理して次の文字を送信するために費やす時間が長くなります。同様に、CPUがシリアルバッファへのデータの配置を処理するために利用できる時間も少なくなります。

回答

テストした関数は、おそらくSerial.write(const uint8_t *buffer, size_t size)です。 HardwareSerial.hで検索すると、

  using Print::write; // pull in write(str) and write(buf, size) from Print  

とが表示されます。実装はPrint.cppにあります:

 /* default implementation: may be overridden */ size_t Print::write(const uint8_t *buffer, size_t size) { size_t n = 0; while (size--) { if (write(*buffer++)) n++; else break; } return n; }  

これはバイト1を書き込んでいます1バイトごとに、HardwareSerial::write(uint8_t c)のコード全体が実行されています。これには、バッファがいっぱいまたは空であるかどうかのテスト、および_tx_buffer_headを更新するための演算が含まれます。

コードの上のコメントで述べたように、これを特殊な実装でオーバーライドします。原則として、memcpy()への最大2回の呼び出しを使用し、_tx_buffer_headを1回だけ更新することで、線形バッファーをリングバッファーにコピーできます。これは、少なくとも使用している範囲のバッファサイズ(リングバッファ容量に近いが、リングバッファ容量未満)では、現在の実装よりも効率的である可能性があります。

それだけの価値はありますか?多分あなたのユースケースのためにそうするでしょう。ただし、コードがより複雑になり、より多くのフラッシュが必要になる可能性もあります。また、小さなバッファを作成する人にとっては、メリットは非常に小さい可能性があります。この種の最適化を実装するプルリクエストが受け入れられるかどうかはわかりません。気になったら試してみてください。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です