Proč je Serial.write () pomalejší než memcpy ()?

K přenosu 53 bajtů do počítače používám Serial.write(). Pro měření času používám micros() (před a po funkci zápisu). Po každém přenosu je zpoždění 1 s.

Čas funkce Serial.write() je 532 us s 10 000 baudovou rychlostí a 360 us s 9600 baudovou rychlostí.

Funkce Serial.write() je jasně asynchronní, protože přenos 53 bajtů s rychlostí 9600 baudů je 53 * 8/9600 * 1e6 = 44167 us. (Mimochodem, v případě přenosové rychlosti 10 000 000 není tak zřejmé, že je funkce asynchronní.)

Používám Serial.availableForWrite() před Serial.write() k potvrzení, že ve vyrovnávací paměti je dostatek místa (při každém se vrátí 63, výchozí velikost vyrovnávací paměti je 64).

Nerozumím těmto číslům. Pomocí memcpy() kopírování 53 bajtů zabere pouze 32 us. Není kopírování do sériové vyrovnávací paměti stejné jako funkce memcpy()? A proč je existuje rozdíl v dobách kopírování, když je přenosová rychlost odlišná? Serial.write () je podle výsledků ještě pomalejší s vyšší přenosovou rychlostí. Proč Serial.availableForWrite() vrátí 63, zatímco vyrovnávací paměť velikost je 64 (podle SERIAL_TX_BUFFER_SIZE)?

Aktualizace: Děkuji za všechny vaše odpovědi.

Zkoušel jsem jinou knihovnu pro sériovou komunikaci: https://github.com/greiman/SerialPort

Zdá se, že je rychlejší než ten původní. Používám 2 tim Měření a zpoždění v kódu, tyto 3 deltaTimes představují celý krok takto:

writeTime + memcpyTime + delaymicros = computedTime! = realTime

Jsou měřeny první 2 časy, delaymicros je teoretické zpoždění (900 us), mohu z nich vypočítat čas kroku. Tento vypočítaný čas kroku se liší od realTime (měřený čas kroku, měřím také celý krok). Zdá se, že další čas najdete ve zpoždění.

Knihovna SerialPort: 100 + 30 + 900 = 1030! = 1350

writeReal = 100 + 1350 – 1030 = 430

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

writeReal = 570 + 1520 – 1500 = 590

Poté jsem změřil delaymicros čas (který teoreticky je 900 nás), chybějící čas najdete tam. Naprogramoval jsem zpoždění 900 nás, ale skutečné zpoždění bylo kolem 1200 v prvním a 920 ve druhém testu.

Tato měření mohou prokázat existenci přerušení, protože měření pouze funkcí zápisu „t“ uveďte veškerý čas pro zápis (zejména se staženou sériovou knihovnou). Stažená knihovna může pracovat rychleji, ale kvůli chybám vyžaduje větší vyrovnávací paměť tx (256 funguje správně místo 64).

Zde je kód : Používám funkci checkSum ve funkci sendig (což je 570-532 = 38 us). Pro příjem a zpracování dat používám 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; }  

Komentáře

  • Můžete poskytnout kód, který jste použili k získání těchto výsledků?
  • 1 bajt vyrovnávací paměti je ztracen kvůli implementaci kruhové vyrovnávací paměti. tail an head of full buffer can ‚ t point at the same index, because tail an head are on same index in případ prázdné vyrovnávací paměti

An swer

Pokud se podíváte na implementaci:

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

V podstatě odpovídá na všechny otázky.

Pokud je vyrovnávací paměť prázdná a nic neposílá, odešle znak přímo, takže je synchronní (ale odeslání znaku bude mnohem rychlejší než režie zpětného volání funkce atd.)

Ne, nemůžete použít memcpy, protože jen nahradí vyrovnávací paměť, ale nedělá nic jiného, jako kdyby byl vlastně spuštěn datový registr připraven přerušení ani správné nastavení čítačů hlavy / ocasu (je to kulatá vyrovnávací paměť, takže může existovat scénář, kdy přepíšete něco mimo vyrovnávací paměť)

A funkce zápisu je volána pro každý znak zvlášť (všechny ostatní specializace of write are using this one)

Také pokud je vyrovnávací paměť plná, bude „čekat, dokud nebude prostor pro další znak.

Odpovědět

Kopírování do sériové vyrovnávací paměti není stejné jako

, č.

Sériová vyrovnávací paměť je kruhová vyrovnávací paměť . Z tohoto důvodu existuje spousta výpočtů zapojených do vypracování přesně tam, kde do vyrovnávací paměti umístit další znak. To vyžaduje čas. memcpy() pouze zkopíruje jeden blok paměti přímo přes jiný. Neprovádí žádné kontroly a nemůže se obejít jako kruhová vyrovnávací paměť.

Důvodem, proč se vyšší přenosová rychlost zdá pomalejší, je to, že každý znak je z vyrovnávací paměti převzat přerušením.Čím vyšší je přenosová rychlost, tím častěji se toto přerušení spouští, a tím více času CPU stráví zpracováním tohoto přerušení k odeslání dalšího znaku. A stejně tak CPU má méně času na zpracování umisťování dat do sériové vyrovnávací paměti.

Odpověď

Funkce, kterou jste testovali, je pravděpodobně Serial.write(const uint8_t *buffer, size_t size). Pokud ji vyhledáte v souboru HardwareSerial.h, uvidíte

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

a implementace je v 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; }  

Toto je psaní bajtů o jeden a pro každý bajt se spouští celý kód HardwareSerial::write(uint8_t c). To zahrnuje testy plné nebo prázdné vyrovnávací paměti a aritmetiku pro aktualizaci _tx_buffer_head.

Jak je uvedeno v komentáři nad kódem, bylo by možné přepsat to specializovanou implementací. V zásadě můžete lineární vyrovnávací paměť zkopírovat do kruhové vyrovnávací paměti pomocí maximálně dvou volání memcpy() a aktualizace _tx_buffer_head pouze jednou. To by pravděpodobně bylo účinnější než současná implementace, alespoň pro velikosti vyrovnávací paměti v rozsahu, který používáte (téměř, ale menší než kapacita kruhové vyrovnávací paměti).

Stálo by to za to? Možná pro váš případ použití by to bylo. Mohlo by to ale také udělat kód složitějším a vyžadovat více blesku. A výhoda bude pravděpodobně opravdu malá pro lidi, kteří píší malé vyrovnávací paměti. Nejsem si jistý, zda může být přijat požadavek na vytažení implementující tento druh optimalizace. Můžete zkusit, pokud na to máte chuť.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *