Jeg bruker Serial.write()
for å overføre 53 byte til PC. For tidsmålingen bruker jeg micros()
(før og etter skrivefunksjonen). Det er en forsinkelse på 1s etter hver overføring.
Tiden til Serial.write()
-funksjonen er 532 oss med 1000000 baudrate, og 360 us med 9600 baud rate.
Serial.write()
-funksjonen er tydelig asynkron fordi overføringen av 53 byte med 9600 baudrate er 53 * 8/9600 * 1e6 = 44167 oss. (Forresten, når det gjelder 1000000 baudrate, er det ikke så åpenbart at funksjonen er asynkron.)
Jeg bruker Serial.availableForWrite()
før Serial.write()
for å bekrefte at det er nok plass i bufferen (dette returnerer 63 hver gang, bufferstørrelsen er standard 64).
Jeg forstår ikke disse tallene. memcpy()
for å kopiere 53 byte tar bare 32 oss. Er kopiering til seriell buffer ikke det samme som memcpy()
-funksjonen? Og hvorfor er det er en forskjell i kopitider når baudhastigheten er forskjellig? Serial.write () er enda langsommere med høyere baudhastigheter i henhold til resultatene. Hvorfor returnerer Serial.availableForWrite()
63 mens bufferen størrelse er 64 (i henhold til SERIAL_TX_BUFFER_SIZE)?
Oppdatering: Takk for alle svarene dine.
Jeg har prøvd et annet bibliotek for seriell kommunikasjon: https://github.com/greiman/SerialPort
Det ser ut til å være raskere enn den opprinnelige. Jeg bruker 2 timer målinger og en forsinkelse i koden, representerer disse 3 deltaTimes hele trinnet som følger:
writeTime + memcpyTime + delaymicros = computedTime! = realTime
De første to gangene blir målt, delaymicros er den teoretiske forsinkelsen (900 us), jeg kan beregne trinnet fra disse. Denne beregnede trinntiden er forskjellig fra realTime (den målte trinntiden, jeg måler også hele trinnet). Det ser ut til at den ekstra tiden kan bli funnet 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 delaymicros-tiden (som er 900 us i teorien), kan den manglende tiden bli funnet der. Jeg programmerte 900 us forsinkelse, men den virkelige forsinkelsen var rundt 1200 i den første, og 920 i den andre testen.
Disse målingene kan bevise forekomsten av avbrudd, fordi måling av bare skrivefunksjonene ikke gi all skrivetid (spesielt med det nedlastede serielle biblioteket). Det nedlastede biblioteket kan fungere raskere, men krever større tx-buffer på grunn av feil (256 fungerer ordentlig i stedet for 64).
Her er koden : Jeg bruker en checkSum i sendig-funksjonen (som er 570-532 = 38 us). Jeg bruker Simulink til å motta 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 oppgi koden du brukte for å få disse resultatene?
- 1 byte buffer går tapt på grunn av implementering av ringbuffer. hale et hode med full buffer kan ‘ t peke på samme indeks, fordi halen et hode er på samme indeks i tilfelle av tom buffer
An sv
Hvis du tar en titt 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; }
Den svarer i utgangspunktet på alle spørsmålene.
Hvis bufferen er tom og den ikke sender noe, vil den sende tegnet direkte slik at det er synkron (men å sende tegnet vil være mye raskere enn overhead av funksjon tilbakeringinger og så)
Nei, du kan ikke bruke memcpy, da det bare erstatter buffer, men det gjør ingenting annet, som å faktisk starte dataregisteret klart avbryte eller riktig innstilling av hode- / haleteller (det er rund buffer, så det kan være et scenario du overskriver noe utenfor bufferen)
Og skrivefunksjon kalles for hvert tegn separat (all annen spesialisering av skriv bruker denne)
Også hvis bufferen er full, vil den vente til det er plass til et annet tegn.
Svar
Kopiering til seriell buffer er ikke det samme som
, nr.
Den serielle bufferen er en sirkulær buffer . På grunn av det er det mange beregninger involvert i å finne ut nøyaktig hvor i bufferen du skal plassere neste karakter. Det tar tid. memcpy()
kopierer bare en blokk med minne direkte over en annen. Den gjør ingen kontroller, og den kan ikke vikles rundt som en sirkulær buffer.
Årsaken til at høyere baudhastigheter virker langsommere, er fordi hvert tegn blir tatt fra bufferen ved et avbrudd.Jo høyere overføringshastighet jo oftere blir avbrudd utløst, og jo mer tid bruker CPU på å behandle avbrudd for å sende neste tegn. Og like mye mindre CPU er tilgjengelig for å behandle plassering av data i den serielle bufferen.
Svar
Funksjonen du testet er antagelig Serial.write(const uint8_t *buffer, size_t size)
. Hvis du søker etter 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 byte-ene én, og for hver byte kjøres hele koden til HardwareSerial::write(uint8_t c)
. Dette inkluderer testene for full eller tom buffer, og aritmetikken for oppdatering av _tx_buffer_head
.
Som nevnt i kommentaren ovenfor koden, ville det vært mulig å overstyre dette med en spesialisert implementering. I prinsippet kan du kopiere den lineære bufferen til ringbufferen ved å bruke maksimalt to samtaler til memcpy()
, og bare oppdatere _tx_buffer_head
en gang. Det vil sannsynligvis være mer effektivt enn den nåværende implementeringen, i det minste for bufferstørrelser i området du bruker (nær, men mindre enn ringbufferkapasiteten).
Ville det være verdt det? Kanskje for din brukstilfelle. Men det kan også gjøre koden mer kompleks og kreve mer blits. Og fordelen vil sannsynligvis være veldig liten for folk som skriver små buffere. Jeg er ikke sikker på at en pull-forespørsel som implementerer denne typen optimalisering kan godtas. Du kan prøve hvis du har lyst til det.