Pulse counter - reading extra pulses

I am working on a motor control system, and am using an interrupt to count pulses from a meter on my Arduino Yun. It appears to count pulses accurately for a short time but will occasionally start counting extra pulses, then revert back to being accurate. The meter is currently putting out a pulse that’s 50ms wide at a constant 50 ms interval. I tried rearranging my interrupt routine but it did not seem to make a difference. Any idea what could be causing these extra pulses?

More info: There’s a 10k pull-down resistor hooked into the signal line. The meter has an open collector and is set to read 0V at zero flow, positive voltage in flow state. Maybe my resistor isn’t properly sized? I’m not much of an electrical engineer.

Code:

#include <Bridge.h>
#include <YunServer.h>
#include <YunClient.h>

byte statusLed    = 13;

int sensorInterrupt = 4;  // 4 = digital pin 7

// The hall-effect flow sensor outputs approximately .5 per gallon of flow
float calibrationFactor = .25;

volatile byte pulseCount;  

float flowRate;
float flowGallons;
float totalGallons;
float lastTotal;

unsigned long oldTime;

YunServer server;

void setup() {
 pinMode(statusLed,OUTPUT);
 digitalWrite(13, LOW);
 Bridge.begin();
 digitalWrite(13, HIGH);
 
 server.listenOnLocalhost();
 server.begin();
 Console.begin();

 pulseCount        = 0;
 flowRate          = 0.0;
 flowGallons   = 0;
 totalGallons  = 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);
}

void loop() {

 YunClient client = server.accept();

 if (client) {
   process(client);
   client.stop();
 }

 delay(50); 
 
}

void process(YunClient client) {
 String command = client.readStringUntil('/');

 if (command == "start") {
   totalGallons = 0;
   Bridge.put("STATE",0);
   startCommand(client);
 }

}

void startCommand(YunClient client){

int requestedGallons = client.parseInt();
char state[1];

startPump();

while(totalGallons <= requestedGallons){

if((millis() - oldTime) > 1000)    // Only process counters once per second
   { 
   detachInterrupt(sensorInterrupt);
   
   Bridge.get("STATE", state, 1);

   String stateString = String(state[0]);
   
   if(stateString == "1"){
     stopPump();
     Console.println("Successfully Stopped");
     lastTotal = totalGallons;
     Bridge.put("GalsPumped", String(lastTotal));
     Console.println(lastTotal);
     goto endRun;
     break;
   }
   

   flowRate = (((1000.0 / (millis() - oldTime)) * (pulseCount))*calibrationFactor)*60;

   oldTime = millis();
   
   flowGallons = (flowRate / 60);
   
   // Add the millilitres passed in this second to the cumulative total
   totalGallons += flowGallons;
     
   unsigned int frac;
   
   // Print the flow rate for this second in litres / minute
   Console.print("Flow rate: ");
   Console.print(int(flowRate));  // Print the integer part of the variable
   Console.print(".");             // Print the decimal point
   // Determine the fractional part. The 10 multiplier gives us 1 decimal place.
   frac = (flowRate - int(flowRate)) * 10;
   Console.print(frac, DEC) ;      // Print the fractional part of the variable
   Console.print(" Gal/Min");
   // Print the number of litres flowed in this second
   Console.print("  Current Liquid Flowing: ");             // Output separator
   Console.print(flowGallons);
   Console.print(" Gal/Sec");

   // Print the cumulative total of litres flowed since starting
   Console.print("  Output Liquid Quantity: ");             // Output separator
   Console.print(totalGallons);
   Console.println(" Gal"); 
   Console.println(pulseCount);

   // 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);
     }
}

 endRun:{
 stopPump();
 Bridge.put("GalsPumped", String(totalGallons));
}

}

/*
Insterrupt Service Routine
*/
void pulseCounter()
{
 // Increment the pulse counter
 pulseCount++;
}

void stopPump(){
//code to stop pump 
}

void startPump(){
 //code to stop pump
}

From serial monitor:

Flow rate: 149.2 Gal/Min  Current Liquid Flowing: 2.49 Gal/Sec  Output Liquid Quantity: 323.10 Gal
10
Flow rate: 149.2 Gal/Min  Current Liquid Flowing: 2.49 Gal/Sec  Output Liquid Quantity: 325.58 Gal
10
Flow rate: 149.2 Gal/Min  Current Liquid Flowing: 2.49 Gal/Sec  Output Liquid Quantity: 328.07 Gal
10
Flow rate: 149.2 Gal/Min  Current Liquid Flowing: 2.49 Gal/Sec  Output Liquid Quantity: 330.56 Gal
10
Flow rate: 149.2 Gal/Min  Current Liquid Flowing: 2.49 Gal/Sec  Output Liquid Quantity: 333.05 Gal
10
Flow rate: 179.1 Gal/Min  Current Liquid Flowing: 2.99 Gal/Sec  Output Liquid Quantity: 336.03 Gal
12
Flow rate: 238.5 Gal/Min  Current Liquid Flowing: 3.98 Gal/Sec  Output Liquid Quantity: 340.01 Gal
16
Flow rate: 253.7 Gal/Min  Current Liquid Flowing: 4.23 Gal/Sec  Output Liquid Quantity: 344.24 Gal
17
Flow rate: 194.0 Gal/Min  Current Liquid Flowing: 3.23 Gal/Sec  Output Liquid Quantity: 347.47 Gal
13
Flow rate: 194.0 Gal/Min  Current Liquid Flowing: 3.23 Gal/Sec  Output Liquid Quantity: 350.70 Gal
13
Flow rate: 208.9 Gal/Min  Current Liquid Flowing: 3.48 Gal/Sec  Output Liquid Quantity: 354.19 Gal
14
Flow rate: 223.8 Gal/Min  Current Liquid Flowing: 3.73 Gal/Sec  Output Liquid Quantity: 357.92 Gal
15
Flow rate: 194.0 Gal/Min  Current Liquid Flowing: 3.23 Gal/Sec  Output Liquid Quantity: 361.15 Gal
13
Flow rate: 238.5 Gal/Min  Current Liquid Flowing: 3.98 Gal/Sec  Output Liquid Quantity: 365.13 Gal
16
Flow rate: 194.0 Gal/Min  Current Liquid Flowing: 3.23 Gal/Sec  Output Liquid Quantity: 368.36 Gal
13
Flow rate: 194.0 Gal/Min  Current Liquid Flowing: 3.23 Gal/Sec  Output Liquid Quantity: 371.60 Gal
13
Flow rate: 328.3 Gal/Min  Current Liquid Flowing: 5.47 Gal/Sec  Output Liquid Quantity: 377.07 Gal
22
Flow rate: 283.5 Gal/Min  Current Liquid Flowing: 4.73 Gal/Sec  Output Liquid Quantity: 381.79 Gal
19
Flow rate: 283.5 Gal/Min  Current Liquid Flowing: 4.73 Gal/Sec  Output Liquid Quantity: 386.52 Gal
19
Flow rate: 298.5 Gal/Min  Current Liquid Flowing: 4.98 Gal/Sec  Output Liquid Quantity: 391.50 Gal
20
Flow rate: 179.1 Gal/Min  Current Liquid Flowing: 2.99 Gal/Sec  Output Liquid Quantity: 394.48 Gal
12
Flow rate: 149.2 Gal/Min  Current Liquid Flowing: 2.49 Gal/Sec  Output Liquid Quantity: 396.97 Gal
10
Flow rate: 149.1 Gal/Min  Current Liquid Flowing: 2.49 Gal/Sec  Output Liquid Quantity: 399.45 Gal
10
Flow rate:

Moderator edit:
</mark> <mark>[code]</mark> <mark>

</mark> <mark>[/code]</mark> <mark>
tags added.

If you had posted your code properly using the code button </> I could load it in my text editor to study it and have more definitive answer.

I suspect your problem is when the main body of your code reads the value of pulseCount. It should do it like this

noInterrupts();
  newPulseCount = pulseCount;
  pulseCount = 0;
interrupts();

and then your code should do its calculations using the value in newPulseCount.

You need to temporarily stop the interrupts so that pulseCount is not incremented while you are reading it.

Personally I would not bother setting pulseCount back to 0. I would just compare newPulseCount with prevPulseCount.

…R

Sorry, this is my first post on the forum and did not see the code button.

Before doing anything with the value of pulseCount, I detach the interrupt, then reattach after the calculations. This should have the same effect, no?

if((millis() - oldTime) > 1000)    // Only process counters once per second
    { 
    detachInterrupt(sensorInterrupt);
    
    Bridge.get("STATE", state, 1);

    String stateString = String(state[0]);
    
    if(stateString == "1"){
      stopPump();
      Console.println("Successfully Stopped");
      lastTotal = totalGallons;
      //Bridge.put("GalsPumped", String(lastTotal));
      Console.println(lastTotal);
      goto endRun;
      break;
    }
    

    flowRate = (((1000.0 / (millis() - oldTime)) * (pulseCount))*calibrationFactor)*60;

    oldTime = millis();
    
    flowGallons = (flowRate / 60);
    
    // Add the millilitres passed in this second to the cumulative total
    totalGallons += flowGallons;
      
    unsigned int frac;
    
    // Print the flow rate for this second in litres / minute
    Console.print("Flow rate: ");
    Console.print(int(flowRate));  // Print the integer part of the variable
    Console.print(".");             // Print the decimal point
    // Determine the fractional part. The 10 multiplier gives us 1 decimal place.
    frac = (flowRate - int(flowRate)) * 10;
    Console.print(frac, DEC) ;      // Print the fractional part of the variable
    Console.print(" Gal/Min");
    // Print the number of litres flowed in this second
    Console.print("  Current Liquid Flowing: ");             // Output separator
    Console.print(flowGallons);
    Console.print(" Gal/Sec");

    // Print the cumulative total of litres flowed since starting
    Console.print("  Output Liquid Quantity: ");             // Output separator
    Console.print(totalGallons);
    Console.println(" Gal"); 
    Console.println(pulseCount);

    // 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);
      }

I detach the interrupt, then reattach after the calculations. This should have the same effect, no?

You would thinks so, but there can be strange results which occur from timing issues (race conditions) when you use detach/attach with an external interrupt rather than the suggested noInterrupts() interrupts() syntax.

I'm not sure i totally understand, but some time ago, the interrupt gurus on this forum told me to do it the way Robin2 suggested and I have adopted it. There is no downside to the suggested syntax. Don't fight it. Use it.

jamesmkehoe:
Before doing anything with the value of pulseCount, I detach the interrupt, then reattach after the calculations. This should have the same effect, no?

No, it will lose pulses. Leave the interrupt running and use atomic sequences to read, write or
read-modify-write any volatile variables it uses (as explained above).

Are you sure your source of pulses produces clean logic signals?

You mention open-collector and a pull-down (normally you'd need a pull-up).

10k is a worrying large value of pull-down or pull-up resistor if the sensor is remote at the
end of a cable, go for something more like 2k2 or 1k to reject noise.

Thanks for the advice. I changed the code and I am now getting garbage in the console, and the pulse count is now completely useless. I am not sure what could be going wrong - cleaned up the code a bit too. Any more suggestions? Here’s the updated code:

#include <Bridge.h>
#include <YunServer.h>
#include <YunClient.h>

byte statusLed    = 13;

int sensorInterrupt = 4;  // 4 = digital pin 7

// The hall-effect flow sensor outputs approximately .5 per gallon of flow
float calibrationFactor = .25;

volatile byte pulseCount;  

float flowRate;
float flowGallons;
float totalGallons;
float lastTotal;
unsigned int newPulseCount;
unsigned int oldPulseCount;
unsigned int currentPulseCount;

unsigned long oldTime;

YunServer server;

void setup() {
  pinMode(statusLed,OUTPUT);
  digitalWrite(13, LOW);
  Bridge.begin();
  digitalWrite(13, HIGH);
  
  server.listenOnLocalhost();
  server.begin();
  Console.begin();

  pulseCount        = 0;
  flowRate          = 0.0;
  flowGallons   = 0;
  totalGallons  = 0;
  oldTime           = 0;
  newPulseCount = 0;
  oldPulseCount = 0;
  currentPulseCount = 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);
}

void loop() {

  YunClient client = server.accept();

  if (client) {
    process(client);
    client.stop();
  }
  delay(50); 
}

void process(YunClient client) {
  String command = client.readStringUntil('/');

  if (command == "start") {
    totalGallons = 0;
    Bridge.put("STATE",0);
    startCommand(client);
  }
}

void startCommand(YunClient client){
 
 int requestedGallons = client.parseInt();
 char state[1];
 
 startPump();
 
 while(totalGallons <= requestedGallons){

 if((millis() - oldTime) > 1000)    // Only process counters once per second
    { 
    
    Bridge.get("STATE", state, 1);

    String stateString = String(state[0]);
    
    if(stateString == "1"){
      goto endRun;
    }
    
    oldPulseCount = newPulseCount;
    
    noInterrupts();
    
    newPulseCount = pulseCount;
    
    interrupts();
    
    Console.println("new pulse count: " + newPulseCount);
    currentPulseCount = newPulseCount - oldPulseCount;
    
    flowRate = (((1000.0 / (millis() - oldTime)) * (currentPulseCount))*calibrationFactor)*60;

    oldTime = millis();
    
    flowGallons = (flowRate / 60);
    
    // Add gallons passed this second to Total Gallons
    totalGallons += flowGallons;
      
    unsigned int frac;
    
    // Print the flow rate for this second in gal / minute
    Console.print("Flow rate: ");
    Console.print(int(flowRate));  
    Console.print(".");             
    frac = (flowRate - int(flowRate)) * 10;
    Console.print(frac, DEC) ;     
    Console.print(" Gal/Min");
    // Print the number of gal flowed in this second
    Console.print("  Current Liquid Flowing: ");             // Output separator
    Console.print(flowGallons);
    Console.print(" Gal/Sec");

    // Print the cumulative total of gal flowed since starting
    Console.print("  Output Liquid Quantity: ");             // Output separator
    Console.print(totalGallons);
    Console.println(" Gal"); 
    // Print pulses over past second
    Console.println(currentPulseCount);
    
      }
 }
 
  endRun:{
    stopPump();
    Console.println("Successfully Stopped");
    lastTotal = totalGallons;
    Console.println(lastTotal);
    Bridge.put("GalsPumped", String(totalGallons));
 }

}

/*
Insterrupt Service Routine
 */
void pulseCounter()
{
  // Increment the pulse counter
  pulseCount++;
}

void stopPump(){
 //code to stop pump 
}

void startPump(){
  //code to stop pump
}

And the console output:

Unable to connect: retrying (1)...
Flow rate: 102.3 Gal/Min Current Liquid Flowing: 1.71 Gal/Sec Output Liquid Quantity: 1.71 Gal
28
connected!
Liquid Flowing:
Flow rate: 321.3 Gal/Min Current Liquid Flowing: 5.36 Gal/Sec Output Liquid Quantity: 7.06 Gal
22
c
Flow rate: 366.9 Gal/Min Current Liquid Flowing: 6.12 Gal/Sec Output Liquid Quantity: 13.18 Gal
25
uantity:
Flow rate: 278.0 Gal/Min Current Liquid Flowing: 4.63 Gal/Sec Output Liquid Quantity: 17.81 Gal
19
ully Stopped
Flow rate: 336.2 Gal/Min Current Liquid Flowing: 5.60 Gal/Sec Output Liquid Quantity: 23.42 Gal
23
ped
Flow rate: 293.5 Gal/Min Current Liquid Flowing: 4.89 Gal/Sec Output Liquid Quantity: 28.31 Gal
20

~
ñ
Flow rate: 365.8 Gal/Min Current Liquid Flowing: 6.10 Gal/Sec Output Liquid Quantity: 34.41 Gal
25

 © Ó

Flow rate: 366.2 Gal/Min Current Liquid Flowing: 6.10 Gal/Sec Output Liquid Quantity: 40.51 Gal
25
ŸHaS¤
Flow rate: 336.9 Gal/Min Current Liquid Flowing: 5.62 Gal/Sec Output Liquid Quantity: 46.12 Gal
23
nf
Flow rate: 322.8 Gal/Min Current Liquid Flowing: 5.38 Gal/Sec Output Liquid Quantity: 51.51 Gal
22

Flow rate: 338.2 Gal/Min Current Liquid Flowing: 5.64 Gal/Sec Output Liquid Quantity: 57.14 Gal
23
Flow rate:
Flow rate: -27445.1 Gal/Min Current Liquid Flowing: 15926.58 Gal/Sec Output Liquid Quantity: 15983.73 Gal
65299
Successfully Stopped
15983.73

jamesmkehoe:
Thanks for the advice. I changed the code and I am now getting garbage in the console, and the pulse count is now completely useless.

Can you provide the sequence of values you get from the line

Console.println("new pulse count: " + newPulseCount);

and can you arrange to print currentPulseCount also

…R

What is the flow rate and pulse count you expect? In the first print out there were many readings of 10 counts per second? The latest readings appear to be more like 20.

My trouble shooting suggestion is to use software pulse generation to get your code working independent of the flow meter. Then you can address issues of pullups/pulldowns and signal quality.

Here is simple square wave generating code output on Pin 8 and the interrupt reading it on Pin 2. Substitute the pulse generating code for the actual flow meter. With that, you can separate the interrupt processing and other code from the hardware function of the flow meter.

//Variables for Pulse generating
const int pulsePin = 8;//pin sending pulses jumper to interrupt pin 2
const long pulseLength = 50000; // microseconds for 10 pps square wave
unsigned long microsLast;//last time the pulse pin changed state

//Variables for pulse reading
volatile unsigned int  count = 0;
unsigned int copyCount = 0;
unsigned long lastRead = 0;
int readInterval = 1000; //read data every second

void isrCount()
{
  count++;
}

void setup() {
  Serial.begin(115200);
  Serial.println("Starting...");
  pinMode(pulsePin, OUTPUT);

  attachInterrupt(0, isrCount, RISING); //interrupt signal to pin2
}

void loop() {
  if (micros() - microsLast > pulseLength)
  {
    digitalWrite(pulsePin, !digitalRead(pulsePin)); //toggle pulsePin
    microsLast = micros();
  }

  if (millis() - lastRead >= readInterval)
  {
    noInterrupts();
    copyCount = count;
    count = 0;
    interrupts();

    lastRead = millis();
    Serial.println(copyCount);
  }
}