Miért lassabb a Serial.write (), mint a memcpy ()?

A Serial.write() funkcióval 53 bájtot továbbítok PC-re. Az időméréshez a micros() -t használom (az írási függvény előtt és után). Minden adás után 1 másodperces késés van.

A Serial.write() függvény ideje 532 minket 1000000 átviteli sebességgel és 360 minket 9600 átviteli sebességgel.

A Serial.write() függvény egyértelműen aszinkron, mert 53 bájt 9600 átviteli sebességgel történő továbbítása 53 * 8/9600 * 1e6 = 44167 minket. (Egyébként 1000000 baud sebesség esetén nem annyira nyilvánvaló, hogy a függvény aszinkron.)

A iv id előtt a Serial.availableForWrite() szót használom. = “7de0d4d647”>

annak megerősítésére, hogy van-e elegendő hely a pufferben (ez minden alkalommal 63-at ad vissza, a puffer mérete alapértelmezés szerint 64).

Nem értem ezeket a számokat. memcpy() az 53 bájt másolása csak 32-et vesz igénybe. A soros pufferre másolás nem azonos a memcpy() függvénnyel? És miért különbség van a másolási időkben, amikor az átviteli sebesség eltér? A Serial.write () az eredmények alapján még lassabb, ha nagyobb az adatátviteli sebesség. Miért adja vissza a Serial.availableForWrite() 63, míg a puffer mérete 64 (a SERIAL_TX_BUFFER_SIZE szerint)?

Frissítés: Köszönöm minden válaszát.

Kipróbáltam egy másik könyvtárat a soros kommunikációhoz: https://github.com/greiman/SerialPort

Úgy tűnik, gyorsabb, mint az eredeti. 2 tim-t használok A mérések és a kód késleltetése miatt ez a 3 deltaTime az egész lépést a következőképpen ábrázolja:

writeTime + memcpyTime + delaymicros = computedTime! = realTime

Az első 2 alkalommal mérünk, a delaymicros az elméleti késés (900 us), ezekből ki tudom számolni a lépésidőt. Ez a kiszámított lépésidő különbözik a realTime-től (a mért lépésidő, én is az egész lépést mérem). Úgy tűnik, hogy a további idő megtalálható a késésben.

SerialPort könyvtár: 100 + 30 + 900 = 1030! = 1350

writeReal = 100 + 1350 – 1030 = 430

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

writeReal = 570 + 1520 – 1500 = 590

Ezután megmértem a delaymicros időt (ami 900 elméletileg), a hiányzó idő ott található. 900 us késleltetést programoztam, de az első késés az elsőnél 1200 körül volt, a második tesztnél pedig 920 körül.

Ezek a mérések igazolhatják a megszakítások létét, mert csak az írási függvények mérése nem “t” adja meg az összes írási időt (különösen a letöltött soros könyvtár esetén). A letöltött könyvtár gyorsabban működhet, de a hibák miatt nagyobb tx puffert igényel (64 helyett 256 működik megfelelően).

Itt van a kód : CheckSum-ot használok a sendig függvényben (ami 570-532 = 38 us). A Simulink-et használom az adatok fogadására és feldolgozására.

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

Megjegyzések

  • Megadhatja a kódot, amellyel ezeket az eredményeket kapta?
  • 1 bájt puffer elvész a gyűrűs puffer megvalósítása miatt. A teljes puffer feje faraghat ‘ t ugyanabba az indexbe, mert a farok egy fej ugyanazon indexen van üres puffer esete

An swer

Ha megnézi a megvalósítást:

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

Alapvetően megválaszolja az összes kérdést.

Ha a puffer üres és nem küld semmit, akkor a karaktert közvetlenül elküldi, így szinkronizálja (de a karakter elküldése sokkal gyorsabb lesz, mint a függvényhívások fölött és így tovább)

Nem, nem használhatod a memcpy-t, mivel az csak a puffert helyettesíti, de semmi mást nem tesz, mint például az adatregiszter valójában készen indítása a fej / farok számlálók megszakítása és megfelelő beállítása (ez egy kerek puffer, így előfordulhat olyan eset, hogy felülírsz valamit a pufferen kívül)

És az írási funkciót külön-külön hívják meg az egyes karakterekhez (minden egyéb specializáció az írást használja ezt)

Ha a puffer megtelt, akkor “várni fog, amíg van hely egy másik karakter számára.

Válasz

A soros pufferbe másolás nem azonos a

, nem.

A soros puffer egy kör alakú puffer . Emiatt rengeteg számítás vesz részt annak meghatározásában, hogy pontosan hol található a pufferben a következő karakter elhelyezése. Ehhez idő kell. memcpy() csak átmásolja az egyik memóriablokkot közvetlenül a másikra. Nem végez ellenőrzést, és nem lehet körkörös pufferként körbefutni.

A magasabb adatátviteli sebesség lassabbnak tűnik, mert az egyes karaktereket egy megszakítás veszi ki a pufferből.Minél nagyobb az adatátviteli sebesség, annál gyakrabban vált ki ez a megszakítás, és így a CPU annál több időt tölt el a megszakítás feldolgozásával a következő karakter küldéséhez. Hasonlóképpen, annál kevesebb idő áll a CPU rendelkezésére az adatok soros pufferbe helyezésének feldolgozásához.

Válasz

A tesztelt funkció feltehetően Serial.write(const uint8_t *buffer, size_t size). Ha a HardwareSerial.h fájlban keresi, akkor megjelenik a

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

és a megvalósítás a Print.cpp fájlban található:

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

Ez a bájtokat írja egyenként és minden bájt esetén a HardwareSerial::write(uint8_t c) teljes kódja fut. Ide tartoznak a teljes vagy üres puffer tesztjei, valamint a _tx_buffer_head frissítésének számtana.

Amint a kód feletti megjegyzésben szerepel, lehetséges lett volna felülírja ezt egy speciális megvalósítással. Elvileg lemásolhatja a lineáris puffert a gyűrűs pufferbe, legfeljebb két hívást használva a memcpy() címre, és a _tx_buffer_head frissítést csak egyszer végezheti el. Ez valószínűleg hatékonyabb lenne, mint a jelenlegi megvalósítás, legalábbis az Ön által használt tartományban lévő pufferméreteknél (közel, de kevesebb, mint a gyűrűs pufferkapacitás).

Megérné? Talán a te esetedben ez lenne. De bonyolultabbá teheti a kódot és több flash-t is igényelhet. Az előny pedig valószínűleg kicsi azok számára, akik kis puffereket írnak. Nem vagyok biztos abban, hogy az ilyen típusú optimalizálást megvalósító húzási kérelem elfogadható. Megpróbálhatja, ha van kedve hozzá.

Vélemény, hozzászólás?

Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük