How to debounce a signal in Interrupt...

Hi everybody!

So recently i was working on a Project where i wanted to meassure the RPM of one of the Wheels on my Car, and from that calculate the Speed, keep track of the Driven distance etc. with an Arduino.
I wanted to use a Magnet and a Reed Switch to meassure the RPM, for that i needed to debouce the Signal from the Reed Switch, since i couldn't find any way to debouce a signal in interrupt functions that didn't require additional hardware for debouncing the signal on the Internet, here is how i debounced the Signal comming from the Reed Switch...

//Software debouncing in Interrupt, by Delphiño K.M.

long debouncing_time = 15; //Debouncing Time in Milliseconds
volatile unsigned long last_micros;

void setup() {
  attachInterrupt(0, debounceInterrupt, RISING);
}

void loop() {
}

void debounceInterrupt() {
  if((long)(micros() - last_micros) >= debouncing_time * 1000) {
    Interrupt();
    last_micros = micros();
  }
}

void Interrupt() {
  //Do Something
}

You could also "Do Something" in the main function, instead of calling a second function that then does something, i just liked it better this way...

Note: I've only tried this on an Arduino Mega (ATmega1280), with Arduino 0022.
Also, this isn't a perfect solution, the interrupt function is stil called multiple times, the follwing calls, in the next 15ms are just ignored, but hey, it does the job, so screw it!

Well, thats about it, i hope this helps someone...

There are probably stil tons of improvements you can do, but for now this solved my problem.

You could also "Do Something" in the main function, instead of calling a second function that then does something, i just liked it better this way...

You liked the overhead of a function call in what is supposed to be a very, very fast routine? OK, whatever floats your boat.

Wow, fast reply...
Well, i thought one if statement comparing the current micros to the micros at the last call to Interrupt(), running a few times after the first pulse wouldn't stop the loop for such a long time that it would cause any problems, correct me if i'm wrong...

edit: Sorry maybe i misunderstood your reply, but if you don't like the 2nd function being called, then just don't call a second function, i guess it is quite a unnecessary waste of time...

The overhead of a function call isn't that great. A few hundred nanoseconds, depending on the number of arguments to the function. But, if it isn't necessary, it isn't necessary.

Yeah, sorry, i misunderstood you, but yeah, your right, it's probably not such a smart beautification... :slight_smile:

Yes you can debounce inputs even if you are using interrupts; but why are you using a reed switch when Hall sensors cost so little and don't need debouncing?

why are you using a reed switch when Hall sensors cost so little and don't need debouncing?

Good point, i was woundering if someone would ask that :slight_smile:
The thing is, i live in Portugal, in a sort of Village where there are no Electronics Store nearby, basically (believe it or not) i would have do drive at least 100 Km (about 60 miles) to get to a Store where i would even have the slightest chance of finding a Hall Sensor, or i would have to order it Online witch would then take 3 Weeks to get here... Of course i could also take something apart (eg. a Cooling Fan from a PC) to get a Hall Sensor (witch i would have problaby done, if it would have been absolutely necessary), but in this case i went for the Reed Switch because my Car actually already hade Reed Switch build in the Speedometer i didn't complete this project jet, so i have no idea if the Reed Swith is actually used or is just there for the Version of my car with i Digital Km Counter (mine has a analog Km Counter and Speedometer), however i thought i'll just take advantage of that Reed Switch witch is apparently not even being used, because this way i don't have to build anything to meassure the RPM...
Is that reason good enough for you?! :slight_smile:

PaulS:
The overhead of a function call isn't that great. A few hundred nanoseconds, depending on the number of arguments to the function. But, if it isn't necessary, it isn't necessary.

The compiler will certainly optimize the function call out if it's only being used in one place. Separating the debouncing code from the actual interrupt code is also good practice.

If you want to talk about interrupt overhead, check out the PCInt or Wire (built in) libraries.

@WizenedEE Thank you.. At least someone sees my point...
I also figured the Compiler would probably correct that, then again, i didn't know it would, so i guess i can't use that as an argument :slight_smile:
But i actually mainly called a Second function with the Interrupt code to make the Code easier to understand, although i guess people wouldn't really have had any problems understanding it since it is quite simple :slight_smile:

I am new to this but a few things occur to me ....
1.
Although I think your method of only acting on times greater than some minimum will work just fine there is another way to denounce.
After the first rising edge detach/disable the pin interrupt and start a timer interrupt.
When the timer expires, some time after the noise window but before the minimum time period you want to measure, re-attach the interrupt.
Record the time interval between interrupts.
That way you only run code that you need, IE the first closure of the switch and your function that responds to it.

You are using micros() to count time ... have you considered what your code will do when micros() rolls back to 0?

You need to detect that no interrupt has taken place in n Seconds.
This is because when your wheel stops turning the time will become infinite and will not be updated.

However you manage the interrupt I would be inclined to suggest that you need to do two things.
Set up a global variable to hold RPM.
Update this with a calculated value, based on interval between interrupts, if the interval was valid, IE not too short.
Update this with 0 if the time between interrupts exceeds some maximum.

I have written some code that uses micros() to implement interrupt like timers, as in count and compare registers.
If you think it may help you are welcome to it, although I must stress I am very new to this stuff and it is my first library.
The code is currently using an array when it should be using a struct ...

Al

Hello Dyslexicbloke, thanks for your reply...

  1. If that works, it would be a good idea since the main Interrupt function wouldn't be Called multiple times, have you tested that yet? I have tried detaching the Interrupt in the Interrupt function itself waiting 15ms and reattaching the Interrupt afterwords (just for testing) but couldn't get that to work, although i didn't put much efford into it because it would have obviously been quite unpractical since it would have stayed in the Interrupt function for 15ms. :slight_smile:

  2. Yes, i have considered that... Thats why i wrote "(long)(micros() - last_micros) >= debouncing_time * 1000" instead of "micros() - last_micros >= debouncing_time * 1000" if it rolls over it will stil count the time correctly... See: http://www.cmiyc.com/blog/2012/07/16/arduino-how-do-you-reset-millis/

  3. Yeah, i will have to detect that, but i think thats not the only thing i will have to detect :smiley: I mean, the Interrupt in the Sketch i've send just calls a empty function :), i stil have include a RPM Counter, and something that in a certain interval calculates the Speed and increments the driven distance, and writes it to a Screen... The only thing i am worrying about is that i think the Reed Switch closes once every turn of the Wheel, whitch would not be so good, because it would make it almost impossible to detect the Car has stopped in 1 Second or so, i mean, even if the Wheel spins only once a Second i would be driving with about 6 kmh (3,7 mph), so to really be sure the Car has stopped the Arduino would have to wait quite a long time to see if the wheel is maybe stil turning really slow, also at low speeds i couldn't get a very frequent update, but hey, maybe the Reed Switch closes in smaller intvervals afterall, ant if not at least i can get a fairly accurate Distance :slight_smile: or i have to somehow make it close a few times per Wheel rotation...
    If you what to you can send me a link or something to your Code, i guess it could come in Handy :slight_smile: Although i think in Calculating the Speed and Distance i probably won't need it so much...

Anyhow thanks, i appreciate your tips/help :wink:

As far as I know you cant use delay functions within an ISR (Interrupt Service Routine) and you wouldnt want to anyway.
Rule of thumb is to do as little as possible and pass flags / values to your main code.

Also anything that you update in an ISR should be declared as volatile so that the compiler know that it may change whilst it wasn't looking so to speak.

I have played with timer interrupts and found them a little fiddly, but that is mostly a function of my ignorance as it is anything technical.

If your ISR will detach the interrupt, you would have to test this, then you could simply set a variable to the NewMicros = micros().

Then in your main loop ...
check once per scan if the new value is different from the current value.
If it is then the interrupt has fired.
You need to calculate the micros() value that should be exceeded before you attach the interrupt again, lets call it AttachAtMicros
You need to calculate RPM from currentMicros and newMicros, where NewMicros is the one you have declared volatile and updated with the ISR
Check once per scan if micros() is greater than AttachAtMicros
If it is then your debounce period has expired.
You need to attach your interrupt ready for the next pulse

I was just about to modify my timer code to make it a little smaller, I can attach it when I am done if you would like to play with it.
There may be better ways though, as I said its is my first Lib but it dose handle the micros roll-over time over a wide range.

I finished the mod ... here it is.
There is an example with two instances of the timer, one handling denouncing of my 3X3 keyboard and one handling repeat key delays

The Zip has the class code and headers.
Use_Notes.txt is an explanation of how to use it.
It made perfect sense when I wrote it but if I haven't been clear, you know how it is, please feel free to get in touch.

GeneralTimer.zip (5.41 KB)

delphino-999:
2. Yes, i have considered that... Thats why i wrote "(long)(micros() - last_micros) >= debouncing_time * 1000" instead of "micros() - last_micros >= debouncing_time * 1000" if it rolls over it will stil count the time correctly... See: http://www.cmiyc.com/blog/2012/07/16/arduino-how-do-you-reset-millis/

Signed and unsigned integers do not behave the same when they overflow. If you're relying on the conventional solution for rollover of millis(), you'd better use unsigned integers.

Hi. I have the same reason for posting this as delphino-999:

i hope this helps someone...

.

I will use this code for menu input in a temperature control system with two pid processes and 8-10 one-wire sensors.

/* Two detent rotary encoders with switch button example. 
   Input signal using interrupts on pin D2/3/4 and D5/6/7.
   Based on BenF forum sketch 25may2011 and debounce from delphino-999 forum sketch 19oct2012.
   HW: Roboduino (http://arduino-direct.com/sunshop) and rotary encoder with pushbutton (Panasonic 30 PPR, audiosector, www.ebay.com)
   Desember 2012, Hans Fredrik Sandberg */

volatile uint16_t counter[2]={0,0};  // Variable to pass rotary encoder activity back to main loop
volatile int push[2] = {0,0};        // Variable to pass rotary encoder switch press back to main loop
long debouncing_time = 40;           // Debouncing Time in Milliseconds
volatile unsigned long last_micros;

void setup() {
  Serial.begin(9600);
  Serial.println("Start");
  // enable pullup for encoder pins
  PORTD |= _BV(PORTD7) | _BV(PORTD6) | _BV(PORTD5) | _BV(PORTD4) | _BV(PORTD3) | _BV(PORTD2);
  // enable button pin change interrupt D2 D3 D4 D5 D6 D7
  PCMSK2 = _BV(PCINT18) | _BV(PCINT19) | _BV(PCINT20) | _BV(PCINT21) | _BV(PCINT22) | _BV(PCINT23);
  PCICR = _BV(PCIE2);  // D-port interrupt enable
}

void loop() {
   Serial.print("Value of counter0 "); Serial.print(counter[0]); Serial.print("   Value of counter1 "); Serial.print(counter[1]);
   Serial.print("   Value of push0 "); Serial.print(push[0]);    Serial.print("   Value of push1 ");    Serial.println(push[1]);
   if (push[0] > 0) { push[0] = 0; Serial.println("Resetting push[0]"); }  
   if (push[1] > 0) { push[1] = 0; Serial.println("Resetting push[1]"); }  
   delay(1000); // change this to check/view that encoder values are updated even if main process is doing something else
}

ISR(PCINT2_vect) // handle pin change interrupt for D0 to D7 here
{
  static uint8_t AB[2] = {0x03, 0x03};     //lookup table index
  static long encval[2] = {0,0};           //encoder value
  static const int8_t enc_states[] =   {0, -1, 1, 0, 1, 0, 0, -1, -1, 0, 0, 1, 0, 1, -1, 0};
  static uint8_t btn = _BV(PIND4) | _BV(PIND7);
  uint8_t t = PIND;  // read port status

  // check for rotary1 state change on pin D2 D3
  AB[0] <<= 2;                  // save previous state
  AB[0] |= (t >> 2) & 0x03;     // add current state
  encval[0] += enc_states[AB[0] & 0x0f];
  if( encval[0] > 1 ) {         // step forward
    counter[0]++;  encval[0] = 0;
  }
  else if( encval[0] < -1 ) {   // step backwards
    counter[0]--; encval[0] = 0;
  }
  
  // check for rotary2 state change on pin D5 D6
  AB[1] <<= 2;                  // save previous state
  AB[1] |= (t >> 5) & 0x03;     // add current state
  encval[1] += enc_states[AB[1] & 0x0f];
  if( encval[1] > 1 ) {         // step forward
    counter[1]++; encval[1] = 0;
  }
  else if( encval[1] < -1 ) {   // step backwards
    counter[1]--; encval[1] = 0;
  }

  // check if buttons are pushed
  t &= _BV(PIND4) | _BV(PIND7); // D4 D7
  if (t != btn) {  // activity on pins, we're only interested in high to low transitions
    if((long)(micros() - last_micros) >= debouncing_time * 1000) {  
      push[0] |= !(t & _BV(PIND4));
      push[1] |= !(t & _BV(PIND7));
      btn = t;
      last_micros = micros();  // start/restart bounce guard timer (covers make and break)
    }
 }
}

Some questions/comments: