Interrupt code trouble

I am failry new to arduino, and as per usual when trying to learn on my own, I took on a project that was probably a little above my head. I'll spare the details on the whole project, but in short, I am having trouble accurately counting pulses from a flow senor using an interrupt. If I poll the sensor, the pulse count is accurate, but when I do it wth an interrupt it counts less pulses. Code is below, any answers appreciated.

#include <LiquidCrystal.h>
//Pin definition
const int fPin = 2; //Flow sensor on digital pin 2
// LCD pinout
LiquidCrystal lcd(8, 9, 4, 5, 6, 7); //typical pinout
//Logic variables
float fRate = 0.0; //flow rate with 1 decimal place
volatile int fCount = 0; //flow count 
const unsigned long Dbounce = 5; // debounce delay 
unsigned long lastDbounce = 0; //initial setting for debounce logic
const unsigned long timer = 1000;  //1 second timer for monitoring 
unsigned long lastTime = 0;  //initial setting for timer logic

void setup() {
//Initialize LCD
lcd.begin(16, 2);  //setup lcd
attachInterrupt(digitalPinToInterrupt(fPin), flowISR, FALLING); //interupt for flow sensor
}

void loop() {
    //1 second monitoring logic
  unsigned long currentTime = millis();
  if (currentTime - lastTime >= timer) {
    lastTime = currentTime;
    
    
    displayfRate();
  }
}

//ISR
void flowISR() {
  if (millis() - lastDbounce >= Dbounce) {
    fCount++;
    lastDbounce = millis();
  }
}

//Display data
void displayfRate() {
  fRate = fCount / 11.0; //calibraiton formula for sensor f=11q
   lcd.setCursor(0, 0);
   lcd.print(fRate);
   fCount = 0;

}

lastBounce should be volatile and when close timing counts, use micros(), not millis().

Avoid using floats.
Consider how we use millivolts rather than volts.xxx.

rate = count * 1000 / 11 to get milli_rate.
Floats are slowww code.

Which count is correct? The chanse is that both are wrong.
Please post a link to the flow sensor data shheet.

Thanks for the response. I implemented those changes. No effect.

The count without the interrupt is correct. The sensor calibration is f=11q. I've adjusted the flow manually using a bypass valve, graduated cylinder and timer. The physical flow rate is 1 l/m +/- a few ml. Using the polling method I get 11 hz, and it changes in real time as it should if I manipulate the bypass valve. I am confident that part is accurate. I cannot see where the interrupt is missing pulses?

How many interrupts per second are you trying to measure?

1 Like

About 11

Depending on the sensor, it may require the pin internal pullup resistor to be activated.

I'll get back to you in an hour or so (supper time here). But in the meantime, here's a little something to try. I'd be a little surprised if it made a difference but it's easy enough to try and rule out.

void displayfRate() {
   noInterrupts();
   int countCopy = fCount;
   fCount = 0;
   interrupts();
   fRate = countCopy / 11.0; //calibraiton formula for sensor f=11q
   lcd.setCursor(0, 0);
   lcd.print(fRate);
}

I already have an external pullup wired in. It ran the same way without one, but I added it to be sure.

I tried, no success, but I would greatly appreciate the assistance. I've been fighting with this for about 2 months as I have time. If it is some odd issue specific to my setup, that's fine, but I am trying to make sure I have the logic right.

Can you post the non-interrupt version of the code that does give the correct results?

@van_der_decken has already checked for the most obvious things, getting an interrupt while accessing fCount, and having an interrupt occur during the rather significant time that it takes to write to the LCD, causing you to lose a count when fCount is set to 0.

With an expected count of 11 per second, I would sample over a longer period of time. The count is likely to alternate between 10 and 12 if one of the pulses gets synchronized exactly with the one second period.

Doubt it will make any difference, but try this modified version of the sketch that doesn't reset fCount, but instead take the difference between the current and previous values. I also reduced fCount to a single byte instead of a two-byte integer, that should be good enough unless the maximum flow rate exceeds 23 l/m.

#include <LiquidCrystal.h>
//Pin definition
const int fPin = 2; //Flow sensor on digital pin 2
// LCD pinout
LiquidCrystal lcd(8, 9, 4, 5, 6, 7); //typical pinout
//Logic variables
float fRate = 0.0; //flow rate with 1 decimal place
volatile uint8_t fCount = 0; //flow count
const unsigned long timer = 1000;  //1 second timer for monitoring
unsigned long lastTime = 0;  //initial setting for timer logic

void setup() {
  //Initialize LCD
  lcd.begin(16, 2);  //setup lcd
  attachInterrupt(digitalPinToInterrupt(fPin), flowISR, FALLING); //interupt for flow sensor
}

void loop() {
  //1 second monitoring logic
  unsigned long currentTime = millis();
  if (currentTime - lastTime >= timer) {
    lastTime += timer;
    displayfRate();
  }
}

//ISR
void flowISR() {
  const unsigned long Dbounce = 5; // debounce delay
  static unsigned long lastDbounce = 0; //initial setting for debounce logic
  unsigned long currentMillis = millis();
  if (currentMillis - lastDbounce >= Dbounce) {
    fCount++;
    lastDbounce = currentMillis;
  }
}

//Display data
void displayfRate() {
  static uint8_t countPrevious = 0;
  noInterrupts();
  uint8_t countCopy = fCount;
  interrupts();
  
  fRate = (countCopy - countPrevious) / 11.0; //calibraiton formula for sensor f=11q
  countPrevious = countCopy;
  lcd.setCursor(0, 0);
  lcd.print(fRate);
}

It does vary as you metnioned as flow slightly changes. Code is below:

#include <LiquidCrystal.h>

// Pin definitions
const int fPin = 2;        // Flow sensor connected to digital pin 2
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

// Flow sensor variables
volatile int fCount = 0;
unsigned long lastDbounce = 0;  // To track when the last pulse was counted
unsigned long debounce = 50;    // Debounce delay for flow sensor

void setup() {
  // Initialize LCD
  lcd.begin(16, 2);  // Set up the LCD

  // Set up flow sensor pin
  pinMode(fPin, INPUT);

  // Display startup message
  lcd.setCursor(0, 0);
  lcd.print("Pulse Count");
  delay(1000);  // 1-second pause before starting pulse counting
  lcd.clear();
}

void loop() {
  static unsigned long lastPulseTime = 0;  // Last time a pulse was counted
  static unsigned long pulseDisplayTime = 0;  // Time to update pulse count display
  unsigned long currentTime = millis();

  // Manually count pulses without using an interrupt
  if (digitalRead(fPin) == LOW) {  
    // Debouncing check
    if (millis() - lastDbounce > debounce) {
      fCount++;  
      lastDbounce = millis();  // Reset debounce timer
    }
  }

  // Update the LCD with pulse count every second
  if (currentTime - pulseDisplayTime >= 1000) {
    pulseDisplayTime = currentTime;  // last display update

    // Display pulse count on LCD
    lcd.setCursor(0, 0);
    lcd.print("Pulses: ");
    lcd.print(fCount);  

    // Reset pulse count for the next period
    fCount = 0;
  }
}

I put together the little test circuit shown below:

using the sketch below (which is a quickly Frankenstein'd version of yours):

#include <Adafruit_SSD1306.h>

Adafruit_SSD1306 display(128, 64, &Wire, -1);

uint16_t rate = 11;

const uint8_t tonePin = 6;
const uint8_t fPin = 2;

float fRate = 0.0; //flow rate with 1 decimal place
volatile int fCount = 0;  //flow count

const unsigned long Dbounce = 5;   // debounce delay
unsigned long lastDbounce = 0;     //initial setting for debounce logic
const unsigned long timer = 1000;  //1 second timer for monitoring
unsigned long lastTime = 0;        //initial setting for timer logic

void setup() {
   if( !display.begin(SSD1306_SWITCHCAPVCC, 0x3C) ) {
      pinMode(LED_BUILTIN, OUTPUT);
      while( true ) {
         digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
         delay(250);
      }
   }
   display.clearDisplay();
   display.display();
   display.setTextColor(SSD1306_WHITE);
   display.setTextSize(2);
   display.setRotation(2);
   attachInterrupt(digitalPinToInterrupt(fPin), flowISR, FALLING);  //interupt for flow sensor
   tone(tonePin, rate*32);
}

void loop() {
   //1 second monitoring logic
   unsigned long currentTime = millis();
   if( currentTime - lastTime >= timer ) {
      lastTime = currentTime;
      displayfRate();
   }
}

//ISR
void flowISR() {
   if( millis() - lastDbounce >= Dbounce ) {
      fCount++;
      lastDbounce = millis();
   }
}

//Display data
void displayfRate() {
   noInterrupts();
   int countCopy = fCount;
   fCount = 0;
   interrupts();
   fRate = countCopy / 11.0;  //calibraiton formula for sensor f=11q
   display.clearDisplay();
   display.setCursor(0, 0);
   display.println(rate);
   display.println(countCopy);
   display.print(fRate);
   display.display();
}

What I've done is to use tone() to output a known frequency and route that output back to the pin that triggers your interrupt. Now, tone() won't go below 32Hz so I did a little jiggery pokery and actually generated 32x the frequency I wanted and used a dual 4 bit TTL counter to divide by 32 to get to what I wanted in the first place: an 11Hz signal coming in to pin 2. The only place it makes any appearance at all in the sketch is when I call tone().

Okay, 'cause it bothers me not to do it, I made a copy of fCount with interrupts turned off and reset it to 0. volatile does not equate to atomic, and I don't know if integer assignment is atomic in AVR and it was just faster to assume that it wasn't than to dig into it.

On the OLED I display the frequency I'm sending to pin 2, the value of fCount, and your rate. Within the +/- jitter I completely expected, it output exactly what I was expecting. 11, 11(+/-1) and 1.00 (ish).

So, good news, your interrupt code is just fine for evenly spaced pulses arriving more than 5mS apart. Whether that's significant or not with your setup, I have no way of knowing.

Your code made no change.

That's excellent to know that at least the code seems sound. I wounder how much the even spacing could effect the count. I would be confident to say that we are not recieving pulses at less than 5ms apart. In all honesty, most of what you said was a foreign language, but I get the jist. I wonder if ther is some hardware issue I am unaware of, but it seems unlikely when manual pulse counting works? I see 2 options if I cannot get the interrupt to work properly. I could adjust the constant (11) to make the dispaly accurate, but I think this will affect resolution a little too much. Or, I could just cont pulses using the polling method with no interrupt. This is just a small piece of the code, i will also be monitoring pressure and temperature (both analog), and triggering a relay for an alarm when anything is out of spec. I am not sure if the polling method will be an issue here.

How many pulses per second are you missing if you directly display fCount in the interrupt version of the code, instead of doing the calculate for fRate?

I didn't try that, but was considering it. give me a minute, I'll check.

Oh. Heh. Looking at your non-interrupt code, I see a Big Problem.

I'd like you to look at this code snippet below and imagine something with me.

  if (digitalRead(fPin) == LOW) {  
    // Debouncing check
    if (millis() - lastDbounce > debounce) {
      fCount++;  
      lastDbounce = millis();  // Reset debounce timer
    }
  }

Your fPin is low for more than 5mS. It's low for, oh 50mS. How many times will fCount be incremented? (Hint: way more than once.)

Your non-interrupt code increments fCount once every 5ms as long as fPin is LOW. Your interrupt code increments fCount once every time there's a falling edge more than 5ms apart.

Completely different things; completely different results. Non-interrupt measures the state of being LOW. Interrupt measures the event of going LOW.

1 Like

So if I change the debounce time to 50 ms I should expect to see a different count?