Kann ich delayMicroseconds genauer machen?


8

Ich versuche, DMX-Daten zu knacken, und das erfordert 4us-Impulse. Ich habe nicht viel Glück mit den Ergebnissen und überprüfe, wie gut der Arduino verzögert ... Scheint ziemlich schrecklich darin zu sein.

Hier ist ein kurzer kleiner Test, den ich gemacht habe:

unsigned long ptime;

void setup() {
  Serial.begin(9600);
}

void loop() {
  ptime = micros();
  delayMicroseconds(4);
  Serial.println(micros() - ptime);

  delay(1000); //just to keep the serial monitor from going nuts
}

Und die Ergebnisse:

8 4 8 4 4 4 4 4 8 8 8 8 4 8 8 4 4 8 4 4 8 8 8 8 4 8 4

Ich war ein bisschen schockiert darüber, wie schlecht die Genauigkeit ist. Es ist doppelt so viel Zeit, wie ich verzögern wollte, aber es stimmt nicht einmal damit überein, wo ich einfach durch 2 teilen könnte!

Kann ich irgendetwas tun, um korrekte und konsistente Ergebnisse zu erzielen?


Warum knallst du, anstatt direkt auf den UART zuzugreifen?
Ignacio Vazquez-Abrams

Ich kann also mehr als einen Ausgang haben.
Bwoogie

Der Mega hat vier UARTs. Selbst wenn Sie eine permanent für die Programmierung halten, erhalten Sie immer noch drei Universen.
Ignacio Vazquez-Abrams

Ich teste nur mit dem Mega, weil es das ist, was ich derzeit zur Verfügung habe. Das endgültige Projekt wird einen ATMEGA328
Bwoogie

1
Das Problem hier ist, wie Sie messen.
Gerben

Antworten:


7

Wie in den vorherigen Antworten erläutert, ist Ihr eigentliches Problem nicht die Genauigkeit von delayMicroseconds(), sondern die Lösung von micros().

Um Ihre eigentliche Frage zu beantworten , gibt es jedoch eine genauere Alternative zu delayMicroseconds(): Die Funktion _delay_us()von AVR-libc ist zyklusgenau und beispielsweise

_delay_us(1.125);

macht genau das, was es sagt. Die wichtigste Einschränkung ist, dass das Argument eine Konstante zur Kompilierungszeit sein muss. Sie müssen, #include <util/delay.h>um Zugriff auf diese Funktion zu haben.

Beachten Sie auch, dass Sie die Interrupts blockieren müssen, wenn Sie eine genaue Verzögerung wünschen.

Bearbeiten : Wenn ich beispielsweise einen 4-µs-Impuls auf PD2 (Pin 19 auf dem Mega) erzeugen würde, würde ich wie folgt vorgehen. Beachten Sie zunächst den folgenden Code

noInterrupts();
PORTD |= _BV(PD2);   // PD2 HIGH
PORTD &= ~_BV(PD2);  // PD2 LOW
interrupts();

macht einen 0,125 µs langen Impuls (2 CPU-Zyklen), da dies die Zeit ist, die benötigt wird, um den Befehl auszuführen, der den Port festlegt LOW. Fügen Sie dann einfach die fehlende Zeit verzögert hinzu:

noInterrupts();
PORTD |= _BV(PD2);   // PD2 HIGH
_delay_us(3.875);
PORTD &= ~_BV(PD2);  // PD2 LOW
interrupts();

und Sie haben eine zyklusgenaue Impulsbreite. Es ist anzumerken, dass dies mit nicht erreicht werden kann digitalWrite(), da ein Aufruf dieser Funktion etwa 5 µs dauert.


3

Ihre Testergebnisse sind irreführend. delayMicroseconds()tatsächlich Verzögerungen ziemlich genau (für Verzögerungen von mehr als 2 oder 3 Mikrosekunden). Sie können den Quellcode in der Datei /usr/share/arduino/hardware/arduino/cores/arduino/wiring.c (auf einem Linux-System oder auf einem ähnlichen Pfad auf anderen Systemen) überprüfen.

Die Auflösung micros()beträgt jedoch vier Mikrosekunden. (Siehe z. B. die Garretlab-Seite übermicros() .) Daher werden Sie niemals einen Messwert zwischen 4 Mikrosekunden und 8 Mikrosekunden sehen. Die tatsächliche Verzögerung beträgt möglicherweise nur einige Zyklen über 4 Mikrosekunden, Ihr Code meldet sie jedoch als 8.

Versuchen Sie, 10 oder 20 delayMicroseconds(4);Aufrufe hintereinander auszuführen (indem Sie den Code duplizieren, nicht mithilfe einer Schleife), und melden Sie dann das Ergebnis von micros().


Mit 10 Verzögerungen von 4 bekomme ich eine Mischung aus 32 und 28 ... aber 4 * 10 = 40.
Bwoogie

Was bekommen Sie zum Beispiel mit 10 Verzögerungen von 5? :) Beachten Sie auch, dass Sie für Bitbanging möglicherweise ziemlich direkte Portzugriffe verwenden müssen, dh nicht digitalWrite(), was mehrere Mikrosekunden dauert, um ausgeführt zu werden.
James Waldby - jwpat7

40 & 44 ... Aber sollte es nicht aufrunden? Was fehlt mir hier?
Bwoogie

Mit "Aufrunden" meinen Sie delayMicroseconds()? Ich sehe das nicht besser als abzurunden. ¶ In Bezug auf die Ursache der Ungenauigkeit hängt das Timing vom umgebenden Code ab, wenn die Routine inline wird. Sie können Montage- oder Demontagelisten lesen, um zu sehen. (Siehe "Erstellen von Baugruppenlisten" in meiner Antwort auf die Frage Äquivalent für PORTB in Arduino Mega 2560 , die ohnehin für Ihr Bitbang-Projekt relevant sein kann
James Waldby - jwpat7

2

Ich überprüfe, wie gut der Arduino in der Verzögerung ist ... Scheint ziemlich schrecklich darin zu sein.

micros()hat eine gut dokumentierte Auflösung von 4 µs.

Sie können die Auflösung verbessern, indem Sie den Vorteiler für Timer 0 ändern (das wirft natürlich die Zahlen aus, aber Sie können das kompensieren).

Verwenden Sie alternativ Timer 1 oder Timer 2 mit einem Prescaler von 1, wodurch Sie eine Auflösung von 62,5 ns erhalten.


 Serial.begin(9600);

Das wird sowieso langsam.


8 4 8 4 4 4 4 4 8 8 8 8 4 8 8 4 4 8 4 4 8 8 8 8 4 8 4

Ihre Ausgabe stimmt genau mit der Auflösung von 4 µs überein, micros()verbunden mit der Tatsache, dass Sie manchmal zwei "Ticks" und manchmal einen bekommen, je nachdem, wann Sie das Timing gestartet haben.


Ihr Code ist ein interessantes Beispiel für Messfehler. delayMicroseconds(4);verzögert sich um fast 4 µs. Ihre Versuche, es zu messen, sind jedoch schuld.

Wenn ein Interrupt auftritt, wird das Intervall etwas verlängert. Sie müssen Interrupts ausschalten, wenn Sie eine genaue Verzögerung wünschen.


1

Bei der Messung mit einem Oszilloskop stellte ich Folgendes fest:

delayMicroseconds(0)= delayMicroseconds(1)= 4 μs reale Verzögerung.

Wenn Sie also eine Verzögerung von 35 μs wünschen, benötigen Sie:

delayMicroseconds(31);

0

Kann ich irgendetwas tun, um korrekte und konsistente Ergebnisse zu erzielen?

Die Arduino-Implementierung ist recht allgemein gehalten und daher in einigen Anwendungen möglicherweise nicht so effektiv. Es gibt einige Möglichkeiten für kurze Verzögerungen, von denen jede ihre eigenen Mängel aufweist.

  1. Verwenden Sie nop. Jeder ist eine Anweisung, also 16. von uns.

  2. Verwenden Sie tcnt0 direkt. Jeder ist 4us, da der Prescaler auf 64 eingestellt ist. Sie können den Prescaker ändern, um eine Auflösung von 16 us zu erreichen.

  3. Verwenden Sie Ticks. Sie können einen Systick-Klon implementieren und als Grundlage für die Verzögerung verwenden. Es bietet eine feinere Auflösung und Genauigkeit.

bearbeiten:

Ich habe den folgenden Block verwendet, um die verschiedenen Ansätze zeitlich abzustimmen:

time0=TCNT0;
delay4us();             //65
//t0delayus(4*16);          //77
//_delay_us(4);             //65
//delayMicroseconds(4);     //45
time1=TCNT0 - time0;        //64 expected

Zuvor hatte ich den Timer0-Prescaler auf 1: 1 zurückgesetzt, sodass jeder TCNT0-Tick 1/16 Mikrosekunde beträgt.

  1. delay4us () wird aus NOP () aufgebaut; es erzeugte eine Verzögerung von 65 Zecken oder etwas mehr als 4us;
  2. t0delayus () wird aus timer0-Verzögerungen aufgebaut. es erzeugte eine Verzögerung von 77 Zecken; etwas schlechter als delay4us ()
  3. _delay_us () ist eine gcc-avr-Funktion. Leistung auf dem Niveau von delay4us ();
  4. delayMicroseconds () erzeugte eine Verzögerung von 45 Ticks. Die Art und Weise, wie Arduino seine Timing-Funktionen implementiert hat, neigt dazu, zu wenig zu zählen, es sei denn, in einer Umgebung mit anderen Interrupts.

ich hoffe es hilft.


Beachten Sie, dass das erwartete Ergebnis 65 Ticks und nicht 64 beträgt, da das Lesen TCNT01 CPU-Zyklus dauert.
Edgar Bonet
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.