Durchflussmesser mit Display und Reset-Taste - wie mit Arduino realisieren

onki

User
Hallo,

der Titel sagt es ja bereits.
Ich möchte ein FlowMeter (15-800ml/min) mit dem Arduino auswerten und die entnommene Menge an einem OLED-Display (SSD 1306) anzeigen.
bei 10.5 Impulsen/ml und max 800ml/min ist mit maximal 140 Impulsen/s zu rechnen.
Wie werte ich die aus? Polling oder erfordert das einen Interrupt. Hab leider noch kein Gefühl für die Performance des ProMini.
Beim Interrupt müsste ja auch alle paar ms der Zähler ausgelesen werden, damit er nicht überläuft (hat ja "nur" 8 bit).
Die Pulse sollen gezählt und die umgerechneten ml auf dem Display mit Beschriftung dargestellt werden (refresh max jede Sekunde).
Alle 10s sollte der aktuelle Wert ins EEPROM gespeichert werden und mittels eines 1s Tastendruck auf Null gesetzt werden können.

Ich bin mir noch nicht sicher, wie ich das umsetzen soll, daher bin ich für Anregungen oder Codebeispiele dankbar.

Gruß
Onki
 

Thomas L

Vereinsmitglied
Interrupt
- es geht kein Puls verloren
- geringere CPU Last

Was meinst Du mit alle paar mS Zähler auslesen und hat nur 8 Bit :confused::confused:
In der Interupt Routine kannst Du eine Variable inkrementieren, je nach Typ 8/16/32 oder sogar 64 Bit groß.

Diese Variable liest Du dann alle n mS aus und setzt sie zurück. So weisst Du genau wieviele Pulse in dem Abfragezyklus angefallen sind.
Das ist jetzt mal eine vereinfachte Darstellung Deiner gewünschten Funktion...
 

onki

User
Hallo Thomas,

ich war der Ansicht der AT328P hätte 2 eingebaute Zähler (Hardware) mit 8 bit Tiefe. Die könnte man dann alle paar ms auslesen.
Ist aber scheinbar nicht so.
Ich kenne das halt so von unseren Geräten. Dort werden aber andere µC eingesetzt und die Zähler sind via Bus (ISP / CAN etc.) eingebunden.

Das Display läuft soweit und der Interrupt scheint auch zu funzen weil das Display bei jedem Puls flachert. Das ist natürlich Mist. Hier muss ich noch viel machen.
Das hier ist der aktuelle Code, der funzt aber nicht weil ich mit den Definitionen noch auf Kriegsfuß stehe:

Code:
#include <Wire.h>
#include <Adafruit_SSD1306.h>

#define OLED_RESET 4 // not used / nicht genutzt bei diesem Display
Adafruit_SSD1306 display(OLED_RESET);

int const signal_pin = 2; // D2
int const signal_pin_interrupt_number = 0; // INT0, 

void setup()   {                

    // initialize with the I2C addr 0x3C / mit I2C-Adresse 0x3c initialisieren
    display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
    
#define DRAW_DELAY 118
#define D_NUM 47
 pinMode(signal_pin, INPUT_PULLUP);
 long counter;
 float verbrauch = counter / 10.5;
// attach an interrupt handler when the signal goes from low to high
 attachInterrupt(signal_pin_interrupt_number, counter, RISING);

}
static int events = 0;
void counter() {
  ++events;
}
void loop() {
  display.clearDisplay();
  
  // set text color / Textfarbe setzen
  display.setTextColor(WHITE);
  // set text size / Textgroesse setzen
  display.setTextSize(1);
  // set text cursor position / Textstartposition einstellen
  display.setCursor(1,0);
  // show text / Text anzeigen
  display.println("Benzinverbrauch:");
  display.setTextSize(3);
  display.setCursor(24,10);
  display.println(verbrauch);
  display.display();
  delay (1000);
    
}

Ist aus verschiedenen Beispielen zusammengesetzt. Irgendwie muss die Anzeige-Schleife noch verschlankt werden, weil ja nur der Verbrauch sich ändert. Zudem tue ich mich mich mit der Berechnung schwer. Das die auch in der Definition auftauchen oder muss das separat erledigt werden?

Gruß
Onki
 
Hi,

Counter ist bei dir eine Funktion. Da brauchst Du das long counter; nicht. Bringt in setup() sowieso nix.

Dafür musst Du aber deinen Zähler "events" deklarieren (macht man aber nicht im setup()!) und auch nicht in der Funktion. Und volatile unsigned int sollte reichen ;) .

Merke: Wenn Du Variablen in einer Funktion deklarierst sind diese auch nur in der Funktion vorhanden.

Aufs Display schreiben brauchst Du nur wenn sich ein Wert ändert. Daher kommt auch dein flackern weil das jede Sekunde gelöscht und neu beschrieben wird.

PS: und gewöhn dir gleich an vernünftige namen für Variablen zu nehmen. Du steigst da sonst in einem Jahr nicht mehr durch!
 
Zuletzt bearbeitet:

wkrug

User
Bei relativ wenig Impulsen / s ist die Verwendung einer festen Torzeit eher nicht geeignet.

Mein Favorit wäre hier der Timer Input Capture Interrupt.
Wenn der durch den Durchflußgeber ausgelöst wird wird der aktuelle Wert des Counter Registers in das Input Capture Register übertragen.
Der ausgelesene Wert ist das Maß für den aktuellen Verbrauch.
Da man für genaue Messungen den Timer nicht mit zu niedrigen Taktraten laufen lassen sollte, würde Ich auch den Timer Overflow Interrupt aktivieren und hier eine Variable hochzählen.
In diesem Interrupt kann man dann auch gleich eine Variable für einen Timeout einbauen, damit bei 0 Spritverbrauch kein Käse angezeigt wird.

Man bekommt so bei jedem Impuls aus dem Impulsgeber ein neues Ergebnis.
Da die Rädchen nicht immer ganz rund laufen würde ich über 4 bis 5 Werte eine gleitende Mittelwertbildung machen.

Die Berechnung ist eigentlich relativ einfach: fixer Wert / (Zählerstand*256 + Overflow) = ml/s.
Nach dem Auslesen der Werte setzt man TCNTx und die Überlaufvariable wieder auf 0 - Möglichst am Anfang der Capture Interrupt Routine.
Das geht aber nur, wenn die Anzahl der Impulse proportional zur verbrauchten Spritmenge ist.
Ist dies nicht der Fall, kann man sich mit Näherungsformeln, oder Tabellen für bestimmte Bereiche der Zählerstände behelfen um eine gute Näherung zu erreichen.

Ist das Display zu zappelig, kann über einen weiteren Timer eine feste Display refresh Rate erreicht werden - 300ms sind hier ein guter Wert.
Das ist auch nützlich, wenn man den Gesamtverbrauch ermitteln will.

In so einem Interrupt bitte keine komplexen Berechnungen anstellen, oder das Display ansteuern.
Lieber nur ein Flag setzen und die Berechnung dann im Hauptprogramm machen lassen.

Das Geflacker im Display kommt vom Löschen des kompletten Displayinhaltes.
Wenn Du bei jedem Schreibvorgang das Display komplett neu beschreibst wird das verschwinden.
 

Thomas L

Vereinsmitglied
Hallo Onki,

so wie ich das gerade interpretiere, sei mir bitte nicht böse, hast Du noch ganz wenig Ahnung vom Programmieren. Ob es dann eine gute Idee ist schon mit Interrupt zu entwickeln ??
Habe mal soweit auf die Schnelle überhaupt möglich meinen Code auf Deine Anwendung hin angepasst, soll Dir als Beispiel dienen wie soetwas realisiert werden könnte. Hoffe das Du damit etwas anfangen kannst.

Mal ein paar Hinweise/Tipps:
Bei meinem XSensor habe ich die Berechnung der aktuellen Durchflussmenge und dem Gesamtverbrauch bereits realisiert, vielleicht schaust Du Dir dort mal etwas ab.
Hierbei bin ich aber auf die kleinste Einheit, die uL / Puls, gegangen. So fallen Messtoleranzen nicht ins Gewicht.
Wichtig ist auch die Verwendung von float Variablen (wo notwendig) damit bei Berechnungen keine Rundungsfehler entstehen.
Im Interrupt, wurde schon drauf hingewiesen, so wenig wie möglich machen. Auf gar keinen Fall irgendwelche Library-Aufrufe ...
Variablentyp mit vorangestellten "volatile" zwingt den Compiler den Code so zu generieren das die Variable IMMER eingelesen wird. Wichtig da Sie sich im IRQ ändert.

Sensor Benzin : BioTech FCH-m-PP-LC
- Durchflussmenge 15mL - 800mL
- 10500Pulse / Liter
> 1mL = 10,5 Pulse
> 0,095mL = 1 Pulse


Code:
// der IRQ-PIN 2/3 auf dem der Sensor geschaltet ist
#define PIN_FUEL	2

#define FLOW_AVG_CNT   8
#define FLOW_AVG_MSK   0x07
#define FLOW_AVG_SH    3  		              // n/8, # shift right für Divison
volatile uint32_t MesureAvg[FLOW_AVG_CNT];  // die jeweils letzten n Messungen in uS




static uint32_t FlowCntTank;                // # Impulse gesamt entnommen

void setup()
{
  // da der Sensor fallende Flanken liefert den PIN intern mit einem PULL-UP versehen
  pinMode( PIN_FUEL, INPUT_PULLUP );
  // dann die Interrupt-Service Routine für diesen PIN definieren, IRQ Auslöser ist fallende Flanke
  attachInterrupt( digitalPinToInterrupt(PIN_FUEL), FlowIRQ, FALLING );
}


// ============================
// capture Durchfluss interrupt
// ============================
void FlowIRQ( void )    // >> ISR( PCINT1_vect ) PIN change IRQ
{
 static uint8_t  CaptureIdx;
 static uint32_t LastMicros;
 register uint32_t uS = micros();

  // Pulse/Min und entspr. Abstände
  //   1mL = 10,5 Pulse = 5714,29mS
  //  50mL =  525 Pulse =  114,29mS
  // 800mL = 8400 Pulse =    7143uS
  // für Flowanzeige je n Messungen zur Durchschnittsberechnung sichern
  if( LastMicros )                                // aus irgendeinem Grund löst der IRQ nach Init 1x aus, 1. IRQ verwerfen
    MesureAvg[CaptureIdx++] = uS - LastMicros;
  CaptureIdx &= FLOW_AVG_MSK;
  LastMicros = uS;

  FlowCntTank++;                               // Summe der Pulse, entnommenes Volumen
}





#define	TIM_BASE 10									// Timeraufruf alle 10mS
static uint8_t TmoVolumeRefresh;
  #define TMO_VOL_REFRESH  (1000/TIM_BASE)  // jede Sek. das Restvolumen berechnen
static uint8_t  TmoFlowCalc;
  #define TMO_FLOW_CALC     (500/TIM_BASE)  // alle 500mS die akt. Durchflussmenge berechnen
static uint8_t TmoFlowOff;
  #define TMO_FLOW_OFF  TMO_FLOW_CALC + (100/TIM_BASE)  // mS ohne Flow Impuls


static float Flow;                            // akt. Flow in mL
static uint16_t VolumeConsumed;               // akt. entnommene Menge in mL

static uint32_t FlowCntLast = -1;


// dann noch eine Routine die alle 10mS aufgerufen wird
// davon abgeleitet dann alle 500mS eine Flowberechnung und jede Sek. eine Restvolumen
// ==========
// 10mS Timer
// ==========
void Tim10mS()
{
  // ------------------------------------
  // Durchflussmenge berechnen, alle n mS
  // ------------------------------------
  if( TmoFlowCalc )
    TmoFlowCalc--;
  else if( FlowCntTank != FlowCntLast )       // liegt neue Messung vor ? oder Init ?
    {
    uint32_t Avg = 0L;
    uint8_t Idx;

    Idx = FLOW_AVG_CNT;
    while( Idx-- )
      Avg += MesureAvg[Idx];                    // Summe der Messungen in uS
    Avg >>= FLOW_AVG_SH;                        // Sum/n, Mittelwert Impulsabstand in uS

    // Impuls Anzahl auf 1 Min hochrechnen, akt. Flow
    float FCntMin;
    if( Avg )                                   // Division durch 0 vermeiden ;)
      {
      FCntMin = (60L*1000L*1000L) / Avg;
      // # uL/Pulse auf ml/Min umrechnen
      Flow = (FCntMin * uL_OnePulse) / 1000.0;  // uL > mL
      }

    // akt. entnommene Menge
    VolumeConsumed = ((float)FlowCntTank * uL_OnePulse) / 1000.0;

    FlowCntLast = NV_FlowCntTot;
    TmoFlowCalc = TMO_FLOW_CALC;            // nur alle n mS bewerten und das nur bei Änderungen
    TmoFlowOff  = TMO_FLOW_OFF;             // Zeit ohne Impulse bis Durchfluss Stopp erkannt
    }


  // ----------------------------
  // Motor steht, kein Durchfluss
  // ----------------------------
  if( TmoFlowOff && (--TmoFlowOff == 0) )   // entnommene Menge sichern nur wenn wir ausreiched Flow hatten
    {
    Flow = 0;                          // Flow Anzeige = 0mL/Min
    //memset( MesureAvg, 0, sizeof(MesureAvg) );  // Durchnitt neu berechnen
    }
}
 

onki

User
Hallo Thomas,

Danke für deine Bemühungen.
In der Tat, ich bin was die Programmiererei angeht recht unbeleckt, umgebe mich aber tagtäglich mit dieser Gattung Mensch.

Leider hab ich im Moment wenig Zeit mich darum zu kümmern aber ich bleibe da auf jeden Fall dran.

Gruß
Onki
 
Ansicht hell / dunkel umschalten
Oben Unten