Mikrosekunden - Genauigkeit

Hallo,

ich habe einen Arduino Uno. Für ein Projekt muss ich mit einer Genauigkeit von wenigen Mikrosekunden arbeiten. Wenn ich allerdings ein delayMicroseconds(6) mache, dann dauert dieser anscheinend über 30us bevor es weiter geht. Gibts eine alternative genauere Methode, ist mein Vorhaben gar nicht möglich oder stimmt vielleicht sogar etwas mit meinem Arduino nicht? Ich hoffe hier kann mit jemand helfen :)

Viele Grüße mowlthrow

  • Ein Arduino mit 16 MHz hat einen Takt von 0,0625 µs. Das ist die absolute Obergrenze.
  • Mit zwei digitalWrite() Aufrufen direkt hintereinander kann ein Puls einige µs lang dauern.
  • micros() hat eine Auflösung von 4 µs. (delayMicroseconds ist allerdings recht genau ab 3 µs)
  • Interrupte können die Ausführung von Befehlen in loop() unterbrechen.

30 µs ist allerdings viel. Das geht besser.

Abhilfe: Direkte Port Manipulationen sind mit ca. 0.1 µs möglich. Es gibt Hardware-Zähler / Timer, deren Geschwindigkeit einstellbar ist. Das ist dann mit direkter Hardware-Programmierung möglich. Kann auch in der Arduino IDE gemacht werden, ist nur nicht hier dokumentiert.

Willst du Zeiten messen oder Impulsdauern einstellen ?

Vileicht solltest Du uns den Sketch geben mit dem Du gemessen hast oder genauer beschreiben wie Du zu diesem Wert kommst. Grüße Uwe

mowlthrow: Wenn ich allerdings ein delayMicroseconds(6) mache, dann dauert dieser anscheinend über 30us bevor es weiter geht.

Wie stellst Du fest, wann es weiter geht?

mowlthrow: Gibts eine alternative genauere Methode

delayMicroseconds(6) hat eine Netto-Ausführungszeit von 6 µs, zuzüglich der Ausführungszeit, die von Interrupt-Behandlungsroutinen verbraucht werden für - Aufruf des Interrupts mit Sichern der Registerinhalte - Ausführung der Behandlungsroutine - Ende des Interrupts mit Wiederherstellung der Registerinhalte Aufgrund des Overheads sind das bei jedem Interrupt Minimum 4 µs für den Overhead beim Aufrufen und Ende.

Wenn Dein delayMicroseconds(6) tatsächlich 30 µs dauert, dann deutet das darauf hin, dass Du das System interruptmäßig bereits an die Wand gefahren hast, durch a) Interruptbehandlungen, die viel zu lange dauern und b) Interruptbehandlungen, die viel zu oft aufgerufen werden

Standardmäßig ist auf einem Arduino nur der Timer0-Interrupt aktiv, der wenn ich mich richtig erinnere, pro Millisekunde einmal mit einer Ausführungszeit von ca. 4 µs zu Buche schlägt. Also standardmäßig könnte einmal pro Millisekunde aus einem delayMicroseconds auch ein Delay von 6+4= 10 µs werden.

Wenn es bei Dir deutlich mehr ist, hast Du es mit den von Dir programmierten Interrupts vergurkt.

mowlthrow: ist mein Vorhaben gar nicht möglich oder stimmt vielleicht sogar etwas mit meinem Arduino nicht?

Gegensteuern kannst Du dadurch, dass während zeitkrititischer Phasen im Programm die Interrupts blockiert werden.

Also wenn Du einen zeitkritischen Code hast:

  noInterrupts();
  // zeitkritischer Code
  delayMicroseconds(6);
  interrupts();

Dann können Dir zwischen noInterrupts() und interrupts() keine Interrupts dazwischenschlagen. Aber dann laufen natürlich auch diese Interrupts nicht, was auch immer Du da über Interrupts am Laufen hast.

Wobei das natürlich nur "normalen" Code betrifft. Innerhalb einer Interrupt-Behandlungsroutine sind die Interrupts ja sowieso gesperrt und weil Interrupts schnell behandelt werden, sollte man in Interruptbehandlungen auch überhaupt kein delayMicroseconds verwenden.

Zum Schalten von Ausgängen unabhängig vom Programmlauf lassen sich Timer/Zähler programmieren. Siehe z.B. Erzeugung von PWM: PWM wird direkt aus dem Systemtakt im Controller erzeugt, und unabhängig von Interruptaufrufen im Programm immer taktrichtig geschaltet.

Vielen Dank für die ganzen Antworten. Ich bin noch ein Neuling in der Materie. Den Code mit dem ich die extreme Ungenauigkeit hinbekommen hatte, habe ich leider nicht mehr, war wohl aber ein grober Schnitzer drin.

Ein neuer Test mit

unsigned long startZeit = micros(); digitalWrite(8,HIGH); delayMicroseconds(4); digitalWrite(8,LOW); unsigned long endZeit = micros(); Serial.println(endZeit-startZeit);

ergab glaube ich einen Unterschied von ca. 10µs (wenn ich mich recht erinnere). Das ganze zeigt aber, dass ich mit dieser Vorgehensweise sowie nicht an das gewünschte Ergebnis komme.

Was ich vorhabe sind die Werte eine SNES Controllers (Spielekonsole) auszulesen. Dazu muss ich für 12µs ein HIGH auf Pin 3 schicken. Nach 6µs muss ich dann 16 Mal hintereinander ein 6µs HIGH und 6µs LOW auf den Pin 2 schickt. Pro mal kann ich dann von Pin 4 den Status des entsprechenden Knopfes abfragen (also bis zu 16 Knöpfe).

Wie kann ich soetwas realisieren?


Every 16.67ms (or about 60Hz), the SNES CPU sends out a 12us wide, positive going data latch pulse on pin 3. This instructs the ICs in the controller to latch the state of all buttons internally. Six microsenconds after the fall of the data latch pulse, the CPU sends out 16 data clock pulses on pin 2. These are 50% duty cycle with 12us per full cycle. The controllers serially shift the latched button states out pin 4 on every rising edge of the clock, and the CPU samples the data on every falling edge.

Each button on the controller is assigned a specific id which corresponds to the clock cycle during which that button's state will be reported. The table in section 4.0 lists the ids for all buttons. Note that multiple buttons may be depressed at any given moment. Also note that a logic "high" on the serial data line means the button is NOT depressed.

At the end of the 16 cycle sequence, the serial data line is driven low until the next data latch pulse.

Du kannst mal statt digitalWrite() diese Library verwenden: https://code.google.com/p/digitalwritefast/downloads/list

Und schon dauert digitalWrite keine 4µs mehr sondern nur ein paar Takte. Das gleiche mit digitalRead()

mowlthrow:
ergab glaube ich einen Unterschied von ca. 10µs (wenn ich mich recht erinnere). Das ganze zeigt aber, dass ich mit dieser Vorgehensweise sowie nicht an das gewünschte Ergebnis komme.

Du solltest dich, wie hier schon erwähnt wurde, von digitalWrite() verabschieden und die Ports direkt ansprechen, das geht wesentlich schneller: http://www.arduino.cc/en/Reference/PortManipulation

[edit] …oder aber den Tipp von Serenifly befolgen :wink:

mowlthrow: Was ich vorhabe sind die Werte eine SNES Controllers (Spielekonsole) auszulesen. ... Wie kann ich soetwas realisieren?

Wenn Du absolut Null Ahnung hast am einfachsten dadurch, dass Du Dir Code von anderen abguckst, die das schon mal gemacht und ihren Code veröffentlich haben.

Mal kurz gegoogelt dund das gefunden, wo Du Dir den controllerRead() Code rausziehen könntest:

http://forum.arduino.cc/index.php?topic=8481.0

Wenn Du enge Mikrosekunden-Timings einhalten möchtest, mußt Du Dir beispielsweise auch darüber im klaren werden, dass jedes "digitalWrite" auch schon vier bis fünf Mikrosekunden dauert.

Wenn Du schneller schalten möchtest, kannst Du digitalWrite vergessen und mußt die Controller-Register "direkt" programmieren. Aber das ist eher ein Thema für Fortgeschrittene und nicht für Anfänger.

Vielen Lieben Dank an alle. Ich werde mir das ganze heute Abend mal angucken. Ich denke das hilft mir alles sehr weiter :) Zur Not kann ich ja auch auf den fertigen Code zurückgreifen :)

Meinst Du Mikrosekunden Auflösung oder wirklich Genauigkeit? Wenn Du Genauigkeit meinst, dann vergiss es. Arduino hat "out of the box" ~30 ppm Genauigkeit und der neue Uno so ~1000-5000 ppm. D.h. bereits nach 1s bist Du immer ein paar Mikrosekunden bzw. neuerdings Millisekunden (!!!) nebendran. Mit genügend viel Aufwand kannst Du einen Quarzbasierten Arduino immerhin auf unter 1 ppm bringen (http://blog.blinkenlight.net/experiments/dcf77/autotune/) Aber auch das reicht nur für ein paar Sekunden.

Wenn Du hingegen nur Mikrosekunden Auflsung brauchst, dann reicht direkte Portmanipulation wie oben beschrieben aus.

Er meint natürlich Genauigkeit. Siehe reply #4. Ein 6µs Puls sollte schon (denke ich mal) auf 1 µs genau sein, und das möglichst über mehrere Pulse. Also ein Fehler bis ca. 5%. Tausend ppm mit Arduino Resonator erlauben schon noch die gewünschte Genauigkeit.

Wenn du kleinkariert zwischen Genauigkeit und Auflösung unterscheidest, solltest du bedenken, dass Genauigkeitsangaben in physikalischen Einheiten eigentlich einen Bezugswert brauchen.

Sorry :wink:

Ein Beispiel wie weit man kommen kann, wenn man's kann, ist die fastSPI Bibliothek zur Ansteuerung von WS2811 bzw WS2812 LED. Die brauchen als Ansteuerung eine Rechteckspannung von 800kHz (das sind 1,25µS Periodendauer) mit einer Genauigkeit der LOW bzw HIGH-Zeiten von 0,15µS. Der 16MHz Arduino hat eine Periodendauer des Taktsignals von 0,0625µS Das wird durch Assemblercode gelöst. Grüße Uwe

Einfache Delays mit sub-µs Auflösung kann man sehr einfach durch inline Assembler Makros realisieren. Man definiert z.B. erst mal ein Makro das 4 NOPs macht (no operation), d.h. 4 * 62,5ns = 250ns. Wenn man dann ein Makros erstellt, das dieses Makro 2 mal aufruft hat man 500ns. usw....

Serenifly: Einfache Delays mit sub-µs Auflösung kann man sehr einfach durch inline Assembler Makros realisieren. Man definiert z.B. erst mal ein Makro das 4 NOPs macht (no operation), d.h. 4 * 62,5ns = 250ns. Wenn man dann ein Makros erstellt, das dieses Makro 2 mal aufruft hat man 500ns. usw....

Nicht ganz. Du mußt auch die Zeit zum Aufführen der Aufrufe mitrechenen.

Grüße Uwe

Zeit für Funktions-Aufrufe gibt es bei Makros nicht. Das ist ja generell das schöne an denen. Man müsste sich mal den disassemblierten Code ansehen, aber eigentlich sollten die NOPs direkt an der Stelle eingefügt werden.

Man muss natürlich noInterrupts() oder cli() aufrufen und bald danach wieder freigeben. Nur dazwischen ist man dann auf 0.1µs genau. Das sind aber auch Makros und keine kompletten Funktionsaufrufe.

binn gerade an nem ähnlichen experiment... aber mit nem n64 controller

http://www.instructables.com/id/Use-an-Arduino-with-an-N64-controller/#step1

kuck dir den code mal an und experimentier am besten mal damit wende nen N64 controller daheim rumliegen hast

was ich bisher gelernt hab ist //hat natürlich auch keinen anspruch auf richtigkeit

der Microkontroller versteht natürlich nur nuller und einser (maschinensprache)

diese maschienensprache kan mann mit assembler sprache ein bisschen besser verstehen (sieht weniger abstrakt aus)

also jedes assembler wort hat nen bestimmten wert von Nuller und einser und wird mit jedem takt weitergegeben

bei diesem C mit dem wir den arduino programmieren werden bei jedem befehl mehrere assembler befehle vereint (weshalb die dann auch mal mehr zeit als nen takt brauchen)

dieser Herr Andrew Brown hat über void ein paar funktionen generiert die er dann zwischen nointerrupts() und interrupts() aufruft

und in den funktionen sagter gib ein signal wart nen takt und noch einen und noch einen... mach was anderes

Wenn man absolut gar keinen Plan hat: einfach mal die Fresse halten.

mit der N64_arduino.zip kriegste aufjedenfall den status vom N64 controller auf den Serielen monitor