Accurately counting square wave pluses on 2 inputs

I am working on a project where I have 2 flowmeters outputting 0 to 5 volt square waves and I want to count the total number of pulses out of each meter to totalize each flow.

Through experimentation I was beginning to think that I couldn't trust the totals I was getting from both of these flowmeters. I put both of the flowmeters onto an oscilloscope and I noticed that there were instances that the rising edges of the 2 square waves were occuring at (as precisely I could tell with the scope anyways) the same time.

This got me wondering, what happens if my interrupt function from the first flowmeter is totalizing pulses while the 2nd flowmeter pulses.

I rewired my breadboard and put a single flowmeter into both of my flowmeter inputs, this way the rise edge would be at precisely the same time for both inputs...I wasn't sure what was going to happen, I thought maybe I would see all the pulses on one of the inputs and none on the other.

What I ended up seeing was maybe even more confusing. I manually set the flow rate of the flowmeter at several different rates within the range I'll be using the meter -- Minimum 2 Hz, maximum 35Hz. I let it run to about 1000 pulses totalization several times at several rates, manually resetting it at the end of each 1000 pulse test. What I saw was a minimum difference of 0 pulses out of 1000 at some of the higher flow rates (however not always 0) to a maximum of about 20 pulses difference out of the 1000 pulses at some of the slower flow rates (but again not always that big of a difference).

Is there a way I can modify my code to ensure that this test would give me 0 pulse out of 1000 difference every time at every flow rate -- bearing in mind I'm talking about the configuration where its the same input signal to both pins.

Here's the code I'm testing with on a Mega 2560:

volatile unsigned int Counts1 = 0;                                                    
volatile unsigned int Counts2 = 0;                                                 

void setup() {
 
  Serial.begin(115200);                                               
  
  attachInterrupt(digitalPinToInterrupt(19), counter2, RISING); 
  attachInterrupt(digitalPinToInterrupt(18), counter1, RISING); 

}

void counter1() {         
  Counts1++;
}

void counter2() {    
  Counts2++;
}

void loop() {
  
  Serial.print(Counts1);
  Serial.print(F(", "));
  Serial.println(Counts2);
  delay(100);

}

What is the pulse width ?

The interrupts should queue up properly in your example. Do you have the pins actually tied together? Try pulling them low or high separately and using small signal diodes like 1n914 to isolate the signal going to each. You might get different results.

The flowmeter has a variable pulse width such that the square wave has a 50% duty cycle. Pretty sure I'm saying that the right way -- the signal spends half it's time at 0v and the other half at 5v. As the frequency of pulses increase the width of each pulse decreases...so at 35Hz the pulse width is about 14ms and at 2 Hz it is about 250ms.

Koepel:
What is the pulse width ?

I can confirm that the pins are tied together.

I don't think I completely follow what your suggestion is. Could you please expand on your suggestion a little?

Thanks, I really appreciate the guidance.

wzaggle:
The interrupts should queue up properly in your example. Do you have the pins actually tied together? Try pulling them low or high separately and using small signal diodes like 1n914 to isolate the signal going to each. You might get different results.

Ok.. To be sure, I just wired up two external interrupt pins together to one signal and got different results for counts.

Then I removed the wires, tied each interrupt pin high with a separate external pullup resistor. Next I put a small diode from the signal wire (cathode) to each of the interrupt pins (anode), isolating them from each other, but allowing each to be pulled low from the same signal.

Counts are now exactly the same.

Correction... Better maybe but not perfect. If the frequency gets too high it can still start to lose a few. It might need the interrupt code written in assembly to capture both signals faster.

Do you really need this or were you just testing to see what happens?

No -- I don't need to be able to measure the same signal twice. I just moved to testing this configuration because the data from my two separate flowmeters didn't seem to be making complete sense and I thought maybe it was a simultaneous pulse measurement issue.

Let me expand on that -- When comparing the totalization of the two flowmeters measuring the same flow at the same time period I would see bad repeatability. I might see a 2% difference in total pulses, restart and then a 10% difference, restart and then a 0.3% difference. The flowmeters being used are industrial and have a repeatably of +/-0.2% so I wouldn't expect such a range from test set to test set but rather something on the order of 0.4% from min to max.

But based on what you are saying -- maybe this isn't my issue.

I'll have to think for a bit on what my next question is here on how to proceed unless there are any blatant things i'm not immediately seeing here.

wzaggle:
Correction... Better maybe but not perfect. If the frequency gets too high it can still start to lose a few. It might need the interrupt code written in assembly to capture both signals faster.

Do you really need this or were you just testing to see what happens?

Okay, Sorry for the delay. Here is some code I just took and modified from the Encoder library that directs interrupts to an in-line assembly routine. It was originally written by Paul Stoffregen to capture the fast turning pulses of a rotary encoder with two external interrupt lines. I changed it to add 1 to the count for any single interrupt and 2 to the count for both interrupts. So in theory, count should always be an even number. It seems to be much better. Feel free to try it. You could modify it further to keep two separate counts, one for each interrupt if it works.

#include "Arduino.h"
#define IO_REG_TYPE      uint8_t
#define PIN_TO_BASEREG(pin)             (portInputRegister(digitalPinToPort(pin)))
#define PIN_TO_BITMASK(pin)             (digitalPinToBitMask(pin))
#define DIRECT_PIN_READ(base, mask)     (((*(base)) & (mask)) ? 1 : 0)

#define pin1 2
#define pin2 3
#define pin3 4

typedef struct {
  volatile IO_REG_TYPE * pin1_register;
  volatile IO_REG_TYPE * pin2_register;
  IO_REG_TYPE            pin1_bitmask;
  IO_REG_TYPE            pin2_bitmask;
  uint8_t                state;
  int32_t                count;
} interrupt_Capture;

interrupt_Capture capture1;
interrupt_Capture capture2;

void setup() {
  capture1.pin1_register = PIN_TO_BASEREG(pin1);
  capture1.pin1_bitmask = PIN_TO_BITMASK(pin1);
  capture1.pin2_register = PIN_TO_BASEREG(pin2);
  capture1.pin2_bitmask = PIN_TO_BITMASK(pin2);

  Serial.begin(9600);
  pinMode(3, INPUT_PULLUP);
  pinMode(2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), counter1, RISING);
  attachInterrupt(digitalPinToInterrupt(3), counter1, RISING);
}

static void counter1(void) {
  update(&capture1);
}

void loop() {

  Serial.println(capture1.count);

  delay(10000);
}

static void update(interrupt_Capture *arg) {

  // The compiler believes this is just 1 line of code, so
  // it will inline this function into each interrupt
  // handler.  That's a tiny bit faster, but grows the code.
  // Especially when used with ENCODER_OPTIMIZE_INTERRUPTS,
  // the inline nature allows the ISR prologue and epilogue
  // to only save/restore necessary registers, for very nice
  // speed increase.
  asm volatile (
    "ld r30, X+"    "\n\t"
    "ld r31, X+"    "\n\t"
    "ld r24, Z"     "\n\t"  // r24 = pin1 input
    "ld r30, X+"    "\n\t"
    "ld r31, X+"    "\n\t"
    "ld r25, Z"     "\n\t"  // r25 = pin2 input
    "ld r30, X+"    "\n\t"  // r30 = pin1 mask
    "ld r31, X+"    "\n\t"  // r31 = pin2 mask
    "ld r22, X"     "\n\t"  // r22 = state
    "andi r22, 3"     "\n\t"
    "and  r24, r30"   "\n\t"
    "breq L%=1"     "\n\t"  // if (pin1)
    "ori  r22, 4"     "\n\t"  //  state |= 4
    "L%=1:" "and  r25, r31"   "\n\t"
    "breq L%=2"     "\n\t"  // if (pin2)
    "ori  r22, 8"     "\n\t"  //  state |= 8
    "L%=2:" "ldi  r30, lo8(pm(L%=table))" "\n\t"
    "ldi  r31, hi8(pm(L%=table))" "\n\t"
    "add  r30, r22"   "\n\t"
    "adc  r31, __zero_reg__"  "\n\t"
    "asr  r22"      "\n\t"
    "asr  r22"      "\n\t"
    "st X+, r22"    "\n\t"  // store new state
    "ld r22, X+"    "\n\t"
    "ld r23, X+"    "\n\t"
    "ld r24, X+"    "\n\t"
    "ld r25, X+"    "\n\t"
    "ijmp"        "\n\t"  // jumps to update_finishup()
    // TODO move this table to another static function,
    // so it doesn't get needlessly duplicated.  Easier
    // said than done, due to linker issues and inlining
    "L%=table:"       "\n\t"
    "rjmp L%=end"     "\n\t"  // 0
    "rjmp L%=end"   "\n\t"  // 1
    "rjmp L%=end"    "\n\t"  // 2
    "rjmp L%=end"   "\n\t"  // 3
    "rjmp L%=plus1"    "\n\t"  // 4
    "rjmp L%=plus1"     "\n\t"  // 5
    "rjmp L%=plus1"    "\n\t"  // 6
    "rjmp L%=plus1"   "\n\t"  // 7
    "rjmp L%=plus1"   "\n\t"  // 8
    "rjmp L%=plus1"    "\n\t"  // 9
    "rjmp L%=plus1"     "\n\t"  // 10
    "rjmp L%=plus1"    "\n\t"  // 11
    "rjmp L%=plus2"   "\n\t"  // 12
    "rjmp L%=plus2"    "\n\t"  // 13
    "rjmp L%=plus2"   "\n\t"  // 14
    "rjmp L%=plus2"     "\n\t"  // 15
    "L%=minus2:"        "\n\t"
    "subi r22, 2"     "\n\t"
    "sbci r23, 0"     "\n\t"
    "sbci r24, 0"     "\n\t"
    "sbci r25, 0"     "\n\t"
    "rjmp L%=store"   "\n\t"
    "L%=minus1:"        "\n\t"
    "subi r22, 1"     "\n\t"
    "sbci r23, 0"     "\n\t"
    "sbci r24, 0"     "\n\t"
    "sbci r25, 0"     "\n\t"
    "rjmp L%=store"   "\n\t"
    "L%=plus2:"       "\n\t"
    "subi r22, 254"   "\n\t"
    "rjmp L%=z"     "\n\t"
    "L%=plus1:"       "\n\t"
    "subi r22, 255"   "\n\t"
    "L%=z:" "sbci r23, 255"   "\n\t"
    "sbci r24, 255"   "\n\t"
    "sbci r25, 255"   "\n\t"
    "L%=store:"       "\n\t"
    "st -X, r25"    "\n\t"
    "st -X, r24"    "\n\t"
    "st -X, r23"    "\n\t"
    "st -X, r22"    "\n\t"
    "L%=end:"       "\n"
    : : "x" (arg) : "r22", "r23", "r24", "r25", "r30", "r31");
}

I can still make this code go to an Odd number eventually. I would think a simple increment of an integer in the interrupt routine would be faster than this assembly anyway. It appears that perfectly simultaneous external interrupts can potentially be missed. I can't see anything I am doing wrong with the circuit or the software.

Maybe some of the really smart guys on this forum can explain it. The ATmega2560 docs say the interrupt latches in hardware which should mean this is would not happen. Unless it just can't clear the latch fast enough somewhere. Maybe there is documentation for this issue out in Google Land someplace.

Maybe each flow meter needs its own external counter and latch? I guess you could farm out the counting to a separate smaller processors or just use discrete components. As it is, this does not look that reliable for dual capture.

I will be curious to see if you find a solution.

I have no clue yet, sorry :slightly_frowning_face:

Manufacturer's page for ATmega2560: Dynamic Product Page | Microchip Technology

Arduino external interrupts : https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/WInterrupts.c

Pinmapping Arduino Mega 2560: http://www.pighixxx.com/test/portfolio-items/mega/

Can you do a test ?
You could try pin 2 and 3. Those are INT4 and INT5 and are synchronous instead of asynchronous.

How good are the signals ?
You can help the signal high level, by setting pinMode(18, INPUT_PULLUP) for both used interrupt pins.
Even better is to create the pulses with the Arduino itself, with two wires from an output pin to two interrupt inputs and generate 1000 pulses in the loop(). Then the signal is for sure 0V and 5V.

A breadboard can have bad contacts. Locate everything on a different location on the breadboard. A bad contact for the GND might perhaps cause this.

It might seem far fetched, but you print the variables while using Serial.println functions (which use interrupts). Perhaps you can fetch the variables at the same time:

void loop() {
  unsigned int copy1, copy2;

  noInterrupts();
  copy1 = Counts1;
  copy2 = Counts2;
  interrupts();

  Serial.print(copy1);
  Serial.print(F(", "));
  Serial.println(copy2);
  delay(100);
}

How is the Arduino Mega 2560 powered ? Use a good USB cable and measure the 5V pin. It needs to be more than 4.5V. The ATmega2560 is an older microcontroller and will not work well when the voltage is below 4.5V.

I can not verify it. Therefor I think that something is wrong with your voltages or hardware.

When the same signal is sent to two pins, and one counter is slightly behind… that is possible if one wire is not connected well.
An open input pin is very high impedance and it can go along with the signal of the neighbour pin. By removing a wire to pin 18 or 19 is the only way that I can get what you describe.

It is possible that the Arduino libraries have bugs. But counting interrupts is such a basic task and so often used, a bug would have been encountered and solved long time ago.

// Arduino IDE 1.8.5 with Arduino Mega 2560 board.

// Pin 11 is connected to pin 18 and 19

// TimerOne is used as hardware timer, it runs independent of the code.
// It is installed with the Library Manager from the Arduino IDE.
#include <TimerOne.h>

const int outPin = 11;

volatile unsigned int Counts1 = 0;
volatile unsigned int Counts2 = 0;


void setup()
{
  Serial.begin( 115200);
  Serial.println( "Started");

  attachInterrupt( digitalPinToInterrupt( 19), counter2, RISING);
  attachInterrupt( digitalPinToInterrupt( 18), counter1, RISING);

  Timer1.initialize( 10000);   // timer interval in micro seconds
  Timer1.pwm( outPin, 50);     // send pulse to pin with 50% duty cycle
}

void counter1()
{
  Counts1++;
}

void counter2()
{
  Counts2++;
}

void loop()
{
  unsigned int copy1, copy2;

  noInterrupts();
  copy1 = Counts1;
  copy2 = Counts2;
  interrupts();

  Serial.print( copy1);
  Serial.print(F( ", "));
  Serial.println( copy2);

  delay( 500);
}

The threshold voltages are slightly different on different pins. If you don't have clean transitions on the signal, it could result in different logic levels on each pin internally.

wzaggle:
I can still make this code go to an Odd number eventually. I would think a simple increment of an integer in the interrupt routine would be faster than this assembly anyway. It appears that perfectly simultaneous external interrupts can potentially be missed. I can't see anything I am doing wrong with the circuit or the software.

Maybe some of the really smart guys on this forum can explain it. The ATmega2560 docs say the interrupt latches in hardware which should mean this is would not happen. Unless it just can't clear the latch fast enough somewhere. Maybe there is documentation for this issue out in Google Land someplace.

Maybe each flow meter needs its own external counter and latch? I guess you could farm out the counting to a separate smaller processors or just use discrete components. As it is, this does not look that reliable for dual capture.

I will be curious to see if you find a solution.

Interesting, I will try this out too when I can, I am surprized, but in theory a race condition may exist (usually
such things are designed for and flip flops added to reduce the chance of metastable states to extremely low levels.

j-ro:
I am working on a project where I have 2 flowmeters outputting 0 to 5 volt square waves and I want to count the total number of pulses out of each meter to totalize each flow.
Here's the code I'm testing with on a Mega 2560:

....

void loop() {
 
 Serial.print(Counts1);
 Serial.print(F(", "));
 Serial.println(Counts2);
 delay(100);

}

You loop code has another issue, its not reading the volatile ints safely (remember its an 8-bit machine so every variable is written/read byte-by-byte to memory - the code in loop will do two byte reads for each of Counts1 and
Counts2, and if one of the ISR's runs between those two reads you'll get a garbled result.

The variables need to be read in a critical section, therefore:

int get_Counts1()
{
  noInterrupts();
  int result = Counts1 ;
  interrupts();
  return result;
}

int get_Counts1()
{
  noInterrupts();
  int result = Counts1 ;  // read in critical section
  interrupts();  // re-enable interrupts ASAP
  return result;
}
void loop() {
  
  Serial.print(get_Counts1());
  Serial.print(F(", "));
  Serial.println(get_Counts2());
  delay(100);
}

Debounce?

Hi GUys,

Sorry for the delay in response. I've been trying a lot of things to identify the root cause (which I now think was mostly breadboard related).

Here are some things I found:

  • Koepel's example using the arduino to generate the input square wave. I did this on a 2nd mega with a different bread board and it seemed to accurately count the same number of pulses for quite a while but would eventually differ in counts. After about 4 hours the difference was about 200 pulses.

  • Adding internal pull up resisters, turning off interrupts and copying the value to a second unsigned int variable which all are probably the right things to do from a robustness standpoint all had what seemed to be a negligible impact on the ability for the arduino to count the same input from my single flowmeter twice. I was looking at at about a 2 or 3 count difference after around 10,000 input pulses. That's acceptable to me. I implemented all of these suggestions in my code.

  • The most frustrating thing that happened through all of this is that my original complaint of being off by up to ~20 pulses out of 1000 was not repeated through any of this latest round of testing. The first thing I did before trying any of the other suggestions was to follow Koepels suggestion of moving my circuit on the bread board. I 1-up'd that and eliminated the Adafruit permaproto breadboard from the circuit all together and direct soldered all of the wires together. Since I can't undo this change back to original condition to check it i can only guess but I'm fairly certain there was something going on with the bread board and my circuit.

My problem is mostly solved now except for this:

I have several different flow rates I've been checking at through out all of this. For my project, as I stated before, I'll have an input frequency ranging between 2Hz to 35Hz with a 50% duty cycle. I've been checking the minimum (2Hz) the maximum (35Hz) and a few spots equally spaced in the middle. What I'm seeing is near perfect counting on all the rates I check EXCEPT the 2Hz rate. This will at times count prefect and others are off as far as 5-10%.

Are there any thoughts on why I would be seeing a difference at this slow 2Hz rate and not at the other faster rates? Is there something about the 50% duty cycle and thus the longer times spent at each logic state causing this? I wouldn't think so but its really the only thing I can think of that's really different between my test points besides the frequency itself.

According to this paper about debouncing there may be something going on that isn't very intuitive.

He says there is a voltage below which the pin is guaranteed to register LOW, and a voltage above which it it guaranteed to register HIGH. In between there is an undefined area where it could register either.

If you analyze the same signal in both analog and digital ways, you get some strange activity going on. Even when the analog signal ramps up the voltage smoothly, the digital signal can experience bounce in the undefined region. Possibly when the switch is moving slower, it has more time to spend in that undefined region, and could experience bounce more often.

I can't see where you have any debouncing at all. What will happen if you get a bounce signal on the falling edge?