Problem Drehzahl ohne Interrupt Berechnen?

Hallo

ich habe einen Arduino Mega und habe ein größeres Programm am laufen

nun möchte ich noch die Drezhzahl eines Motors (0-3000)RPM auf meinem TFT-Display ausgeben

mit dem Scatch von Proto:

// Drehzahlmesser
// by Proto
// -------------------------------------------------------------------
#include <Wire.h>

int maxU = 3000;


const int Eingang = 2  ;                      // Pin 2 ist Eingang für Sensor

const int AVG = 10;                            // Glättung über mindestens 4 Messwerte 
const unsigned long sampleTime=1000;
volatile unsigned long dauer=0;               // microsekunden seit dem letzten Interrupt
volatile unsigned long last=0;                // Zählerwert beim letzten Interrupt
volatile unsigned long average=1;             // Integrierte Dauer // =1: divide by 0 vermeiden
volatile int avgcnt=0;                        // Anzahl Messwerte im Puffer

void setup() {
  Serial.begin(9600);
  pinMode(Eingang, INPUT);                    // Eingangspin auf Eingang stellen
                                              // und Pullup-Widerstand einschalten


    // Interrupt 0 auf Routine readmillis setzen
}                                             // reagiert auf steigende Flanken an Pin 2

void loop() {                                 // Hauptprogramm
  char buf[17];                               // Pufferstring für sprintf
  unsigned long drehzahl=0;                   // selbstredend
  if (dauer != 0) {
    drehzahl = myround((60000000 / average)/230);   // Drehzahl ausrechnen und runden (230 Getriebe Übersetzung)
  } else {
    drehzahl = 0;                             // keine Messung? -> Stillstand
    avgcnt = 0;                               // entwerten des Average-Puffers
  }
  sprintf(buf, "%4lu/min ", drehzahl);        // als 5stellig formatierte Zahl in den Puffer schreiben                       // cursor an den Anfang der 2. Zeile (fängt mit 0 an)
  Serial.println(buf);                             // Puffer ausgeben
 
  dauer >>= 10;                               // Flag für Stillstand ( : 1024 )
  delay(500);                                 // eine halbe Sekunde schlafen
}                                             // mehr als zwei Werte pro Sekunde kann eh keiner lesen

void readmicros() {                           // Interrupt-Routine
    int kount=0;
  boolean kflag=LOW;
  unsigned long currentTime=0;
  unsigned long startTime=millis();
  while (us<=sampleTime)
  {
    if (digitalRead(Eingang)==HIGH)
    {
      kflag=HIGH;
    }
    if (digitalRead(Eingang)==LOW && kflag==HIGH)
    {
      kount++;
      kflag=LOW;
    }unsigned long us = micros(); 
    us=millis()-startTime;
  }
  
  
  
  // Interrupt ausschalten damit er uns nicht beißt
  int avgmax;
                 // Microsekundenzähler auslesen
  if (last == 0) {                            // erster Messwert?  
    last = us;                                // merken und nicht weiter bearbeiten
  } else { 
    if ( us < last ) {                        // Zählerüberlauf
      dauer = 4294967295 - last + us;         // erzeugt einen Fehler von 1µS - vernachlässigbar
    } else {
      dauer = us - last;                      // Differenz zum letzten Durchlauf berechnen
    }
    if (dauer > 5000) {                       // ignorieren wenn <= 5ms (Kontaktpreller)
      average = dauer + average * avgcnt++;   // Wert in buffer und mit Faktor avgcnt glätten
      average /= avgcnt;                      // und zurückrechnen
      avgmax = 1000000 / dauer;               // dynamische Größe des Integrationspuffers
      if (avgmax < AVG) avgmax = AVG;         // Trägheit mindestens 1 Sekunde
      if (avgcnt >= avgmax) avgcnt--;
      last = us;                              // und wieder den letzten Wert merken
    }
  }
     // Interrupt wieder einschalten.
}

unsigned long myround(unsigned long value) {  // Gewichtete Rundung
  int rto;
  if (value > 3000) {                         // Rundungswert bestimmen  
    rto = 100;
  } else if (value > 1500) {
    rto = 50;
  } else if (value > 500) {
    rto = 10;
  } else if (value > 100) {
    rto = 5;
  } else {
    return (value);
  }
  return (_myround(value, rto));
}

unsigned long _myround(unsigned long value, int roundto) {
  value += (roundto >> 1);                    // halben roundto Wert addieren
  value /= roundto;                           // integer division  
  value *= roundto;                           // integer multiplikation
  return (value);
}

hat das auch geklapt jedoch ist dies ein größeres Programm und der Interrupt unterbricht mir dieses und Sorgt auch dafür das mein Touchscreen nicht richtig funktionert( druck wird erst nach 2 sec erkannt)

wie kann ich diesen code ummodeln sodas er ohne Interrupt funktioniert?

Hoffe ihr könnt mir helfen?

Gruß Kratos

Und wo ist da die Interrupt Routine?

Sorry Hab damit schon etwas rumgespielt

Hier der code der Funktioniert hatt

// Drehzahlmesser
// by Proto
// -------------------------------------------------------------------
#include <Wire.h>              // LCD i2c Library einbinden

int maxU = 2700;

const int Eingang = 52;                        // Pin 2 ist Eingang für Sensor

const int AVG = 4;                            // Glättung über mindestens 4 Messwerte

volatile unsigned long dauer=0;               // microsekunden seit dem letzten Interrupt
volatile unsigned long last=0;                // Zählerwert beim letzten Interrupt
volatile unsigned long average=1;             // Integrierte Dauer // =1: divide by 0 vermeiden
volatile int avgcnt=0;                        // Anzahl Messwerte im Puffer

void setup() {
  pinMode(Eingang, INPUT);                    // Eingangspin auf Eingang stellen
  digitalWrite(Eingang, HIGH);                // und Pullup-Widerstand einschalten

 
  attachInterrupt(0, readmicros, RISING );    // Interrupt 0 auf Routine readmillis setzen
}                                             // reagiert auf steigende Flanken an Pin 2

void loop() {                                 // Hauptprogramm
  char buf[17];                               // Pufferstring für sprintf
  unsigned long drehzahl=0;                   // selbstredend
  if (dauer != 0) {
    drehzahl = myround(60000000 / average);   // Drehzahl ausrechnen und runden
  } else {
    drehzahl = 0;                             // keine Messung? -> Stillstand
    avgcnt = 0;                               // entwerten des Average-Puffers
  }
  sprintf(buf, "%4lu/min ", drehzahl);        // als 5stellig formatierte Zahl in den Puffer schreiben
  Serial.print(buf);                             // Puffer ausgeben
 
}                                             // mehr als zwei Werte pro Sekunde kann eh keiner lesen

void readmicros() {                           // Interrupt-Routine
  detachInterrupt(0);                         // Interrupt ausschalten damit er uns nicht beißt
  int avgmax;
  unsigned long us = micros();                // Microsekundenzähler auslesen
  if (last == 0) {                            // erster Messwert? 
    last = us;                                // merken und nicht weiter bearbeiten
  } else {
    if ( us < last ) {                        // Zählerüberlauf
      dauer = 4294967295 - last + us;         // erzeugt einen Fehler von 1µS - vernachlässigbar
    } else {
      dauer = us - last;                      // Differenz zum letzten Durchlauf berechnen
    }
    if (dauer > 5000) {                       // ignorieren wenn <= 5ms (Kontaktpreller)
      average = dauer + average * avgcnt++;   // Wert in buffer und mit Faktor avgcnt glätten
      average /= avgcnt;                      // und zurückrechnen
      avgmax = 1000000 / dauer;               // dynamische Größe des Integrationspuffers
      if (avgmax < AVG) avgmax = AVG;         // Trägheit mindestens 1 Sekunde
      if (avgcnt >= avgmax) avgcnt--;
      last = us;                              // und wieder den letzten Wert merken
    }
  }
  attachInterrupt(0, readmicros, RISING );    // Interrupt wieder einschalten.
}

unsigned long myround(unsigned long value) {  // Gewichtete Rundung
  int rto;
  if (value > 3000) {                         // Rundungswert bestimmen 
    rto = 100;
  } else if (value > 1500) {
    rto = 50;
  } else if (value > 500) {
    rto = 10;
  } else if (value > 100) {
    rto = 5;
  } else {
    return (value);
  }
  return (_myround(value, rto));
}

unsigned long _myround(unsigned long value, int roundto) {
  value += (roundto >> 1);                    // halben roundto Wert addieren
  value /= roundto;                           // integer division 
  value *= roundto;                           // integer multiplikation
  return (value);
}

Die Interrupt Routine ist viel zu komplex. Lies wirklich nur die Zeit ein und mach alle Auswertung und Mittelwertbildung im loop.

Und nimm besser cli() und sei(), obwohl das hier gar nicht notwendig sein sollte

aber das verkürzt nur meinen Interrupt oder? würde ihn gern ganz rausnehmen.

Hab in meinem Hauptprogramm noch 2 anderen Interrupts wie reagieren diese Miteinander wenn ich sie benutze?

und was meinst du mit cli() und sei()??

Kratos:
und was meinst du mit cli() und sei()??

Steht für "clear interrupts" und "set interrupts". Statt die attach und detach Funktionen zu verwenden. Alternativ geht auch noInterrupts() und interrupts()

Wobei das nicht in der ISR selbst brauchst! Interrupts können nicht von anderen Interrupts unterbrochen werden. Aber man muss das machen wenn man Multi-Byte Variablen die in der ISR verändert werden außerhalb ausliest

Hab in meinem Hauptprogramm noch 2 anderen Interrupts wie reagieren diese Miteinander wenn ich sie benutze?

Die Interrupts werden nacheinander abgearbeitet. Es gehen erst Interrupts verloren wenn während eines Interrupts (oder während diese sonstwie gesperrt sind) ein anderer zwei mal auftritt.

Hallo,

Wo deine 2s Verzögerung herkommen ist für mich so erstmal nicht ersichtlich. Wenn das genau der Code ist mit den ominösen 2s.

Deine ISR intern ausschalten und einschalten ist jedoch wirklich nonsens, denn das Programm befindet sich ja schon in der ISR, also muss man das nicht noch aus- und wieder einschalten. Ich kann mir vorstellen warum du das machst, zum sicheren auslesen, aber das ist hier an der falschen Stelle. Ein Interrupt wird eigentlich nicht durch einen anderen Interrupt unterbrochen. Es mag Sonderfälle geben. Es wird im Normalfall ein Interrupt abgearbeitet, dann wird eine Rechenoperation ausgeführt oder ein Takt im Hauptprogramm ausgeführt und dann gehts, sofern ein neuer ansteht, zum nächsten Interrupt.

Also lass die erste und letzte Zeile in der readmicros ISR weg. Desweiteren ist in der ISR avgmax nicht sauber initialisiert. Entweder mit 0 initialisieren oder wenn es erhalten bleiben soll mit static und 0.

Der richtige Weg wäre den Zählerstand in der ISR nur zu erfassen und in der loop auszuwerten. Genau hier kommt das sichere auslesen zum tragen. Macht man mit atomic. Deine ISR beschränkt sich dann genau auf eine Zeile.

void readmicros() {                         
    us = micros();                             
}

Den Rest machste in der loop oder einer aufgerufenen Funktion.

ATOMIC_BLOCK (ATOMIC_RESTORESTATE)       // cli()+sei() mit SREG Sicherung
{
      _us = us;                   // Interrupt sicher übernehmen
}

>>> ab hier folgt dein Code, nur mit _us statt us Variable, musste anpassen !!!

if (last == 0) {                            // erster Messwert?
    last = us;                                // merken und nicht weiter bearbeiten
  } else {
    if ( us < last ) {                        // Zählerüberlauf
      dauer = 4294967295 - last + us;         // erzeugt einen Fehler von 1µS - vernachlässigbar
    } else {
      dauer = us - last;                      // Differenz zum letzten Durchlauf berechnen
    }
    if (dauer > 5000) {                       // ignorieren wenn <= 5ms (Kontaktpreller)
      average = dauer + average * avgcnt++;   // Wert in buffer und mit Faktor avgcnt glätten
      average /= avgcnt;                      // und zurückrechnen
      avgmax = 1000000 / dauer;               // dynamische Größe des Integrationspuffers
      if (avgmax < AVG) avgmax = AVG;         // Trägheit mindestens 1 Sekunde
      if (avgcnt >= avgmax) avgcnt--;
      last = us;                              // und wieder den letzten Wert merken
    }
  }

us, _us und avgmax global machen und zusätzlich muss us volatile sein.
und noch #include <util/atomic.h> einfügen

Optimieren kann man das noch das durch ein zusätzliches Flag in der ISR, was man auch in der Funktion abfragt, sodass die ganze Berechnung nur gemacht wird wenn es in der ISR einen neuen Wert gibt, also wenn die ISR auch wirklich erneut aktiv wurde. In der ISR das Flag setzen, am Anfang der Funktion auswerten und am Ende der Auswertung zurücksetzen. So würde ich das machen. Ansonsten würde die Berechnung jedesmal stattfinden ob es keinen neuen Wert aus der ISR gibt.