Debouncing an ISR

Hi all,

In the interest of time, I've tried to keep things brief and organized. Thank you again!

Hardware pieces:
Arduino Mega, IR LED (T-1 3/4), IR Receiver (TSOPS38238)

Code:

volatile bool f0 = 0;
volatile bool f1 = 0;

void setup () {
  Serial.begin (9600);
  pinMode (2, INPUT_PULLUP);
  pinMode (12, OUTPUT);
  attachInterrupt (digitalPinToInterrupt (2), ISR_IRCheck, CHANGE);
}

void loop () {
  tone (12, 37000);
  if (f0 == 1) {
    Serial.println ("High");
    f0 = 0;
  } else if (f1 == 1) {
    Serial.println ("Low");
    f1 = 0;
  };


}



void ISR_IRCheck () {
  if (digitalRead (2) == HIGH) {
    f0 = 1;
  } else {
    f1 = 1 ;
  };
};

Expectation:
Every time I break the beam between the IR LED and the IR receiver, I expect to get a serial printout. I expect the serial printout only ONCE per change (or my perceived or expected change in the IR signal).

Reality and my issue:
As I cross the IR "line"' with my finger, it seems like my Arduino detects many changes (LOWs to HIGHs) before settling on one. I'm inclined to want to somehow "debounce" this interrupt, where if an interrupt is not temporarily separated enough from the previous interrupt, the ISR is not ran. I think I then need to somehow timestamp the occurrence of the interrupts, but since millis() does not work in an ISR (and micros() is too small for the time differences that I'm interested in) I'm at a loss.

What should I do?

can the ISR count events and loop() monitor for a change in the number of events followed by a consistent event count for some period of time?

Im not following "consistent event count." What do you mean by that?

no further change in count

Ok, so just to make sure we're on the same page:

The ISR has a count++ of some sort, then in the void loop () part of the code this count is monitored after a certain amount of time passes between 2 point?

I'm inclined to want to somehow "debounce" this interrupt, where if an interrupt is not temporarily separated enough from the previous interrupt, the ISR is not ran. I think I then need to somehow timestamp the occurrence of the interrupts, but since millis() does not work in an ISR (and micros() is too small for the time differences that I'm interested in) I'm at a loss.

millis() will not change within an interrupt, but the value can be accessed and used for a timestamp to determine a lock out period.

void isrWithLockOut()
{
  unsigned long debounceLockOut = 100;//set lock out time
  static unsigned long last_interrupt_time = 0;
  unsigned long interrupt_time = millis();
  if (interrupt_time - last_interrupt_time > debounceLockOut)
  {
    //execute isr
  }
  last_interrupt_time = interrupt_time;

}

whenever loop() sees the count change it resets a timer. when timeout occurs it reads the state and takes some action, then waits from another change

Got it! thank you.

volatile byte pinState = 0;
volatile uint32_t pinTime = 0;
const uint18_t DEBOUNCE = 30;

void isr()
{
    pinState = digitalRead(pin);
    pinTime = millis();
}


void loop()
{
    noInterrupts();
    if (pinTime && ((millis() - pinTime) > DEBOUNCE))
    {
        pinTime = 0;
        interrupts();
        if (pinState)
        {
            // Pin is stable HIGH
        }
        else
        {
            // Pin is stable LOW
        }
        pinTime = 0;
    }
    interrupts();
}

But, this can be done just as well WITHOUT interrupts....

Regards,
Ray L.

Hi all,

Quick update: I added a debounce portion, but the serial monitor sometimes shows a couple of back to back "Highs." I have a couple of ideas why this might have happened, and would also greatly appreciate your ideas.

Here's the new code:

volatile bool f0 = 0;
volatile bool f1 = 0;
volatile unsigned long tDebounce = 0;
volatile unsigned long debounceDelay = 300;

void setup () {
  Serial.begin (9600);
  pinMode (2, INPUT_PULLUP);
  pinMode (12, OUTPUT);
  attachInterrupt (digitalPinToInterrupt (2), ISR_IRCheck, CHANGE);
}

void loop () {
  tone (12, 37000);
  if (f0 == 1) {
    Serial.println ("High");
    f0 = 0;
  } else if (f1 == 1) {
    Serial.println ("Low");
    f1 = 0;
  };


}



void ISR_IRCheck () {
  if (abs (millis () - tDebounce) > debounceDelay) {
    if (digitalRead (2) == HIGH) {
      f0 = 1;
    } else {
      f1 = 1 ;
    };
    tDebounce = millis ();
  };
};

Here's an excerpt from the serial monitor:
Low
High
High
High
High
Low
High
Low

First, you need to learn when and where you need to put semi-colons, and when and where you don't. You have several extra ones in that short snippet of code. Hint: You DON'T need a semi-colon after the closing '}' of any if, for, while, function definition, or any other multi-statement code block.

Second, your de-bounce logic is wrong. You CANNOT properly de-bounce in the ISR. When the bouncing stops, the ISR is no longer called, so how can you use the ISR to determine when the bouncing has stopped?

Look at the code I provided that uses the ISR to tell you WHEN the pin changes state, but uses loop to tell you WHEN the pin has STOPPED bouncing.

Regards,
Ray L.

debouncing typically looks for a repeated condition after some minimal amount of time. some people just check to see if it's the same value as previously read.

your ISR sets a state regardless of the previous value and only after is just changed. it could be bouncing, but the ISR will likely report the first change without waiting for it to be stable.

wouldn't it be much less complicated to not use an ISR and monitor changes in loop()?

Hi Ray,

Thank you for the tips, I definitely will change my habit of ';' input. Also I apologize for not looking at your code earlier—my mind totally blanked.

Quick question about my code (just so I know exactly what's wrong with it):

You mentioned that my debounce logic was wrong. I want to articulate my logic here first, and then would really appreciate your feedback on it:

I wanted this to happen: let's say a change is detected on the interrupt pin. Then the ISR is ran. That particular ISR is then time stamped with the millis() function and the value of that is compared with the tDebounce, the time stamp from the last debounced ISR. If this value is less than the debounceDelay, then the ISR is effectively "ignored" we get back to the loop(). Next time the ISR is called, and if millis()-tDebounce is greater than the debounceDelay, the ISR is ran and tDebounce is updated.

I would not use an ISR and just debounce the input like you would a mechanical button. There many examples of button debounce and your code would be a lot simpler.

Hi Todd,

The issue it that this is not a button, and it's gonna be activated by a rat!

I want to articulate my logic here first, and then would really appreciate your feedback on it:

Your logic is correct. Accept the first interrupt and lock out anything that follows for a period of time. I don't see any real difference between having the lock out in loop or in the isr itself.

The issue is that you accept the initial interrupt, and do not use a routine which requires a stable signal for a period of time. This makes thing susceptible to "noise" which can generate a interrupt not triggered by the sensor response.

You will need to do some study of your IR sensor, and whether or not you see false triggers with no beam break. If there is a beam break, how long of a signal do you get?

I think you are using a 38KHz modulated sensor and its not clear to me if the output from that unit is well suited to an interrupt.

ArianKS:
The issue it that this is not a button, and it's gonna be activated by a rat!

Please explain the rationale for using an ISR instead of the more conventional pushbutton debounce code.

ArianKS:
The issue it that this is not a button

irrelavent -- its in input. both mechanical switches and optical detectors typically pull an input with a pullup, LOW

Also Ray,

You code also had the same issue:
15:45:50.511 -> high
15:45:51.131 -> low
15:45:51.759 -> high
15:45:52.351 -> low
15:45:53.195 -> high
15:45:53.753 -> low
15:45:54.581 -> high
15:45:55.213 -> low
15:45:56.636 -> high
15:45:57.235 -> low
15:45:58.225 -> high
15:45:58.609 -> low
15:45:59.239 -> high
15:45:59.587 -> high
15:45:59.872 -> low
15:46:00.370 -> high
15:46:00.515 -> low
15:46:00.923 -> low
15:46:01.410 -> low
15:46:01.689 -> low
15:46:01.934 -> low
15:46:02.070 -> low
15:46:02.529 -> low
15:46:03.494 -> high
15:46:03.667 -> low
15:46:04.672 -> low
15:46:05.748 -> low
15:46:06.754 -> high
15:46:07.059 -> low
15:46:08.556 -> high
15:46:09.006 -> low
15:46:10.754 -> low
15:46:12.282 -> low
15:46:13.324 -> low
15:46:14.093 -> low
15:46:15.238 -> low
15:46:16.095 -> low
15:46:17.984 -> low
15:46:18.536 -> low
15:46:19.048 -> low
15:47:04.624 -> high
15:47:05.042 -> low
15:47:07.004 -> high
15:47:07.364 -> low
15:47:08.643 -> low
15:47:09.718 -> low

If I cross the IR barrier quickly with my finger, it seems like by the time the digitalRead () function is called inside the ISR, the pin state has already gone back to LOW. If I cross the barrier slower, this issue does not happen.

Hardware pieces:
Arduino Mega, IR LED (T-1 3/4), IR Receiver (TSOPS38238)

It appears you have a constant source transmitter. Are you driving it in a modulated fashion with code, or just turning it on?

You are using a modulated burst mode receiver. According to the data sheet, the receiver will suppress a continuous signal.

I think you would do better with an IR photo transistor matched to your sender.

Can you describe more about the IR transmitter/receiver geometry and the movement of the rat you are trying to detect?