The object is to use a water flow sensor to determine whether a specific amount of water has been poured into a receptacle. I used the tutorial at https://www.instructables.com/id/How-to-Use-Water-Flow-Sensor-Arduino-Tutorial/ and the code there, courtesy of Arvind Sanjeev, as my guideline. I made adjustments in an attempt to calibrate, as I measured and poured 500mL each time.
Adjusted the "calibrationFactor" in Arvind's code till it got closer to 500mL consistently. Also instead of taking the reading once every second, I modified a couple lines of the code to compute ten times per second, thinking it would be more precise.
The problem I'm seeing is, if I pour the water faster or slower, the results vary. I poured extremely slow and the result was way off, by almost +200mL! I'm wondering how to account for this, and whether it needs to be dealt with via code and/or the physical setup of my fixture? My theory is that when it's just a small flow of water being poured, the sensor keeps spinning (coasting?) resulting in a higher reading?
/*
Liquid flow rate sensor -DIYhacking.com Arvind Sanjeev
Measure the liquid/water flow rate using this code.
Connect Vcc and Gnd of sensor to arduino, and the
signal line to arduino digital pin 2.
*/
byte statusLed = 13;
byte sensorInterrupt = 0; // 0 = digital pin 2
byte sensorPin = 2;
// The hall-effect flow sensor outputs approximately 4.5 pulses per second per
// litre/minute of flow.
float calibrationFactor = 5.1;
//float calibrationFactor = 4.5;
volatile byte pulseCount;
float flowRate;
unsigned int flowMilliLitres;
unsigned long totalMilliLitres;
unsigned long oldTime;
void setup()
{
// Initialize a serial connection for reporting values to the host
Serial.begin(9600);
// Set up the status LED line as an output
pinMode(statusLed, OUTPUT);
digitalWrite(statusLed, HIGH); // We have an active-low LED attached
pinMode(sensorPin, INPUT);
digitalWrite(sensorPin, HIGH);
pulseCount = 0;
flowRate = 0.0;
flowMilliLitres = 0;
totalMilliLitres = 0;
oldTime = 0;
// The Hall-effect sensor is connected to pin 2 which uses interrupt 0.
// Configured to trigger on a FALLING state change (transition from HIGH
// state to LOW state)
attachInterrupt(sensorInterrupt, pulseCounter, FALLING);
}
/**
* Main program loop
*/
void loop()
{
if((millis() - oldTime) > 100) // Only process counters once per tenth of second
{
// Disable the interrupt while calculating flow rate and sending the value to
// the host
detachInterrupt(sensorInterrupt);
// Because this loop may not complete in exactly 1 second intervals we calculate
// the number of milliseconds that have passed since the last execution and use
// that to scale the output. We also apply the calibrationFactor to scale the output
// based on the number of pulses per second per units of measure (litres/minute in
// this case) coming from the sensor.
flowRate = ((1000.0 / (millis() - oldTime)) * pulseCount) / calibrationFactor;
// Note the time this processing pass was executed. Note that because we've
// disabled interrupts the millis() function won't actually be incrementing right
// at this point, but it will still return the value it was set to just before
// interrupts went away.
oldTime = millis();
// Divide the flow rate in litres/minute by 60 to determine how many litres have
// passed through the sensor in this 1 second interval, then multiply by 1000 to
// convert to millilitres.
flowMilliLitres = (flowRate / 600) * 1000; // divide by 600 for 1/10 second
// Add the millilitres passed in this second to the cumulative total
totalMilliLitres += flowMilliLitres;
unsigned int frac;
// Print the flow rate for this second in litres / minute
Serial.print("Flow rate: ");
Serial.print(flowRate); // Print the integer part of the variable
Serial.print("L/min");
Serial.print("\t"); // Print tab space
// Print the cumulative total of litres flowed since starting
Serial.print("Output Liquid Quantity: ");
Serial.print(totalMilliLitres);
Serial.println("mL");
Serial.print("\t"); // Print tab space
Serial.print(totalMilliLitres/1000);
Serial.print("L");
// Reset the pulse counter so we can start incrementing again
pulseCount = 0;
// Enable the interrupt again now that we've finished sending output
attachInterrupt(sensorInterrupt, pulseCounter, FALLING);
}
}
/*
Insterrupt Service Routine
*/
void pulseCounter()
{
// Increment the pulse counter
pulseCount++;
}
The problem I'm seeing is, if I pour the water faster or slower, the results vary. I poured extremely slow and the result was way off, by almost +200mL! I'm wondering how to account for this, and whether it needs to be dealt with via code and/or the physical setup of my fixture? My theory is that when it's just a small flow of water being poured, the sensor keeps spinning (coasting?) resulting in a higher reading?
There are two problems I see:
in your code you should re-enable interrupts immediately after you used the counter variable (so in the line after you assigned flowRate).
a flow meter of good quality should measure the flow correctly as long as the tube is always filled with fluid and a certain minimal flow is hold. The datasheet of your flow sensor should tell you which range it is able to cover. Outside this range the accuracy drops significantly. So if your application needs to handle very low volumes use a smaller tube and a smaller sensor.
Any flow meter will have a characteristic curve ; errors in readings will vary with flow rate ( there will also be a flow rate at which the meter either won’t pass the water or won’t start running ).
Errors may also be affected by the head of water or water temperature too.
The low cost meters can be quite poor in this respect .
There is no reason, nor is it good practise, to use detachInterrupt() and then re-attach.
Use attachInterrupt once in setup() and you are done.
The proper way to protect multibyte variables that are shared with interrupt routines from corruption is to make a copy with the interrupts turned off, then turn the interrupts on again.
Use the variable copy in subsequent calculations. For example:
noInterrupts(); //or just cli()
pulseCountCopy=pulseCount;
interrupts(); //or just sei()
flowRate = ((1000.0 / (millis() - oldTime)) * pulseCountCopy) / calibrationFactor;
museescape:
Also instead of taking the reading once every second, I modified a couple lines of the code to compute ten times per second, thinking it would be more precise.
I think your plumbing is extremely suss. You should try to have properly matched solid straight pipe for a couple of hundred mm each side of the turbine. Here, you have changes right at the inlet and outlet. The latter may well be fatal. Also, I submit your thinking is wrong, and the longer the time interval the better. Contrary to comment above, these devices can be very accurate. As Pylon says, they do need to be sensibly chosen for anticipated flow rate. Even then, the flow rate displayed over one second intervals is unreadable, but the daily quantity can still be within 1% of commercial devices costing serious money.
I wouldn't trust anything coming out of Instructables.com but it seems familiar. Another source is here
First as mentioned the plumbing is important, an old rule was ten pipe diameters upstream and five pipe diameters downstream of straight pipe. That is the inside diameter of the pipe. That means if I am using 1/2" pipe (12.7mm) I want at least a minimum of 5.0" (127mm) upstream of my flow sensor and about half that downstream. The idea is to remove as much distortion and swirl in the medium being measured. Straight pipe means no fittings, elbows, reducers or any other obstructions to the flow. How important this is really depends on how accurate and important your measurement is.
All of this applies to turbine and paddle wheel type sensors with a pulse output. There are plenty of other types but just a focus on what was used in the instructable referenced. When really getting into flow it can be a science unto itself. We are looking at this from a hobby perspective and using inexpensive parts. Not concerned with things like pressure and temperature.
The main things to be concerned with are the range and what is called K factor. The range will have a minimum and maximum value expressed in engineering units of flow and the K factor in this case is pulses per unit of volume. No reference to time at this point. Flow systems normally provide us with a rate function and a totalizer function. The rate could be expressed as units of measure per units of time Liters per Min (LPM) or Gallons per Min. Totalizer function merely counts pulses and divides or multiplies. If My K Factor is 5.0 that means I get five pulses per unit of measure. This is where limits apply. That is only true of being inside the range. It does not mean I can apply flow using an eye dropper and expect results. This is where things like pipe diameter figure in.
Something you can try for a simple dirty check is place a ball valve inline. Apply your water or whatever. Open the ball valve filling a precision marked container. Count the pulses increment up +1 count for each pulse. When the container is at a calibrated level (volume) shut the ball valve off. Whatever the pulse count is your K factor. If you want to get cool about it use a solenoid valve to start and stop the flow. If you want to get really cool place the container on a scale. One US liquid gallon of fresh water weighs roughly 8.34 pounds (lb) or 3.785 kilograms (kg) at room temperature. Have the scale turn off the solenoid. This sort of stuff can really get dragged out.
Follow the code sample and you can see what is being done and how. If all you want is a totalizer function then just write some code to increment a pulse count up using a start and stop. Once you know the K factor for a given flow turbine over a range (actually you get an Average Mean because it won't be a true constant over a range of flow).
That likely is more than you ever wanted to know but hey, it's an overview.
Thanks all. This is an escape room geared project, intended for a puzzle where the object is to follow clues to produce a definite measurement of water... for example, 500mL. Pour the 500mL through the sensor, the door unlocks (with some allowance for variance). Pour the wrong amount and the test is unsuccessful... also of course trying to come up with ways to prevent them from just "eyeballing" it or continuing to pour water until the successful amount is reached.
The sensor I was using is rated for 1-30L, but wondering if I might need one that's more sensitive and designed to work with smaller but more precise values.
This is actually my very first Arduino project. Programming background with VB and Lotus Notes, played with the Raspberry Pi a bit as well. Definitely already learning a bunch.
Also thinking an alternative approach would be to put the correct measurement of water into a specific container, then just do something to weigh it. Might be more precise?
Just give them a scale, running water and two jugs.
Make it interesting.
Ron
Yes! This is pretty much what the puzzle is supposed to be, except they are given 4 gallon and 9 gallon container and have to make 6. (Although it would be a fictional measurement smaller than gallons.) The Arduino part is to determine that they've gotten the correct amount.
Then in the interest of keeping it simple I would just have them weigh their result using a S type load cell in conjunction with the Arduino. Flow can also be used totalized but there is so much more room for error. Cool though to get a door to open.
Yeah that's what I'm thinking now that I've done more testing and applied some of the suggestions here. In addition to this being my first Arduino project, I'm novice when it comes to the plumbing/water flow stuff.
One challenge with regard to having the result weighed: the ability for a player to trick the system. Example: if the puzzle calls for exactly one quart of water, player fills the jug about halfway because "that looks like about a quart" and then if the lock doesn't open, player incrementally adds a little more water.
I guess some of depends on how many attempts a player gets. Either with weight or counting pulses there is a window. That goes into your code. Just for example a quart of water weighs about 2.09 Lb. so you create a window, consider the tare weight of the container. You code will define if the weight total is greater than or less than, then open the door, else do nothing but you could say else increment a count +1. When the count reaches a preset number that's it. Then you get a system reset.
Be it a pulse count based on volume from a flow meter or a weight count you still have a window. Plenty of possibilities. The thing with writing code is as you go along you keep thinking of features to add. Finding the best sensor often involves some experimenting.