Probleme mit Interrupt Routine - Projekt Impulszähler mit ext. Initialisierung

Hallo, ich bin neu in diesem Forum!

Mein Projekt ist eigentlich simpel.
Arduino MEGA2560
Ich habe zwei Signale (jeweils 5V TTL), ein langes (ca. 5 - 20 ms) als Zählfenster und ein kurzes (ca. 0,1 ms als Zählimpuls.
Die Zählimpulse liegen quasi immer an und sollen nur innerhalb des Zählfensters gewertet werden.

Als Ausgangspunkt habe ich beide Signale mit dem Beispielsketch “Digital/StateChangeDetection” getestet. Klappt prima, aber halt nur getrennt.
Wenn ich das ganze zusammen bringen will, schwebt mir eine Lösung wie folgt vor:

const int wertenPin = 2; // an diesem Pin haengt das langsame 5V TTL Signal

// Variables will change:
volatile int zaehler = 0; // Zaehlerstand kurze Impulse
volatile int werten = 0; // zur Pruefung, ob Zaehlimpuls gewertet werden soll;
// Ja fuer 0 ; Nein fuer 1
int maxzaehler = 0; // Puffer fuer max Zaehlerstand

void setup() {

// initialisiere Pin2 als digitaler Input:
pinMode(wertenPin, INPUT);
// initialisiere Interrupt an Pin 3
attachInterrupt(1, zaehlerpuls, LOW);

// initialize serial communication:
Serial.begin(9600);
}

void zaehlerpuls() {
werten = wertenPin;
if (werten == 0) { // hier soll die Wertung der Impulse erfolgen; gibt aber immer nur “0” aus
zaehler++; }
else {
zaehler = 0; }
}

void loop() {
if (zaehler > 0) {
maxzaehler = zaehler;
Serial.println(zaehler); }
else{
Serial.println(maxzaehler);
maxzaehler = 0;
}
}

Das Zählen der Impulse funktioniert aber nur ohne die “if” Wertung während des Interrupts.
Liegt das daran, dass der MEGA2560 zu langsam ist?

Einen Sketch zu diesem Thema habe ich im Forum nicht finden können. Kennt jemand eine Lösung?

Sowas macht man besser über einen extern getakteten Timer. Damit kann man zählen ohne das ständig ein Interrupt ausgelöst wird. Gerade für schnelle Signale ist das ideal. Man muss lediglich den Overflow Interrupt auswerten und der kommt nur alle 2^16 Impulse. Mit dem Torsignal kann man dann den Timer aktivieren und deaktivieren. Danach werten man den Zählerstand und die Anzahl der Überlaufe aus.

Wobei der Überlauf bei dir wohl gar nicht auftritt, da die Torzeit sehr kurz ist.

Auf dem Mega steht dafür Timer5 zur Verfügung. Bei den restlichen Timern hat man dummerweise die Taktpins nicht auf das Arduino Board gelegt.

Das hatte ich hier mal grob beschrieben (ohne die externe Torschaltung):

Hallo,
vielen Dank für die schnelle Antwort.
Wenn ich Dich richtig verstanden habe, dann soll ich die kurzen Zählimpulse über den Timer5 laufen lassen.
Meine Torzeit ist allerdings ja nicht fest vorgegeben, sondern von dem externen Signal (bislang an Pin2) vorgegeben.
(Wenn Signal LOW, dann zählen). Kann ich das irgendwie beibehalten?
Brauch ich zum Programmieren von Timer5 eine andere Software, oder kann ich das einfach unter Arduino machen?
Bin ziemlich froh, dass ich mich da so gerade ein bisschen eingearbeitet habe. Bin eigentlich mehr von der Hardware Seite.

Die Torzeit könntest du über einen externen Interrupt auswerten. Vielleicht auf CHANGE stellen, dann wird bei beiden Flanken ausgelöst. Bei der einen Flanke aktivierst du dann den Timer über die CS Bits. Bei der anderen Flanke deaktivierst du ihn (CS Bits auf 0) und wertest aus.

Auf den Überlauf kannst wirst du wie gesagt verzichten können, wenn du unter 2^16 pro Torzeit bleibst. Aber bei 20ms und 100µs Pulslänge wirst du nur wenige Pulse haben.

Die Timer kannst du auch in der Arduino Software programmieren. Du verzichtest lediglich auf die Arduino Abstraktion, aber die Prozessor Register sind natürlich immer noch direkt ansprechbar.

Schau dir dazu mal das Datenblatt an. Da tut es theoretisch auch das Atmega328 Datenblatt über Timer1. Der funktioniert genauso.

Atmega2560 Datenblatt:

Die 16 Bit Timer sind ab Seite 133 beschrieben. Die Taktquelle auf Seite 156f. Im TCCR5B Register (Timer Counter Control Register 5B ) gibt es 3 CS Bits (Clock Select). Wenn die alle auf 0 sind ist der Timer aus. Bei 110 triggerst du auf die fallende Flanke des T5 Pins (Pin 47) und bei 111 auf die steigende Flanke. Wenn also dein Torsignal kommt schaltest du die entsprechenden CS Bits auf 1. Wenn die Torzeit vorbei ist wieder auf 0.

Das geht so:

TCCR5B |= _BV(CS52) | _BV(CS51) | _BV(CS50);    //alle 3 CS Bits setzen

TCCR5B &= ~(_BV(CS52) | _BV(CS51) | _BV(CS50));    //CSBits löschen

_BV steht für Bit Value und macht das:

(1 << n)

CS52 hat dann den Wert 2. Also wird die 1 zweimal nach links geschoben und man hat 0000 0100

Löschen geht durch ein Und mit dem Inversen. Alternativ auch so:

TCCR5B &= 0xF8;  //untere 3 Bits löschen

oder

TCCR5B &= ~0x07; //untere 3 Bits löschen

Aber ich finde die Schreibweise mit den Bitnamen leserlicher

Das war es eigentlich auch schon. Der Wert des Zählers ist im 16 Bit TCNT5 (Timer Counter 5) Register. Das muss man vorher jeder Zählperiode natürlich auf 0 setzen.

Falls du den Überlauf doch brauchst, dann einfach noch das TOIE5 Bit (Timer Overflow Interrupt Enable) im TIMSK5 Register (Timer Interrupt Mask) aktivieren und eine entsprechende ISR schreiben:

volatile unsigned int overflow;

ISR(TIMER5_OVF_vect)
{
    overflow++;
}

Dann hast entspricht ein Count der overflow Variable 2^16 Impulsen

EDIT:
Hatte einen Fehler beim _BV() Makro und Löschen gemacht. Man darf nicht die einzelnen Bits invertieren und sondern nur das Oder der Bits. Entsprechend muss man Klammern setzen.

Noch eine Alternative sind hier die bitSet() und bitClear() Makros der Arduino IDE. Damit braucht man aber 3 Zeilen für ein Register mit 3 Bits.
Und fast alle Tutorials verwenden entweder _BV() oder (1 << (bit)). Man muss also wissen was das bedeutet.

Danke nochmals!
Werde ich heute Abend ausprobieren und melde mich dann mit dem Ergebnis.
Kann ich Arduino Code und AVR-Code einfach nach belieben mischen?

Kleine Frage am Rande (wenn ich schon mal jemanden am Draht habe, der sich mit den Tiefen der MCs auskennt:
Ich habe anfänglich die Interrupts an Pin 18 und 19 auf dem MEGA2560 belegt, da ich eigentlich noch den Adafruit Motor-Shield in der Anwendung verwenden wollte. Habe dann bei ersten Tests den Eindruck gehabt, dass sich diese PINs auch auf die USB Schnittstelle auswirken (wenn z.B. die kurzen Impulse an PIN18 anstanden, dann hat sich die USB aufgehängt. War das so´n Anfängerfehler, oder kann man die beiden nicht zusammen betreiben?

Einen schönen Abend

rukosag:
Kann ich Arduino Code und AVR-Code einfach nach belieben mischen?

Ja. Die Libraries machen ja im Hintergrund auch nichts anderes.

Das wird auf dem Arduino nur abstrahiert weil die Plattform für Anfänger gedacht ist. Aber dich hindert nichts daran bestimmte Dinge per Hand zu machen. Es sind auch nicht alle Funktionen des Prozessors in der Arduino IDE implementiert.

Habe dann bei ersten Tests den Eindruck gehabt, dass sich diese PINs auch auf die USB Schnittstelle auswirken (wenn z.B. die kurzen Impulse an PIN18 anstanden, dann hat sich die USB aufgehängt. War das so´n Anfängerfehler, oder kann man die beiden nicht zusammen betreiben?

Sicher bin ich mir hier nicht, aber ich glaube das wird eher was mit der recht hohen Frequenz deines Signals zu tun haben. Das kann damit zusammenhängen dass die externen Interrupts die höchste Priorität haben (direkt nach dem Reset Vektor) und damit vor allem anderen drankommt.

Interrupts können sich standardmäßig nicht gegenseitig unterbrechen, aber deine Interrupts kommen relativ schnell und lassen daher nicht so viel Zeit für anderen Code. Genau dafür gibt es eben die Timer. Damit kann man periodische Signale auswerten und Ausgänge schalten ohne das wirklich Code ausgeführt wird.

Ich habe mal auf dem UNO (daher Timer1) einen ganz primitiven Test gemacht:

void setup()
{
	Serial.begin(115200);

	pinMode(5, INPUT_PULLUP);          //nur wenn man einen Taster/Draht anschließt!
	TCNT1 = 0;
	TCCR1B = _BV(CS12) | _BV(CS11);    //negative Flanke
}

void loop() 
{
	Serial.print("Count: ");
	Serial.println(TCNT1);
	delay(1000);
}

Funktioniert :slight_smile:

Wenn man Pin 5 auf Masse zieht zählt der Zähler hoch. Wobei man da das Tasterprellen merkt. Wenn man vorsichtig einen Draht in den Pin steckt ohne viel herumzuwackeln kann man aber auch +1 zählen. Ist aber natürlich für aktive externe Signale gedacht. Nicht für Taster.

Hallo Serenifly,
ich habe mal versucht, das umzusetzen.
Hier mein Code:

// Variables will change:
volatile int zaehler = 0; // Zaehlerstand kurze Impulse
//volatile int interval = 0; // zur Pruefung, ob Zaehlimpuls gewertet werden soll;
volatile boolean cycle = false; //Zyklusüberwachung für Interrupt CHANGE Flanke // Ja fuer 0 ; Nein fuer 1
//int maxzaehler = 0; // Puffer fuer max Zaehlerstand
volatile boolean drucken = true; // damit immer nur einmal pro Messzyklus der Zählerstand gedruckt wird

void setup() {

// initialisiere Pin47 Timer5 als digitaler Input:
pinMode(47, INPUT);
// initialisiere Interrupt an Pin 3
attachInterrupt(0, torzeit, CHANGE);
TCNT5 = 0; // initialisiere Timer5
// initialize serial communication:
Serial.begin(9600);
}

void torzeit() {
if (cycle = true) { // bei Eintritt in das Messfenster
TCCR5B |= _BV(CS52) | _BV(CS51) | _BV(CS50); // TimerCounterControl auf triggern von steigenden Flanken setzten
cycle = false;
Serial.println("hallo"); // wegen Erfolglosigkeit als Debug Output angelegt
Serial.println(TCNT5); //dito
}
else { // bei Austritt aus dem Messfenster (warum kommt der nicht in diese Schleife??????
TCCR5B &= ~(_BV(CS52) | _BV(CS51) | _BV(CS50)); // TimerCounterControl Trigger abschalten
cycle = true;
drucken = false;
Serial.println("tschuess"); // auch hier Debug Output
}
}

void loop() {
if (drucken = false) { //nach Ende des Messzyklus soll Timer5 ausgewertet und der Zaehlerstand gedrukt werden
zaehler = TCNT5;
TCNT5 = 0;
Serial.println(zaehler);
drucken != drucken;
}
}

Hat leider etwas gedauert. Ich hatte noch einen Kabelbruch in dem Jumper-Wire, der das Zählersignal transportiert. Habe natürlich erst alle super komplizierten Szenarien durchgetestet, bevor ich auf das simpelste kam.
Fazit meines Tests: Es zählt! Leider allerdings nur bis 255 (doch nur 8.bit Timer?).
Da das doofe Programm (immer so doof wie der, der es geschrieben hat) nicht in die "else"-Schleife in der ISR wechselt, zählt es halt immer weiter. Es ist schon spät und ich sehe bestimmt den Wald vor lauter Bäumen nicht, aber ich kann mir einfach nicht erklären, warum ich nicht in die "else" Schleife komme.
Ich denke, wenn der Fehler in meinem Code behoben ist, funktioniert aller (sogar mit 8bit Zähler), da meine Zählergebnisse zwischen 30 und 150 liegen werden.
Siehst Du den Fehler????

Pack deinen Code generell mal in Code Tags. Entweder per Hand oder den Raute Knopf # in der Leiste drücken

Das ist falsch:

if (cycle = true)
if (drucken = false)

== !!!!
Typischer Fehler. Erst auch nicht gesehen :slight_smile:

Serial hat in Interrupts nichts zu suchen! Das geht nicht! Was du mit "drucken" machst ist genau die Lösung. Man setzt in der ISR eine Variable und fragt in loop() ab ob was zu tun ist. Aber kein print() in ISRs!

Leider allerdings nur bis 255 (doch nur 8.bit Timer?).

Nein. TCNT5 hat zwei Register die man auch getrennt ansprechen kann. Das sollte gehen wenn der Rest korrekt ist.

Die Grundstruktur ist so glaube ich ok. So ähnlich hätte ich das auch gemacht :slight_smile:
Du kannst auch mit digitalRead() auf den Tor-Pin abfragen in welchem Zustand du bist.

Du kannst vielleicht sicherheitshalber das machen:

void loop() {
     if (drucken == false) 
    { 
       cli();
       zaehler = TCNT5;
       TCNT5 = 0;
       sei();
       Serial.println(zaehler);
       drucken != drucken;       
      }     
}

Durch das cli()/sei() werden die Interrupts während dieser Zeit gesperrt. Dann fängt nicht vielleicht der Zähler schon wieder an zu Takten während du noch das Register ausliest. Dazu müsste aber die nächste Torzeit sehr, sehr kurz hinter der aktuellen kommen.

Was auch nichts schadet ist die Serial Baudrate auf 115200 zu erhöhen. Bei 9600 Baud dauert ein Zeichen 1ms. Bei 115200 Baud 86µs. 1 / Baudrate * 10 Sekunden. Das wird zwar im Hintergrund abgearbeitet, aber du schreibst doch relativ schnell in den Puffer.

Super! Funktioniert zu 95%!!!
Ab und an kommt wohl ein Interrupt nicht durch, und der Zähler kommt durcheinander.
Habe deswegen noch einen Endschalter eingebaut, der konsequent den cycle auf true setzt, wenn das Messfenster verlassen wurde.
Quote jetzt ca. 98% richtige Ergebnisse.
Ich denke, die anderen 2% liegen in der Signalaufbereitung ausserhalb des Arduinos.

Probiere ich morgen aus.
Vielen Dank nochmal

Kann man den Thread hier irgendwo auf gelöst setzen?

Du kannst den Titel editieren

Nochmal wegen dem 16 Bit Counter Register.

Ich habe das bei mir mal getestet und irgendwas passt da nicht. Schreiben klappt, aber beim Lesen habe ich auch immer nur das L Register, egal was ich machen :o Muss auch sagen, dass auf dem AVR ich die Timer bisher so eigentlich nicht verwendet habe. In vielen Modi fasst man das Counter Register nicht lesend an, sondern lässt die CPU irgendwas machen wenn es den Wert eines anderen Registers erreicht hat.

Intern ist es so, dass bei einem Lesezugriff auf das L Register das H Register in ein temporäres Register gepuffert wird und dann beim Zugriff auf H dieses Temp Register liest. Dadurch wird der Gesamtwert nicht durcheinander gebracht wenn weitergezählt wird.

Für Interrupt-sichereren Code wird das empfohlen:
http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_16bitio
Das bringt aber auch nichts. Außerdem findet gar kein Interrupt-gesteuerter Zugriff statt. Sollte also gar nicht nötig sein.

Seltsam. Und jeder Code den man findet sagt dass man einfach “unsigned int value = TCNTn” machen kann. Sogar das Datenblatt. Den korrekten Registerzugriff soll der Compiler übernehmen :~ :0

Nachtrag:
Habe jetzt auch den generellen Fehler in deinem originalen Code gefunden:

werten = wertenPin;
if(werten == 0)

Du musst digitalRead() machen um den Zustand eines Pins zu lesen! Ist offensichtlich, aber hatte auch erst völlig übersehen. :slight_smile:

Das ist aber trotzdem so besser. Du hast ja auch gemerkt, dass du durch die Interrupts in kurzen Abständen Probleme mit Serial bekommen hast. Außerdem willst du denke ich während der Zeit noch andere Dinge erledigen wie Motoren ansteuern.

Langsamere Impulse kann man aber durchaus mit externen Interrupts zählen.

So, jetzt läuft alles, wie es soll.
Durch die Umstellung auf Baudrate 115200 gibt es jetzt nur noch Fehler nach mehr als 200 Messzyklen.

Ich hab den Code noch weiter kommentiert.
Falls ihn mal jemand sucht, hier ist er:

Arduino Sketch für MEGA2560 - Impulszähler mit externem Trigger

// Dieses Programm macht aus dem Arduino einen extern geriggerten Impulszaehler
// Die Idee mit Timer5 stammt von serenifly aus dem Arduino-Forum. Dafuer herzlichen Dank!
// Beachtet werden muss, dass das Programm immer ausserhalb des Messfensters beginnt,
// sonst stimmen die Interrupt-Reihenfolgen nicht.
// Anderenfalls muss zur Zeit noch extern einmal per Schaltkontakt synchronisiert werden.
// Das Programm laeuft mit 115200 Baudrate sehr zuverlaessig (ca. 1x asynchron pro 200 Messwerte
// eventuell sollte noch eine automatische Synchronisierung eingebaut werden.
// z.B. durch Auswertung des Timer-Ueberlaufregisters


// Variables will change:
volatile int zaehler = 0;   // Zaehlerstand kurze Impulse
//volatile int interval = 0;    // zur Pruefung, ob Zaehlimpuls gewertet werden soll; 
volatile boolean cycle = false;  //Zyklusüberwachung für Interrupt CHANGE Flanke                        // Ja fuer 0 ; Nein fuer 1
//int maxzaehler = 0;         // Puffer fuer max Zaehlerstand
volatile boolean drucken = true;  // damit immer nur einmal pro Messzyklus der Zählerstand gedruckt wird

void setup() {

  // initialisiere Pin47 Timer5 als digitaler Input
  pinMode(47, INPUT);
  //  initialisiere Interrupt an Pin 2 fuer Torzeit
  attachInterrupt(3, torzeit, CHANGE);
  //   initialisiere Interrupt an Pin 3 fuer Endschalter
  //wenn Messungen unplausibel, dann ist das Messfenster invertiert 
  // Endschalter 2x schnell druecken, wenn ausserhalb des echten Messfensters
  // dann kommen die Interrupts wieder richtig
  // attachInterrupt(3, endschalter,LOW);
  // initialisiere Timer5
  TCNT5 = 0; 
  // initialize serial communication:
  Serial.begin(9600);
}

void torzeit() {
  // bei Eintritt in das Messfenster
  if (cycle == true) {        
   // TimerCounterControl auf triggern von steigenden Flanken setzten
    TCCR5B |= _BV(CS52) | _BV(CS51) | _BV(CS50);  
   cycle = false;
  }
  // bei Austritt aus dem Messfenster 
  else {     
    // TimerCounterControl Trigger abschalten
    TCCR5B &= ~(_BV(CS52) | _BV(CS51) | _BV(CS50));  
    cycle = true;
    // drucken auf false setzen, damit der Messwert genau einmal ausgegeben wird
    drucken = false;
  }
}

void endschalter() {
  // Interrupt extern; um Messzyklus wieder zu synchronisieren
  cycle = true;
}


void loop() {
     //nach Ende des Messzyklus soll Timer5 ausgewertet und der Zaehlerstand gedrukt werden
     if (drucken == false) { 
       // an dieser Stelle kann man die Interrupts unterbrechen 
       // hat sich aber nicht positiv auf die Messungen ausgewirkt
       //cli();   
       zaehler = TCNT5;
       TCNT5 = 0;
      // wenn "cli" aktiviert wurde, muss auch "sei" aktiviert werden, um die Interrupts wieder einzuschalten 
      //sei();
       Serial.println(zaehler);
       // drucken = true, damit nur einmal der Messwert gedruckt wird
       drucken = true;        
      }      
}

Ich hab das ganze dann noch erweitert für die Nutzung des Adafruit-Motor-Shield.
Ziel war es, immer nach 10 Messungen ein neues Messobjekt in das Messfenster zu transportieren, oder das Messobjekt zu drehen.
War wieder mal unnötig zeitraubend, da ich erst die Interrupts an Pin 20 und 21 genutzt habe. Konnte ja nicht ahnen, dass die irgendwie von dem Motor-Shield beeinflusst werden - die PINs sind ja nicht belegt.
Hat ein ziemliches Messchaos gegeben. Eigentlich habe ich nur Infos zu Motor-Shield V2.0 zu diesem Thema gefunden.
Na ja, bin dann auf Pin 18 und 19 umgestiegen. Jetzt läufst super.

Hier noch der Code für
Arduino Sketch MEGA2560 - Kombination Impulszähler mit externem Trigger und Stepper-Motor Steuerung - Adafruit Motor Shield

// Dieses Programm kombiniert den Sketch "MEGA2560_Impulszaehler_ext_Trigger
// und die Library "AFMotor.h" von ADAFRUIT

// Die Idee mit Timer5 stammt von serenifly aus dem Arduino-Forum. Dafuer herzlichen Dank!
// Beachtet werden muss, dass das Programm immer ausserhalb des Messfensters beginnt,
// sonst stimmen die Interrupt-Reihenfolgen nicht.
// Anderenfalls muss zur Zeit noch extern einmal per Schaltkontakt synchronisiert werden.
// Das Programm laeuft mit 115200 Baudrate sehr zuverlaessig (ca. 1x asynchron pro 200 Messwerte
// eventuell sollte noch eine automatische Synchronisierung eingebaut werden.
// z.B. durch Auswertung des Timer-Ueberlaufregisters

// Nach Anlauf der Messungen soll nach jeweils 10 Messungen der Steppermotor um
// eine Anzahl vogegebener Schritte weiterdrehen (z.B. um ein neues Messobjekt in
// das Messfenster zu transportieren
// durch die Verwendung des ADAFRUIT Motor-Shields sind nur noch 3 Interrupt-Eingaenge 
// auf dem MEGA2560 frei. Allein schon aus diesem Grund ist die Verwendung von Timer5 als Zaehler
// praktisch

// Problem mit dem Zaehler: zur Zeit koennen nur maximial 255 Impulse ausgewertet werde,
// obwohl Timer5 ein 16Bit Timer ist. An diesem Problem muss noch gearbeitet werden

// importiere ADAFRUIT Library
#include <AFMotor.h>

// Variables will change:
// Stepper Motor mit 200 Schritten (1.8 Grad) an Port #1(M1 und M2) am AMS angeschlossen
AF_Stepper motor(200, 1);
// Zaehlerstand kurze Impulse
volatile int zaehler = 0;  
// hier angeben nach wie vielen Messzyklen der Motor drehen soll
volatile int motorfahrt = 10;    
//Zyklusüberwachung für Interrupt CHANGE Flanke                     
volatile boolean cycle = false;    
// damit immer nur einmal pro Messzyklus der Zählerstand gedruckt wird
volatile boolean drucken = true;  
// hier die Anzahl der Schritte fuer die Motorfahrt angeben
int schritte = 10;
int motorposition = 0;
// hier gleichen Wert wie Motorfahr eingeben
int mfi = 10;

void setup() {

  // Motorgeschwindigkeit auf 10 U/min setzten
  motor.setSpeed(10); 
  // initialisiere Pin47 Timer5 als digitaler Input
  pinMode(47, INPUT);
  //  initialisiere Interrupt an Pin 18 fuer Torzeit
  // Achtung!!! Keine Interrupts an Pin 20 und 21 nutzen
  // die werden - vorfuer auch immer - durch das AMShield beeinflusst!
  attachInterrupt(5, torzeit, CHANGE);
  //   initialisiere Interrupt an Pin 19 fuer Endschalter
  //wenn Messungen unplausibel, dann ist das Messfenster invertiert 
  // Endschalter 2x schnell druecken, wenn ausserhalb des echten Messfensters
  // dann kommen die Interrupts wieder richtig
  attachInterrupt(4, endschalter,LOW);
  // initialisiere Timer5
  TCNT5 = 0; 
  // initialize serial communication:
  Serial.begin(115200);
}

void torzeit() {
  // bei Eintritt in das Messfenster
  if (cycle == true) {        
   // TimerCounterControl auf triggern von steigenden Flanken setzten
    TCCR5B |= _BV(CS52) | _BV(CS51) | _BV(CS50);  
   cycle = false;
  }
  // bei Austritt aus dem Messfenster 
  else {     
    // TimerCounterControl Trigger abschalten
    TCCR5B &= ~(_BV(CS52) | _BV(CS51) | _BV(CS50));  
    cycle = true;
    // drucken auf false setzen, damit der Messwert genau einmal ausgegeben wird
    drucken = false;
  }
}

void endschalter() {
  // Interrupt extern; um Messzyklus wieder zu synchronisieren
  cycle = true;
}


void loop() {
     //nach Ende des Messzyklus soll Timer5 ausgewertet und der Zaehlerstand gedrukt werden
     if (drucken == false) { 
       // an dieser Stelle kann man die Interrupts unterbrechen 
       // hat sich aber nicht positiv auf die Messungen ausgewirkt
       //cli();   
       zaehler = TCNT5;
       TCNT5 = 0;
       mfi --;
      // wenn "cli" aktiviert wurde, muss auch "sei" aktiviert werden, um die Interrupts wieder einzuschalten 
      //sei();
       Serial.println(zaehler);
       // drucken = true, damit nur einmal der Messwert gedruckt wird
       drucken = true;  
       // drehe Motor nach Ablauf der oben vorgegebenen Messzyklen
         if (mfi < 1) {
           motor.step(schritte, FORWARD, SINGLE);
           mfi = motorfahrt;
           // zur Info
           motorposition ++;
           Serial.print("MPOS ");
           Serial.println(motorposition);
           
         }      
      }      
}

Ich hoffe, ich mülle das Forum nicht mit zu viel Trivialcode zu. Aber ich hätte mich sehr gefreut, das vorgestern hier zu finden.
Nochmal vielen Dank an Serenifly für die tolle Hilfe, sonst säße ich noch nächsten Monat an dieser Sache.

// eventuell sollte noch eine automatische Synchronisierung eingebaut werden.
// z.B. durch Auswertung des Timer-Ueberlaufregisters

Der Überlauf tritt wie gesagt nur ein wenn mehr als 2^16 Impulse eintreffen. Das ist bei dir nicht der Fall.

Eher sollte man vielleicht mal nachschauen wieso das TCNTnH Register immer 0 zurückgibt, bzw. wieso TNCTn immer nur den Wert des L Registers zurückgibt wenn Werte > 255 auftreten.

Und ich habe die Lösung gefunden. Peinlich. Peinlich =(

Ich dachte bisher immer die Register sind standardmäßig immer mit 0 initialisiert. Und auf einem blanken System mag das so sein. Hier wird aber der Arduino Initialisierungscode reinpfuschen. Der Arduino macht PWM mit allen Timern. Dämmert es jetzt was da passiert?

Wenn man TCCRnA ausliest, steht es auf 1. Das bedeutet das WGMn0 Bit ist gesetzt und der Timer ist im Modus "Phase Correct 8-Bit PWM". Dann setzt er jedes mal das Counter Register auf 0 zurück wenn der TOP Wert von 0x00FF erreicht ist :stuck_out_tongue_closed_eyes:

Also einfach das TCCRnA Register auf 0 setzen und schon passt es :slight_smile:
Sicherheitshalber sollte man auch TCCRnB in setup() auf 0 setzen. Dann kann man sicher sein, dass die Register für die verschiedenen Timer Modi alle im Standardzustand sind.

Beachtet werden muss, dass das Programm immer ausserhalb des Messfensters beginnt,
sonst stimmen die Interrupt-Reihenfolgen nicht.

Das kannst du glaube ich umgehen wenn du statt der boolschen Variable den Zustand des Interrupt-Pins in der ISR mit digitalRead() abfragst. Also ob du LOW oder HIGH hast. Der Interrupt tritt mit dem Wechsel auf und danach kannst du nachschauen was anliegt.
Dann stimmt zwar immer noch die erste Messung nicht, aber danach sollte es glaube ich passen.

Das ist unnötig:

volatile int zaehler = 0;   // Zaehlerstand kurze Impulse

Variablen müssen nur volatile sein wenn sie innerhalb und außerhalb von ISRs verwenden werden. Diese Variable kann lokal in loop() sein.

Dein Original-Code enthält übrigens noch einen Fehler, den ich nicht gleich gesehen habe:

attachInterrupt(1, zaehlerpuls, LOW);

Das löst ständig Interrupts aus so lange der Pin LOW ist. Das ist keine Flankentriggerung (dafür gibt es RISING, FALLING und CHANGING)! Das wird der Hauptgrund sein weshalb du den Rest des Prozessors blockiert hast.

// Dieses Programm macht aus dem Arduino einen extern geriggerten Impulszaehler
// Die Idee mit Timer5 stammt von serenifly aus dem Arduino-Forum. Dafuer herzlichen Dank!
// Beachtet werden muss, dass das Programm immer ausserhalb des Messfensters beginnt,
// sonst stimmen die Interrupt-Reihenfolgen nicht.
// Anderenfalls muss zur Zeit noch extern einmal per Schaltkontakt synchronisiert werden.
// Das Programm laeuft mit 115200 Baudrate sehr zuverlaessig (ca. 1x asynchron pro 200 Messwerte
// eventuell sollte noch eine automatische Synchronisierung eingebaut werden.
// z.B. durch Auswertung des Timer-Ueberlaufregisters


// Variables will change:
volatile int zaehler = 0;   // Zaehlerstand kurze Impulse
//volatile int interval = 0;    // zur Pruefung, ob Zaehlimpuls gewertet werden soll; 
volatile boolean cycle = false;  //Zyklusüberwachung für Interrupt CHANGE Flanke                        // Ja fuer 0 ; Nein fuer 1
//int maxzaehler = 0;         // Puffer fuer max Zaehlerstand
volatile boolean drucken = true;  // damit immer nur einmal pro Messzyklus der Zählerstand gedruckt wird
volatile int expect = 114;
void setup() {

  // initialisiere Pin47 Timer5 als digitaler Input
  pinMode(47, INPUT);
  //  initialisiere Interrupt an Pin 18 fuer Torzeit
  attachInterrupt(5, torzeit, CHANGE);
  //   initialisiere Interrupt an Pin 3 fuer Endschalter
  //wenn Messungen unplausibel, dann ist das Messfenster invertiert 
  // Endschalter 2x schnell druecken, wenn ausserhalb des echten Messfensters
  // dann kommen die Interrupts wieder richtig
  // attachInterrupt(3, endschalter,LOW);
  // initialisiere Timer5
  TCNT5 = 0; 
  TCCRnA = 0;
  TCCRnB = 0;
  // initialize serial communication:
  Serial.begin(115200);
}

void torzeit() {
  // bei Eintritt in das Messfenster
 // if (cycle == true) {
    if (digitalRead(18 == LOW)) {   
   // TimerCounterControl auf triggern von steigenden Flanken setzten
    TCCR5B |= _BV(CS52) | _BV(CS51) | _BV(CS50);  
  /*   if (abs(TCCR5B-expect) > 20) {
   cycle = true;
   }
   else {  
   cycle = false;
   }
   cycle = false; */
  }
  // bei Austritt aus dem Messfenster 
  else {     
    // TimerCounterControl Trigger abschalten
    TCCR5B &= ~(_BV(CS52) | _BV(CS51) | _BV(CS50));  
 //   cycle = true;
    // drucken auf false setzen, damit der Messwert genau einmal ausgegeben wird
    drucken = false;
  }
}

void endschalter() {
  // Interrupt extern; um Messzyklus wieder zu synchronisieren
  cycle = true;
}


void loop() {
     //nach Ende des Messzyklus soll Timer5 ausgewertet und der Zaehlerstand gedrukt werden
     if (drucken == false) { 
       // an dieser Stelle kann man die Interrupts unterbrechen 
       // hat sich aber nicht positiv auf die Messungen ausgewirkt
       //cli();   
       zaehler = TCNT5;
       TCNT5 = 0;
      // wenn "cli" aktiviert wurde, muss auch "sei" aktiviert werden, um die Interrupts wieder einzuschalten 
      //sei();
       Serial.println(zaehler);
       // drucken = true, damit nur einmal der Messwert gedruckt wird
       drucken = true;        
      }      
}

Hallo Serenifly,
habe gestern einen Tag an der Processing Schnittstelle gearbeitet.
Aktiviere heute meinen Arduino am Arbeitstisch und habe irgendwie nicht den besten Tag.
Zuerst musste ich erkennen, dass der Arduino in Verbindung mit dem MotoShield offensichtlich sehr viel störungsempfindlicher gegen Funkstörungen ist, als ohne.
Habe also beschlossen, zwei Arduinos einzusetzen, einen ohne MS zum messen und dann einen zweiten über digOut zu triggern und den Motor zu drehen (ist nicht zeitkritisch),
Aber irgendwie läuft das heute mit den Interrupts generell nicht so sauber wie vorgestern.
Habe zwei Varianten der Plausibilitätsprüfung ausprobiert:
a) berechne Absolutwert des Messergebnisses-Erwartung und setze den Zyklus zurück, wenn die Abweichung zu hoch.
Hatte das Gefühl, dass die Berechnung vom MC geflissentlich ignoriert wird. Hatte einen Messzyklus mit mehr als 30 aufeinanderfolgenden Fehlmessungen!
b) die von Dir vorgeschlagene Variante mit dem digitalRead. Wenn ich die aktiviere bekomme ich gar keine Ausgabe, als ob der Interrupt nicht auslöst.

Habe übrigens auch versucht die Register zu Nullen. Dabei bekomme ich vom Compiler aber eine Fehlermeldung (TCCRnA was not declared in this scope).

Stehe mal wieder auf dem Schlauch. Bestimmt wieder irgend ein Komma falsch, aber ich komm nicht dahinter.

if (digitalRead(18 == LOW))

Du machst immer noch einfache Syntax-Fehler. Was du machst ist 18 == LOW zu testen. Das gibt 0 zurück. Dann machst du digitalRead(0) und fragst ab ob das ungleich 0 ist.

Korrekt:

if (digitalRead(18) == LOW)

Auch sicherheitshalber in setup(), pinMode(18, INPUT) machen. Das wird bei Interrupts nicht gebraucht (auch bei dem Timer nicht unbedingt), aber ob da digitalRead() ohne korrekt funktioniert ist was anderes. Da bin ich mir nicht sicher.

Dann das hier:

attachInterrupt(3, endschalter,LOW);

Das ist zwar nur ein Kommentar, aber auch hier wieder: lass die Finger von Level Interrupts wenn dir nicht klar ist was die machen! LOW löst Interrupts aus solange der Pegel anliegt. Das ist nicht was du willst. Man kann auf den Level triggern wenn man sich nicht sicher ist die Flanke gezielt zu erwischen (weil gerade in anderer Interrupt aktiv ist), aber dann muss man detachInterrupt() machen bevor man die ISR verlässt.

Außerdem ist Interrupt 3 → Pin 20

Abgesehen davon würde ich Endschalter bei dir eher in loop() durch Polling abfragen. Du machst da eigentlich nichts, also ist fast endlos Zeit da Schalter mit digitalRead() abzufragen. Hat auch den Vorteil, dass man den Schalter leicht Entprellen kann (was man unbedingt tun sollte), was in einer ISR schwieriger wird. Und den Endschalter entprellen würde ich auf jeden Fall machen. Ein RC-Glied tut es eventuell auch wenn du es lieber in Hardware machen willst:
http://www.mikrocontroller.net/articles/Entprellung#Wechselschalter (der Teil bei Wechselschalter ohne Flip-Flop, falls dein Endschalter ein Wechselschalter ist)

Um Taster in Software zu entprellen reicht ein kurzes delay() von 5-10ms nach der Zustandsänderung (das geht aber nicht Interrupts!!!), oder besser das hier:

oder eine fertige Library:

Auch da nicht vergessen den Pin per pinMode() als Eingang zu schalten.

Du braucht jedoch nicht für alles Interrupts zu verwenden :slight_smile: Gerade bei Tastern ist das oft keine so gute Idee.

rukosag:
Habe übrigens auch versucht die Register zu Nullen. Dabei bekomme ich vom Compiler aber eine Fehlermeldung (TCCRnA was not declared in this scope).

Das n ist ein Platzhalter für die Timer-Nummer. Wird so in den Datenblättern ständig verwendet. Bei dir dann TCCR5A

Kleine Pause, neuer Zwischenstand.
Diesmal eher zur Hardware Seite.
Beide Code-Varianten laufen - sowohl die mit als auch die ohne Timer.
Der Vorteil der Timer - man kann z.B. das Motor-Shield u.v.m. auf dem gleichen Arduino laufen lassen - wird leider durch die Beeinflussung innerhalb der Hardwarekomponenten zunichte gemacht.
Sobald der Stepper anläuft, geht auf der Timer Seite der Hengst durch - unabhängig, ob der Stepper (hier ein sehr schwacher mit 0,4A Spulenstrom) intern oder extern gespeist wird.
Überhaupt schein das Motor-Shield wesentlich mehr Einfluss auf die Signale an noch freien Pins auszuüben, als einem zunächst bewusst und recht ist. Für die Auswertung des Motoransteuerungssignals aus Arduino 1 kann man bei weitem nicht jeden beliebigen digitalen I/O Pin nehmen, der gerade frei ist. An Pin 18 und 19 habe ich wieder vergeblich versucht, ein Signal auszulesen.
Umswitchen auf Pin22 hast dann erledigt.
Ich habe mich nun endgültig für die Variante mit zwei Arduinos entschieden - einen für die Messaufgaben und einen für die Motorsteuerung.
Mit beiden Codes bin ich nun bei gefühlten 100% Messsicherheit angelangt. Ausreisser im Messsignal gibt es quasi gar nicht mehr. Das auszuwertende Zählersignal (am Interrupt) liegt knapp oberhalb von 10kHz.

Ich lasse aber zur Zeit den Code ohne Timerverwendung laufen, da der Interrupteingan anscheinend besser auf meine Klimaanlage zu sprechen ist (macht einfach den robusteren Eindruck) - ein CE-Zeichen würde ich trotzdem nicht draufpappen ;-).