Switch Counts (debouncing?)

I wanted a simple program to just count door open/close events. I installed a switch on the door that is closed when the door is closed (it might be vice-verse as I did not measure the switch).

This seemed to work for the first day, but the second day, count would keep counting every 300 ms the when door was open.

The project is not important, but I want to understand what could be happening and why would the micro behave differently when running the same program and hardware?

Here are the code pieces I use:

#define BUTTON 3    //interrupt 1
volatile int count = 0;
long debouncing_time = 300000; //debouncing time in microseconds (300 ms)
volatile unsigned long last_micros = 0;


setup:

pinMode(BUTTON, INPUT_PULLUP);
attachInterrupt(1, debounce, CHANGE);


void debounce() {
  if((long)(micros() - last_micros) >= debouncing_time) {
    last_micros = micros();
    count = count + 1;
  }
}

//loop prints count

The switch is wired with speaker wire to pin 3 and ground, so the only thing I can think of is there is a lot of EMI being picked up by the wire or that the switch is not fully closing or opening, and the voltage is hovering around the high/low threshold.

Draw us a schematic of how the switch is wired to the Arduino.
Why are you using an interrupt?

That code doesn’t compile.

  if((long)(micros() - last_micros) >= debouncing_time) {

Why are you casting this to a signed long?

It's missing some curlies and a void loop(), for a start.

  if((long)(micros() - last_micros) >= debouncing_time) {

Why are you casting this to a signed long?

And why is he using micros() instead of millis()?
And where does he reset last_micros?

Draw us a schematic of how the switch is wired to the Arduino.

Simple enough to visualize, a 10 foot long piece of speaker wire with a momentary switch on the end that the door presses closed. The other ends of the speaker wire connected direct to Arduino pin 3 and ground.

Why are you using an interrupt?

There is no specific need for an interrup, it just seemed an easy way to slap something together (I already had the ISR code in the LCD demo program that I started with, all I had to do was add a line to print a count to the LCD).

Why are you casting this to a signed long?
And why is he using micros() instead of millis()?

I found the ISR debounce function online, that is the way it was. Perhaps it was designed that way due to limitation in earlier Arduino IDEs? I recall reading several times that millis() does not function in an ISR, or something bad happens to the timer? I would prefer to use millis(), but I do not readily see a real problem with using micros(). I also recall some issues about math operations on unlike variable types, so perhaps the cast is to match the variable type of last_micros? I don't know if it matters or breaks something to cast the micros() value as a long...maybe it truncates or affects rollover?

But, would those issues be the cause of what I'm observing?

It's missing some curlies and a void loop(), for a start.
And where does he reset last_micros?

I reset last_micros right after:
if((long)(micros() - last_micros) >= debouncing_time) {
last_micros = micros();
}
I don't think I'm missing curlies, and obviously I did not post compilable code. I just wanted to post the relevant code so anyone willing to help would have a better basis for what was happening.

I discovered that if I disconnect the wires from Arduino pins, the counting stops. A 5 inch long piece of wire, or a touch of pin 3, causes the counter to start counting. It seems that that is a product of EMI. I do not understand why that would be the case since I am using an internal pullup, so in open circuit, the voltage should be held 5V at the pin until there is enough of a load to pull the voltage down. How is the voltage getting low enough with just a short piece of wire dangling from the pin?

Note that I've been using this ISR and a regular pushbutton in breadboard circuits on several projects and it has always worked fine, but perhaps that's just a lucky set of circumstances.

While door closed, do nothing.
delay (50); To take care of any bounce
Door must be open now, so increment count once and output to display.
While door open, do nothing //wait for door to be closed
//somebody might be holding it open.
delay (50); To take care of any bounce
Door must now be closed, loop.

No need for micros() or interupts.

I hope you're not expecting to count people with this method, only the number of times the door is opened. The door could be opened once and the whole of the population of China could walk through before the door is again closed. :slight_smile:

a 10 foot long piece of speaker wire with a momentary switch

This is going to pick up noise, make sure you have some kind of SWC (capacitor, transorb, MOV) on the i/p pin.

Henry, thanks for the alternative approach.

Eventually, this project will turn into a home alarm system, and I may not even use door switches...I still just want to understand what is happening with the current setup, just for reasons of understanding and hopefully an ability to apply that understanding to any other schemes I may consider.

Larry, EMI was my first thought, but I also wonder if something else is going on as the counter starts incrementing even with a few inch piece of wire or a finger touch of the pin. I was expecting pinMode(BUTTON, INPUT_PULLUP); to at least stabilize the open circuit above the threshold voltage. Why would that not be happening? Why is the interrupt continuing to fire, just with a few inches of wire picking up EMF?

Would you attach all your code as of now?

I'll be able to post the entire code after about 4 hours.

Code below includes time display that I was working on at the time.

//Sample using LiquidCrystal library
#include <LiquidCrystal.h>


/*
 * TimeRTC.pde
 * example code illustrating Time library with Real Time Clock.
 * 
 */

#include <Time.h>  
#include <Wire.h>  
#include <DS1307RTC.h>  // a basic DS1307 library that returns time as a time_t

int set = 0;

long debouncing_time = 300000; //debouncing time in microseconds
volatile unsigned long last_micros = 0;
volatile int door = 0;
boolean backlight = 1;


/*******************************************************
 * 
 * This program will test the LCD panel and the buttons
 * Mark Bramwell, July 2010
 * 
 ********************************************************/

// select the pins used on the LCD panel
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
int backLight = 10; // pin 13 will control the backlight

// define some values used by the panel and buttons
int lcd_key     = 0;
int adc_key_in  = 0;
#define btnRIGHT  0
#define btnUP     1
#define btnDOWN   2
#define btnLEFT   3
#define btnSELECT 4
#define btnNONE   5
#define swPin     15

// read the buttons
int read_LCD_buttons()
{
  adc_key_in = analogRead(0);      // read the value from the sensor 
  // my buttons when read are centered at these valies: 0, 144, 329, 504, 741
  // we add approx 50 to those values and check to see if we are close
  if (adc_key_in > 1000) return btnNONE; // We make this the 1st option for speed reasons since it will be the most likely result
  if (adc_key_in < 50)   return btnRIGHT;  
  if (adc_key_in < 195)  return btnUP; 
  if (adc_key_in < 380)  return btnDOWN; 
  if (adc_key_in < 555)  return btnLEFT; 
  if (adc_key_in < 790)  return btnSELECT;   
  return btnNONE;  // when all others fail, return this...
}

void setup() {

  pinMode(backLight, OUTPUT);
  digitalWrite(backLight, LOW); // turn backlight on. Replace 'HIGH' with 'LOW' to turn it off.

  lcd.begin(16, 2);              // start the library
  lcd.setCursor(0,0);
  lcd.print("      Door Count");   

  pinMode(swPin, INPUT_PULLUP);
  attachInterrupt(1, debounce, RISING);

}

void debounce() {
  if((long)(micros() - last_micros) >= debouncing_time) {
    last_micros = micros();
    door = door + 1;
  }
}


void loop()
{
  lcd.setCursor(2,1);            // move cursor to second line "1" and 9 spaces over
  digitalClockDisplay();
  lcd.setCursor(0,0);            // move to 
  lcd.print(door);
  lcd.setCursor(0,1);            // move to the begining of the second line
  lcd_key = read_LCD_buttons();  // read the buttons
  switch (lcd_key)               // depending on which button was pushed, we perform an action
  {
  case btnRIGHT:
    {
      lcd.print("R");
      set = set + 10;
      break;
    }
  case btnLEFT:
    {
      lcd.print("L");
      set = set - 10;
      break;
    }
  case btnUP:
    {
      lcd.print("U");
      set = set + 1;
      break;
    }
  case btnDOWN:
    {
      lcd.print("D");
      set = set - 1;
      backlight = 0;
      break;
    }
  case btnSELECT:
    {
      lcd.print("S");
      backlight = 1;
      break;
    }
  case btnNONE:
    {
      lcd.print(" ");
      break;
    }
  }

  if (backlight) {
    digitalWrite(backLight, HIGH);
  }
  else {
    digitalWrite(backLight, LOW);
  }
}

void digitalClockDisplay() {
  // digital clock display of the time
  //  time_t t=now();
  lcd.print(hour());
  printDigits(minute());
  printDigits(second());
  lcd.print(" ");
  lcd.print(month());
  lcd.print("-");
  lcd.print(day());
  lcd.print(" ");
  lcd.print(year()); 
}

void printDigits(int digits){
  // utility function for digital clock display: prints preceding colon and leading 0
  lcd.print(":");
  if(digits < 10)
    lcd.print('0');
  lcd.print(digits);
}

What Arduino board are you using?

Edit:
I would later use an Opto Isolator to drive the Arduino I/P.

I’m using a Duemilanove.

It works on my UNO, not sure of the differences with the Duemilanove
FYI

I changed:
long debouncing_time = 300000; //debouncing time in microseconds
TO
const unsigned long debouncing_time = 300000;
AND
Switch is on pin D3 attachInterrupt(1, debounce, RISING); Interrupt 1 is on DIGITAL PIN 3!
EDIT:
AND
#define swPin 15
TO
#define swPin 3

I had to struggle with that last comment. I knew interrupt 1 was on pin 3, but I cannot understand how swPin got set to 15!

I really do not understand what all of those other defines are for, either (they came in the LCD shield example).

So now I have:
#define btnLEFT 3
#define swPin 3

I guess because I left the pin floating that anything would be expected. I wonder what grounding that pin directly could have done...

Thanks for pointing out the flaw in my program.

C2:
Code below includes time display that I was working on at the time.

volatile unsigned long last_micros = 0;

void debounce() {
  if((long)(micros() - last_micros) >= debouncing_time) {
  //When it gets here the first time, last_micros will be zero
  //so the if statement will be true (unless micros() has just rolled over),
  //even if debouncing_time hasn't been reached. To solve it,
  //set last_micros = micros(); in setup().
    last_micros = micros();
    door = door + 1;
  }

Good it's working.

What do you think this is doing #define btnLEFT 3?

I think it is just giving a numerical value to btnLEFT, so I could use the word or nv (i.e., I could use case 3: rather than case btnLEFT:).

But why do that?

Because something to do with the int read_LCD_buttons() function, which will return a word value disguised as a nv (or vice-verse?). Well, I guess the function has to return an int.

Could we not skip the nv assignments and define the function to return a string instead, or is it just that's easier to work with numbers?

I think it is just giving a numerical value to btnLEFT

Yes, every where there is a btnLEFT the compiler replaces it with 3.
Hence, we can easily change the value without having to manually go through the code line by line.

Henry_Best:

C2:
Code below includes time display that I was working on at the time.

volatile unsigned long last_micros = 0;

void debounce() {
  if((long)(micros() - last_micros) >= debouncing_time) {
  //When it gets here the first time, last_micros will be zero
  //so the if statement will be true (unless micros() has just rolled over),
  //even if debouncing_time hasn't been reached. To solve it,
  //set last_micros = micros(); in setup().
    last_micros = micros();
    door = door + 1;
  }

Henry, I'm not seeing the problem or change in behaviour by setting last_micros to micros() at setup rather than =0, unless it could cause the ISR to fire more than once the first time. In either case, last_micros will always be less than micros() and thus fire the first time pressed. Is that not what would be desired?

I do have a similar issue though with the encoder in another project. Even though I use global variables and read/set the last_encoder value in setup, it always triggers an encoder event during boot so something happens. I have to initially set last_encoder to encoder.read minus one.