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.