Warum ist der exteren Interrupt so langsam?

Hallo,

ich habe fogende Problemstellung: Mein Arduino Board ist das Mega2560 womit ich einen Glasmaßstab auslesen möchte. Der Glasmaßstab mit einer Länge von ca. 19cm gibt zwei Rechtecksignale die um 90° versetzt sind aus. Die Periodenlänge eines Rechtecksignals ist T=1um. Wenn ich nun jede Flanke auswerte bekomme ich eine Auflösung (Flanke zu Flanke) von 0,25um.
Mit jedem Rechtecksignal gehe ich auf einen exteren Interrupt des Arduino und lasse bei jedem Interrupt eine Variable hochzählen. Diese möchte ich dann über den virtuellen seriellen Port (USB) (erst mal mit dem Serial Monitor, später über LabVIEW) ausgeben. Wenn ich den Glasmaßstab mit 1cm/s verfahre erhalte ich ja bei einen Falankenabstan von 0,25um alle 25us einen Interrupt (0,25um Flankenabstand -> 1cm Weg ->40000 Flanken -> 40kHz -> 0,25us).
Der Atmega2560 ist mit 16MHz getaktet, also ist ein Taktzyklus 62,5ns bzw. alle 62,5ns kann ein Maschinenbefehl ausgeführt werden. Das ergibt für micht 400 Maschinenbefehle je Interrupt.
Mein Programm sieht (verreinfacht) so aus:

//----------------------------------------------------------------------------------------------------------------------Headerdateien
#include <stdint.h> 

//----------------------------------------------------------------------------------------------------------------------Deklaration

volatile uint32_t signalCounter = 0;                                                        // Zähler für die Rechteckimpulse
uint32_t tmpSignalCounter = 0;                                                              // temporärer Zähler für Ausgabe der Rechteckimpulse
char newZustand, oldZustand = 0, richtung;
unsigned char currentCommand;                                                              //Current Command For The Arduino To Process


//--------------------------------------------------------------------------------------------------------------------Initialisierung
void setup() {


  // Initialize Serial Port With The Default Baud Rate
  Serial.begin(9600); 
  Serial.flush();

  attachInterrupt(0, counter, CHANGE);                                                      // Interrupt bei Flankenänderung von T1
  attachInterrupt(1, counter, CHANGE);                                                      // Interrupt bei Flankenänderung von T2
}



//----------------------------------------------------------------------------------------------------------------------Endlosschleif

void loop() {

  //mehrfaches einlesen von signalCounter (da länger als 8Bit) falls währendessen ein interrupt passiert
  uint32_t count1 = signalCounter;
  uint32_t count2 = signalCounter;
  while (count1 != count2) {
    count1 = count2;
    count2 = signalCounter;
  }
  tmpSignalCounter = count1;    //weiter mit temporärer variable


  // Check for commands from LabVIEW and process them.
  LabVIEW_Command();



  //-----------------------------------------------------------Richtung erkennen
  /*  newZustand = getZustand();
   if (newZustand == oldZustand){                                                 // Bewegt sich nichts
   }
   else
   { switch (newZustand){
   case 0:
   if (oldZustand == 1) richtung = 1; else richtung = 0; 
   break;
   case 1:
   if (oldZustand == 3) richtung = 1; else richtung = 0;
   break;
   case 2:
   if (oldZustand == 0) richtung = 1; else richtung = 0;
   break;
   case 3:
   if (oldZustand == 2) richtung = 1; else richtung = 0;
   }
   oldZustand = newZustand;
   }
   */

}

//-----------------------------------------------------------------------Zustandsreihenfolge HIGH LOW ermitteln
/*char getZustand (void){
 char Zustand1;
 char Zustand2;
 Zustand1 = (PINE & 0x10);                                                      //T1 auslesen
 Zustand1 = Zustand1 >> 3;
 Zustand2 = (PINE & 0x20);                                                      //T2 auslesen
 Zustand2 = Zustand2 >> 5;
 Zustand1 = Zustand1 | Zustand2;
 return (Zustand1);
 }
 */
//------------------------------------------------------------------------signalCounter duch Externe Interrupt 0 und 1
void counter () {
  if(1){
    signalCounter++;
  }
  else{
    signalCounter--;
  }
}


//--------------------------------------------------------------------------Befehle von LabVIEW
void LabVIEW_Command(void)
{
  int bufferByte = Serial.available();

  if(bufferByte >= 1) 
  {
    currentCommand = Serial.read();             

    switch(currentCommand)
    {    
      
    case 0x00:                                         // Reset signalCounter
      signalCounter = 0;
      pinMode(12, OUTPUT);
      digitalWrite(12, HIGH);
      break;


    case 0x11:                                         // Ausgabe signalCounter
      if(tmpSignalCounter < 10){
        Serial.print("00000");
        Serial.print(tmpSignalCounter, DEC);
      }
      if((tmpSignalCounter >= 10) && (tmpSignalCounter < 100)){
        Serial.print("0000");
        Serial.print(tmpSignalCounter, DEC);
      }
      if((tmpSignalCounter >= 100) && (tmpSignalCounter < 1000)){
        Serial.print("000");
        Serial.print(tmpSignalCounter, DEC);
      }
      if((tmpSignalCounter >= 1000) && (tmpSignalCounter < 10000)){
        Serial.print("00");
        Serial.print(tmpSignalCounter, DEC);
      }
      if((tmpSignalCounter >= 10000) && (tmpSignalCounter < 100000)){
        Serial.print("0");
        Serial.print(tmpSignalCounter, DEC);
      }
      if((tmpSignalCounter >= 100000) && (tmpSignalCounter < 1000000)){
        Serial.print(tmpSignalCounter, DEC);
      }
      break;  
    }
  }
}

Meine Frage ist nun: Wenn ich mit 1cm/s verfahre kommt es schon vereinzelt zu Zählaussetzern. Wie kommt das? Den Interrupt aufrufen, die Variable "signalCounter hochzählen" und aus dem Interrupt wieder zurück sind doch keine 400 Maschinenbefehle oder? Warum kann ich nicht schneller verfahren bzw. habe ich eine Programmfehler/Denkfehler?

Schon mal vielen dank für die Antworten.

Hallo,
versuch doch mal die Serial.begin auf 56700 zu setzen.
Ich habe den Effekt, dass bei 9600 es ca. 10 Sekunden dauert, bis USB und Arduino miteinander reden.

Gruss Kalli

void counter () {
  if(1){
    signalCounter++;
  }
  else{
    signalCounter--;
  }
}

Mich wundert das nicht, daß es 400 Maschienenzyklen sein können. Du mußt bedenken Du inkrementierst einen 32 Bit Zahl.

Was soll dieses if bewirken?

es ist immer wahr also kannst Du gleich schreiben:

void counter () {
   signalCounter++;
  }

Grüße Uwe

@Uwe:
Du hast recht, die if Anweisung ist momentan etwas sinnlos. Das ist ein Überbleibsel aus der Richtungserkennung genauso wie die auskommentierten Befehle von der Zustandserkennung. Ich habe die if Anweisung nur gelassen, weil die Variable später abhängig von der Richtung (wird über Antrieb vorgegeben) inkrementiert oder dekrementiert wird. Ich habe es auch schon ohne die if-Anweisung probiert und es ist nicht erkennbar schneller.

Meine Überlegung ist auch, dass es an der 32-Bit Interger liegt. Die Variable soll allerdings von 0-765000 zählen können (Anzahl der Flanken über den gesamten Weg) und mit einer 16-Bit Integer kann ich doch nur bis 65000 zählen?!
Gibt es dafür vllt einen geschickteren Weg?

Code bitte mit Code Tags versehen.

  uint32_t count1 = signalCounter;
  uint32_t count2 = signalCounter;
  while (count1 != count2) {
    count1 = count2;
    count2 = signalCounter;
  }

ist der falsche Weg, besser ist:

cli()
uint32_t count1 = signalCounter;
sei()

Davon abgesehen gehst Du bei Deiner Frage davon aus, daß es keine anderen Interruptroutinen gibt die den Prozessor vieleicht gerade zu einem ungünstigen Zeitpunkt belasten. Gibt es aber, z.B. den Timerinterrupt von Timer0 für die Millisekunden. Außerdem kommt Dein Rechtecksignal mit hoher Wahrscheinlichkeit nicht ohne Frequenzjitter --> nochmal weniger Zeit in einigen Intervallen.

Serieller Input geht vermutlich (ich habe jetzt nicht nachgeschaut) auch per ISR --> und nochmal Last für den Prozessor.

Und das geht alles von Deinen optimistisch geschätzten 400 Zyklen weg.

Erstmal vielen danke für die schnellen Antworten.
Ich habe mein Programm nun folgendermaßen geändert:

//----------------------------------------------------------------------------------------------------------------------Headerdateien
#include <stdint.h> 
//----------------------------------------------------------------------------------------------------------------------Deklaration

volatile uint32_t signalCounter = 0;                                                        // Zähler für die Rechteckimpulse
uint32_t tmpSignalCounter = 0;                                                              // temporärer Zählerstand für Ausgabe der Rechteckimpulse
char newZustand, oldZustand = 0, richtung;
unsigned char currentCommand;                                                              //Current Command For The Arduino To Process


//--------------------------------------------------------------------------------------------------------------------Initialisierung
void setup() {

  // Initialize Serial Port With The Default Baud Rate
  Serial.begin(9600); 
  Serial.flush();

  attachInterrupt(0, counter, CHANGE);                                                      // Interrupt bei Flankenänderung von T1
  attachInterrupt(1, counter, CHANGE);                                                      // Interrupt bei Flankenänderung von T2
}


//----------------------------------------------------------------------------------------------------------------------Endlosschleif
void loop() {

  cli();
  tmpSignalCounter = signalCounter;                                       // weiter mit temporärer Variable
  sei();

  // Check for commands from LabVIEW and process them.
  LabVIEW_Command();

}

//-------------------------------------------------------------------signalCounter inkrementieren duch Externe Interrupt 0 und 1
void counter () {
  signalCounter++;
}


//--------------------------------------------------------------------------Befehle von LabVIEW
void LabVIEW_Command(void)
{
  int bufferByte = Serial.available();

  if(bufferByte >= 1) 
  {
    currentCommand = Serial.read();             

    switch(currentCommand)
    {    

    case 0x00:                                         // Reset signalCounter
      signalCounter = 0;
      pinMode(12, OUTPUT);                             //Anzeige über LED
      digitalWrite(12, HIGH);
      break;


    case 0x11:                                         // Ausgabe signalCounter
      if(tmpSignalCounter < 10){
        Serial.print("00000");
        Serial.print(tmpSignalCounter, DEC);
      }
      if((tmpSignalCounter >= 10) && (tmpSignalCounter < 100)){
        Serial.print("0000");
        Serial.print(tmpSignalCounter, DEC);
      }
      if((tmpSignalCounter >= 100) && (tmpSignalCounter < 1000)){
        Serial.print("000");
        Serial.print(tmpSignalCounter, DEC);
      }
      if((tmpSignalCounter >= 1000) && (tmpSignalCounter < 10000)){
        Serial.print("00");
        Serial.print(tmpSignalCounter, DEC);
      }
      if((tmpSignalCounter >= 10000) && (tmpSignalCounter < 100000)){
        Serial.print("0");
        Serial.print(tmpSignalCounter, DEC);
      }
      if((tmpSignalCounter >= 100000) && (tmpSignalCounter < 1000000)){
        Serial.print(tmpSignalCounter, DEC);
      }
      break;  
    }
  }
}

Wenn ich die Zählvariable nun so kopiere:

  cli();
  tmpSignalCounter = signalCounter;                                       // weiter mit temporärer Variable
  sei();

Bekommt der Mikrocontroller aber in der Zeit die exteren Interrupts nicht mit?! Ist es da überhaupt empfehlenswert die Variable zu kopieren oder soll ich besser gleich die original Variable kopieren? (Ich habe das eigentlich nur gemacht, da ich mal gelesen habe, dass das abfragen und evtl. gleichzeitige schreiben der Variable duch die ISR zu Fehlern führen kann?!).

Ja die anderen Interruptroutinen habe ich jetzt nicht berücksichtigt... da sind die 400 Zyklen wirklich ziemlich optimistisch gerechnet. Gibt es eine Möglichkeit den TimerInterrupt von Timer0 abzuschalten? (Ich weiß jetzt nicht ob das eine gute Idee ist, aber soweit ich weiß ist der ja nur für das PWM Signal, die milis() Funktion usw.)
Oder falls nicht, wenigstens den Timerinterrupt zu nutzen? Hintergrund ist folgender: ich muss das Programm noch erweitern und damit einen Schrittmotor ansteuern der einen konstanten Takt braucht (Pin HIGH LOW schalten) und den hätte ich gerne mit einem Timerinterrupt generiert... Sonst müsste ich ja nochmal einen Timer (z.B. Timer2) aktivieren, der dann über die Interruptroutine wieder die externen Interrupts "verlangsamt".

Interrupts auf wechselnde Flanken werden in einer "Queue" gepuffert. D.h. sie gehen nicht notwendig verloren. Zumindest solange bis die Queue überläuft. Und klar kann man auch Timer Interrupts abschalten. Das steht alles im Datenblatt oder auch hier:
Interrupt – Mikrocontroller.net bzw. hier: AVR-Tutorial: Timer – Mikrocontroller.net

Ok das ist gut zu wissen mit der "Queue" :).
Was für neine Studienarbeit noch wichtig wäre: wie kann ich mir nun ausrechnen wieviele Taktzyklen das Inkrementieren der 32-Bit Interger "signalCounter" benötigt?
Ich muss das nämlich einigermaßen wissenschaftlich begründen warum das Zählen nicht mehr schneller geht und da wär es gut, wenn ich wenigstens eine Hausnummer/Überschlagsrechnung angeben könnte...

Indem Du den erzeugten Maschinencode aus dem *.elf File mit avr-objdump anzeigst und nachschaust was der Compiler generiert. Dann in den Atmel Datenblättern die Takte für die Befehle nachschauen fertig. Wenn es um Hochzählen geht sind aber Optimierungen machbar mit denen das schneller wird. Google nach "loop unrolling".
Zur "wissenschaftlichen Arbeit" gehört auch die eigene Recherche. Viel Spaß beim Suchen, den Anfang solltest Du ja jetzt haben :wink: