Geschwindigkeit digitalRead, digitalWrite

Hallo,

ich hatte ja neulich in einem Thread angekündigt, mal die Zeiten von digitalRead und digitalWrite zu messen. Da ich nun diesen Thread nicht weiter kapern wollte, hier mal meine Ergebnisse als eigener Thread.

Zuerst einmal für das direkte Laden eines Registers (LDI) und dann die Ausgabe (OUT) messe ich 130ns. Das liegt ziemlich nahe an den erwarteten 125ns für zwei Befehlstakte bei 16MHz. Gemessen habe ich mit einem Uno mit, wie schon geschrieben, 16MHz.

digitalWrite(7, HIGH) Pin ohne PWM 3,78µs
digitalWrite(7, LOW) 3,78µs
digitalWrite(6, HIGH) Pin mit PWM 4,74µs
digitalWrite(6, LOW) 4,62µs

Einen NOP messe ich mit 0,06µs - erwarten sollte man 62,5ns - ist also auch nahe dran.

Vielleicht ist das ja für den einen oder anderen interessant.

Gruß,
Ralf

2 Likes

Hallo,

es fehlt noch die Angabe wie und womit Du gemessen hast. :wink:

Schachmann:
digitalWrite(6, HIGH) Pin mit PWM 4,74µs
digitalWrite(6, LOW) 4,62µs

Wo kommen die 2 Takte Unterschied her ? Sehr interessant!

Danke!

Ansonsten nicht "nahe dran", sondern "offensichtlich richtig".
Das ist ja das Einfache mit Digitalen Prozessoren, dass die Zeit nur in ganzen Zahlen gemessen wird.
(0 oder 0,06 oder 0,13 oder 0,19 oder mehr µs, aber dazwischen gibts nichts, bei 16 MHz)

Doc_Arduino:
es fehlt noch die Angabe wie und womit Du gemessen hast. :wink:

Kommt sofort: Gemessen habe ich mit einem Siglent SDS1052DL Digital-Storage-Oscilloscope (dig. Speicher-Oszilloskop).

Wie: Indem ich zuerst in einem solchen Sketch (hier nur ein Bruchstück):

digitalWrite(pin13, LOW);
digitaleWrite(pin13, HIGH);

gemessen habe, wie lange der Pin braucht um von LOW nach HIGH zu wechseln und dann dieses Ergebnis subtrahiert habe von:

digitalWrite(pin13, LOW);
// Hier war dann der zu messende Befehl
digitalWrite(pin13, HIGH);

Den NOP habe ich als 'asm("nop\n\t");' eingefügt.

Gruß,
Ralf

michael_x:
Wo kommen die 2 Takte Unterschied her ? Sehr interessant!

Tja, wenn es kein Messfehler ist, weiß ich es ehrlich gesagt auch nicht. Aber vielleicht hat ja hier jemand eine passende Erklärung.

Gruß,
Ralf

[Edit:] Möglichweise wird im Compilat bei einem write...(HIGH) das entsprechende Bit einfach in das Ausgabe-Register reingeODERt und bei reinem write...(LOW) das entsprechende Register zusätzlich gelesen und dann das passende Bit ausmaskiert.

Schachmann:

michael_x:
Wo kommen die 2 Takte Unterschied her ? Sehr interessant!

Tja, wenn es kein Messfehler ist, weiß ich es ehrlich gesagt auch nicht. Aber vielleicht hat ja hier jemand eine passende Erklärung.

Das ergibt sich aus dem Quellcode der digitalWrite-Funktion: Es wird mit einer if-Abfrage erst auf LOW abgefragt und ggf. LOW gesetzt, und im else-Zweig wird dann erst HIGH gesetzt, wenn LOW nicht zutrifft.

if (val == LOW) {
  *out &= ~bit;
} else {
  *out |= bit;
}

Die Ausführung dauert unterschiedlich, weil beide Funktionen auf verschiedenen Logik-Zweigen einer if-Abfrage liegen, und LOW wird dabei zuerst abgefragt und ist daher schneller.

Hast Du mal meine Software-Zeitmessung zum Vergleich laufen lassen?
In meiner Messsoftware werden die Ausführungszeiten von Code mit drei Nachkommastellen auf Nanosekunden aufgelöst angezeigt.

jurs:
Das ergibt sich aus dem Quellcode der digitalWrite-Funktion: Es wird mit einer if-Abfrage erst auf LOW abgefragt und ggf. LOW gesetzt, und im else-Zweig wird dann erst HIGH gesetzt, wenn LOW nicht zutrifft.

Scheint mir plausibel. Wobei ich beim LOW und HIGH schreiben von Pins ohne PWM auf gleiche Zeiten komme. Aber so genau lässt sich der Cursor auf dem Oszi-Bildschirm auch nicht positionieren, ein Pixel vor oder zurück macht schon über 10ns in Delta-t aus.

jurs:
Hast Du mal meine Software-Zeitmessung zum Vergleich laufen lassen?

Ich habe Deinen Sketch (den ich übrigens richtig gut finde) natürlich auch laufen lassen, wollte da aber keine Zeitvergleiche anstellen, denn: 1. Dein Sketch misst ja mit dem gleichen Takt, mit dem er auch die Befehle ausführt. Wenn also die "Uhr" im Arduino abweichend tickt, bleibt das Messergebnis doch gleich. Und 2. wie genau die Uhr in meinem Oszi tickt, weiß ich garnicht. Ich nehme an, da wird wohl auch nur ein Quarz die Zeit bestimmen und die beiden Quarze können gegeneinander schon um ein paar PPM abweichen.

Aber, da Du fragst, beim HIGH schreiben von Pin 7 kommt Dein Sketch auf 3,836ms und meine Messung auf 3,78ms. Damit liegen die beiden Messungen nur rd. 50 Nanosekunden auseinander. Das finde ich schon erstaunlich gut - ehrlich gesagt hätte ich nicht gedacht, dass es so genau passt, sondern mit einer höheren Abweichung gerechnet.

Gruß,
Ralf

Schachmann:
Aber, da Du fragst, beim HIGH schreiben von Pin 7 kommt Dein Sketch auf 3,836ms und meine Messung auf 3,78ms. Damit liegen die beiden Messungen nur rd. 50 Nanosekunden auseinander. Das finde ich schon erstaunlich gut - ehrlich gesagt hätte ich nicht gedacht, dass es so genau passt, sondern mit einer höheren Abweichung gerechnet.

Wobei mein Sketch und Dein Oszilloskop zwei unterschiedliche Dinge messen:

Dein Oszi mißt die Ausführungsdauer von EINEM Ereignis.

Mein Sketch mißt (prinzipbedingt) die Ausführungsdauer von 10000 Ereignissen und teilt die gemessene Dauer durch 10000.

Wo liegt der Unterschied?

Wenn Du mit Deinem Oszi mißt, dann mißt Du als Ausführungsdauer bei 10000 Messungen geschätzte 9950 mal 3,78µs und geschätzte 50 mal ca. 7,78µs. Die längere Dauer kommt dann zustande, wenn der bei Arduino stets aktive Interrupt von Timer0 dazwischenhaut und seine Werte für millis() und micros() auf den aktuellen Stand bringt. Die kürzere Dauer kommt dann zustande, wenn kein Interrupt dazwischenkommt.

Wenn ich mit meinem Sketch messe, dann wird auf die tatsächliche Ausführungsdauer die durchschnittliche Verzögerung draufgeschlagen, die durch die zwischenzeitlichen Interrupts entstehen. D.h. was in 10000 Ereignissen an durchschnittlicher Zeit in Interrupts verbracht wird, wird auf meinen Messwert draufgeschlagen.

Deshalb sind die von meinem Sketch per Software gemessenen Zeiten auch einen Tick länger als Deine per Oszi ermittelten Zeiten: 3,836µs vs 3,78µs, und die Plusdifferenz ist (theoretisch) genau das, was zwischendurch an Zeit für die Ausführung von Interrupts im System draufgeht.

In der Praxis dürfte allerdings auch Deine Messung nicht ganz korrekt sein: Die gemessene Zeit müßte ja theoretisch ein Vielfaches der Taktfreqeuenz sein. Bei 16 MHz wären das 62,5ns pro Takt und 3780/62,5= 60,48 ist nicht annährend ganzzahlig, sondern liegt genau zwischen zwei Takten. Die "echte" Zeit dürfte daher eher entweder bei 60 Takten * 62,5ns = 3,75 µs oder bei 61 Takten * 62,5ns = 3,813 µs liegen. Denn "halbe Takte" führt der Atmega328 wohl nicht aus.

Was der "richtige" Wert ist, kannst Du ja bei Dir mal auskalibrieren:
Miß einmal aus, wie lange 60 NOP Befehle dauern (Soll: 3,75 µs).
Und vergleiche mal, wie lange 61 NOP Befehle brauchen (Soll: 3,81 µs).
Und dann siehst Du ja, was Dein Oszi tatsächlich mißt.

Interessant wäre jetzt noch der Vergleich mit digitalWriteFast aus dieser Lib Google Code Archive - Long-term storage for Google Code Project Hosting.

Hallo,

@ Schachmann
kannst Du nicht Deine Zeitbasis kleiner einstellen? Bei einem Röhrenoszi geht das bis 100ns/Teilung runter. Da man nur vielfache von 62,5ns sehen kann, sollte das ausreichen. Mich wunderte das Du gerundete 0,06µs angegeben hattest. Was hat den der Bildschirm für eine Auflösung in Pixel?

Doc_Arduino:
kannst Du nicht Deine Zeitbasis kleiner einstellen? Bei einem Röhrenoszi geht das bis 100ns/Teilung runter. Da man nur vielfache von 62,5ns sehen kann, sollte das ausreichen.
Was hat den der Bildschirm für eine Auflösung in Pixel?

Die Zeitbasis geht von 5ns bis 50s pro Div. Der Bildschirm hat 480 x 234 pixel. Darin werden hor. 18 Div und vert. 8 Div dargestellt.

Das Problem liegt aber nicht nur an der Zeiteinstellung, denn das zu messende Signal muss ja auf den Schirm (bzw. in den Speicher) passen, wenn ich den Anfang des Signals nicht mehr habe, kann ich nicht messen wie lange es bis zum Ende gedauert hat.

Gruß,
Ralf

jurs:
Was der "richtige" Wert ist, kannst Du ja bei Dir mal auskalibrieren:
Miß einmal aus, wie lange 60 NOP Befehle dauern (Soll: 3,75 µs).
Und vergleiche mal, wie lange 61 NOP Befehle brauchen (Soll: 3,81 µs).

Hallo jurs,

also vorab, ich habe das Oszi erst gestern bekommen, deswegen bin ich noch nicht besonders versiert in der Bedienung. Ich habe aber jetzt - nach Handbuch-Studium und Probieren - mal den Messaufbau so abgewandelt:

Die loop()-Schleife sieht so aus:

void loop() {
  while(true) {
    cli();
    digitalWrite(13,HIGH);
    // Hier der/die zu messende/n Anweisungen
    digitalWrite(13,LOW);
    sei();
  }
}

Getriggert wird vom steigenden Pegel an Pin 13. Gemessen wird über eine Messeinstellung namens "Pulsbreite+". Diese misst die Zeit von der steigenden bis zur fallenden Flanke des ersten Signals nach der Triggerung.

Da mein Uno mittlerweile anderweitig verkabelt ist, habe ich mit einem ATMega168 mit 16MHz (mit Quarz) experimentiert. Die Taktfrequenz habe ich gemessen: 15.9997MHz. Das entspricht einer Taktdauer von rd. 62,50117189ns/Takt. Da das SDS1052DL einen eingebauten Hardware-Frequenzzähler hat, denke ich, die Messung der Taktfrequenz ist ziemlich genau.

An die markierte Stelle im Sketch habe ich die zu messende Anweisung eingefügt. Leer, also ohne eine "Mess-Anweisung" benötigt der Sketch für einen Impuls 3,94µs = rd. 61 Takte.

Pin 7 High -> insg. 7,75µs - Leerlauf 3,94µs = 3,81µs = rd. 61 Takte
Pin 7 Low -> insg. 7,88µs - Leerlauf 3,94µs = 3,94µs = rd. 63 Takte
Pin 6 High -> insg. 8,56µs - Leerlauf 3,94µs = 4,62µs = rd. 74 Takte
Pin 6 Low -> insg. 8,69µs - Leerlauf 3,94µs = 4,75µs = rd. 76 Takte

Für 60 NOPs messe ich insg. 7.69µs - Leerlauf 3,94µs = 3,75µs = rd. 60 Takte
Für 61 NOPs 7,75µs - Leerlauf 3,94µs = 3,81µs = rd. 61 Takte

Da die Messung der NOPs in Verbindung mit den 15,9997MHz nahezu 100%ig passt, denke ich, dass auch die anderen Messungen rel. genau sind.

jurs:
Wenn Du mit Deinem Oszi mißt, dann mißt Du als Ausführungsdauer bei 10000 Messungen geschätzte 9950 mal 3,78µs und geschätzte 50 mal ca. 7,78µs.

Die obige Schleife leer, ohne das cli() und sei(), also mit eingeschalteten Interrupts habe ich mal durch die Pass/Fail-Messung überprüfen lassen. Hierbei wird getriggert und das gemessene Signal wird gegen ein vorgegebenes Signal verglichen. In diesem Fall sollte ein Fehler gezählt werden, wenn das Signal länger als 3,94µs +/- 2.5% dauert, wenn also ein Interrupt auftritt, der das Signal verlängert.

Bei 10.000 Durchläufen wurden 91 Interrupts gefunden. Allerdings ist dazu zu sagen, dass die Schleife mehr als 10.000 Mal durchlaufen wurde, da das Oszi nach dem Triggern misst, vergleicht, zählt und dann erst wieder bereit für den nächsten Trigger ist. Es finden also Schleifendurchläufe statt, die das Oszi nicht sieht, weil es mit Vergleichen beschäftigt ist. Außerdem weiß ich nicht, wie sicher das Siglent die Abweichungen findet. Aus dem Internet habe ich erlesen, dass es Oszilloskope gibt, die über 8 Stunden brauchen um eine einmal pro Sekunde stattfindende Abweichung von 1ms zu finden.

Der Interrupt verlängert das Schreiben von Pin 6 Low von 8,69µs gesamt auf 14,81µs.

Gruß,
Ralf

Schachmann:
Pin 7 High -> insg. 7,75µs - Leerlauf 3,94µs = 3,81µs = rd. 61 Takte
...
Für 61 NOPs 7,75µs - Leerlauf 3,94µs = 3,81µs = rd. 61 Takte

Also 61 Takte für ein digitalWrite(HIGH) an einem Nicht-PWM Pin.
Scheint so, als wenn Du den Bogen mit dem taktgenauen Messen nun raus hast.
Gratulation zum neuen Oszi!

rudirabbit:
Interessant wäre jetzt noch der Vergleich mit digitalWriteFast aus dieser Lib Google Code Archive - Long-term storage for Google Code Project Hosting.

Hallo,

das habe ich dann auch noch mal runtergeladen und probiert:

Mit pinModeFast() und digitalWriteFast() ergibt sich bei Pin 6 auf LOW schalten insgesamt 4,06µs/4,07µs - Leerlauf 3,94µs = 0,12µs (zwei Werte weil, die Anzeige springt hin- und her und kann sich nicht Recht entscheiden)

Wenn man also den Pin vorher kennt und es "eilig" hat, scheint das digitalWriteFast() sich zu lohnen. Ich habe übrigens die Interrupt-sichere Version genommen.

Gruß,
Ralf

jurs:
Scheint so, als wenn Du den Bogen mit dem taktgenauen Messen nun raus hast.

Es gibt sicherlich noch einiges zu Üben :slight_smile: Aber vor allem bei den NOPs war ich wirklich erstaunt, wie gut die Messung passt.

jurs:
Gratulation zum neuen Oszi!

Dankeschön!

Gruß,
Ralf