Arduino Periodendauer messen

Hallo,

ich möchte mit folgendem Code die Periodendauer (in µs) einer Rechteckschwingung messen.
Es klappt soweit auch ganz gut, jedoch kommen in bestimmten Abständen Ausreißer die ich mir nicht ganz erklären kann.

volatile unsigned long T=0,last=0;
void setup () {
  Serial.begin(9600);
  pinMode(5,INPUT);
  attachInterrupt(5,TMessen,RISING);
}

void loop () 
{
  Serial.println(T);
}


void TMessen ()
{
  detachInterrupt(5);
  T=micros()-last;
  last=micros();
  attachInterrupt(5,TMessen,RISING);
}

Ausgabe (Rechtecksignal mit FG erzeugt,ca. 8000Hz) :

...
124
124
4294966420
124
124
124
1124
124
123
123
4294966420
124
124
124
124
124
124
124
124
124
124
124
124
123
...

Ist das der ganze Code?

Addi

So wie es aussieht, findet hier ein Variablenüberlauf statt: http://arduino.cc/en/Reference/UnsignedLong

Danke für die schnelle Antwort.
Gibt es eine Möglichkeit sich nicht immer auf die andauernd anwachsende micros-Zeit zu beziehen.
Z.B. einen Timer zu starten der wieder auf 0 gesetzt werden kann?

Wenn du das detachInterrupts / attachInterrupts weglässt, sollte micros() sich innerhalb deiner interrupt-Routine nicht ändern. Dann funktioniert die Differenz-Bildung auch bei Überlauf.

Oder du machst in TMessen:

unsigned long current = micros(); // nur einmal lesen
T=current-last;
last=current;

Edit: Aber eigentlich sollten mit deiner Methode nur ein paar micros fehlen ... Differenzbildung geht auch über den Überlauf hinweg richtig

Ok, habe jetzt den Code wie folgt verändert um den Einfluss des Überlaufs zu reduzieren:

volatile unsigned long T=0,last=0;

void setup () {
Serial.begin(9600);
pinMode(5,INPUT);
attachInterrupt(5,TMessen,RISING);
}

void loop () 
{
 Serial.println(T);
}

  
void TMessen ()
{
unsigned long current = micros(); 
if (current>last)
{T=current-last;}
last=current;
}

Allerdings treten noch immer einige "Störungen" auf:

...
125
125
125
9
125
125
125
125
125
...
125
1125
125
125
124
125
...

Kurze Zwischenfrage: Du sagst, dass das zu messende Signal 8000 kHz hat.
Mit t=125 µS=0,125ms=0,000125s ergibt sich f=1/t=8000Hz. Oder habe ich einen Denkfehler?

[Edit] Und mit welchem Arduino-Board experimentierst du? Pin 5 deutet auf einen Arduino Due hin, oder?

Ja, stimmt.
Woran könnten die Abweichungen liegen bzw. wie kann ich diese minimieren?

Selachii:
Ja, stimmt.
Woran könnten die Abweichungen liegen bzw. wie kann ich diese minimieren?

detachInterrupt(5);
...
attachInterrupt(5,TMessen,RISING);

Was sollten denn diese Aufrufe innerhalb der Interrupt-Routine bewirken, ausser das Interrupt-System völlig aus dem Takt zu bringen?

Und im übrigen, rechne bitte mal nach, wie oft Deine loop-Funktion läuft, wie viele Zeichen da wohl pro Sekunde ausgegeben werden sollen, wieviele Zeichen pro Sekunde bei der gesetzten Baudrate tatsächlich über die Schnittstelle rausgehen und wie oft pro Sekunde der serielle Sendepuffer wohl schätzungsweise überläuft!

Ändere Deine loop-Funktion mal auf:

void loop ()
{
Serial.println(T);
Serial.flush();
}

Und wie sieht es dann aus?

Erst einmal danke für die Antworten.
Leider besteht das Problem trotz „Serial.flush()“ weiterhin.

volatile unsigned long T=0,last=0;

void setup () {
Serial.begin(9600);
pinMode(5,INPUT);
attachInterrupt(5,TMessen,RISING);
}

void loop () 
{
 Serial.println(T);
 Serial.flush();
}

  
void TMessen ()
{
unsigned long current = micros(); 
if (current>last)
{T=current-last;}
last=current;
}

Selachii:
Leider besteht das Problem trotz „Serial.flush()“ weiterhin.

Leider stören sich auch regelmäßig die Timer-Interrupts beim Senden über die serielle Schnittstelle und die Rising-Interrupts Deiner Frequenz, da es ziemlich kleine gemeinsame Vielfache zwischen der Baudrate (9600) und der Frequenz (8000) gibt.

68000 = 48000
5
9600 = 48000

D.h. ein Konflikt tritt rein rechnerisch alle 48000 Schwingungen = ca. 6 Sekunden auf.

Kommt das ungefähr hin, ein Fehler alle ca. 6 Sekunden?
Oder mit welcher Häufigkeit gibt es die Ausreißer (circa) bei den Messwerten?

Blöde Frage, aber warum verwendest Du nicht
pulseIn()?
http://arduino.cc/en/Reference/PulseIn

Blöde Frage, aber warum verwendest Du nicht pulseIn()?

Weil das ein ganz anderer Ansatz ist ... ohne Interrupts ist zu einfach :wink:

Aber, was mir auffällt:
volatile unsigned long T;
darf eigentlich in loop() nicht "einfach so" verwendet werden, da der Zugriff auf die 4 byte von T von der Interrupt-Routine unterbrochen werden kann.

Rein theoretisch müsste es so aussehen:

void loop() 
{
 noInterrupts(); // oder cli();
 unsigned long tmp = T;
 interrupts();    // oder sei();
 Serial.println(tmp);
 Serial.flush();
}

Da aber der "richtige" Wert nur ca. 125 ist, spielt das hier keine Rolle und dein Effekt rührt wohl daher, dass Serial oder gar Timer-Interrupts manchmal deinen Interrupt 5 blockieren.

Da du ja sowieso asynchron zur Messung deine Ausgaben machst: Wie wäre es mit einer "statistischen" Auswertung ( max/min/avg ) oder sammeln eines Histogramms ( Anzahl Werte <123, 123, 124, 125, 126, 127 , >127 ) und Ausgabe erst nach der Messung ? Da brauchst du auch nicht so auf den Schirm zu starren, um Ausreisser zu sehen, bevor sie durchgerauscht sind.

In >100 µs hast du eigentlich Zeit genug ...

Benutze doch den Input Capture Interrupt. Schau Dir mal mein Beispiel hier an: Power Grid Monitor 2 | Blinkenlight.

Das mit dem Input Capture Interrupt scheint auf jedenfall sehr nützlich zu sein, da es hardwareseitig abläuft und dadurch nicht so schnell durch andere Interrupts gestört werden kann (... wenn ich das richtig verstanden habe).
Ist das Ganze in dieser Form auch auf den ARM basierenden DUE übertragbar oder sollte dazu das Datenblatt zu Rate gezogen werden?

Leider fange ich gerade erst an mich in solche µc-Themen einzulesen...

Deswegen auch die Frage:
Gibt es innerhalb der "Arduino-Sprache" die Möglichkeit hardwareseitig die ankommenden Impulse zu zählen und den Zähler dann im "Hauptprogramm" auszulesen und im Anschluss einen Reset durchzuführen (dazu die Zeit-Diff. mittels micros() ermitteln)?

Nein, wenn Du nach Hardware frägst, dann ist die Sprache schon aussen vor. Die Sprache heisst c++ und kann sowas nicht. Höchstens irgendwelche Libraries könnten sowas. Und nein, die UNO und die DUE Hardware sind nicht ausreichend kompatibel um sowas 1:1 zu übernehmen.