Ich verwende Serial.write()
, um 53 Bytes an den PC zu übertragen. Für die Zeitmessung verwende ich micros()
(vor und nach der Schreibfunktion). Nach jeder Übertragung tritt eine Verzögerung von 1 s auf.
Die Zeit der Funktion Serial.write()
beträgt 532 us bei einer Baudrate von 1000000 und 360 us bei einer Baudrate von 9600 us.
Die Funktion Serial.write()
ist eindeutig asynchron, da die Übertragung von 53 Bytes mit einer Baudrate von 9600 53 * 8/9600 * 1e6 = 44167 us beträgt. (Bei einer Baudrate von 1000000 ist es übrigens nicht so offensichtlich, dass die Funktion asynchron ist.)
Ich verwende Serial.availableForWrite()
vor Serial.write()
, um zu bestätigen, dass genügend Speicherplatz im Puffer vorhanden ist (dies gibt jedes Mal 63 zurück, die Puffergröße ist Standard 64).
Ich verstehe diese Zahlen nicht memcpy()
zum Kopieren der 53 Bytes sind nur 32 us erforderlich. Ist das Kopieren in den seriellen Puffer nicht dasselbe wie die Funktion memcpy()
? Und warum? Es gibt einen Unterschied in den Kopierzeiten, wenn die Baudrate unterschiedlich ist. Serial.write () ist laut Ergebnissen mit höheren Baudraten sogar noch langsamer. Warum gibt Serial.availableForWrite()
63 zurück, während der Puffer Größe ist 64 (gemäß SERIAL_TX_BUFFER_SIZE)?
Update: Vielen Dank für all Ihre Antworten.
Ich habe eine andere Bibliothek für die serielle Kommunikation ausprobiert: https://github.com/greiman/SerialPort
Es scheint schneller zu sein als das Original. Ich verwende 2 tim Bei den Messungen und einer Verzögerung im Code stellen diese 3 DeltaTimes den gesamten Schritt wie folgt dar:
writeTime + memcpyTime + delaymicros = computedTime! = realTime
Die ersten 2 Zeiten werden gemessen. Das Delaymicros ist die theoretische Verzögerung (900 us), ich kann die Schrittzeit daraus berechnen. Diese berechnete Schrittzeit unterscheidet sich von realTime (die gemessene Schrittzeit, ich messe auch den gesamten Schritt). Es scheint, dass die zusätzliche Zeit in der Verzögerung gefunden werden kann.
SerialPort-Bibliothek: 100 + 30 + 900 = 1030! = 1350
writeReal = 100 + 1350 – 1030 = 430
Arduino Serial: 570 + 30 + 900 = 1500! = 1520
writeReal = 570 + 1520 – 1500 = 590
Dann habe ich die Delaymicros-Zeit gemessen (welche ist 900 us in der Theorie), die fehlende Zeit kann dort gefunden werden. Ich habe eine Verzögerung von 900 us programmiert, aber die tatsächliche Verzögerung lag im ersten Test bei 1200 und im zweiten Test bei 920.
Diese Messungen können das Vorhandensein der Interrupts beweisen, da das Messen nur der Schreibfunktionen nicht funktioniert Geben Sie die gesamte Schreibzeit an (insbesondere bei der heruntergeladenen seriellen Bibliothek). Die heruntergeladene Bibliothek kann schneller arbeiten, erfordert jedoch aufgrund von Fehlern einen größeren Sendepuffer (256 funktioniert ordnungsgemäß anstelle von 64).
Hier ist der Code : Ich verwende eine Prüfsumme in der Sendefunktion (570-532 = 38 us). Ich verwende Simulink, um die Daten zu empfangen und zu verarbeiten.
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; }
Kommentare
- Können Sie den Code angeben, mit dem Sie diese Ergebnisse erhalten haben?
- 1 Byte Puffer geht aufgrund der Ringpufferimplementierung verloren. Ein Kopf mit vollem Puffer kann ‚ nicht auf denselben Index zeigen, da sich ein Kopf auf demselben Index befindet Fall von leerem Puffer
An swer
Wenn Sie sich die Implementierung ansehen:
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; }
Es beantwortet im Grunde alle Fragen.
Wenn der Puffer leer ist und nichts sendet, sendet es das Zeichen direkt, damit es synchron ist (Das Senden des Zeichens ist jedoch viel schneller als der Aufwand für Funktionsrückrufe und so weiter.)
Nein, Sie können memcpy nicht verwenden, da es nur den Puffer ersetzt, aber nichts anderes tut, als das Datenregister tatsächlich fertig zu starten
Die Schreibfunktion wird für jedes Zeichen separat aufgerufen (alle anderen Spezialisierungen werden aufgerufen).
Und die Schreibfunktion wird für jedes Zeichen separat aufgerufen (alle anderen Spezialisierungen) of write verwenden dieses)
Auch wenn der Puffer voll ist, wird er warten, bis ein Leerzeichen für ein anderes Zeichen vorhanden ist.
Antwort
Das Kopieren in den seriellen Puffer ist nicht dasselbe wie
, Nr.
Der serielle Puffer ist ein kreisförmiger Puffer . Aus diesem Grund sind viele Berechnungen erforderlich, um genau herauszufinden, wo im Puffer das nächste Zeichen platziert werden soll. Das braucht Zeit. memcpy()
kopiert nur einen Speicherblock direkt über einen anderen. Es werden keine Überprüfungen durchgeführt und es kann nicht wie ein kreisförmiger Puffer herumlaufen.
Der Grund, warum höhere Baudraten langsamer erscheinen, liegt darin, dass jedes Zeichen durch einen Interrupt aus dem Puffer genommen wird.Je höher die Baudrate, desto häufiger wird dieser Interrupt ausgelöst. Je mehr Zeit die CPU für die Verarbeitung dieses Interrupts benötigt, um das nächste Zeichen zu senden. Ebenso steht der CPU weniger Zeit zur Verfügung, um das Ablegen von Daten in den seriellen Puffer zu verarbeiten.
Antwort
Die Funktion, die Sie getestet haben, ist vermutlich Serial.write(const uint8_t *buffer, size_t size)
. Wenn Sie in HardwareSerial.h danach suchen, werden
using Print::write; // pull in write(str) and write(buf, size) from Print
und angezeigt Die Implementierung befindet sich in 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; }
Hiermit werden die Bytes eins geschrieben um eins und für jedes Byte wird der gesamte Code von HardwareSerial::write(uint8_t c)
ausgeführt. Dies schließt die Tests für vollen oder leeren Puffer und die Arithmetik zum Aktualisieren von _tx_buffer_head
ein.
Wie im Kommentar über dem Code angegeben, wäre dies möglich gewesen Überschreiben Sie dies mit einer speziellen Implementierung. Im Prinzip können Sie den linearen Puffer mit höchstens zwei Aufrufen von memcpy()
in den Ringpuffer kopieren und _tx_buffer_head
nur einmal aktualisieren. Dies wäre wahrscheinlich effizienter als die aktuelle Implementierung, zumindest für Puffergrößen in dem von Ihnen verwendeten Bereich (nahe, aber weniger als die Ringpufferkapazität).
Wäre es das wert? Vielleicht für Ihren Anwendungsfall. Es könnte aber auch den Code komplexer machen und mehr Flash erfordern. Und der Nutzen ist wahrscheinlich sehr gering für Leute, die kleine Puffer schreiben. Ich bin nicht sicher, ob eine Pull-Anfrage, die diese Art der Optimierung implementiert, akzeptiert werden kann. Sie können es versuchen, wenn Sie Lust dazu haben.