error in flow measurement sketch

Dear All,

I have a basic setup where I measure the level and the flow rate of a water reservoir (which is located far from my house) and transmit these values to a receiver located in my house.

I am using two Arduino ProMicro boards and two nRF24L01+ for the RF link.

The system is working fine, but, for some unknown reason I have to add an "empirical correction factor" to correct the flow rate displayed.

I started using the FlowMeter sketch from Sebastian Kraus (GitHub - sekdiy/FlowMeter: Arduino flow meter library that provides calibrated liquid flow and volume measurement with flow sensors.) and noticed that the displayed value was off by about 30%.

Since I could not find the mistake and there was no reference to this behaviour on the Internet, I decide to try another code.

I choose to use one from Jaycar (Jaycar Water Flow Gauge | Freetronics).
My thought was that since it was used in a "comercial product", it should have been tested by many buyers and all mistakes been corrected.

To my surprise, I need also to use a correction factor in this case.

I test the setup by injecting a pulse stream of known frequency to simulate the hall sensor output for the flow measuring device, which is a YF-S201.
According to the datasheet: Q(l/m) = F(Hz) / 7

But to obtain the correct values I have to multiply the calculated value by 1.636.

Another strange behaviour that I noticed is that this "correction factor" is dependent of the code used, because I have, in fact, two reservoirs that I monitor and each transmitter side measures different variable an use very different codes (apart from the flow measurement snippet).

My guess is that this issue is caused by the use of the RFnetwork.h or other part of the code that interferes with the interrupt, but the Jaycar code is supposed to correct for that.
Any ideas?
best regards,
Roger

YF-S201.pdf (50 KB)

Caixa_Superior_NewFlow_rev_1.ino (10.9 KB)

The best approach to debugging is to isolate the components and verify proper operation of each of them, in succession.

(1) Make sure that the flow meter works correctly, and that you get the correct flow rate from the measurements.

(2) Correctly transmit that measurement from one Arduino to another.

(3) Correctly display the measurement on the remote Arduino.

detachInterrupt(digitalPinToInterrupt(7)); 
flowRate = experimCorrectionFactor * ((1000.0 / (millis() - oldTime)) * pulseCount) / calibrationFactor;
oldTime = millis();
....
delay(2000);
pulseCount = 0;
attachInterrupt(digitalPinToInterrupt(7), pulseCounter, FALLING);

That 2 second delay will be included in your millis() - oldTime calculation. But during that 2 seconds you aren't counting pulses.

TBH you'd be far better not attaching/detaching interrupts all the time, and not resetting the counter.
Just record the pulseCount and millis() when you start, then subtract out when you end...

uint32_t now = millis();
uint32_t elaplsedTime = now - oldTime;
byte tempCount = pulseCount;
if (elapsedTime >= interval)
{
    byte elapsedCount = tempCount - oldCount;
    flowRate = experimCorrectionFactor * ((1000.0 / elapsedTime * elapsedCount) / calibrationFactor;
    oldTime = now;
    oldCount = tempCount;
}

I forgot to mention that the wrong value is displayed at the arduino to which the flow meter is attached so, we can rule out transmission errors

Also, I am using a signal generator to simulate the pulsed generated by the hall sensor when the water flows through the sensor, so that can be ruled out also.

Something is messing with my pulse count, probably something using the interrupt associated to the flow measurement that I am not aware of.

Does anyone knows if the RFnewtork.h and/or rf24 use the INT4 (or INT6)?
Thank you.

rogerio414:
Something is messing with my pulse count, probably something using the interrupt associated to the flow measurement that I am not aware of.

Did you fix the fact that for 2 seconds of your interval time you aren’t counting any pulses?

Dear Pcbbc,
Thank you for your reply.

Could you please detail your explanation?

I do not have much knowledge in C, only in Fortran.

Is there a way of avoiding this problem?

Regards,
Roger

I do not have much knowledge in C, only in Fortran.

It is time to learn. There are many excellent on-line tutorials, and we strongly recommend that beginners in Arduino work their way through some of the provided tutorials.

To fix your code, go through it, line by line, until you understand what each line does. pcbbc has given you an extremely important hint. But, the code you chose is a bad example to study, with many poor programming practices, and is far more complicated than what you need.

Alternatively, you can post on the Gigs and Collaborations forum section. You may be expected to pay for the help.

I would double-check the flow meter before adding magic correction factors. Try this:

// The flow sensor is connected to pin 7 (an interrupt pin on the Arduino Leonardo/Micro).
const byte SensorPin  = 7;
volatile byte FlowSensorPulseCount = 0;  // 255 counts is about 0.6 liters


// Flow Volume
// 7 Pulses Per Second == 1 Liter Per Minute
// 7*60 Pulses Per Minute == 1 Liter Per Minute
// 420 Pulses Per Minute == 1 Liter Per Minute
// 420 Pulses == 1 Liter
// Pulses / 420 = Liters


// * Invoked by interrupt once per pulse of the flow sensor.
// * Interrupt handlers should be kept as small as possible so they return quickly.
void pulseCounter()
{
  FlowSensorPulseCount++;  // Increment the pulse counter
}


void setup()
{
  Serial.begin(115200); // start serial communication


  // New Flowmeter initialization
  pinMode(SensorPin, INPUT_PULLUP);

  // Configured to trigger on a FALLING edge (transition from HIGH state to LOW state)
  attachInterrupt(digitalPinToInterrupt(SensorPin), pulseCounter, FALLING);
}

void loop()
{
  unsigned long currentMillis = millis();
  static unsigned long previousMillis = 0;
  static unsigned long totalFlowSensorPulseCount = 0;


  //--------- Calculate flow once per interval --------
  const unsigned long interval = 1000;  //
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis += interval;


    noInterrupts();  // Prevent an interrupt while we sample and reset the pulse count
    byte currentFlowSensorPulseCount = FlowSensorPulseCount;
    FlowSensorPulseCount = 0;  // Re-start the counter for the next second
    interrupts();
    totalFlowSensorPulseCount += currentFlowSensorPulseCount;


    float flowRateLPM = (currentFlowSensorPulseCount / 7.0) * (1000.0 / interval); // 7 PPS == 1 LPM
    float totalLiters = totalFlowSensorPulseCount / (7.0 * 60.0);  // 7 PPS == 1 LPM so 7*60 P == 1 L


    Serial.print("flowRateLPM = ");
    Serial.print(flowRateLPM);
    Serial.print("\totalLiters = ");
    Serial.println(totalLiters);
  }
}

Thanks to jremington and johnwasser for the replyes.

I have programmed mainframes, calculators, etc to make enginneering calculations, since I have worked 37 years designing chemical plants, so think I have an adequate "mindset".
Unfortunately, due to the lack of specific experience in working with low level languages, I make poor judgement choices, like the use of Jaycar code as a working example.

However I have tried at least 4 different ones, so I think am commiting the same error over and over again.

The sensors have been checked but they are not the root of the error because I use a signal generator to simulate the output of the sensor.

The typical results I get are exemplified on the table below, where I vary the frequency of the signal to simulate different flowrates and see if the behavior is linear ( because "correction factors" can only be applied if the relation between input (pulse count) and output (flowrate) is linear.
And it is.
displayed expected
frequency pulse count value flowrate
10 11 0.29 1.43 1.44
20 22 0.59 2.86 2.94
30 32 0.86 4.29 4.28
40 43 1.15 5.71 5.72
50 53 1.42 7.14 7.07
60 63 1.68 8.57 8.36

Reply #2 explains at least ONE problem quite clearly. There are many others with that code.

You unnecessarily detach the sensor from the system, execute a totally unnecessary, do-nothing, delay for 2 seconds, then attach the sensor again.

Sensor counts are ignored when it is detached from the system.

This should not be difficult to understand.

My approach would be to have the transmitter sketch simply accumulate sensor counts for (say) 1 minute, and every minute transmit that count, along with a sequence number to detect possibly missing transmissions, to the base station for processing. Very simple and easy to code.

Dear jremington,

thank you for your reply and patience.

I had an understanding that by the time I added the 2000 ms dalay to allow the results been visible on the display, all the pulse count and flow calculations have been executed, so the delay would not affect the results.
I see now that this is not the case.

Is it possible avoid this situation by putting this part of the code inside a function?
Is there other simpler way of avoiding this condition?
I will always have to use a delay to allow the displayed results to be visible.

I will always have to use a delay to allow the displayed results to be visible.

No, you do NOT have to use delay(). In fact, you should strenuously avoid using delay().

You can simply leave the data on the screen until it is time to write new data, or ignore display updates until a suitable period has elapsed.

Please study this excellent tutorial to understand how to get around using delay().

If you only want an update/display every 2 seconds instead of every 1 second, just change 'interval' to 2000.

What is your maximum flow rate?

The one-byte pulse counter can only hold 255 counts so about 0.6 liters. If you are checking 30 times per minute (every two seconds) that would limit you to 18 liters per minute. If you check every second (60 times per minute) you can handle up to 36 liters per minute. You could change the count to an unsigned integer and that would work for any practical flow rate: 156 liters per interval (9360 liters per minute at 1 second, 4680 liters per minute at 2 seconds).

Hi,
thank you the the replies again.
I started reading the tutorials and this was my first approach.
I have made some stupid mistake (not known yet) and that did not worked so I resorted to the "easiest" delay() method.

The maximum flowrate expected is about 15 l/min, so I changing to 2 min should work.

I spent a lot of time learning how to make the RF link work with the two modules and since I had a workaround for the flow measurement issue, I have forgotten the issue.

I will probably do this as a first approach, but I intend to use the code provided in this thread along with millis() to develop a new code.

regards,
roger

I’m not sure how I can be any clearer, but I will try....

To calculate flow you need the number of pulses that occurred over a certain elapsed time. I’m sure you know this. If you look at the pertinent sequence of events in your code (line numbers added so I can refer to them)....

1. detachInterrupt(digitalPinToInterrupt(7)); 
2. flowRate = experimCorrectionFactor * ((1000.0 / (millis() - oldTime)) * pulseCount) / calibrationFactor;
3. oldTime = millis();
....
4. delay(2000);
5. pulseCount = 0;
6. attachInterrupt(digitalPinToInterrupt(7), pulseCounter, FALLING);

Number of pulses is being counted between lines 5/6 (where you reset it to zero and attach the interrupt) and line 1 (where you stop counting by detaching the interrupt). The accumulated pulseCount is then used in the equation on line 2. Let’s call this number of pulses P over period of time T.

Elapsed time is calculated by the expression on line 2 as the time now (millis()) minus the time at which oldTime was captured on line 3. However that time is at least T + 2000ms long because of the existence of line 4 which waits twiddling it’s thumbs for a 2 whole seconds.

So instead of calculating: P * T
You are calculating: P * (T + 2000)

At the very least, for this code to stand a chance of working without a “correction factor” (or at least a small a correction factor as possible), you need to get as close to your time period being the same as the period over which you are counting pulses.

Either...
a) Move line 5 and 6 so they occur immediately after line 3 and before the delay at line 4.
b) Move line 3 so it occurs after line 4.
c) Stop with all the resetting of the count and attaching detaching interrupts, and instead just measure an elapsed time over a given period (current millis - start millis) and an elapsed pulse count over a given period (current count - start count)

Option c) is how I would do it as it is (at least for me) the simplest. I provided some outline code for this in my previous reply. It’s no different from how you might calculate your flow rate on a water or gas meter in real life. Given you neither have a stopwatch available or any way to reset the meter to zero because your utility company, for some reason, don’t see fit to provide such a useful cost saving feature :wink:

You’d note down the start time and the initial meter reading, wait an amount of time, and then some time later note down the end time and final meter reading. Now subtracting the start values from the end values gives you the elapsed time and number of elapsed meter revolutions over that same period.

rogerio414:
The maximum flowrate expected is about 15 l/min, so I changing to 2 min should work.

And by "2 min" you mean "a sample interval of 2 seconds", right. :slight_smile:

If you can believe that datasheet chart, 15 lpm is 93% of the meter's capacity (16 lpm) and extrapolating 90.2Hz at 12 lpm to 15, you get 112.75Hz or about 7.52Hz / lpm. I think you need a bigger meter that would put 15 lpm into the 3rd quarter of it's range.

Dear pcbbc,

Thank you for your patience and time to post your reply.

It is amazing how you can look at a thing and do not see...

I went through this code a dozen times and have not noticed it.

I intend to trash the Jaycar code and adapt the one included in this thread since it seems to be better.

Thank you all for the replies.

Where did you get the "7 pulses per second == 1 liter per minute"?

The datasheet says:
2 L/M = 120 L/H = 16 Hz (1 L/M = 8.00 Hz)
4 L/M = 240 L/H = 32.5 Hz (1 L/M = 8.13 Hz)
6 L/M = 360 L/H = 49.3 Hz (1 L/M = 8.22 Hz)
8 L/M = 480 L/H = 65.5 Hz (1 L/M = 8.19 Hz)
10 L/M = 600 L/H = 82 Hz (1 L/M = 8.20 Hz)
12 L/M =720 L/H = 90.2 Hz (1 L/M = 7.52 Hz)
The datasheet says the values are only within 10% of correct so figure 8.2 +/-0.8

Hi,

I tried the code posted here by johnwasser (as posted) but the readings are all zero.
Just to be sure, i tried injecting a 30Hz squarewave signal to the pin 7 and the result was the same.
Any ideas?
Thanks

// The flow sensor is connected to pin 7 (an interrupt pin on the Arduino Leonardo/Micro).
const byte SensorPin  = 7;
volatile byte FlowSensorPulseCount = 0;  // 255 counts is about 0.6 liters


// Flow Volume
// 7 Pulses Per Second == 1 Liter Per Minute
// 7*60 Pulses Per Minute == 1 Liter Per Minute
// 420 Pulses Per Minute == 1 Liter Per Minute
// 420 Pulses == 1 Liter
// Pulses / 420 = Liters


// * Invoked by interrupt once per pulse of the flow sensor.
// * Interrupt handlers should be kept as small as possible so they return quickly.
void pulseCounter()
{
  FlowSensorPulseCount++;  // Increment the pulse counter
}


void setup()
{
  Serial.begin(115200); // start serial communication


  // New Flowmeter initialization
  pinMode(SensorPin, INPUT_PULLUP);

  // Configured to trigger on a FALLING edge (transition from HIGH state to LOW state)
//  attachInterrupt(digitalPinToInterrupt(SensorPin), pulseCounter, FALLING);
  attachInterrupt(digitalPinToInterrupt(SensorPin), pulseCounter, FALLING); 

}

void loop()
{
  unsigned long currentMillis = millis();
  static unsigned long previousMillis = 0;
  static unsigned long totalFlowSensorPulseCount = 0;


  //--------- Calculate flow once per interval --------
  const unsigned long interval = 1000;  //
  if (currentMillis - previousMillis >= interval)
  {
    previousMillis += interval;


    noInterrupts();  // Prevent an interrupt while we sample and reset the pulse count
    byte currentFlowSensorPulseCount = FlowSensorPulseCount;
    FlowSensorPulseCount = 0;  // Re-start the counter for the next second
    interrupts();
    totalFlowSensorPulseCount += currentFlowSensorPulseCount;


    float flowRateLPM = (currentFlowSensorPulseCount / 7.0) * (1000.0 / interval); // 7 PPS == 1 LPM
    float totalLiters = totalFlowSensorPulseCount / (7.0 * 60.0);  // 7 PPS == 1 LPM so 7*60 P == 1 L


    Serial.print("flowRateLPM = ");
    Serial.print(flowRateLPM);
    Serial.print("\totalLiters = ");
    Serial.println(totalLiters);
  }
}