Impulsmessung für Durchflussmesser

Hi,

hab mir wieder was Neues ausgesucht :slight_smile:
Ich will einen Durchflussmesser an meinen Arduino hängen. Der bringt wohl so max 1000 Impulse / Minute.
Da jetzt mit der millis() Funktion rumzuspielen, glaub ich ist nicht der Burner.
Hab mir mal die PulseIn-Funktion angeschaut, aber die bringt ja nur die Länge des High bzw. Low-Anteils zurück.
Ich bräuchte aber die Zeitspanne zwischen 2 gleichen Flanken.
Da wär was Interrupt gesteuertes eher zu gebrauchen.

Kann mir da jemand einen Schubs in die richtige Richtung geben?

gruß/hk007

Hänge das Ding an Pin 2 oder 3 (INT 0 oder 1), mit attachInterrupt() kannst Du einen entsprechenden Handler einrichten, dort kriegst Du mit micros() eine höher auflösende Zeitmessung.

Du kannst ka mal den H Teil und dann den L Teil des Impulses messen. Du mißt zwar einige Impulse später aber so schnell ändert sich die Frequenz nicht. Es ist zu kontrollieren wie sich die Impulse mit der Änderung der Frequenz ändern. Wenn sie sich proportional zur frequenz ändern dann kannst Du auch nur den H oder L Teil messen.
Grüße Uwe

pylon:
Hänge das Ding an Pin 2 oder 3 (INT 0 oder 1), mit attachInterrupt() kannst Du einen entsprechenden Handler einrichten

Du brauchst nur entweder die kommende oder die gehende Flanke.
Statt micros kannst du im Handler auch nur die Anzahl Impulse zählen.
Und in loop dann alle (paar) Sekunden die neue Impulsdifferenz als Durchfluss ermitteln. (So kriegst du auch gleich eine 0, wenn keine Impulse kommen)

Allerdings muss in loop kurzzeitig noInterrupts(); gesetzt werden, während eine Aktion mit einer volatile unsigned long Variablen gemacht wird, da Operationen mit Werten > 1 byte unterbrochen werden könnten...

pylon:
Hänge das Ding an Pin 2 oder 3 (INT 0 oder 1), mit attachInterrupt() kannst Du einen entsprechenden Handler einrichten, dort kriegst Du mit micros() eine höher auflösende Zeitmessung.

Glaube, dass wird die beste Methode sein, da ich nebenbei noch andere Sachen am laufen habe, wie z.B. einen Webserver.
Läuft das dann so?

attachInterrupt("PIN", "SUB", RISING)

Für PIN trage ich die 2 oder 3 ein, SUB ist der name meines Unterprogrammes, und RISING bedeutet, daß er bei jeder positiven Flanke ins Unterprogramm springt.
Im Unterprogramm kann ich dann das Delta von micros() auswerten und habe so meine Zeit zwischen 2 Impulsen. Der Rest ist dann reine Mathematik.
Richtig so?
Muss wohl aufpassen, daß ich alle 70 Minuten den Overflow rausfiltere. Oder ich mach das so, wie im Playground, wo der Overflow von millis() abgefangen wird.

Das mit dem Interrupt macht er doch hoffentlich so, wie man es landüblich gewohnt ist:
Beim Interrupt bricht er das laufende Anwenderprogramm ab, rettet die Speicherinhalte, läuft durch die Interrupt-Routine, und springt dann wieder zur Abbruchstelle mit den vorher geretteten Speicherinhalten zurück? Oder muss ich mich da um irgendwas kümmern?

gruß/hk007

** **attachInterrupt( 0, irhandler, RISING);** **

Nicht Pin 2 / 3, sondern Interrupt 0 / 1 !
Und der Funktionsname natürlich nicht als String ( für alle Fälle, sorry falls dir das klar war )

hk007:
Oder muss ich mich da um irgendwas kümmern?

Nein, das stimmt schon so.

Kümmern musst du dich um Variable, die gemeinsam vom Interrupt-Handler und der Hauptroutine verwendet werden:
s. Schlüsselwort "volatile", um falsche Compiler-Optimierungen zu vermeiden.

Und bedenke, dass im Zugriff auf solche Variable die Hauptroutine "mittendrin" unterbrochen werden kann, da wir auf einem 8bit Prozessor arbeiten.
Ich würde die IR Routine so einfach wie möglich halten :

volatile unsigned long pulses;
void irhandler() { pulses++;}

const unsigned int CYCLE = 1000;  // z.B. je Sekunde, oder so oft, dass die Anzahl Pulse genau eine Durchfluss-Einheit ist
unsigned long lastpulses;
unsigned long lastmillis;
void loop () 
{
  unsigned long now=millis();
  if (now - lastmillis >= CYCLE)   
  {
    noInterrupts();
    unsigned long pcount = pulses - lastpulses;
    lastpulses = pulses;
    interrupts();
    lastmillis += CYCLE;
    Serial.print (pcount); Serial.println ( " pulse/s ");
  }
}

Die Implementierung von setup() überlasse ich dir :wink:

edit: Syntax verbessert, aber immer noch ungeprüft

Hi michael_x
THX 8)

michael_x:
** **attachInterrupt( 0, irhandler, RISING);** **

Nicht Pin 2 / 3, sondern Interrupt 0 / 1 !
Und der Funktionsname natürlich nicht als String ( für alle Fälle, sorry falls dir das klar war )

Ne ne schon klar, hab den Befehl nur "angedeutet". Aber das mit dem Pin<->INT hätte ich wohl falsch gemacht.

Aber so ist der Ansatz vllt. besser: Impulse/pro Zeit zählen, und nicht die Zeit zwischen Impulsen messen.
Ich hab sowieso eine Sub, die ich alle Sekunden aufrufe, da kann ich dann die Impulse verarbeiten :slight_smile:
Evtl. noch untersetzen, falls es zu wenig Impulse sind.

Das mit "volatile" hab ich noch nie gebraucht.... Muss ich mich mal einlesen, was das für eine Sinn hat.
Aber du meinst, es wäre wichtig, beim Zugriff auf die "pulses"-Variable die Interrupts zu disablen, damit ich nicht zufällig beim Schreiben der long-Variable einen Interrupt bekomme und dann die Variable "verbogen" ist?

hk007:
Aber du meinst, es wäre wichtig, beim Zugriff auf die "pulses"-Variable die Interrupts zu disablen, damit ich nicht zufällig beim Schreiben der long-Variable einen Interrupt bekomme und dann die Variable "verbogen" ist?

Was ist schon wichtig? Wenn du es nicht machst, wird nur sehr, sehr selten ein Fehler auftreten. Und wenn du das Ergebnis nur irgendwo anzeigst, wird das "nie" jemand merken.
In der Automatisierungstechnik sind allerdings Fehler, die ganz sporadisch auftreten und nicht reproduziert werden können, sehr unangenehm.
Das ist die 2 Machinenbefehle sei und cli , die zusätzlich erzeugt werden, immer wert.

"volatile" ist ein anderer Fall: das sagt dem Compiler nur, dass die Variable sich irgendwie ändern kann, obwohl der Compiler nichts davon weiss.
Nimm an, dein Interrupt-Handler ist in einer anderen Datei, die separat übersetzt wird:

pulses++;  // könnte einfach weggelassen werden, weil es nie gelesen wird ;)

oder der Compiler meint, die Variable braucht er nicht aus dem RAM zu laden, weil er sie noch in einem Register hat ...

Nachdem du die 8 Buchstaben einmal eingetippt hast, ist alles klar: kein zusätzlicher Code, der eigentlich nicht gebraucht wird, aber auch keine falsche Optimierung.

Evtl. noch untersetzen, falls es zu wenig Impulse sind.

Ja. Hängt alles auch davon ab, was ein Puls bedeutet, ab wann du 0 anzeigen willst, und wie du möglichst ohne float Division auskommst.