Count pin changes in 60seconds

Hi,
I want to measure very slow water flow using tipping bucket method. I have the hardware and counting methods set up, I just can't work out how to get a Tip Per Minute (tpm) reading.

I need to count tips for 60 seconds, which is fine, but I'm lost on ideas of how to continue to count but drop off old values after 60 seconds.

It may be tipping from 1 to 30 times a minute.

I don't want to measure for 60 seconds, display a value then clear it and start again, I want to to continually be the last 60 seconds.

Cheers.

gr0p3r:
It may be tipping from 1 to 30 times a minute.

Is that maximum (30) guaranteed?

Sounds like you're going to have to store timestamps for each tip. When it is time to display, go through the timestamps and knock out the ones more than 60 seconds old. If you keep them in a ring buffer, you can just start at the tail and knock them out until you hit the first one that is less than 60 seconds old.

I would modify @Delta_G's idea a little. I would make a ring buffer that stores the most recent 40 timestamps. When you want to view the data you can notionally draw a line anywhere based on time, but it will be easier to manage the buffer if it is based on a fixed number of values rather than a varying time value.

...R

Thanks for snappy replies!
Coding badly, no 30 was just a number I pulled out of my head. But it takes time for the bucket to tip and spring back, so if it were more than once every 2 seconds, it would just stay tipped and water pouring through.

That's not necessarily a problem though. What I need it for is to shut off some other equipment when the "TPM" gets too slow.

It will start off quite fast, then eventually get really slow.

I would need to be constantly taking readings. Also storing them to get an average over say 10 minutes (too weed out erroneous values) before switching it all off.
That's after, first I need to get the TPM value.

I'm not sure how a ring buffer works. I think updating the TPM value every 10 seconds or so would be fine.

Time stamps could also work. But the value is always gonna be "1" or "HIGH" that it is stored with. So would you just look at all time stamps for last 60 seconds then count how many there are?

I'm sure this is simple, I just need that switch to be flicked so I can go from confused to understanding.

You don't need interrupts for this.

If you only change the display 1 time per second then you can keep tip counts (0 or 1) for the last 60 seconds in an array and every new second count the total and make the old last second be the new second by using a pointer for which to change next. As long as the new data writes over the oldest it doesn't matter how you count the total as long as you get them all.

Arduino millis() returns an unsigned long of milliseconds since startup. It may be off by a few seconds a day. It's probably more than close enough for tip-bucket measuring?

You could get on-the-second by looking for ( millis() % 1000UL == 0 ) but if you miss the millisecond of change (could happen during a print) then it's no good.

If you keep the start of each second stored as say startMillis....

if ( millis() - startMillis >= 1000UL )
{
startMillis += 1000UL; // always the next second
// 1 second has passed, display the total and move the new data pointer
}

gr0p3r:
I’m not sure how a ring buffer works.

This code should store the value of millis() everytime the bucket tips and will keep the most recent 40 values. For simplicity make the variables global.

const byte buffSize = 40;
unsigned long tipsBuffer[buffSize];
byte tipsIndex = 0;


if (digitalRead(tipPn) == LOW) {    // assumes active LOW
    tipsBuffer[tipsIndex] = millis();
    tipsIndex = (tipsIndex + 1) % buffSize;    // returns to 0 when it gets to buffSize
}

At any time the variable tipsIndex will point at the place the next value will go. And the most recent value will be at tipsIndex - 1. Obviously if tipsIndex = 0 the most recent value will be at index 39.

…R

You can also keep a running average of the time between tips using simple
digital filtering

double average = 0.0 ;
double rate_per_minute = 0.0 ;
unsigned long prev_ts = 0L ;

void loop ()
{
  if (... tip detected ...)
  {
    unsigned long ts = millis () ;
    update_average (ts - prev_ts) ;
    prev_ts = ts ;
  }
}

#define filter_factor 0.2  // smaller values have longer time constant.

void update_average (double ms)
{
  average += filter_factor * (ms - average) ; // digital filter
  rate_per_minute = 60000.0 / average ;
}

This isn't ideal though, as the time constant of the filter depends on the
time between tips, but it needs less storage than explicitly averaging the
last N readings.

MarkT:
but it needs less storage than explicitly averaging the
last N readings.

I thought the OP wants to count the number of TIPs in the last minute. My idea of storing (say) 40 is that he can then traverse the buffer backwards counting until he gets to one with a time difference greater than 60 seconds. (I am assuming there will never be more than 40 in 60 secs - the OP said 30).

...R

Let's say we are happy with 5 seconds of slop in the accuracy.

Keep an array of 12 ints.

int tipsIn5Seconds[12];

Be sure to zero it out before starting! It will contain random crud if you dont.

keep an index into the array, starting at zero, and keep track of when 5 seconds have elapsed.

int currentIndex = 0;
unsigned long mostRecentCheck = millis();

Now, each time there's a tip, increment tipsIn5Seconds[currentIndex];

watch the value millis()-mostRecentCheck (ie: check it at each iteration of loop() ).

When that value >=5000, then:

add up the values in tipsIn5Seconds. This will tell you how many tips there were in the last minute. Do whatever you need to do if the value is not what you want.

Increment currentIndex. If it >= 12 after this increment, then reset it to zero.

Set tipsIn5Seconds[currentIndex]=0;

Add 5000 to mostRecentCheck (don't just set it to millis(), because this can drift depending on how long it takes to do whatever you need to do)

The array of 12 ints will always contain the count of the tips over the last minute, accurate to a 5-second interval.

Great.
I think I understand. It's late here now and I won't get a chance to try these out until tomorrow night.
I think either Robin or PaulMurrays ideas will work for me.

I have used delay() in my code as a debounce. That's going to be an issue while trying to implement these methods isn't it?

I'm in my phone now, but I can post code, pics etc tomorrow. But Basically it's a simple design. Half a thin metal tube (berocca) cut long ways and suspended and weighted on copper wire in a PVC pipe. A screw as a stopper.

Wire from the copper wire and from the screw to a resistor, 5v and a digital input.
When it tips, contact with the screw is broken. When it comes back down, it bounces a bit. So debounce was a simple delay of 200ms.

I'll show you tomorrow. Cheers. :slight_smile:

gr0p3r:
I have used delay() in my code as a debounce. That's going to be an issue while trying to implement these methods isn't it?

Use millis() instead of delay() as illustrated in several things at a time. It also includes a simple debounce system.

...R

Robin2:
Use millis() instead of delay() as illustrated in several things at a time.

:astonished: Now I have to go back a rewrite my other sketch!! That's such a better way of doing it.

gr0p3r:
I have used delay() in my code as a debounce. That's going to be an issue while trying to implement these methods isn't it?

I'm a programmer, so my natural inclination is to do debounce in software. But I'm becoming convinced that the best way to do it is with capacitors and resistors. The guys over on the electronics sub-board know about that.

oh.... hardware debounce hey... oh well, ive done it with software now.
I think i have made it overly complicated and duplicated the debounce. but it didnt sem to work any other way.

I wanted it to only count one for a open and close (then debounce). my first few attempts counted constantly while open or closed. a couple attempts counted once or twice (not consistent) for each tip.

this is working, Now that its counting without delays, i need to get my TPM value.

#define FlowPin 9   // the number of the pushbutton pin
int reading = LOW;  //reads from FlowPin
int bucketcount = 0;   //Number of times bucket has tipped
int tipped = 0;  //is it in a state of tipping or tipped? 1 or 0.
unsigned long tiptime;  //time that a tip completes

void setup() {
  pinMode(FlowPin, INPUT);
  Serial.begin(9600);
}

void loop() {
   reading = digitalRead(FlowPin);
   if (reading == LOW && tipped == 0){   //if the bucket is tipping and not already in progress
     if(millis() > tiptime + 300){       // debounce?
       tipped = 1;                        //mark as being tip in progress
       tiptime = millis();               //last time it completed a tip (could be a a few in case of a bounce only take last time it closed)
     } 
   }
   if (reading == HIGH){                 //bucket collecting water again
     if(tipped == 1) {                    // a tip was in progress, must have completed now
        if(millis() > tiptime + 300){    //debounc?
         tipped = 0;                     // say this tip has finished
         bucketcount++;                  // increment counter
        }
     }  
   }
   Serial.println(bucketcount);          //output result
}

Dammit. I was wrong. It isnt debouncing it properly. mostly it counts by 1's, but sometimes it counts by 2's. :angry:

Ive attached 2 pics of my "sensor", maybe that will help or may just be interesting. certainly easier than describing it.

pic not attached as i thought.

gr0p3r:
pic not attached as i thought.

FYI you can go back and edit your post to add the pictures.

If you know the max tipping speed will be once per two seconds then why not just delay/debounce for about 1½ seconds?

I think you need to swap these two lines

  if (reading == LOW && tipped == 0){   //if the bucket is tipping and not already in progress
     if(millis() > tiptime + 300){

In other words, only check the reading if the time has expired.

Also the correct way to use millis() is

if (millis() - tipTime >= 300)

as that works correctly when millis() rolls over to 0 after 49 days

...R

PaulMurrayCbr:
I’m a programmer, so my natural inclination is to do debounce in software. But I’m becoming convinced that the best way to do it is with capacitors and resistors. The guys over on the electronics sub-board know about that.

Complete treatment including scope output. Where he shows hardware debounce, he uses a cap.
http://www.gammon.com.au/switches

Code is cheaper and can be a good deal closer to the point.