Introduction
Liquid flow meters measure water flow and water volume. In this tutorial, I compare the flow rates of two flow meters and produce a signal when the two do not match.
Parts
- Arduino Uno (x1)
- Liquid Flow Meters – Plastic 1/2″ NPS Threaded (x2) (https://www.adafruit.com/product/828)
- LED (x1)
- Breadboard (x1)
- Jumper wires (x9)
Liquid Flow Meter
The Liquid Flow Meter measures flow rate using a pinwheel. Flow rate is represented by the frequency of the pinwheel’s spinning. Each time the pinwheel completes another cycle, the sensor generates a “HIGH” signal and feeds it back to the Arduino. Then, we can calculate the water volume by a linear conversion from the number of completed cycles.
Comparing Two Meters
Here, we are comparing the flow rates from two meters. In this case, I set the threshold at 500 Hz – namely, if the flow rate (or spinning frequency) of the Flow Meter 1 is 500 Hz more than Flow Meter 2 or more, an LED lights up. When the difference shrinks down to lower than 500 Hz, the LED goes off.
Circuit
The Liquid Flow Meter has three wires to be connected. The red wire connects to 5V, the yellow wire connects to a digital pin (e.g., pin 2), and the black wire connects to GND. The 5V pin powers the sensor, and the digital pin collects signals from the sensor and feeds them to the Arduino.
The Code
Setting variables
First, we set up the necessary variables. It might look confusing at first, but it will clear up once you read through the rest of the tutorial. Note that each group contains two variables, one for each Flow Meter.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// Defining which pins are used for the two flow sensors. Pin 2 for the first sensor, and Pin 4 for the second. #define flowSensorPin1 2 #define flowSensorPin2 4 // The variables pulses1 and pulses2 are used to count how many pulses there have been so far; for each sensor, respectively. volatile uint16_t pulses1 = 0; volatile uint16_t pulses2 = 0; // The variables still1 and still2 are used to examin whether the water has stopped. // If no activation has been recorded for more than three seconds, then the water is regarded as having stopped. volatile uint16_t still1 = 0; volatile uint16_t still2 = 0; // To track the previous pin states of the two meters. volatile uint8_t lastFlowPinState1; volatile uint8_t lastFlowPinState2; // Keeping the time of how long it is between pulses. volatile uint32_t lastFlowRateTimer1 = 0; volatile uint32_t lastFlowRateTimer2 = 0; // Using the time between pulses to calculate flow rates. volatile float flowRate1; volatile float flowRate2; |
Timer interrupts
The Flow Meter produces a “HIGH” pulse when the pinwheel completes another cycle. Rather than checking for pulses continuously and incorporating it in the main loop, it is more convenient if we set an automatic “timer interrupts” mechanism (see https://learn.adafruit.com/multi-tasking-the-arduino-part-2/timers) that checks for pulse every millisecond.
To do this, first we need to add some code in the setup:
1 2 |
OCR0A = 0xAF; TIMSK0 |= _BV(OCIE0A); |
This means that the frequency of this “timer interrupts” mechanism is approximately 976 Hz. We can take that for 1000 Hz for our purpose.
Then, we have a separate section that includes everything that should happen when an “interrupt” happens every millisecond. This section is separate from the main loop.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
SIGNAL(TIMER0_COMPA_vect) { // The program tests whether the flow pins return the same state as the previous one. uint8_t x1 = digitalRead(flowSensorPin1); uint8_t x2 = digitalRead(flowSensorPin2); // If this pin state is the same as the last one, it means that a cycle has not finished. // lastFlowRateTimer1 and lastFlowRateTimer2, which are used to record the gap between two pulses, accumulate on themselves. if (x1 == lastFlowPinState1) { lastFlowRateTimer1++; // If a cycle is longer than 3 seconds, it means that the water has stopped. if (lastFlowRateTimer1 >= 2000){ flowRate1 = 0; } } else { // If this pin state differs from the last one, namely activated as HIGH, // then the accumulation of lastFlowRateTimer1 and lastFlowRateTimer2 stops (because the gap has ended), // and flow rate is the reciprocal of the accumulated time // then lastFlowRateTimer1 and lastFlowRateTimer2 are reset. if (x1 == HIGH) { pulses1++; } lastFlowPinState1 = x1; flowRate1 = 1000.0; flowRate1 /= lastFlowRateTimer1; lastFlowRateTimer1 = 0; } // Same for the second flow meter. if (x2 == lastFlowPinState2) { lastFlowRateTimer2++; // If a cycle is longer than 3 seconds, it means that the water has stopped. if (lastFlowRateTimer2 >= 3000){ flowRate2 = 0; } } else{ if (x2 == HIGH) { pulses2++; } lastFlowPinState2 = x2; flowRate2 = 1000.0; flowRate2 /= lastFlowRateTimer2; lastFlowRateTimer2 = 0; } } |
The code basically does this (and the process repeats itself for both meters):
- We have a timer and a counter for each sensor. They are set to 0 in the beginning. We also have a variable to record what the sensor’s state the last time we checked and a variable to record the accumulate number of pulses.
- Every millisecond, we check if the meter returns the same deactivated state (“LOW”) as the last time we checked.
- If so, then the timer adds 1. This means that the deactivated period of this cycle adds one millisecond (since we check every millisecond).
- If not – if the sensor returns “HIGH” – then it means that this cycle is at its end. The number of pulses adds 1.
- The next time, the sensor gets “LOW” again. Since this cycle is complete, we can now calculate the flow rate (i.e., frequency of the pinwheel) at this point, which equals the reciprocal of the cycle time length, multiplied by 1,000 (1 second equals 1,000 milliseconds).
- Then, the timer is reset. But the flow rate variable remains until the next activation.
- Repeat from step 2.
Main loop
While we “interrupt” and check sensor statuses every millisecond, the flow rate variables are constantly and rapidly updating in the background. Meanwhile in the main loop, we access the flow rate of each Flow Meter every 2 seconds, printing them to the Serial Monitor.
1 2 3 4 5 6 7 |
// Printing out the flow rates of the two flow meters, respectively. Serial.print("First meter's flow rate: "); Serial.print(flowRate1); Serial.println("; "); Serial.print("second meter's flow rate: "); Serial.print(flowRate2); Serial.println("."); |
We can also calculate the accumulate water volume of each meter and print them to the Serial Monitor.
1 2 3 4 5 6 7 8 9 10 |
float liters1 = pulses1 / 7.5 / 60; float liters2 = pulses2 / 7.5 / 60; Serial.print("First meter's accumulate water volume:"); Serial.print(liters1); Serial.println(" liters;"); Serial.print("Second meter's accumulate water volume:"); Serial.print(liters2); Serial.println(" liters."); |
When the flow rate from Meter 1 is 500 Hz greater than that from Meter 2 or more, the LED lights up. When the gap shrinks to lower than 500 Hz, the LED goes off. Then we delay 2 seconds until the next reading.
1 2 3 4 5 6 7 |
if (flowRate1 - flowRate2 > 500){ digitalWrite(LED_BUILTIN, HIGH); } else{ digitalWrite(LED_BUILTIN, LOW); } delay(2000); |
The entire code is as follows.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
/********************************************************** In this tutorial, I compare the flow rates of two flow meters and produce an LED signal when the two do not match. Author: Jie Li Tested and works great with the Adafruit plastic and brass meters ------> http://www.adafruit.com/products/828 ------> http://www.adafruit.com/products/833 Connect the red wire to +5V, the black wire to common ground and the yellow sensor wire to pin #2 This code is built upon a test code produced by Limor Fried/Ladyada for Adafruit Industries. Source: https://github.com/adafruit/Adafruit-Flow-Meter BSD license, check license.txt for more information All text above must be included in any redistribution **********************************************************/ // Defining which pins are used for the two flow sensors. Pin 2 for the first sensor, and Pin 4 for the second. #define flowSensorPin1 2 #define flowSensorPin2 4 // The variables pulses1 and pulses2 are used to count how many pulses there have been so far; for each sensor, respectively. volatile uint16_t pulses1 = 0; volatile uint16_t pulses2 = 0; // The variables still1 and still2 are used to examin whether the water has stopped. // If no activation has been recorded for more than three seconds, then the water is regarded as having stopped. volatile uint16_t still1 = 0; volatile uint16_t still2 = 0; // To track the previous pin states of the two meters. volatile uint8_t lastFlowPinState1; volatile uint8_t lastFlowPinState2; // Keeping the time of how long it is between pulses. volatile uint32_t lastFlowRateTimer1 = 0; volatile uint32_t lastFlowRateTimer2 = 0; // Using the time between pulses to calculate flow rates. volatile float flowRate1; volatile float flowRate2; // Every millisecond, the main loop is interrupted. SIGNAL(TIMER0_COMPA_vect) { // The program tests whether the flow pins return the same state as the previous one. uint8_t x1 = digitalRead(flowSensorPin1); uint8_t x2 = digitalRead(flowSensorPin2); // If this pin state is the same as the last one, it means that a cycle has not finished. // lastFlowRateTimer1 and lastFlowRateTimer2, which are used to record the gap between two pulses, accumulate on themselves. if (x1 == lastFlowPinState1) { lastFlowRateTimer1++; // If a cycle is longer than 3 seconds, it means that the water has stopped. if (lastFlowRateTimer1 >= 2000){ flowRate1 = 0; } } else { // If this pin state differs from the last one, namely activated as HIGH, // then the accumulation of lastFlowRateTimer1 and lastFlowRateTimer2 stops (because the gap has ended), // and flow rate is the reciprocal of the accumulated time // then lastFlowRateTimer1 and lastFlowRateTimer2 are reset. if (x1 == HIGH) { pulses1++; } lastFlowPinState1 = x1; flowRate1 = 1000.0; flowRate1 /= lastFlowRateTimer1; lastFlowRateTimer1 = 0; } // Same for the second flow meter. if (x2 == lastFlowPinState2) { lastFlowRateTimer2++; // If a cycle is longer than 3 seconds, it means that the water has stopped. if (lastFlowRateTimer2 >= 3000){ flowRate2 = 0; } } else{ if (x2 == HIGH) { pulses2++; } lastFlowPinState2 = x2; flowRate2 = 1000.0; flowRate2 /= lastFlowRateTimer2; lastFlowRateTimer2 = 0; } } void useInterrupt(boolean v) { if (v) { // Timer0 is already used for millis() - we'll just interrupt somewhere // in the middle and call the "Compare A" function above OCR0A = 0xAF; TIMSK0 |= _BV(OCIE0A); } else { // do not call the interrupt function COMPA anymore TIMSK0 &= ~_BV(OCIE0A); } } void setup() { Serial.begin(9600); Serial.println("Water flow comparison"); pinMode(flowSensorPin1, INPUT); pinMode(flowSensorPin2, INPUT); pinMode(LED_BUILTIN, OUTPUT); digitalWrite(flowSensorPin1, HIGH); digitalWrite(flowSensorPin2, HIGH); lastFlowPinState1 = digitalRead(flowSensorPin1); lastFlowPinState2 = digitalRead(flowSensorPin2); useInterrupt(true); } void loop() { // Printing out the flow rates of the two flow meters, respectively. Serial.print("First meter's flow rate: "); Serial.print(flowRate1); Serial.println("; "); Serial.print("second meter's flow rate: "); Serial.print(flowRate2); Serial.println("."); // Printing out the total water volumes that have passed through the two flow meters, respectively. float liters1 = pulses1 / 7.5 / 60; float liters2 = pulses2 / 7.5 / 60; Serial.print("First meter's accumulate water volume:"); Serial.print(liters1); Serial.println(" liters;"); Serial.print("Second meter's accumulate water volume:"); Serial.print(liters2); Serial.println(" liters."); if (flowRate1 - flowRate2 > 500){ digitalWrite(LED_BUILTIN, HIGH); } else{ digitalWrite(LED_BUILTIN, LOW); } delay(2000); } |