Timing duration of sensor inputs using Arduino Uno

Hello,

I am using a hall effect flow sensor to measure flow rates, but I am having a problem with calculating gallons per minute. I want the timer to only run when there is flow through the sensor, and not run when there is no flow. Everything else in my sketch works as intended, but the timer always starts when the serial monitor is opened, and continues running when the flow stops. I have tried a few of the suggestions I found in other forum posts (such as having a separate timer variable and subtracting it from the start time) but the time always returns 0 when I attempt to implement them. I am trying to keep my code simple and have not tried using libraries yet. If anyone can let me know what I am doing wrong I would much appreciate it.

Here is my sketch as currently written:

const int flowPin = 2;
int flowSensorState = 0; // currently unused
unsigned long pulseTime = 0;
volatile int pulses = 0;
unsigned long time = 0;
unsigned long timeRun = 0; // currently unused
volatile float seconds = 0.00;
volatile float gallons = 0.00;
volatile float gpm = 0.00;

void pulseCount()
{
  pulses++;
}

void setup()
{
  pinMode(flowPin, INPUT_PULLUP);
  Serial.begin(9600);
  attachInterrupt(0, pulseCount, RISING); // interrupt on pin 2
}

void loop()
{
  pulseTime = pulseIn(flowPin, LOW);
  flowSensorState = digitalRead(flowPin); // currently unused
  gallons = (float)pulses / 3800.00;
  gpm = (float)gallons * 60.000 / seconds ;
  if (pulseTime != 0) // when flow is active
  {
    time = millis();
    seconds = time / 1000;
    Serial.print("Seconds: ");
    Serial.println(seconds);
    Serial.print("Gallons: ");
    Serial.println(gallons);
    Serial.print("GPM: ");
    Serial.println(gpm);
    Serial.println();
  }
}

I would also like to have the results print out only when the flow stops, instead of continually while the flow is active, but I have not figured out how to do that yet. If there is a way to attach an interrupt that activates when the flow sensor stops changing after it has begun changing, that seems like the easiest way to do it, but I haven't seen anything like !CHANGE that I could use.

Thanks!

sketch_flow.ino (969 Bytes)

Not sure if PulseIn is what you want here. From the docs:

The timing of this function has been determined empirically and will probably show errors in longer pulses. Works on pulses from 10 microseconds to 3 minutes in length.

and ...

timeout (optional): the number of microseconds to wait for the pulse to start; default is one second (unsigned long)

So in your case, if the pulse is longer than 1 second, and I assume it is if your measuring gallons per minute, then it will constantly timeout and return 0.

You use three things for a single pin: an interrupts, the pulseIn, and digitalRead.
Why do you use pulseIn ?
https://www.arduino.cc/en/Reference/PulseIn

If the timer should not be used when there is no flow... how is that possible ? To measure if there is no flow, the flow should be measured.

In my opinion there is only one good solution, and that is to measure the flow all the time. In the sketch decisions can be made upon the flow of that moment.

Thanks for your replies,

I am using pulseIn (in microseconds) to determine if the little fan in the flow sensor is turning, hence if there is active flow. When I run the sketch, it works as intended in that the results are only printed when the flow is active, instead of constantly whether there is active flow or not. The pulses are definitely shorter than 1 second; I have tried printing the pulses and the pulseTime and pulses per second, and there are generally between 10 and 200 pulses per second.

Pulses: 201
pulseTime: 2987
Seconds: 1.08
Pulses per second: 196.84
Gallons: 0.05
GPM: 3.26

The problem I am trying to solve is to only run the timer when an event condition is present, such as when there are any pulses at all. Currently, the timer starts as soon as the sketch starts, instead of when pulses begin.

Might be misunderstanding the question, but the only part that isnt working, is the time whilst flow is enabled? ie is this the solution?:

unsigned long time_at_flow_start = 0;	// this is a global


if (pulseTime != 0) // when flow is active
{
	if( time_at_flow_start == 0 )
		time_at_flow_start = millis(); 

	time = millis() - time_at_flow_start;
	seconds = time / 1000;
	Serial.print("Seconds: ");
	Serial.println(seconds);
	Serial.print("Gallons: ");
	Serial.println(gallons);
	Serial.print("GPM: ");
	Serial.println(gpm);
	Serial.println();
}
else
{
	time_at_flow_start = 0;
}

Could you just use the interrupt to update a counter.
A fixed timing can be used with millis() to read the counter. For example once per second.
In the sketch you can test if the flow is above a limit and display it.

I would like to have seperate parts in the code according to the functionality.
One part is measuring the flow.
The other part is print the flow or not.

If you need an exception, for example a flag that no pulse has been received for 500ms, then it can be added to the code with millis(). The last pulse can be timestamped with millis(). I still don't see any use for the pulseIn() function.

When I use:

"time = millis() - time_at_flow_start;"

is when I end up getting time returned as 0.

and since the timer still starts when the sketch starts (for whatever reason),

"if( time_at_flow_start == 0 )
time_at_flow_start = millis();"

will never be true, and the rest of the loop will be ignored.

A colleague suggested I tie the loop to a button press, which I suppose I will ultimately do, and hopefully that will resolve the issue with the timer.

To timestamp the last pulse, would I need a separate variable?

Well, I ended up doing a major re-write of your code (and there were 3 replies since I started.) As I did this, I discovered your main error - you calculate seconds AFTER you used seconds to calculate GPM and your seconds variable only contains seconds-since-program-reset, which is why you were asking that odd question in the first place.

OK, since you have used an interrupt, I'll keep it. Let's give the interrupt one more task and then we never need to use any other method of accessing that pin....

/* Flow sensor demo for https://forum.arduino.cc/index.php?topic=332347.0
*/
#define FLOW_TIMEOUT 1000              //use this to decide if flow has stopped or started (milliseconds)
#define PULSES_PER_GALLON 3800.0       //Calibration for the flow sensor

const int flowPin = 2;                 //since we use an interrupt, we never need this value, but it's good documentation to put it here
volatile unsigned long pulseTime = 0;  //repurposed - now contains the time when the last pulse ocurred
volatile unsigned long pulses = 0;     //change to unsigned since we know it's never going to be negative and make it long because there could be more than 17gal
//moved other global variables into local scope in loop()

void pulseCount()
{
  pulses++;
  pulseTime = millis();
}

void setup()
{
  pinMode(flowPin, INPUT_PULLUP);
  Serial.begin(9600);
  attachInterrupt(0, pulseCount, RISING); // interrupt on pin 2
}

void loop()
{
  static bool flowIsStopped = true;      //make this a boolean and name it to reflect its logical meaning
  unsigned long time;                    //repurposed - now used as a temporary copy of pulseTime
  static unsigned long timeRun = 0;      //repurposed - now contains the start time of the flow event
  unsigned long pulseCount;
  float seconds;
  float gallons;
  float gpm;
  
  //first, make a 'clean' copy of the timer 
  noInterrupts();
  time = pulseTime;
  interrupts();  //Even if an interrupt arrived while we turned off the interrupts, it will be handled now
  
  if(flowIsStopped) {
    //There was previously no flow, has this changed?
    if(millis() - time < FLOW_TIMEOUT) {
      //flow has started - record that start time
      timeRun = time;
      flowIsStopped = false;
    } //else no recent pulses - flow is still stopped
  } else {
    //we previously recorded flow - has it stopped?
    if(millis() - time < FLOW_TIMEOUT) {
      //flow is still flowing - do nothing
    } else {
      //flow has stopped 
      flowIsStopped = true;
      
      //Grab a clean copy of the pulse count, and reset it back to zero so it's ready to count immediately
      //Since we think the flow has stopped, we don't expect this to have changed since we grabbed the time above
      //If we were trying to measure actual flow, there may have been pulses counted after we copied pulseTime.
      noInterrupts();
      pulseCount = pulses;
      pulses = 0;
      interrupts();
      
      //OK, so at this point we have copies of the total pulses, the last time a pulse was recorded 
      //and the first time that a pulse was recorded in this flow event
      //Do the math and print it out...
      gallons = (float)pulseCount / PULSES_PER_GALLON; 
      seconds = (float)(time - timeRun) / 60000;
      gpm = gallons / seconds ;
      Serial.print("Seconds: ");
      Serial.println(seconds);
      Serial.print("Gallons: ");
      Serial.println(gallons);
      Serial.print("GPM: ");
      Serial.println(gpm);
      Serial.println();
      
      //Anything else you want to do with this calculated flow?
      //Maybe store the pulseCount in an EEPROM location so
      //we can record total-Flow-Since-System-Installed?
    }
  }
}

Thanks MorganS,

The code you posted does indeed only print out when the flow has stopped, and only count time while the flow is active. Unfortunately, when I tested it, the recorded time was not accurate. I tried changing the "seconds = (float)(time - timeRun) / 60000;" to "seconds = (float)(time - timeRun) / 1000;" and that seemed to fix it. I also changed "gpm = gallons / seconds;" to "gpm = gallons / seconds * 60;" and it now gives accurate readings.

I am still pretty new at this, so I don't understand everything in your sketch, but thank you so much, it seems to work now so I am going to use it.

The last thing I was wanting to do with these readings is to print them to an excel spreadsheet. I was planning on using Gobetwino, but I am still learning how to use that. If there is an easier way to upload data to a spreadsheet I haven't seen one. The forum postings I've found mainly direct queries back to the Gobetwino examples and manuals, but I'll keep looking, or perhaps post a new topic.

Thanks again!

My go-to solution for an Excel spreadsheet is to save CSV values to a file on an SD card.