Counting Multiple Pulses with Interrupts

I’m having trouble trying to figure out how to measure and accumulate more than 1 pulse input using 2 interrupts. I have 2 pulse flow meters with different pulses per gallons. I can measure each one separately fine.

The interrupt works fine one at a time, but I can not get a second to work. I know it’s not reading or accumulating anything on the second input as it writes to the EEPROM and the value is still the default 4294967295.
I’m guessing the issue related to the timing of the interrupts, I played with priority with no luck.
I’m just pulling water meter only relevant parts of a bigger code section.

I’m new to coding, need some expert advice on how to fix this! Thank You in advanced

#define waterPin1 D5    //Pulse Meter #1  //Works fine
#define waterPin2 D6    //Pulse Meter #2  //Not working with D5

//The Gals Per Pulse are different value in real life, maybe not the debounce not, set up just in case
double GPP1 =          1;       // Volume Per Pulse - volume  Gals Per Pulse
double GPP2 =          1;       // Volume Per Pulse - volume  Gals Per Pulse
#define DEBOUNCE_1      50  // (ms)  Meter 1 Debounce (Reed Switch), 
#define DEBOUNCE_2      50  // (ms)  Meter 1 Debounce (Reed Switch), 

int interval      =          2;         // (min) Publish Interval in MINUTES
unsigned long eepromInterval =  12* 60 * 60000;     //  8  * (60 * 60000) = (8hours) How often to Save Totalizer Values to EEPROM milliseconds

float FlowRate_1                         = 0 ;   //  Total Pulse over sample interval time x GPP
float FlowRate_2                         = 0 ;   //  Total Pulse over sample interval time x GPP

unsigned long int accumPulseCt_1         = 0;
unsigned long int TGals_1                = 0;
unsigned long int accumPulseCt_2         = 0;
unsigned long int TGals_2                = 0;

volatile int PulsesPerInterval_1         = 0 ;  // Flushed after every Interval / Publish
volatile int PulsesPerInterval_2         = 0 ;  // Flushed after every Interval / Publish

void WaterPulseCounter(void)   // Water Sensor interrupts
    {
        accumPulseCt_1++;     //Total Pulses                                       
        TGals_1++;           //Total Gallons
        accumPulseCt_2++;                                            
        TGals_2++;    

    }
//AREA I NEED HELP  They don't play nice.
void setup()
    {                        
      pinMode(waterPin1, INPUT);
      pinMode(waterPin2, INPUT);
      attachInterrupt(waterPin1, waterInterrupt1, FALLING);    //PriorityDefault 13 ????
      attachInterrupt(waterPin2, waterInterrupt1, FALLING, 12);   //Priority????
   }

void loop()
    { if (millis() - previousPub_1 >= (interval * 60000) )
          {
                // It's time to Publish the data,
                FlowRate_1 = FlowRate_1 + (PulsesPerInterval_1 * GPP1) ;
                TGals_1 += FlowRate_1;
                accumPulseCt_1 += PulsesPerInterval_1;

                FlowRate_2 = FlowRate_2 + (PulsesPerInterval_2 * GPP2) ;
                TGals_2 += FlowRate_2;
                accumPulseCt_2 += PulsesPerInterval_2;

                PubDebugFlow();       //publishDataToCloud for debug
                FlowRate_1 = 0;        // Flushed after every Interval / Publish, since the Interval has ellapsed.
                PulsesPerInterval_1 = 0 ;   // Flushed after every Interval / Publish...
                FlowRate_2= 0;        // Flushed after every Interval / Publish...
                PulsesPerInterval_2 = 0 ;   // Flushed after every Interval / Publish... 

                previousPub_1 = millis();   //required
                newDataP_1 = false;         // Turn Off the Publish Flag, since we just pubilished.
                if (millis() - previousEEPROM >= eepromInterval )
                    {      // Write to EEPROM every HOUR, Day, etc. cannot make the same at the publish interval
                    EEPROM.put(0, TGals_1);
                    EEPROM.put(20, TGals_2);
                    previousEEPROM = millis();
                    }
}


void waterInterrupt1()
    {  // Interrupt for Water Meter 1
      if (millis() - LastWaterIntTime1 >= DEBOUNCE_1 )
      {
          PulsesPerInterval_1++;                          // Tracks the # of Pulses from Meter for this time interval ONLY
          newDataP_1 = true;             // Set Global Flag because interrupt has fired
          LastWaterIntTime1 = millis();
      }
    }
//Help! Tried compairing to  LastWaterIntTime1 to fire after interrupt #1
void waterInterrupt2()
    {   // Interrupt for Water Meter 2
      if (LastWaterIntTime1 - LastWaterIntTime2 >= DEBOUNCE_2 ) 
     //millis() was not working at LastWaerIntTime1 above
        {
          PulsesPerInterval_2++;                         // Tracks the # of Pulses from Meter for this time interval ONLY
          newDataP_1 = true;             // Set Global Flag because interrupt has fired
          LastWaterIntTime2 = millis();
        }
    }


Dont you mean to make one of the callbacks “waterInterrupt2”? In the code you posted, both pins trigger the same ISR function.

You might have to explain the relationship between the two sensors. I don’t understand the comments here:

//Help! Tried compairing to  LastWaterIntTime1 to fire after interrupt #1
void waterInterrupt2()
    {   // Interrupt for Water Meter 2
      if (LastWaterIntTime1 - LastWaterIntTime2 >= DEBOUNCE_2 )

Does the second sensor fire independently of the first? If so, I would make the millis() timer the same as the interrupt 1 like this:

if (millis() - LastWaterIntTime2 >= DEBOUNCE_2 )

Thanks Ninjatill!
The two flow meters are two separate water flow meters with pulse outputs, measuring different water flows in different areas. Yes, both meters fire independently of each other and are rather continuous.

Should have been waterInterrupt2… Had that. I was playing around to try to get something to work.

attachInterrupt(waterPin1, waterInterrupt1, FALLING); 
attachInterrupt(waterPin2, waterInterrupt2, FALLING, 12); here

Changed water Interrupt back to millis()

void waterInterrupt1()
    {           // Interrupt for Water Meter
      if (millis() - LastWaterIntTime1 >= DEBOUNCE_1 )
      {
          PulsesPerInterval_1++;                          // Tracks the # of Pulses from Meter for this time interval ONLY
          newDataP_1 = true;             // Set Global Flag because interrupt has fired
          LastWaterIntTime1 = millis();
      }
    }

void waterInterrupt2()
    {           // Interrupt for Water Meter
      if (millis() - LastWaterIntTime2 >= DEBOUNCE_2 )
        {
          PulsesPerInterval_2++;                         
          newDataP_1 = true;             // Set Global Flag because interrupt has fired
          LastWaterIntTime2 = millis();
        }
    }

Made those changes & updated, but no luck. This is the published data I get back. Makes me think interrupt#2 gets cut off by interrupt#1 or some timing thing…I’m new to this concept so if there is a better way to tackle the same problem, I’m all ears!

{"FlowRate_1":9.700000,
"FlowRate_2":**0.000000,**
"accumPs_1":97,
"accumPs_2":0, 
"TGals_1":544569, 
"TGals_2":**4294967295**} 

It’s probably not related, but funny things are happening to D5 on Borons.
It may be worth trying your (2) interrupts on Analog Pins instead ?
I know, it’s a long shot :wink:

image

Thanks! I read that too. I’ll give it a try and report back.
Funny that D5 is the only one I’m having luck with! I’ll move them around a bit.

You may just need INPUT_PULLUP on your D6 pinMode… if you’re not pulling the Flow Meter’s line High externally.
Looks like the D5 Bug might be doing that for you “behind the scenes” :

1 Like

I can only see where PulsesPerInterval was declared as a Volitile… are all those variables Volitile?

Yes, these two are volatile;
PulsePerInterval_2
&
LastWaterIntTime2

Thoughts to that?

unsigned long previousPub_1             = 0;
volatile bool newDataP_1                = true;      // Flag set by Interrupts
volatile uint32_t LastWaterIntTime1     = 0;
volatile uint32_t LastWaterIntTime2     = 0;
unsigned long previousEEPROM            = 0;
/////////////////////////////////////////////////////////////////////////////////////
float FlowRate_1                        = 0;         //  Total Pulse over sample interval time x GPP
float FlowRate_2                        = 0;         //  Total Pulse over sample interval time x GPP
unsigned long int accumPulseCt_1        = 0;
unsigned long int TGals_1               = 0;
unsigned long int accumPulseCt_2        = 0;
unsigned long int TGals_2               = 0;
volatile int PulsesPerInterval_1        = 0;         // Flushed after every Interval / Publish
volatile int PulsesPerInterval_2        = 0;         // Flushed after every Interval / Publish

Still have the issues of running (2) Interrupts at a time: made following changes
Switched to A3 & A4.
I have a 4.7k resistor to ground, so it goes high on closure. Confirmed with a meter.

I can have successfully switch interrupt by commenting out one or the other.

I think it might be related to the LasWaterIntTimeX = millis()…one for each? Same seems like they would fire at the same time.
Inside the interrupt or in Void Loop()?
Priority did not fix it either.

Interrupt#2 will not work here: Something obvious to more experienced folks?

`
#define DEBOUNCE_1         100      // (ms)  min time for Debounce, set similar (but higher) to Water Meter Pulse Width Setting.
volatile int PulsesPerInterval_1        = 0;   // Flushed after every Interval / Publish
unsigned long int accumPulseCt_1        = 0;
volatile uint32_t LastWaterIntTime1     = 0;

volatile int PulsesPerInterval_2        = 0;         // Flushed after every Interval / Publish
unsigned long int accumPulseCt_2     = 0;
volatile uint32_t LastWaterIntTime2     = 0;

char msg[256]  ;               // used for snprintf for Publish.

void setup()
    {
      pinMode(A3, INPUT_PULLDOWN);  //With 4.7k to ground, goes HIGH 1 on closure
      attachInterrupt(A3, waterInterrupt1, RISING,9);
      
      pinMode(A4, INPUT_PULLDOWN);  //With 4.7k to ground, goes HIGH 1 on closure
      attachInterrupt(A4, waterInterrupt2, RISING,8);
}

void WaterPulseCounter(void)                         // Water Sensor interrupts
    {
        accumPulseCt_1++;  
        accumPulseCt_2++; 
    }

void loop()
{

      accumPulseCt_1 += PulsesPerInterval_1;
      accumPulseCt_2 += PulsesPerInterval_2;


      PubDebugPins(); //publishing results
      PulsesPerInterval_1 =0;
      PulsesPerInterval_2 =0;
      delay(15000);
      LastWaterIntTime1 = millis();
      LastWaterIntTime2 = millis();
}

void waterInterrupt1()
    {           // Interrupt for Water Meter
      if (millis() - LastWaterIntTime1 >= DEBOUNCE_1 )
      {
          PulsesPerInterval_1++;                          // Tracks the # of Pulses from Meter for this time interval ONLY
          
      }
    }
    
void waterInterrupt2()
    {           // Interrupt for Water Meter
      if (millis() - LastWaterIntTime2 >= DEBOUNCE_1 )
      {
          PulsesPerInterval_2++;                          // Tracks the # of Pulses from Meter for this time interval ONLY
          
      }
    }

void PubDebugPins()
          {
              snprintf(msg, sizeof(msg), "{\"1Ps\":%d,\"1TPs\":%d,\"2Ps\":%d,\"2TPs\":%d}",  PulsesPerInterval_1, accumPulseCt_1, PulsesPerInterval_2, accumPulseCt_2);
              Particle.publish("PubDebugPins", msg, PRIVATE, NO_ACK);
              return;
          }`

Your debounce doesn’t look right.
You are not setting LastWaterTimeX inside the ISR but in loop() - is that intentional?
To my understanding that would not actually debounce the switch but only block the first trigger if it happened within the first 100ms after aour 15 second delay.

With this test code I can confirm that in deed even perfectly simultaneous and same level interrupts are registered as expected.

volatile uint32_t a        = 0;
volatile uint32_t b        = 0;
volatile uint32_t msLastA  = 0; 
volatile uint32_t msLastB  = 0;
const    uint32_t DEBOUNCE = 0; // digial pin isn't bouncing ;-)

void inca() {
  if (millis() - msLastA < DEBOUNCE) return;
  msLastA = millis();  
  a++;
}
void incb() {
  if (millis() - msLastB < DEBOUNCE) return;
  msLastB = millis();  
  b++;
}

void setup() 
{
  pinMode(D7, OUTPUT);                // for visual feedback of potential errors

  pinMode(D2, OUTPUT);                // signal source connected via jumper wires to
  pinMode(A3, INPUT_PULLDOWN);        // signal detector pin A
  pinMode(A4, INPUT_PULLDOWN);        // signal detector pin B
  attachInterrupt(A3, inca, RISING);
  attachInterrupt(A4, incb, RISING);
  
  analogWrite(D2, 128, 5000);
}

void loop()
{
  static uint32_t ms     = 0;
  static int      errors = 0;
  if (millis() - ms < 1000L) return;
  ms = millis();
  
  Serial.printlnf("%lu / %lu (errors %d) %s", a, b, errors, (a != b) ? "<-- error" : "");
  if (a != b) {
    errors++;
    digitalWrite(D7, !digitalRead(D7));
    a = b;
  }
}

(even with 5000Hz after 10 minutes not a single error)

So there must be something else going on with your setup.


Update:
Now I’ve also (slightly) modified your code and it behaves just as expected
https://go.particle.io/shared_apps/5cd29e912327c100178758a8

3 Likes

Thanks ScruffR!!
I did have hardware issues as well which complicated troubleshooting. I’ll make these changes and let you know. Thanks for the help!!

Do you still have this code above? The link does not seem to work. Eitherway thanks for the help, my readings have been way off, so this is helpful.

Unfortunately not.
This was before I realised that a shared revision only lives as long as the base project exists.
Ever since, I use one permanent TEST project to pull these revisions for sharing.

However, my code above should give you a good starting point to extend to your needs.

I saved it… I was impressed with how a professional coder handled it.
Link Below:
ScruffR’s Code

2 Likes

Thanks for the kind words :blush: and glad you kept the code.
I guess the second time round I may have been a bit more sloppy in recreating it :flushed:

Just slow to respond. This is kind of a side project, not a full-time gig, so don’t get to work on it as much as I’d like.

Yes, this code works now! Thank You. However, I still have something fishy as I have flow meters with register counters, counts every 100 pulses/clicks. I’m still off by about 1/2. made some reading over about an hour and took the difference which came out to 24 Gal Per Min, I was closer to 10 Gals Per Min.

Thought maybe the INPUT parameters RISING vs CHANGE (would count twice). Debouce time, etc.

This is the code as it related to reading two pulse flow meters.


//A3 - P2 -    0.1GPP Yellow
//A4 - P1 -    0.1GPP Orange

#include <math.h>

//////////////////////////////////Time sync
#define ONE_DAY_MILLIS (24 * 60 * 60 * 1000)            // Time for 24Hr Time Sync & Day totals upload
#define ONE_HR_MILLIS  (2 *  60 * 60 * 1000)            // Hourly Totals Upload to cloud
unsigned long DaySync = millis();                       //Day Timer start
unsigned long HrSync  = millis();                       //Hr Timer start
//////////////////////////////////
#define waterPin1 A3                                    //Pulse Meter #1
#define waterPin2 A4                                    //Pulse Meter #2

//////////////////////////////////
double GPP1 =                              0.1;     // Volume Per Pulse - volume  Gals Per Pulse
double GPP2 =                              0.1;     // Volume Per Pulse - volume  Gals Per Pulse
#define DEBOUNCE_1          40                      // (ms)  min time for Debounce, set similar (but higher) to Water Meter Pulse Width Setting.
#define DEBOUNCE_2          40                      // (ms)  min time for Debounce, set similar (but higher) to Water Meter Pulse Width Setting.
int interval                            =2;         // (min) Publish Interval in MINUTES
unsigned long eepromInterval =  15* 60 * 1000;      //  2  * (60 * 60000) = (8hours) How often to Save Totalizer Values to EEPROM milliseconds

//////////////////////////////////
char msg[256]                           ;               // used for snprintf for Publish.
//////////////////////////////////
unsigned long previousPub_1             = millis();
volatile uint32_t LastWaterIntTime1     = 0;
volatile uint32_t LastWaterIntTime2     = 0;
unsigned long previousEEPROM            = 0;
//////////////////////////////////
float FlowRate_1                        = 0;         //  Total Pulse over sample interval time x GPP
float FlowRate_2                        = 0;         //  Total Pulse over sample interval time x GPP

unsigned long int accumPulseCt_1        = 0;
unsigned long int TGals_1               = 0;
unsigned long int accumPulseCt_2        = 0;
unsigned long int TGals_2               = 0;
volatile int PulsesPerInterval_1        = 0;         // Flushed after every Interval / Publish
volatile int PulsesPerInterval_2        = 0;         // Flushed after every Interval / Publish

unsigned long int T_HRGals_1            = 0;
unsigned long int T_HRGals_2            = 0;
unsigned long int T_DAYGals_1           = 0;
unsigned long int T_DAYGals_2           = 0;

unsigned long int meterReadingP1        = 0;
unsigned long int meterReadingP2        = 0;

void WaterPulseCounter(void)                         // Water Sensor interrupts
    {
        accumPulseCt_1++;                            //WaterPulseCountMinInterval++;
        accumPulseCt_2++;                            //WaterPulseCountMinInterval++;

        TGals_1++;
        TGals_2++;                                   //Pulse Per Min

        T_HRGals_1++;
        T_HRGals_2++;

        T_DAYGals_1++;
        T_DAYGals_2++;
    }

void setup()
    {
          pinMode(waterPin1, INPUT);
          pinMode(waterPin2, INPUT);
          attachInterrupt(waterPin1, waterInterrupt1, RISING);
          attachInterrupt(waterPin2, waterInterrupt2, RISING);

          //if the EEPROM value isn't non-written (i.e. the default value ) then get it
          if ((EEPROM.get(0, TGals_1)) !=4294967295) ;
              {   EEPROM.get(0, TGals_1);
              }
          if ((EEPROM.get(100, TGals_2)) !=4294967295) ;
              {   EEPROM.get(100, TGals_2);
              }
          if ((EEPROM.get(200, accumPulseCt_1)) !=4294967295) ;
              {   EEPROM.get(200, accumPulseCt_1);
              }
          if ((EEPROM.get(300, accumPulseCt_2)) !=4294967295) ;
              {   EEPROM.get(300, accumPulseCt_2);
              }
          if ((EEPROM.get(400, meterReadingP1)) !=4294967295) ;
              {   EEPROM.get(400, meterReadingP1);
              }
          if ((EEPROM.get(500, meterReadingP2)) !=4294967295) ;
              {   EEPROM.get(500, meterReadingP1);
              }


        Particle.variable("GPP1", GPP1);
        Particle.variable("GPP2", GPP2);

      }

void loop()
      {
          if (millis() - DaySync >= ONE_DAY_MILLIS)
          {
               Particle.syncTime();                 // Request time synchronization from the Particle Device Cloud
               DaySync = millis();
               DayPub();
               T_DAYGals_1        = 0;
               T_DAYGals_2        = 0;
      0;
          }

          if (millis() - HrSync >= ONE_HR_MILLIS)
          {
               HrPub();
               HrSync         = millis();
               T_HRGals_1     = 0;
               T_HRGals_2     = 0;
  
          }

          if (millis() - previousPub_1 >= (interval * 60000) )
          {
                FlowRate_1 = FlowRate_1 + (PulsesPerInterval_1 * GPP1) ;
                TGals_1 += FlowRate_1;
                accumPulseCt_1 += PulsesPerInterval_1;
                meterReadingP1 = TGals_1 / 100;

                T_HRGals_1 += FlowRate_1;
                T_DAYGals_1 += FlowRate_1;

                FlowRate_2 = FlowRate_2 + (PulsesPerInterval_2 * GPP2) ;
                TGals_2 += FlowRate_2;
                accumPulseCt_2 += PulsesPerInterval_2;
                meterReadingP2 = TGals_2 / 100;

                T_HRGals_2 += FlowRate_2;
                T_DAYGals_2 += FlowRate_2;

                PubDebugFlow();       //publishDataToCloud for debug

                FlowRate_1= 0;        // Flushed after every Interval / Publish, since the Interval has ellapsed.
                FlowRate_2= 0;        // Flushed after every Interval / Publish, since the Interval has ellapsed.
  
                PulsesPerInterval_1 = 0 ;   // Flushed after every Interval / Publish, since the Interval has ellapsed.
                PulsesPerInterval_2 = 0 ;   // Flushed after every Interval / Publish, since the Interval has ellapsed.

                previousPub_1 = millis();   //required

                if (millis() - previousEEPROM >= eepromInterval )
                    {      // Write to EEPROM every HOUR, Day, etc. cannot make the same at the publish interval
                    EEPROM.put(0,   TGals_1);
                    EEPROM.put(100, TGals_2);
                    EEPROM.put(200, accumPulseCt_1);
                    EEPROM.put(300, accumPulseCt_2);
                    EEPROM.put(400, meterReadingP1);
                    EEPROM.put(500, meterReadingP2);
                    previousEEPROM = millis();
                    }
          }
          
      }


void waterInterrupt1()         // Interrupt for Water Meter1
      {
        if (millis() - LastWaterIntTime1 < DEBOUNCE_1 ) return;
              LastWaterIntTime1 = millis();
              PulsesPerInterval_1++;
      }

void waterInterrupt2()       // Interrupt for Water Meter2
     {
      if (millis() - LastWaterIntTime2 < DEBOUNCE_2 )return;
              LastWaterIntTime2 = millis();
              PulsesPerInterval_2++;
     }

void HrPub()
        {
          snprintf(msg, sizeof(msg), "{\"T_HRGals_1\":%lu, \"T_HRGals_2\":%lu, \"MeterRead#1\":%lu, \"MeterRead#2\":%lu}",  T_HRGals_1, T_HRGals_2, meterReadingP1, meterReadingP2);
          Particle.publish("HrPub", msg, PRIVATE, NO_ACK);
          return;
        }
void DayPub()
        {
          snprintf(msg, sizeof(msg), "{\"T_DAYGals_1\":%lu, \"T_DAYGals_2\":%lu, \"MeterRead#1\":%lu, \"MeterRead#2\":%lu}",  T_DAYGals_1, T_DAYGals_2, meterReadingP1, meterReadingP2);
          Particle.publish("DayPub", msg, PRIVATE, NO_ACK);
          return;
        }
void PubDebugFlow()
        {
          snprintf(msg, sizeof(msg), "{\"FlowRate_1\":%f,\"FlowRate_2\":%f,\"accumPs_1\":%lu,\"accumPs_2\":%lu, \"TGals_1\":%lu, \"TGals_2\":%lu,\"MeterRead#1\":%lu, \"MeterRead#2\":%lu}",  FlowRate_1, FlowRate_2, accumPulseCt_1, accumPulseCt_2, TGals_1, TGals_2, meterReadingP1, meterReadingP2);
          Particle.publish("DebugFlow", msg, PRIVATE, NO_ACK);
          return;
        }

Does your meter have an Open Collector Output?
Can you share the make & model # of the meter ?

1 Like

I have two meter types:
Smaller pipes have mechanical meters, Carlon Meters with magnetic Reed switches, like this:
http://www.carlonmeter.com/products/industrial-meters-controls/mrs-industrial-meter

2 larger units which have micro processor outputs. Onicon https://www.onicon.com/products/f1000-series-turbine-flow-meters/

I should note that I have played around with the circuit and have two set up as pulldown and two with pull-up circuits.

After posting this code above, I changed a board with smaller mechanical units with pulldown circuit, to INPUT_PULLDOWN with FALLING, and that seems to make a difference by 2x the pulses per interval.

Played with denounce time from 5, 50, 500ms with little difference, I think because the units don’t spin extremely fast.

For Pulse Meters, I use INPUT_PULLUP and FALLING trigger.

You are using the Dry Contacts on the Onicon meter, correct?
image

1 Like

Yes, on the Onicons we are using the scaled pulse output as you show.

I think maybe added to the issue on these Onicon is that the factory gave us a pulse out of 1 pulse per 100 gals which is really slow. I’m looking to see if I can use their software to change to 1 pulse per 1 gal.

1 Like