Durchfluss(-impuls-)zähler zeitlich auswerten und Durchschnittswert bilden

Hallo!

Ich möchte einen Durchflusssensor auswerten (Drehflügelrad mit Hall-Sensor als Impulsgeber). Das funktioniert grundsätzlich schon einmal. Allerdings benötige ich unter anderem einen Durchschnittswert über einen Durchflussvorgang, wofür ich also die Zeit messen muss von Start (Impulszahl > 0) bis Ende (Impulszahl ==0). Das haut leider noch nicht hin, die Zeitnahme muss irgendwie anders aussehen - hat jemand einen Tipp für mich?

#define flow 2

unsigned long flow_pulse = 0;                                       // Zähler für Messintervall
unsigned long flow_pulse_all = 0;                                  // Zähler für Durchflussvorgang
unsigned long flow_pulse_overall = 0;                           // Zähler für Gesamtdurchfluss
const unsigned long flow_intervall = 1e6;                       //Messintervall = 1s

unsigned long flow_meas_time;                                    //Startzeit Messintervall
unsigned long flow_start_time;                                     //Startzeit Durchflussvorgang
unsigned long flow_time;                                             //Gesamtzeit Durchflussvorgang

float q_av = 0;                                                             //Durchschnittsdurchfluss pro Vorgang in counts/s


void flow_meas() {
  flow_pulse = 0;                                                          //Zähler für Messintervall zurücksetzen
  flow_meas_time = micros();                                             //Startzeit für Impulszählung

  if (pulseIn(flow, HIGH) > 0) {
    flow_start_time = millis();                                          //Startzeitpunkt für Filtrationszeit

    do {
      if (pulseIn(flow, HIGH) > 0) {                                    //wenn ein HIGH-Puls mit Dauer>0 am Pin erkannt wird
        flow_pulse++;
        flow_pulse_all++;
        flow_pulse_overall++;                                            //Alle Pulszähler++
        flow_time = flow_time + millis() - flow_start_time;   //Filtrationszeit = jetzt - Startzeit
      }
    } while ( micros() < flow_meas_time + flow_intervall);  //prüfen, ob Messintervallzeit erreicht
  }

  q_av = flow_pulse_all / (flow_time / 1000.0);                 //Durchfluss über gesamten Vorgang in Counts/s


  if (flow_pulse == 0) {                                                  //wenn kein Durchfluss
    if (flow_time > 0)                                                     //wenn Filtration stattgefunden hat
    {
      flow_pulse_all = 0;                                                 //Filtrationszähler zurücksetzen
    }
  }
}

void setup()   {
  pinMode(flow, INPUT);
  Serial.begin(9600);
}

void loop() {
  flow_meas();
  Schreiben();
}

unsigned long schreib_last_millis;
unsigned long schreib_intervall = 1000;

void Schreiben() {
  if (millis() - schreib_last_millis < schreib_intervall) return;  // Zeit noch nicht erreicht, Funktion abbrechen
  schreib_last_millis += schreib_intervall;

  Serial.print(flow_time / 1000.0, 2);
  Serial.print(" s");
  Serial.print("\t");


  Serial.print(flow_pulse);
  Serial.print("\t");
  Serial.print(flow_pulse_all);
  Serial.print("\t");
  Serial.print(flow_pulse_overall);

  Serial.print("\t");
  Serial.print(q_av);
  Serial.println("");
}

pulseIn blockiert. Während es läuft, tut der Prozessor nichts anderes. Du willst doch die Impulsanzahl, nicht die Impulsdauer messen.

Gruß Tommy

Tommy56:
pulseIn blockiert. Während es läuft, tut der Prozessor nichts anderes. Du willst doch die Impulsanzahl, nicht die Impulsdauer messen.

Gruß Tommy

Hmm, das stimmt. Aber die Pulszahl die ich bekomme ist korrekt, das funktioniert also trotzdem. Aber das könnte der Grund sein, dass die Zeitnahme nicht funktioniert.

Lies die Impulse besser über eine Flankendetektion im Interrupt ein, Zähle dort eine Variable hoch. Wenn die Messzeit vorbei ist, Variable auslesen und auf 0 setzen (atomar). Infos hier.
Wenn Du 1 Sekunde abmessen willst genügt millis. Micros bringt nicht mehr Genauigkeit.

Gruß Tommy

Tommy56:
Lies die Impulse besser über eine Flankendetektion im Interrupt ein, Zähle dort eine Variable hoch. Wenn die Messzeit vorbei ist, Variable auslesen und auf 0 setzen (atomar).

Hallo Tommy,
danke für den Tipp und den Link!

Ich habe es mal mit Interrupt versucht, muss aber gestehen, dass ich das Konzept wohl noch nicht ganz durchdrungen habe (obwohl es mir simpel erscheint). Mir fehlt noch das Verständnis, an welcher Stelle ich jetzt welchen Wert setzen/zurücksetzen muss, sei es Impuls oder Zeit. Hier ist mein erster Versuch:

long int pulse = 0.0;
long int pulse_all=0.0;
long int pulse_overall=0.0;
float q; 
long durchfluss_lastmillis = 0;
long durchfluss_intervall = 1000;
long schreiben_lastmillis = 0;
long schreiben_intervall = 1000;
unsigned long durchfluss_start;
unsigned long schreiben_start;
unsigned long Filtrationszeit=0;

void setup()
{ Serial.begin(9600);
  attachInterrupt(0, Impulszaehler, RISING);  
}

void Impulszaehler() {
  pulse++;
  pulse_all++;
  pulse_overall++;             
}

float durchfluss() {
  durchfluss_start = millis();
  if (durchfluss_start - durchfluss_lastmillis >= durchfluss_intervall) {    
    durchfluss_lastmillis = durchfluss_start;                    
    q = pulse*3600;     //aktuelle Pulse pro Stunde                                     
    pulse = 0;
                                                     
    return q;                                                 
  }
}

void schreiben() {
  schreiben_start = millis();
  if (schreiben_start - schreiben_lastmillis >= schreiben_intervall) { 
    schreiben_lastmillis = schreiben_start;                   

    Serial.print(pulse_all);Serial.print("\t");
    Serial.print(pulse_overall);Serial.print("\t");
    
    //Serial.print(Filtrationszeit);Serial.print("\t");
    
    Serial.println(q);
  }
}

void loop() {
  q = durchfluss();
  
  schreiben();
}

long hat keine Kommastellen. Warum zählst Du 3 verschiedene Werte hoch?
ungetestet:

volatile uint32_t pulse;
uint32_t lastMillis;
const uint8_t durchflussPin = ???; // Wo ist der dran?
const uint32_t intervall = 1000;

void pulseISR() {
  pulse++;
}

void setup() {
  ...
  attachInterrupt(digitalPinToInterrupt (durchflussPin), pulseISR,RISING);
  ...
}

void loop() {
  uint32_t aktMillis = millis();
  if (aktMillis - lastMillis >= intervall) {
    lesen();
    lastMillis = aktMillis;
  }
}

void lesen() {
  uint32_t readPulse;
  noInterrupts();
  readPulse = pulse;
  pulse = 0;
  interrupts();
  // mache irgendwas mit readPulse
}

Es ist auch sinnvoll die verlinkten Beiträge durchzuarbeiten.

Gruß Tommy

Tommy56:
Warum zählst Du 3 verschiedene Werte hoch?

In meinem vorigen Sketch habe ich das so gemacht, weil ich die Pulse pro aktueller Messzeit, die Pulse pro Vorgang, und die Pulse seit Systemstart auswerten möchte. Spricht etwas gegen diese Vorgehensweise?

Es ist auch sinnvoll die verlinkten Beiträge durchzuarbeiten.

Natürlich, da gebe ich Dir Recht. Ich habe den Link nicht vollständig gelesen, aber versucht das Interrupt-Prinzip überhaupt erst ein Mal auf meine Anwendung zu übertragen.

Man sollte in einer ISR so wenig, wie möglich tun. Du kannst doch den Wert readPulse auf die anderen aufaddieren.

Gruß Tommy

Ich muss mich doch noch mal melden wegen des Durchflusszählers. Dank Deines Hinweises tommy hab ich jetzt ein (hoffentlich) Interruptmäßig korrektes Programm. Es funktioniert und gibt mir alle relevanten Werte aus. Auffällig ist nur, dass der aktuelle Durchflusswert filt_q, und auch alle anderen impulsabhängigen Werte mehr von einem mittels Referenzmessung per Industriemessgerät auf Ultraschallbasis ermittelten Wert abweicht, als mein erster Code-Versuch. Die Abweichung zum 'wahren' Wert beträgt immerhin knapp 3 %, der verwendete Impulsgeber selber hat +/- 2 %. Lässt sich diese Abweichung irgendwie auf den Code oder den Controller zurückführen?

const uint8_t durchflussPin = 2; 

uint32_t readPulse;                                                         // Pulszähler für Messintervall
volatile uint32_t pulse;                                                    // Zäher für ISR
uint32_t pulse_all = 0;                                                     // Pulszähler für Filtrationsvorgang
uint32_t pulse_overall = 0;                                              // Gesamtpulse 
uint32_t filt_counter = 0;                                                  // Zähler für Filtrationsvorgänge
bool filtration = 0;                                                           // Filtrationsvorgang J/N
float filt_q = 0;                                                               // Momentanwert L/s
float filt_Q = 0;                                                               // Durchschnittswert L/h für laufenden Vorgang

float filt_Q_av = 0;                                                     // Durchschnittswert L/h nach Vorgang

float filt_Q_av_min = 999.9;                                        // Durchschnittswert L/h minimal 

float filt_V = 0;                                                           //Gesamtfiltrationsvolumen
const float filt_calib = 2500.0;                                    // Umrechnung pulse pro Liter bei 20 °C

uint32_t meas_lastMillis;
const uint32_t meas_intervall = 1000;                                       // Messintervall

uint32_t write_lastMillis;
const uint32_t write_intervall = 1000;                                       // Schreibintervall

unsigned long filt_start_time;                                              // Startzeitpunkt der Filtration
unsigned long filt_duration = 0;                                            // Dauer der Filtration in s
unsigned long filt_end_time;                                             //?// Endzeitpunkt der Filtration

void pulseISR() {
  pulse++;                                                                  // inkrementiert Pulszähler
}

void setup() {
  Serial.begin(9600);
  attachInterrupt(digitalPinToInterrupt (durchflussPin), pulseISR, RISING); // bei steigender Flanke an Pin wird pulseISR aufgerufen
}


void loop() {
  uint32_t meas_aktMillis = millis();                                       // Zeitabgleich; wird ausgeführt, wenn Messintervall erreicht oder überschritten
  if (meas_aktMillis - meas_lastMillis >= meas_intervall) {
    filt_meas();
    meas_lastMillis = meas_aktMillis;
  }

  uint32_t write_aktMillis = millis();                                       // Zeitabgleich; wird ausgeführt, wenn Schreibintervall erreicht oder überschritten
  if (write_aktMillis - write_lastMillis >= write_intervall) {
    schreiben();
    write_lastMillis = write_aktMillis;
  }
}

void filt_meas() {

  noInterrupts();                                                           // clear Interruptflag

  readPulse = pulse;                                                        // überschreiben von ISR-pulse auf Messintervallzähler
  pulse = 0;                                                                // leert ISR-Pulse


  if (readPulse == 0) {                                                     // keine Filtration (mehr) -> entprellen?

    if (filtration == 1) {                                                  // wird einmalig nach Filtration ausgeführt
      //v-01://filt_Q_av = pulse_all / filt_calib / filt_duration * 3600.0;          // Q_av = V / t * 3600 s/h
      filt_counter = filt_counter + filtration;                             // zählt Filtrationsvorgänge

      if (filt_Q_av_min > filt_Q_av) {
        filt_Q_av_min = filt_Q_av;
      }

    }

    filtration = 0;
    pulse_all = 0;
    filt_q = 0;
    filt_Q = 0;

  } else {                                                                           // Filtration detektiert
    if (filtration == 0) {                                                        // wird einmalig pro Filtration ausgeführt
      filt_start_time = millis() / 1000.0;                               // Startzeitpunkt setzen
    }
    filtration = 1;                                                                // Filtrationsvorgänge inkrementieren
    pulse_all = pulse_all + readPulse;                               // Filtrationszähler
    pulse_overall += readPulse;                                        // Gesamtzähler aufaddieren
    filt_duration = millis() / 1000.0 - filt_start_time;            // Filtrationszeit in s
    //filt_qs = readPulse / filt_calib;                                    // Momentanwert Durchfluss in L/s
    filt_q = readPulse / filt_calib * 60.0;                              // Momentanwert Durchfluss in L/min
    filt_Q = pulse_all / filt_calib / filt_duration * 60.0 * 60.0;           // Durchschnittswert pro Vorgang in L/h, sekündlich aktuell?
    filt_Q_av = pulse_all / filt_calib / filt_duration * 3600.0;          //v-02// Q_av = V / t * 3600 s/h
  }

  filt_V = pulse_overall / filt_calib / 1000.0;                            // Gesamtfiltrationsvolumen seit Start in m^3

  interrupts();                                                            // set Interruptflag
}




void schreiben() {
  Serial.print(readPulse);  Serial.print(" ");
  Serial.print(pulse_all);  Serial.print(" ");
  Serial.print(pulse_overall);  Serial.print("\t");

  Serial.print(filt_duration);   Serial.print(" s"); Serial.print("\t");

  Serial.print(filt_q, 2);  Serial.print(" L/s"); Serial.print(" ");
  Serial.print("("); Serial.print(filt_Q, 2);  Serial.print(" L/min)"); Serial.print("\t");
  Serial.print(filt_Q_av, 2 );  Serial.print(" L/h"); Serial.print("\t");
  Serial.print(filt_Q_av_min, 2 );  Serial.print(" L/h Minimum"); Serial.print("\t");
  Serial.print(filt_counter);  Serial.print("x filtriert,"); Serial.print("\t");
  Serial.print(filt_V, 3);  Serial.print(" m3 Gesamtfiltration"); Serial.print("\t");

  Serial.println("");
}

Was mir auf anhieb auffällt, du hast das interrupts() zu spät.

void filt_meas() {

  noInterrupts();                                                           // clear Interruptflag

  readPulse = pulse;                                                        // überschreiben von ISR-pulse auf Messintervallzähler
  pulse = 0;                                                                // leert ISR-Pulse

  interrupts();                                                            // set Interruptflag


  if (readPulse == 0) {                                                     // keine Filtration (mehr) -> entprellen?

    if (filtration == 1) {                                                  // wird einmalig nach Filtration ausgeführt
      //v-01://filt_Q_av = pulse_all / filt_calib / filt_duration * 3600.0;          // Q_av = V / t * 3600 s/h
      filt_counter = filt_counter + filtration;                             // zählt Filtrationsvorgänge

      if (filt_Q_av_min > filt_Q_av) {
        filt_Q_av_min = filt_Q_av;
      }

    }

    filtration = 0;
    pulse_all = 0;
    filt_q = 0;
    filt_Q = 0;

  } else {                                                                           // Filtration detektiert
    if (filtration == 0) {                                                        // wird einmalig pro Filtration ausgeführt
      filt_start_time = millis() / 1000.0;                               // Startzeitpunkt setzen
    }
    filtration = 1;                                                                // Filtrationsvorgänge inkrementieren
    pulse_all = pulse_all + readPulse;                               // Filtrationszähler
    pulse_overall += readPulse;                                        // Gesamtzähler aufaddieren
    filt_duration = millis() / 1000.0 - filt_start_time;            // Filtrationszeit in s
    //filt_qs = readPulse / filt_calib;                                    // Momentanwert Durchfluss in L/s
    filt_q = readPulse / filt_calib * 60.0;                              // Momentanwert Durchfluss in L/min
    filt_Q = pulse_all / filt_calib / filt_duration * 60.0 * 60.0;           // Durchschnittswert pro Vorgang in L/h, sekündlich aktuell?
    filt_Q_av = pulse_all / filt_calib / filt_duration * 3600.0;          //v-02// Q_av = V / t * 3600 s/h
  }

  filt_V = pulse_overall / filt_calib / 1000.0;                            // Gesamtfiltrationsvolumen seit Start in m^3

}

Das muss gleich nach der Bearbeitung von pulse, sonst kannst Du evtl. Zählimpulse verlieren.

Die Variablen meas_aktMillis und write_aktMillis kannst Du zu einer Variablen aktMillis zusammenfassen.

Gruß Tommy

Tommy56:
Was mir auf anhieb auffällt, du hast das interrupts() zu spät.

Das muss gleich nach der Bearbeitung von pulse, sonst kannst Du evtl. Zählimpulse verlieren.

Die Variablen meas_aktMillis und write_aktMillis kannst Du zu einer Variablen aktMillis zusammenfassen.

Gruß Tommy

Das wird es sein! Ich probiere es gleich mal aus, vielen Dank!

edit: Genau so ist es, die Abweichung ist jetzt < 2 %!

Schön, dass es funktioniert.

Gruß Tommy

Ich muss dieses Thema jetzt doch noch mal ausgraben: Das Programm funktioniert weiterhin soweit wie gehabt. Jetzt möchte ich den Sensor allerdings an einen Uno (vorher Nano) anschließen, um dort mittels SD-Card Shield aufzuzeichnen. Vielleicht ist der Uno defekt, aber das Programm gibt mir nur noch sporadisch Impulse aus, z.B. ein Mal kurz am Anfang eines Durchflussereignisses, und dann wieder am Ende. Da zwischen ist der Zähler 0. Die Verdrahtung ist die selbe, ich habe schon alle Komponenten getauscht (außer den Uno), mit und ohne Kartenschild … Gibt es dafür irgend eine naheliegende Erklärung oder wird es der der Arduino sein?

Hi

Spontan hätte ich auf die SD-Karten-Funktionalität getippt - die Lib wird ja immer noch vorhanden sein, auch, wenn Du das Shield abgenommen hast und die 'SD-Karte nicht bereit' ausgeremmt wurde.

Wird hier aber andere Leuz geben, Die mehr Erfahrung mit SD und 'anderem Kram gleichzeitig' haben dürften.

MfG