Advice on pulseIn for low frequency measurement, counting.

I'm using pulseIn to measure the frequency of a square wave from a magnetic flow meter: Fixed Depth, Polypropylene, Insertion Electromagnetic Flow Meter
Which we're using on these awesome river models: http://emriver.com/

After hours of frustration searching and reading about this topic here, I hope to help others doing this.

--My freqs are low, from 15 to around 600 Hz at most.
--No sampling, polling, or averaging are needed, the wave is very regular; plateaus and valleys are the same (I'm a geologist, sorry). The readout we needed can fluctuate a bit and a human can tell if it's squirrelly (UK friends, that means spastic as in the animal, an American idiom).

PulseIn() is a great way to do this, especially for low frequency, regular signals, but it's very tricky I think, especially for those!

  1. Note that pulseIn() measures in MICROseconds; which are 1000 x milliseconds, this tripped me up. Microsecond values for low Hz will quickly exceed int ranges!
  2. If after hours of frustration and testing your code's not working, check to see if the input pin you're using is dead. Mine was.
  3. Be careful of variable types, this has nearly driven me mad with this sort of math. I'm hoping somebody will dissect the working code below tell me what's necessary and what's not.

One thing I don't understand: pulseIn seems to return an integer value, so math using it has to consider that, again low frequencies are a problem, right? See my casting in the code. Did I do it right?

And thanks for all the help from my forum pals. :slight_smile:

void readMagmeter ()
{
//let's try reading just one pulse :-P
durationLow = (float)pulseIn(magMeterPin, LOW);  //returns period in MICROseconds between low and high pulses, i.e. half of square wave
float durationMillis = durationLow/1000.000;        //converts to milliseconds
freQuency = 1000.000/(durationMillis *2.000);       //frequency in Hz
magFlowrate = 1000*(freQuency/350.00) ;    //K factor in liters, so multiply by 1000 to get ml  (all per second)
}   //end magMeter

Do you really need the frequency or is that just a means to calculate the flow?

Is the flow rate used in other calculations?

The meter measures velocity in a pipe, and outputs a square wave; frequency of the wave is directly proportional to velocity and also flow. In this case, you get 350 pulses per liter. So yes, you need frequency to calculate flow. Make sense?

Long story, but this is actually a legacy from industrial control equipment where paddle wheels or turbines were used and these output one or more pulses per revolution.

gravelbar:
The meter measures velocity in a pipe, and outputs a square wave; frequency of the wave is directly proportional to velocity and also flow. In this case, you get 350 pulses per liter. So yes, you need frequency to calculate flow. Make sense?

Yes, it makes sense.

Do you use the "freQuency" variable anywhere else in your Sketch?

Long story, but this is actually a legacy from industrial control equipment where paddle wheels or turbines were used and these output one or more pulses per revolution.

I've worked (a bit) with those things measuring natural gas and propane flow. They have an annoying trait when there's a large outdoor tank on one side.

Please respond to the second question ... Is the flow rate (magFlowrate) used in any calculations?

Here's all the code, not sure what you're getting at, but thanks for the analysis and help!

////////////////Simple Em4 pump controller for Arduino /////////////////////
//Steve Gough ------ Little River Research & Design
/////////// ------ tested and working very nicely 10-23-2010 -------------
////////// Modified 4-22-2011 to read Omega mag meter on pin 6

#include <LiquidCrystal.h>; // initialize the library with the numbers of the interface pins
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
/////////////////////////////// variables declaration //////////////////////
int pwmPin = 5; // = analog pin for input from PWM unit after passing through divider/low pass filter
int magMeterPin = 6; // input pin for mag meter open collector pulses

unsigned long startTime = 0;
float pwmVolts = 0;
float flowRate = 0; //flowrate in ml/s basd on average voltage
int flowRateInt = 0; // hold flowRate converted to integer for LCD priting without decimals
float rawVolts10Bits = 0; //diagnostic, raw reading on pin 5 before dividing by 1023
float rawVolts = 0; //voltage calc
float rawVoltsScaledVfd = 0; // volts into pin 5 * 5/1023; this is actuall Vfd, or voltage from filter/divider cirtuit into pin 5

//calibration variables - Q = aVal *Vfd^2 + bVal * Vfd + cVal

float aVal = -3.84; //add appropriate values
float bVal = 118.4;
float cVal = -472.8;

float durationHigh = 0; //meter reading variables
int durationLow = 0;
float durationAve = 0;
float duration = 0;
float durationSum = 0;
float freQuency = 0;
float magFlowrate = 0; //flowrate in ml/s
long hugeDurationLow = 0; //diagnostic variable

int magMeterPinState;
int lastState;
int pulseCount;

//=======================================================================================
void setup()
{
Serial.begin(9600); // Initialize a serial connection for reporting values to the host

lcd.begin(16, 2); // ----setup LCD screen and display welcome message
lcd.clear(); //clears lcd screen and goes to beginning of first line
lcd.print("Turning on");
lcd.setCursor(0, 1); // go to beginning of second line
lcd.print("pump controller");
delay (2000);
pinMode(magMeterPin, INPUT);
digitalWrite(magMeterPin, HIGH); //turn on pullup resistors for magmeter pin (which goes low for pulse)
}

void loop() ////////////////////////////----end setup ---- start loop ///////////////
{

if (millis() - startTime >1000) // if statement run at 1Hz, updates flow reading and LCD
{
updateFlowVfd(); // runs PWM voltage and flow calcs, updates flowRate variable
readMagmeter();
updateLcd(); //updates LCD readings and calcs flow and time totals
startTime = millis ();
// add amp reading here /// CurrentACS712 \ Learning \ Wiring
}
} //////////////////////// close loop ////////////////////////////////////////////////////////

void updateFlowVfd() /////////////////////// reads Vfd, calculates flow in ml/s, /////////
{ /////////////////////// calcs Vfd and Q in ml/s; also //////////////
////////////////////// totalizes Q and time //////////////////////

rawVolts10Bits = analogRead(pwmPin); //10bit voltage reading from Vfd on pin 5
rawVoltsScaledVfd = (5 * rawVolts10Bits) /1023; //Scaled to actual voltage from Vfd; actual voltage
// is (4.02)/1023 * reading; 4.02 is full scale voltage
//(but 5.0 works; 9-23 testing, go figure)
// from Vfd circuit, from 7-30 testing; this may vary
pwmVolts = (-2.873 * rawVoltsScaledVfd) +13.27; //Converted to actual PWM voltage output (max ~13.6 volts)
//remember this value will depend on voltage
//divider circuit precision and may need to be
//calibrated for each circuit; see Excel sheets for
//analysis and relationships

flowRate = ((aVal*(pow(pwmVolts,2))) + (pwmVolts*bVal) + cVal) ;

} //////////////////////////// end function updateFlowVfd ////////////////

void updateLcd () //////////////////////// function updateLcd() /////////////////////////////
{
lcd.clear ();
if (flowRate <= 0) // Prints zero to LCD so nonsensical numbers
flowRate = 0.00; // do not appear ; could cause BUG
flowRateInt = (int) flowRate; //converts to integer so decimals aren't printed to LCD
lcd.print(flowRateInt); //print flow and voltage on top line
lcd.print(" mls ");
lcd.print(pwmVolts);
lcd.print("V");
lcd.setCursor(0, 1);
lcd.print ("freq ");
lcd.print(freQuency);

//diagnostics
Serial.print ("freq");
Serial.println (freQuency);
Serial.print ("magflow");
Serial.println (magFlowrate);
Serial.print ("durationLow ");
Serial.println (hugeDurationLow);

} ///////////////////////////////////////// end updateLcd function /////////////////////////

void readMagmeter ()
{
//let's try reading just one pulse :stuck_out_tongue:
durationLow = pulseIn(magMeterPin, HIGH); //returns period in MICROseconds between low and high pulses, i.e. half of square wave
//float durationLow = durationLow;
hugeDurationLow = (durationLow*1000);
freQuency = 10000.000/(durationLow 2.000);
magFlowrate = 1000
(350.00/freQuency) ;
Serial.println (hugeDurationLow);

} //end magMeter

The code in loop is not quite correct. The code executes every 1001 milliseconds (plus some forward drift). I suggest the following changes...

void loop() ////////////////////////////----end setup ---- start loop ///////////////
{
unsigned long ThisMillis;

ThisMillis = millis();

if (ThisMillis - startTime >=1000) // if statement run at 1Hz, updates flow reading and LCD
{

// code here stays the same

startTime = ThisMillis;
}
} //////////////////////// close loop ////////////////////////////////////////////////////////

pulseIn runs with interrupts enabled. Very rarely, the millis interrupt service routine will add a bit of time to the pulse reading; the value returned from pulseIn will be a bit larger than the actual pulse width. For your application, the 15 Hz readings are never effected enough to matter. At the 600 Hz end, the returned value can be up to 0.38% higher than the actual pulse width. Will a rarely occurring up to 0.38% higher value cause any problems?

Good point, thanks. But this is like a car speedometer; a little fluctuation now and then is no problem, we just need a good average, and the human observer can judge.

pulseIn returns an "unsigned long". If there is nothing else to consider, that is the appropriate data-type to use...

unsigned long durationLow;
...
durationLow = pulseIn(magMeterPin, HIGH); //returns period in MICROseconds between low and high pulses, i.e. half of square wave

In this application: The pulseIn value is only being used in floating-point calculations. The potential minor precision loss is not a concern. So, "float" is a more appropriate choice...

float durationLow;
...
durationLow = pulseIn(magMeterPin, HIGH); //returns period in MICROseconds between low and high pulses, i.e. half of square wave