Using an ATMega328P to relaibily report interrupts greater than 255 counts.

Hi all,

I’ve been struggling with this for a couple of days now so any help would be greatly appreciated. My full code is at the bottom of this post.

I have a relatively simple code which uses interrupts to count how many pulses are created by a flowmeter. Once the target number of pulses of 300 are reached, a success value is sent to the Serial unless a delay of 10 seconds is reached which would signify an inadequate amount of flow in a given time.

Physical Setup

An Arduino Nano ATmega328P and a hall effect flowmeter connected to physical pin 2 with a 10K pull-up.

Problem

Approximitely every 1 in 10 cycles results in my pulse count not being greater, equal to, or less than my target value, pushing the code to the ‘else’ statement in the code below. When this happens, the value always reported back is 256.

After a lot of trial/error with various count targets, testing parts of the code and reading up on forums I suspect that because any int more than 255 requires more than 1 byte/8bits, which to my understanding means that the 8 bit arduino chip will have to run twice to copy (save?) a 16bit number. So if the interrupt detects another pulse between the copy, the the second 8bit of the number won’t be correct, hence pushing me to the ‘else’ statement?

From reading other posts there are some hints at disabling interrupts which I’ve attempted (as mentioned in the points below) but I am unclear at what point and for how long to do this in my main loop? I’m also concerned with accuracy and I’m unsure about how disabling interrupts would affect this?

If disabling interrupts intermitently is the answer then it would be great to understand where to do this in my main code.

What I’ve tried

  • using ‘ATOMIC_BLOCK(ATOMIC_RESTORESTATE)’ from the atomic library and various trial and error of disabling and enabling interrupts inside and just before my ‘while (count < Target)’ loop. This appears to reduce the amount of times this error happens to 1 in ~30 but it is still an issue
  • I was concered that my millis() timer could have had an effect but I have the same results if this is commented out.
  • I’ve also tried the below code in my ‘while (count < Target)’ loop to continously print all count values. This has produced no errors after multiple attempts, I’m not 100% sure why but I would rather not send all this data to the Serial when the connected computer is only looking for one value… I’m also uncertain as of yet how this affects the accuracy.
     while (count < Target) 
     {
       Serial.println(count);
       if (delayRunning && ((millis() - delayStart) >= DELAY_TIME)) // Check if the timeout has been reached
       {
       delayRunning = false; // finished delay
         fillerror(); // Send Error signal
         break;
       }
     }

My full code below. (I’m relatively new to this so I’ve tried to make it as legible as possible in case I’ve done something odd…)

int flowPin = 2;    //Input pin on the Arduino
volatile int count; //Integer set as volatile 

// Set variable defaults
int Target = 350;  //Target number of pulses from the flowmeter

unsigned long DELAY_TIME = 10000; // 10 sec
unsigned long delayStart = 0; // the time the delay started
bool delayRunning = false; // true if still waiting for delay to finish

void setup() 
{
  pinMode(flowPin, INPUT);           //Set the pin as an input
  attachInterrupt(digitalPinToInterrupt(2), Flow, FALLING);  //  Configures interrupt and 'FLOW' ISR
  Serial.begin(9600);  //Start Serial
  while (!Serial) {}; // Wait for serial to start
  interrupts();   //Enable interrupts (should be on by default but I thought it was no harm to add?)
}

void loop() 
{
  if (Serial.available())
  {
    char ch = Serial.read();
    // If '1' is sent to the serial, reset the counter and mills start time, and send back '5' to the Serial to notify an external computer.
    if (ch == '1') 
    {
     delayStart = millis();
     delayRunning = true;
     count = 0; //Reset Counter
     Serial.println("5");
 
     while (count < Target) 
     {
       if (delayRunning && ((millis() - delayStart) >= DELAY_TIME)) // Check if the timeout has been reached
       {
         delayRunning = false; // finished delay
         fillerror(); // Send Error signal
         break;
       }
     }
     

     if (count >= Target)
     {
       fillstop(); // Send success signal
     }
     
     
     else
     {
       Serial.println("What Happened Here?....");
       Serial.println(count);
     }
     
    }
  }
}
 
void Flow() {
  count++; //Every time this function is called, increment "count" by 1
}

void fillstop() {
  Serial.println("9"); //success value sent to Serial
}

void fillerror() {
  Serial.println("0"); // Timeout value sent to Serial
  Serial.println(count);
}

Your count variable is a multibyte variable. You should disable interrupts while resetting it and should disable interrupts, make a temporary copy of it, and re-enable interrupts before using it in loops or calculations.

try this:

int flowPin = 2;    //Input pin on the Arduino
volatile int count; //Integer set as volatile

// Set variable defaults
int Target = 350;  //Target number of pulses from the flowmeter

unsigned long DELAY_TIME = 10000; // 10 sec
unsigned long delayStart = 0; // the time the delay started
bool delayRunning = false; // true if still waiting for delay to finish

void setup()
{
  pinMode(flowPin, INPUT);           //Set the pin as an input
  attachInterrupt(digitalPinToInterrupt(2), Flow, FALLING);  //  Configures interrupt and 'FLOW' ISR
  Serial.begin(9600);  //Start Serial
  while (!Serial) {}; // Wait for serial to start
  interrupts();   //Enable interrupts (should be on by default but I thought it was no harm to add?)
}

void loop()
{
  if (Serial.available())
  {
    char ch = Serial.read();
    // If '1' is sent to the serial, reset the counter and mills start time, and send back '5' to the Serial to notify an external computer.
    if (ch == '1')
    {
      delayStart = millis();
      delayRunning = true;
      noInterrupts();
      count = 0; //Reset Counter
      interrupts();
      Serial.println("5");

      while (1)
      {
        noInterrupts();
        if (count >= Target)
        {
          interrupts();
          break;
        }
        interrupts();
        if (delayRunning && ((millis() - delayStart) >= DELAY_TIME)) // Check if the timeout has been reached
        {
          delayRunning = false; // finished delay
          fillerror(); // Send Error signal
          break;
        }
      }


      if (count >= Target)
      {
        fillstop(); // Send success signal
      }
      else
      {
        Serial.println("What Happened Here?....");
        noInterrupts();
        int tmpCount = count;
        interrupts();
        Serial.println(tmpCount);
      }

    }
  }
}

void Flow() {
  count++; //Every time this function is called, increment "count" by 1
}

void fillstop() {
  Serial.println("9"); //success value sent to Serial
}

void fillerror() {
  Serial.println("0"); // Timeout value sent to Serial
  noInterrupts();
  int tmpCount = count;
  interrupts();
  Serial.println(tmpCount);
}

Also, I should mention that you may not even need an interrupt for this. I note that with a 10 second timeout and 350 pulses to fill you could have as much as 28 milliseconds between pulses. Do you know what the minimum time between pulses can be? If it is on the order of milliseconds I would propose not using an interrupt at all.

Thanks ToddL1962 for the code and quick response. I've just ran 120 cycles of the code and have had no errors!

ToddL1962: Also, I should mention that you may not even need an interrupt for this. I note that with a 10 second timeout and 350 pulses to fill you could have as much as 28 milliseconds between pulses. Do you know what the minimum time between pulses can be? If it is on the order of milliseconds I would propose not using an interrupt at all.

In most cases my target count would be 850 and would be completed within 4-6 seconds so it would be around 5 milliseconds/pulse... would you suggest using some form of PWM instead? I'm needing quite a high accuracy which is mainly why I went for interrupts, just incase a pulse was missed but I'm open to suggestions.

Thanks again!

You can use interrupts, I'm just saying you don't have to. If you don't use interrupts you don't have to deal with the volatile count and all that goes with it.

I would suggest trying the code I posted to see if it clears up your problem then you'll at least have something working. You could then code it without interrupts and make sure you get comparable results.

Perfect! I'll do some more testing with this first and then I'll look into some other options to compare. Thanks again!

I'm needing quite a high accuracy which is mainly why I went for interrupts

Mistake. As you see, using interrupts actually introduces many more problems.

Polling an input is often all you need for something like a flow meter, but make sure that the sensor output does not "bounce", or rapidly switch during a transition (as does a mechanical switch).