Hvorfor er Serial.write () langsommere end memcpy ()?

Jeg bruger Serial.write() til at sende 53 byte til pc. Til tidsmåling bruger jeg micros() (før og efter skrivefunktionen). Der er en forsinkelse på 1 sek efter hver transmission.

Tiden for Serial.write() -funktionen er 532 os med 1000000 baudhastighed og 360 us med 9600 baudhastighed.

Funktionen Serial.write() er klart asynkron, fordi transmission af 53 bytes med 9600 baudhastighed er 53 * 8/9600 * 1e6 = 44167 us. (Forresten, i tilfælde af 1000000 baudrate er det ikke så indlysende, at funktionen er asynkron.)

Jeg bruger Serial.availableForWrite() før Serial.write() for at bekræfte, at der er nok plads i bufferen (dette returnerer 63 hver gang, bufferstørrelsen er standard 64).

Jeg forstår ikke disse tal. memcpy() for at kopiere de 53 byte tager kun 32 os. Er kopiering til den serielle buffer ikke den samme som memcpy() -funktionen? Og hvorfor er der er en forskel i kopitiderne, hvor baudhastigheden er forskellig? Serial.write () er endnu langsommere med højere baudhastigheder i henhold til resultaterne. Hvorfor returnerer Serial.availableForWrite() 63, mens bufferen størrelse er 64 (ifølge SERIAL_TX_BUFFER_SIZE)?

Opdatering: Tak for alle dine svar.

Jeg har prøvet et andet bibliotek til seriel kommunikation: https://github.com/greiman/SerialPort

Det ser ud til at være hurtigere end den oprindelige. Jeg bruger 2 tim målinger og en forsinkelse i koden repræsenterer disse 3 deltaTimes hele trin som følger:

writeTime + memcpyTime + delaymicros = computedTime! = realTime

De første 2 gange måles, delaymicros er den teoretiske forsinkelse (900 us), jeg kan beregne trintiden ud fra disse. Denne beregnede trin tid er forskellig fra realTime (den målte trin tid, jeg måler også hele trinnet). Det ser ud til, at den ekstra tid kan findes i forsinkelsen.

SerialPort-bibliotek: 100 + 30 + 900 = 1030! = 1350

writeReal = 100 + 1350 – 1030 = 430

Arduino Serial: 570 + 30 + 900 = 1500! = 1520

writeReal = 570 + 1520 – 1500 = 590

Så målte jeg forsinkelsesmikro-tiden (som er 900 us i teorien), kan den manglende tid findes der. Jeg programmerede 900 us forsinkelse, men den virkelige forsinkelse var omkring 1200 i den første og 920 i den anden test.

Disse målinger kan bevise forekomsten af afbrydelser, fordi måling af kun skrivefunktionerne ikke “t giv al skrivetid (især med det downloadede serielle bibliotek). Det downloadede bibliotek kan arbejde hurtigere, men kræver større tx-buffer på grund af fejl (256 fungerer korrekt i stedet for 64).

Her er koden : Jeg bruger en checkSum i sendig-funktionen (som er 570-532 = 38 us). Jeg bruger Simulink til at modtage og behandle dataene.

 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; }  

Kommentarer

  • Kan du give den kode, som du brugte til at få disse resultater?
  • 1 byte af buffer går tabt på grund af ringbufferimplementering. hale et hoved af fuld buffer kan ‘ t pege på det samme indeks, fordi halen et hoved er på samme indeks i tilfælde af tom buffer

An svare

Hvis du ser på implementeringen:

 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; }  

Det svarer stort set på alle spørgsmål.

Hvis bufferen er tom, og den ikke sender noget, sender den karakteren direkte, så den er synkron (men afsendelse af tegnet vil være meget hurtigere end overhead af funktions tilbagekald og så)

Nej, du kan ikke bruge memcpy, da det bare erstatter buffer, men det gør intet andet, som faktisk at starte dataregistret klar afbryde eller korrekt indstilling af hoved- / haletællere (det er en rund buffer, så der kan være et scenario, du overskriver noget uden for bufferen)

Og skrivefunktion kaldes for hvert tegn separat (al anden specialisering af skrivning bruger denne)

Også hvis bufferen er fuld, venter den, indtil der er plads til et andet tegn.

Svar

Kopiering til den serielle buffer er ikke det samme som

, nr.

Den serielle buffer er en cirkulær buffer . På grund af det er der mange beregninger involveret i at finde ud af nøjagtigt hvor i bufferen der skal placeres det næste tegn. Det tager tid. memcpy() kopierer bare en blok hukommelse direkte over en anden. Det foretager ingen kontrol, og det kan ikke vikles rundt som en cirkulær buffer.

Årsagen til, at højere baudhastigheder synes langsommere, er fordi hvert tegn tages fra bufferen ved en afbrydelse.Jo højere baudhastigheden er, jo oftere udløses afbrydelsen, og jo mere tid bruger CPUen på at behandle den afbrydelse for at sende det næste tegn. Og ligeledes, jo mindre tid der er til rådighed for CPUen til at behandle placeringen af data i den serielle buffer.

Svar

Den funktion, du testede, er formodentlig Serial.write(const uint8_t *buffer, size_t size). Hvis du søger efter det i HardwareSerial.h, vil du se

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

og implementeringen er i 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; }  

Dette skriver bytes en én, og for hver byte køres hele koden for HardwareSerial::write(uint8_t c). Dette inkluderer test for fuld eller tom buffer og aritmetik til opdatering af _tx_buffer_head.

Som nævnt i kommentaren ovenfor koden, ville det have været muligt at tilsidesætte dette med en specialiseret implementering. I princippet kunne du kopiere den lineære buffer til ringbufferen ved højst at bruge to opkald til memcpy() og kun opdatere _tx_buffer_head. Det ville sandsynligvis være mere effektivt end den nuværende implementering, i det mindste for bufferstørrelser i det område, du bruger (tæt på, men mindre end ringbufferkapaciteten).

Ville det være det værd? Måske for din brugssag ville det. Men det kan også gøre koden mere kompleks og kræve mere flash. Og fordelen vil sandsynligvis være virkelig lille for folk, der skriver små buffere. Jeg er ikke sikker på, at en pull-anmodning, der implementerer denne form for optimering, muligvis accepteres. Du kan prøve, hvis du har lyst til det.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *