Forum: Mikrocontroller und Digitale Elektronik Durchflussensor auslesen Arduino


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Paul H. (pauleheisster)


Bewertung
0 lesenswert
nicht lesenswert
Hallo Zusammen,

ich bin dabei einen Durchflusssensor der Firma Biotech mit dem Arduino 
Mega auszulesen.
Es handelt es sich um das gute Stück:
https://www.conrad.de/de/durchfluss-sensor-1-st-fch-c-pa-2-x-g-12-ag-bio-tech-ek-betriebsspannung-bereich-5-24-vdc-messbereich-05-503591.html

Datenblatt:
http://www.produktinfo.conrad.com/datenblaetter/500000-524999/503591-da-01-ml-DURCHFLUSSMESSER_FCH_C_PA_de_en.pdf

Angeschlossen wie folgt:
+Ub an 5V von Arduino
GND an GND des Arduino
S an Pin 2 des Arduino

Im Datenblatt steht: 435 Imp./L. Also im Programmcode mittels Interrupt 
die Impulse gezählt und ab bestimmter Messzeit das ganze ausgerechnet.
Soweit klappt das alles auch und ich bekomme wohl realistische Werte von 
bis zu 16,5 l/min.
Allerdings stimmt mein Wert nicht mehr wenn der Arduino im Loop mit 
beispielsweise einer For Schleife für eine gewisse längere Zeit 
beschäftigt ist. Je länger ein Delay in dem Loop desto mehr sinkt der 
Wert. Das ganze verwundert mich weil ich für die Berechnung die 
tatsächliche Zeit (differenceMillis) seit der letzten Rechnung bis zur 
aktuellen Rechnung als Referenz nehme.

Hab ich ein Denkfehler?

Code:
1
double sensorCount, periode = 5000; //5 sec Imp. zählen
2
float durchfluss;
3
unsigned long startMillis;
4
unsigned long currentMillis;
5
unsigned long differenceMillis;
6
7
void setup() {
8
Serial1.begin(9600);
9
attachInterrupt(digitalPinToInterrupt(2), isr_durchfluss, CHANGE); //Datenkabel an Pin 2
10
startMillis = millis(); 
11
}
12
13
void loop() {
14
15
currentMillis = millis();
16
differenceMillis = currentMillis - startMillis; 
17
if (currentMillis - startMillis >= periode)
18
  {
19
  durchfluss = (sensorCount/435)*(60000/differenceMillis); //435 Imp./L, 1 min = 60 000 ms
20
  startMillis = millis();
21
  sensorCount =0;
22
  }
23
24
//Hier Bspw.:
25
//delay(6000); //je höher desto mehr sinkt der Durchfluss
26
}
27
28
void isr_durchfluss()
29
{
30
sensorCount++;
31
}

: Bearbeitet durch User
von Stefan ⛄ F. (stefanus)


Bewertung
-1 lesenswert
nicht lesenswert
Das hier riecht nach Problemen:

> durchfluss = (sensorCount/435)*(60000/differenceMillis);

Da treten bestimmt mehrere Rundungsfehler auf. bedenke auch, dass 
double=float ist. Doppelte Genauigkeit ist nämlich einfach nicht 
implementiert.

Ich würde das ganze auf Integer Arithmetik umstellen, aber dabei auf die 
Bereiche (gültige Werte) achten.

von Paul H. (pauleheisster)


Bewertung
0 lesenswert
nicht lesenswert
Stefanus F. schrieb:
> Ich würde das ganze auf Integer Arithmetik umstellen, aber dabei auf die
> Bereiche (gültige Werte) achten.

Stefanus danke für dein Hinweis. Kannst du das nochmal genauer 
ausführen? Hab noch nicht verstanden wie ich das mit Integer lösen soll.

von Wolfgang (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Paul H. schrieb:
> double sensorCount, periode = 5000; //5 sec Imp. zählen

> if (currentMillis - startMillis >= periode)

Es ist keine gute Idee, die Variablentypen nach dem Zufallsprinzip zu 
verteilen, sozusagen frei nach dem Motto: "Der Compiler wird's schon 
richten".

millis() liefert unsigned long und alles was damit rechnet, sollte gerne 
dazu passen ;-)

von Stefan ⛄ F. (stefanus)


Bewertung
2 lesenswert
nicht lesenswert
Ungeprüfter Vorschlag:
1
#define PERIODE 5000
2
unsigned long lastTime;
3
volatile unsigned long sensorCount;
4
unsigned long lastSensorCount;
5
6
void setup() {
7
    attachInterrupt(digitalPinToInterrupt(2), isr_durchfluss, CHANGE); 
8
    sensorCount=0;
9
    lastSensorCount=0;
10
    lastTime=millis();
11
}
12
13
void loop() {
14
    unsigned long elapsedTime = millis()-lastTime;
15
    if (elapsedTime >= PERIODE)
16
    {
17
        lastTime=millis();
18
        cli();
19
        unsigned long tmp = sensorCount;
20
        sei();
21
        unsigned long count = tmp-lastSensorCount;
22
        lastSensorCount = tmp;
23
24
        unsigned long milliLiters = count*1000/435;
25
        unsigned long milliLitersPerHour = milliLiters*60000/elapsedTime;
26
    }
27
28
    delay(...);
29
}
30
31
void isr_durchfluss()
32
{
33
    sensorCount++;
34
}

Folgendes habe ich mir dabei gedacht:
- Die PERIODE habe ich als Konstante definiert, das kann der Compiler 
nutzen, um den Code zu optimieren. Ist hier nicht wirklich wichtig.
- Die Variable sensorCount ist volatile, das muss man mit allen 
variablen machen, die in einer ISR beschrieben und außerhalb der ISR 
gelesen werden. Ansonsten wird sie eventuelll falsch in einem CPU 
register gecached weil der Compiler "denkt" dass sie sich 
zwischenzeitlich nicht ändern wird.
- Die doppelte Berechnung von "currentMillis - startMillis" habe ich 
optimiert.
- SensorCount wird niemals auf 0 zurück gesetzt. Stattdessen berechne 
ich die Differenz zum vorher verwendeten Wert. Dadurch vermeide ich das 
Verlieren von Counts, wenn Interrupts während der Ausführung der 
Berechnung auftreten.
- Beim Lesen von sensorCount sperre ich die Interrupts, da it die 
variable nicht verändert wird, während sie gelesen wird. Das muss man 
immer machen, sobald die Variable größer als ein Byte ist, sonst kommt 
im Konfliktfall Schrott heraus.
- Bei der Berechnung verwende ich ausschließlich Integer Arithmetik. Bei 
beiden Zeilen multipliziere ich zuerst, bevor ich dividiere, um 
Rundungsfehler zu minimieren. Hierbei muss man aber aufpassen, den 
Wertebereich nicht zu überschreiten. In diesem Fall ist es relativ 
einfach, weil unsigned long groß genug ist (sonst hätte ich noch casten 
müssen).

Wenn der gemessene Wert zur Anzeige gebracht wird, kann man ja immer 
noch durch 1000 dividieren, um Milliliter in Liter umzurechnen. Mit dem 
Modulus Operator käme man dann an die Nachkommastellen.
1
vorDemKomma  = milliLitersPerHour / 1000;
2
nachDemKomma = milliLitersPerHour % 1000;

Wegen dem Casten, folgende Rechnung würde nicht funktionieren:
1
unsigned int a=30000;
2
unsigned int b=40000;
3
unsigned int c=(a+b)/1000;

Weil a+b nicht mehr in einen Integer rein passt. Aber durch casten kann 
man erzwingen, dass die Zwischenergebnisse einen größeren Typ bekommen:
1
unsigned int c= ((unsigned long) a+b)/1000;

a+b passt in einen unsigned long rein, und das kann man danach auch 
korrekt durch 1000 teilen.

Nachdem man das mit dem Casten verstanden hat, kann man die Berechnung 
so umstellen, damit sie genauer wird (weniger Rundungsfehler):
1
unsigned long milliLitersPerHour = (unsigned long long) count*1000*60000/435/elapsedTime;

Das wäre dann aber eine 64 Bit Operation - für den kleinen 8 Bitter 
ziemlich teuer.

: Bearbeitet durch User
von Paul H. (pauleheisster)


Bewertung
0 lesenswert
nicht lesenswert
Stefanus F. schrieb:
> Ungeprüfter Vorschlag:

Danke dir vielmals für dein Vorschlag Stefanus. Ich merke jetzt erst wie 
oberflächlich meine C++ (oder C#?)-Kenntnisse sind. Da ist noch viel zu 
lernen.

Ich werde dein Vorschlag gleich mal testen und berichte dann!

von Tobias S. (x12z34)


Bewertung
1 lesenswert
nicht lesenswert
<offtopic>

Ich bin mal so frei und klinke mich ein, da mich das Thema ebenfalls 
interessiert...

Ich hätte da mal eine Frage zu einem Teilaspekt:

Stefanus F. schrieb:
> - Beim Lesen von sensorCount sperre ich die Interrupts, da it die
> variable nicht verändert wird, während sie gelesen wird. Das muss man
> immer machen, sobald die Variable größer als ein Byte ist, sonst kommt
> im Konfliktfall Schrott heraus.

Ich bin über die gleiche Problematik gestolpert, ein Bekannter empfahl 
(ohne Begründung) die Verwendung des ATOMIC_BLOCK.

Du verwendet jedoch cli()/sei()

Kannst Du / könnt Ihr mir sagen, ob das eine deutliche Vorteile 
gegenüber dem anderen hat bzgl. Geschwindigkeit / Speicherverbrauch oder 
ist das "gehupft wie gesprungen"?

</offtopic>

von STK500-Besitzer (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Tobias S. schrieb:
>Ich bin über die gleiche Problematik gestolpert, ein Bekannter empfahl
> (ohne Begründung) die Verwendung des ATOMIC_BLOCK.

> Du verwendet jedoch cli()/sei()
>
> Kannst Du / könnt Ihr mir sagen, ob das eine deutliche Vorteile
> gegenüber dem anderen hat bzgl. Geschwindigkeit / Speicherverbrauch oder
> ist das "gehupft wie gesprungen"?

Das kommt am Ende beides auf das Gleiche hinaus.

> ATOMIC_BLOCK
ist ein Makro, das auf cli/sei basiert.
Irgendwo müsste die Definition (".h"-Datei) zu finden sein.

von Paul H. (pauleheisster)


Bewertung
0 lesenswert
nicht lesenswert
Klappt alles hervorragend!

Stefanus F. schrieb:
> Wenn der gemessene Wert zur Anzeige gebracht wird, kann man ja immer
> noch durch 1000 dividieren, um Milliliter in Liter umzurechnen. Mit dem
> Modulus Operator käme man dann an die Nachkommastellen.
> vorDemKomma  = milliLitersPerHour / 1000;
> nachDemKomma = milliLitersPerHour % 1000;

Für die Anzeige auf einem Nextion Display benötige ich das Ergebnis 
zwingend in Float.

Ich habe das ganze jetzt so gelöst:
1
   float durchfluss = (float)milliLitersPerHour;
2
   durchfluss = durchfluss/1000;
3
4
   Serial1.print("t6.txt=");
5
   Serial1.write(0x22);  // \"
6
   Serial1.print(durchfluss2);
7
   Serial1.write(0x22);
8
   Serial1.write(0xFF);
9
   Serial1.write(0xFF);
10
   Serial1.write(0xFF);

Sollte das Ergebnis auch nicht mehr verfälschen oder?

: Bearbeitet durch User
von Wolfgang (Gast)


Bewertung
1 lesenswert
nicht lesenswert
Paul H. schrieb:
> float durchfluss = (float)milliLitersPerHour;
>    durchfluss = durchfluss/1000;

Du verwendest du Variablentypen schon wieder nach dem Zufallsprinzip
durchfluss ist ein float, 1000 ein int ;-)
1000.0 wäre ein float-Konstante

von Paul H. (pauleheisster)


Bewertung
0 lesenswert
nicht lesenswert
Wolfgang schrieb:
> Paul H. schrieb:
>> float durchfluss = (float)milliLitersPerHour;
>>    durchfluss = durchfluss/1000;
>
> Du verwendest du Variablentypen schon wieder nach dem Zufallsprinzip
> durchfluss ist ein float, 1000 ein int ;-)
> 1000.0 wäre ein float-Konstante

Ganz getreu dem Motto "Der Compiler wirds schon richten".
Spaß beiseite. Recht hast du. Dankeschön Wolfgang!

von Stefan ⛄ F. (stefanus)


Bewertung
0 lesenswert
nicht lesenswert
So macht man das ohne float, ist etwas effizienter:
1
unsigned long vorDemKomma  = milliLitersPerHour / 1000;
2
unsigned long nachDemKomma = milliLitersPerHour % 1000;
3
char buffer[10];
4
sprintf(buffer,"%lu,%03lu",vorDemKomma,nachDemKomma);
5
Serial1.print(buffer);

: Bearbeitet durch User

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.