├── DISTRIBUTION ├── README └── WaterFlowGauge.pde /DISTRIBUTION: -------------------------------------------------------------------------------- 1 | Copyright 2009 Jonathan Oxer 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | http://www.gnu.org/licenses/ 9 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Water Flow Gauge 2 | ================ 3 | 4 | Copyright 2009 Jonathan Oxer 5 | Copyright 2009 Hugh Blemings 6 | 7 | +---------------------------------------------------------------------+ 8 | | This project is featured in the book "Practical Arduino" by | 9 | | Jonathan Oxer and Hugh Blemings (Apress, 2009). More information | 10 | | about the book and this project is available at: | 11 | | | 12 | | www.practicalarduino.com/projects/easy/water-flow-gauge | 13 | +---------------------------------------------------------------------+ 14 | 15 | Measuring the consumption of a resource that is measured by volume can 16 | be more tricky than it sounds. Usage of materials such as water, gas, 17 | and even electricity is typically measured by gauges that determine 18 | either instantaneous flow rate or cumulative volume over time. Both 19 | techniques have problems: measuring flow rate at frequent intervals 20 | allows you to do time-based reporting and generate a graph of how the 21 | flow rate varied over time, but to determine the total consumption by 22 | volume across a specific time period you then have to integrate the 23 | data and there is the danger of under-reporting usage if your sample 24 | rate is slow and usage rapidly fluctuates or spikes. Measuring 25 | cumulative volume makes it easy to determine total consumption by 26 | volume across a period and is accurate in terms of total usage but to 27 | generate a flow rate graph you then need to calculate the difference 28 | between each sample, and if your recording interval isn't brief enough 29 | any short-term spikes in usage will be averaged out across the recording 30 | interval and may not show clearly on the graph. 31 | 32 | Flow gauges typically output a series of pulses proportional to the flow 33 | rate which means that to interpret them it's necessary to implement a 34 | simple frequency counter. This is actually the same way a car speed 35 | sensor works: it outputs a pulse for each rotation of a wheel so that 36 | the pulse frequency varies proportionally to the vehicle speed. The car 37 | speedo then displays a scaled version of the current pulse frequency 38 | while the odometer displays a scaled cumulative pulse count. 39 | 40 | This project uses a flow rate gauge containing a hall-effect sensor 41 | that outputs a pulse rate proportional to flow rate, so not only is it a 42 | useful project in its own right but it also demonstrates a very useful 43 | technique that you can use in a wide range of projects that need to 44 | measure the rate at which something happens. 45 | -------------------------------------------------------------------------------- /WaterFlowGauge.pde: -------------------------------------------------------------------------------- 1 | /** 2 | * Water Flow Gauge 3 | * 4 | * Uses a hall-effect flow sensor to measure the rate of water flow and 5 | * output it via the serial connection once per second. The hall-effect 6 | * sensor connects to pin 2 and uses interrupt 0, and an LED on pin 13 7 | * pulses with each interrupt. Two volume counters and current flow rate 8 | * are also displayed on a 2-line by 16-character LCD module, and the 9 | * accumulated totals are stored in non-volatile memory to allow them to 10 | * continue incrementing after the device is reset or is power-cycled. 11 | * 12 | * Two counter-reset buttons are provided to reset the two accumulating 13 | * counters. This allows one counter to be left accumulating indefinitely 14 | * as a "total" flow volume, while the other can be reset regularly to 15 | * provide a counter for specific events such as having a shower, running 16 | * an irrigation system, or filling a washing machine. 17 | * 18 | * Copyright 2009 Jonathan Oxer 19 | * Copyright 2009 Hugh Blemings 20 | * 21 | * This program is free software: you can redistribute it and/or modify 22 | * it under the terms of the GNU General Public License as published by 23 | * the Free Software Foundation, either version 3 of the License, or 24 | * (at your option) any later version. http://www.gnu.org/licenses/ 25 | * 26 | * www.practicalarduino.com/projects/water-flow-gauge 27 | 123456789abcdef 28 | 1239.4L 8073.4L 29 | */ 30 | 31 | #include 32 | // initialize the library with the numbers of the interface pins 33 | LiquidCrystal lcd(9, 8, 7, 6, 5, 4); 34 | 35 | // Specify the pins for the two counter reset buttons and indicator LED 36 | byte resetButtonA = 11; 37 | byte resetButtonB = 12; 38 | byte statusLed = 13; 39 | 40 | byte sensorInterrupt = 0; // 0 = pin 2; 1 = pin 3 41 | byte sensorPin = 2; 42 | 43 | // The hall-effect flow sensor outputs approximately 4.5 pulses per second per 44 | // litre/minute of flow. 45 | float calibrationFactor = 4.5; 46 | 47 | volatile byte pulseCount; 48 | 49 | float flowRate; 50 | unsigned int flowMilliLitres; 51 | unsigned long totalMilliLitresA; 52 | unsigned long totalMilliLitresB; 53 | 54 | unsigned long oldTime; 55 | 56 | void setup() 57 | { 58 | lcd.begin(16, 2); 59 | lcd.setCursor(0, 0); 60 | lcd.print(" "); 61 | lcd.setCursor(0, 1); 62 | lcd.print(" "); 63 | 64 | // Initialize a serial connection for reporting values to the host 65 | Serial.begin(38400); 66 | 67 | // Set up the status LED line as an output 68 | pinMode(statusLed, OUTPUT); 69 | digitalWrite(statusLed, HIGH); // We have an active-low LED attached 70 | 71 | // Set up the pair of counter reset buttons and activate internal pull-up resistors 72 | pinMode(resetButtonA, INPUT); 73 | digitalWrite(resetButtonA, HIGH); 74 | pinMode(resetButtonB, INPUT); 75 | digitalWrite(resetButtonB, HIGH); 76 | 77 | pinMode(sensorPin, INPUT); 78 | digitalWrite(sensorPin, HIGH); 79 | 80 | pulseCount = 0; 81 | flowRate = 0.0; 82 | flowMilliLitres = 0; 83 | totalMilliLitresA = 0; 84 | totalMilliLitresB = 0; 85 | oldTime = 0; 86 | 87 | // The Hall-effect sensor is connected to pin 2 which uses interrupt 0. 88 | // Configured to trigger on a FALLING state change (transition from HIGH 89 | // state to LOW state) 90 | attachInterrupt(sensorInterrupt, pulseCounter, FALLING); 91 | } 92 | 93 | /** 94 | * Main program loop 95 | */ 96 | void loop() 97 | { 98 | if(digitalRead(resetButtonA) == LOW) 99 | { 100 | totalMilliLitresA = 0; 101 | lcd.setCursor(0, 1); 102 | lcd.print("0L "); 103 | } 104 | if(digitalRead(resetButtonB) == LOW) 105 | { 106 | totalMilliLitresB = 0; 107 | lcd.setCursor(8, 1); 108 | lcd.print("0L "); 109 | } 110 | 111 | if( (digitalRead(resetButtonA) == LOW) || (digitalRead(resetButtonB) == LOW) ) 112 | { 113 | digitalWrite(statusLed, LOW); 114 | } else { 115 | digitalWrite(statusLed, HIGH); 116 | } 117 | 118 | if((millis() - oldTime) > 1000) // Only process counters once per second 119 | { 120 | // Disable the interrupt while calculating flow rate and sending the value to 121 | // the host 122 | detachInterrupt(sensorInterrupt); 123 | //lcd.setCursor(15, 0); 124 | //lcd.print("*"); 125 | 126 | // Because this loop may not complete in exactly 1 second intervals we calculate 127 | // the number of milliseconds that have passed since the last execution and use 128 | // that to scale the output. We also apply the calibrationFactor to scale the output 129 | // based on the number of pulses per second per units of measure (litres/minute in 130 | // this case) coming from the sensor. 131 | flowRate = ((1000.0 / (millis() - oldTime)) * pulseCount) / calibrationFactor; 132 | 133 | // Note the time this processing pass was executed. Note that because we've 134 | // disabled interrupts the millis() function won't actually be incrementing right 135 | // at this point, but it will still return the value it was set to just before 136 | // interrupts went away. 137 | oldTime = millis(); 138 | 139 | // Divide the flow rate in litres/minute by 60 to determine how many litres have 140 | // passed through the sensor in this 1 second interval, then multiply by 1000 to 141 | // convert to millilitres. 142 | flowMilliLitres = (flowRate / 60) * 1000; 143 | 144 | // Add the millilitres passed in this second to the cumulative total 145 | totalMilliLitresA += flowMilliLitres; 146 | totalMilliLitresB += flowMilliLitres; 147 | 148 | // During testing it can be useful to output the literal pulse count value so you 149 | // can compare that and the calculated flow rate against the data sheets for the 150 | // flow sensor. Uncomment the following two lines to display the count value. 151 | //Serial.print(pulseCount, DEC); 152 | //Serial.print(" "); 153 | 154 | // Write the calculated value to the serial port. Because we want to output a 155 | // floating point value and print() can't handle floats we have to do some trickery 156 | // to output the whole number part, then a decimal point, then the fractional part. 157 | unsigned int frac; 158 | 159 | // Print the flow rate for this second in litres / minute 160 | Serial.print(int(flowRate)); // Print the integer part of the variable 161 | Serial.print("."); // Print the decimal point 162 | // Determine the fractional part. The 10 multiplier gives us 1 decimal place. 163 | frac = (flowRate - int(flowRate)) * 10; 164 | Serial.print(frac, DEC) ; // Print the fractional part of the variable 165 | 166 | // Print the number of litres flowed in this second 167 | Serial.print(" "); // Output separator 168 | Serial.print(flowMilliLitres); 169 | 170 | // Print the cumulative total of litres flowed since starting 171 | Serial.print(" "); // Output separator 172 | Serial.print(totalMilliLitresA); 173 | Serial.print(" "); // Output separator 174 | Serial.println(totalMilliLitresB); 175 | 176 | lcd.setCursor(0, 0); 177 | lcd.print(" "); 178 | lcd.setCursor(0, 0); 179 | lcd.print("Flow: "); 180 | if(int(flowRate) < 10) 181 | { 182 | lcd.print(" "); 183 | } 184 | lcd.print((int)flowRate); // Print the integer part of the variable 185 | lcd.print('.'); // Print the decimal point 186 | lcd.print(frac, DEC) ; // Print the fractional part of the variable 187 | lcd.print(" L"); 188 | lcd.print("/min"); 189 | 190 | lcd.setCursor(0, 1); 191 | lcd.print(int(totalMilliLitresA / 1000)); 192 | lcd.print("L"); 193 | lcd.setCursor(8, 1); 194 | lcd.print(int(totalMilliLitresB / 1000)); 195 | lcd.print("L"); 196 | 197 | // Reset the pulse counter so we can start incrementing again 198 | pulseCount = 0; 199 | 200 | // Enable the interrupt again now that we've finished sending output 201 | attachInterrupt(sensorInterrupt, pulseCounter, FALLING); 202 | } 203 | } 204 | 205 | /** 206 | * Invoked by interrupt0 once per rotation of the hall-effect sensor. Interrupt 207 | * handlers should be kept as small as possible so they return quickly. 208 | */ 209 | void pulseCounter() 210 | { 211 | // Increment the pulse counter 212 | pulseCount++; 213 | } 214 | --------------------------------------------------------------------------------