Part 3: Debouncing digital inputs.

See also: Part 1, Part 1b, Part 2, and Part 3.

It seems I completely forgot the story I started telling awhile ago. If you did, too, you can go back and read about how I got started with Arduino, along with a few more notes, and finally, the one where I started dissecting my first sketch which read inputs from a haunted house pressure mat and turned them in to serial signals some software could read.

Back in February, I was discussing a problem I noticed with my first Arduino project. When switches would connect, and generate the serial strings for on/off like I wanted, I would often see the data stutter, toggling back and forth quickly before resting at on or off. I recognized this problem from learning about how the TRS-80 Color Computer’s keyboard matrix worked, and I knew the solution would be to add some debounce code to my sketch.

There actually is a Debounce library available to the Arduino, but I chose to write my own since it was a good opportunity to learn. Plus, the Bounce library seemed to provide much more capability than I needed. You can find this library here:

http://playground.arduino.cc//Code/Bounce

It seems very easy to use, but instead, I chose to apply some of the information I read on another section of the Arduino site. My debounce code would look like this:

/* For I/O pin status and debounce. */
unsigned int pinStatus[DI_PIN_COUNT];    // Last set PIN mode.
unsigned long pinDebounce[DI_PIN_COUNT]; // Debounce time.
unsigned int debounceRate = DEBOUNCE_MS; // Debounce rate.

I would create arrays to hold the last known pin status, and a time value for that pin. The time value would be used to mark debounce time. The debounce rate variable would be used for how long the debounce should be.

First, notice I was using “unsigned int” for the pin status. On the Arduino, and int is a 16-bit value, and takes up 2-bytes of RAM. The pin count could easily be represented with byte variable instead, saving a byte for each instance. It’s not a big deal wasting a few bytes for a small sketch, but for big projects, every last byte can matter. SO, don’t code like that. Use a byte. Or if you are really in a crunch, remember that a byte is made up of eight bits, so one byte could actually be used to track the status of eight different pins. So, my original version would take 16 bytes of RAM to track eight pins, but I could have done that using only one byte. That’s quite a savings. I can discuss using bits in a future article.

Okay, so those were my variables. The actual code that did all the work looks like this. First, I defined the pins I would be using for the project:

/* TX/RX pins are needed to USB/serial. */
#define DI_PIN_START  2
#define DI_PIN_END    12
#define DI_PIN_COUNT  (DI_PIN_END-DI_PIN_START+1)

The different Arduinos and Teensys and such have different I/O pin numbers. I decided I would just use a range of pins, so I specify the starting pin and ending pin. In this example, I skipped pin 0 and 1 since those are used by the serail port. Pin 13 is connected to an LED I wanted to blink (letting me know the board was alive and processing), so I could use pins 2-12 and get eleven pins to wire up to the pressure mats in the haunted house.

Inside setup(), I did a loop through these pins to put them in INPUT mode (using the pull-up resistor so I didn’t need extra wiring), and to initialize my arrays. You will notice I have something commented out. Originally, I read the current state of all the pins so I could mark them as changing later. So, if a pressure mat was “on” when it started, it would do nothing until the mat was released. This sounded like a good idea, but it meant there would be no way for the software to know things needed to go “now” on power up, so instead I set the status to HIGH (meaning off), thus if a mat were depressed when the program started, it would immediately see that as a change to LOW and send out the serial data.

  // Initialize the pins and pinStatus array.
  for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ )
  {
    // Set pin to be digital input using pullup resistor.
    pinMode(thisPin+DI_PIN_START, INPUT_PULLUP);
    // Read and save the current pin status.
    pinStatus[thisPin] = HIGH; //digitalRead(thisPin+DI_PIN_START);
    // Clear debounce time.
    pinDebounce[thisPin] = 0;
  }

Inside the main loop(), I would once again loop through all the pins and check their status:

  // Loop through each Digital Input pin.
  for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ )
  {
    // Read the pin's current status.
    int status = digitalRead(thisPin+DI_PIN_START);

Next, I would compare the current status to the one stored in the array and see if it had changed. No need to do anything for a pin that reads HIGH if it previously read HIGH. We only care about changes in status, so someone standing on a pressure mat wouldn’t trigger it dozens of times before they stepped off.

    // In pin status has changed from our last toggle...
    if (status != pinStatus[thisPin])
    {

And now it gets fun. The pinDebounce[] array is used to hold a time value, or 0 if there is nothing going on with that pin. If it has a number in it, that means we are in the process of counting down a debounce value. If it is 0, we need to start a debounce counter. The way we do that is by looking at the current time using millis(), and adding the debounce time to it, and storing that. So if millis() is 1200 when a pressure mat is triggered, and we are using a debounce rate of 1 second (1000 millis), we would store 1200+1000=2200 in that variable:

      // Remember when it changed, starting debounce mode.
      // If not currently in debounce mode,
      if (pinDebounce[thisPin]==0)
      {
        // Set when we can accept this as valid (debounce is considered
        // done if the time gets to this point with the status still the same).
        pinDebounce[thisPin] = millis()+debounceRate;
      }

Next, we check the pin to see if it’s non-zero. If it is, it indicates this pin is in debounce mode, and we need to see if the current time in millis() is greater than the pin’s pinDebounce[] time that was set earlier. If it is, we have seen the status hold long enough to believe it is a real trigger, and emit either “on” or “off” messages depending on which way the pin just toggled:

      // Check to see if we are in debounce detect mode.
      if (pinDebounce[thisPin]>0)
      {
        // Yes we are. Have we delayed long enough yet?
        if ( (long)(millis()-pinDebounce[thisPin]) >= 0 )
        {
            // Yes, so consider it switched.
            // If pin is Active LOW,
            if (status==LOW)
            {
              // Emit UPPERCASE "On" character.
              Serial.println(char(65+thisPin));
            } else {
              // Emit lowercase "Off" character.
              Serial.println(char(97+thisPin));
              if (pinsOn>0) pinsOn--;
              if (pinsOn==0) digitalWrite(LED_PIN, LOW);
            }

Then we remember this new status, and reset the debounce counter for that pin back to zero.

            // Remember current (last set) status for this pin.
            pinStatus[thisPin] = status;
            // Reset debounce time (disable, not looking any more).
            pinDebounce[thisPin] = 0;
        } // End of if ( (long)(millis()-pinDebounce[thisPin]) >= 0 )

      } // End of if (pinDebounce[thisPin]>0)
    }
    else // No change? Flag no change.
    {
      // If we were debouncing, we are no longer debouncing.
      pinDebounce[thisPin] = 0;
    }
  } // End of for()

At the very end, we have a condition that is met only if the pin’s status has not changed since the last time.

Does this look correct to you? If the pin hasn’t changed, I am resetting the debounce back to 0. But what if the pin were in a debounce mode? Wouldn’t we want to keep counting? It looked weird to me as I put the code here, but it is correct. If the pin has not changed, we are not interested in debouncing. If the pin is HIGH, it just resets to zero over and over (useless), but if it changes from HIGH to LOW, we enter the code and start the debounce. Note that the pinStatus[] variable has not been updated yet, so it remains HIGH.

The next loop through, the status of the pin is read again. If it is still LOW, we enter the code again because it is still different than the last pinStatus[], which is still HIGH. We do more debounce checks. And this continues.

The reason the reset to 0 is correct is because of this. We are not comparing the status to the last time we read, but to the last time we considered it switched after a debounce. As we continue the example, at some point, the pin has held its status long enough to be considered debounced, and we toggle it and update pinStatus[] to now be LOW, and the debounce counter is reset.

Clear as mud? Because we have not really change the status, we keep setting it to 0. But as long as the current pin status is different than the previously saved pinStatus[], we will keep entering that loop and checking, never getting to the reset 0. In fact, the only time we get to the reset 0 if current pinStatus is the same as the last pinStatus[]. So, a switch not switching will just reset 0 all the time, but once the switch has changed, it will fall in to the code to check debounce and never touch the reset 0 code. This is what resets the debounce timer if the switch goes from ON to OFF (which should trigger, but not yet), then pops back to ON quickly… resetting the counter… Then if it pops to OFF again, it starts the counter fresh.

I hope that makes sense.

Here’s the full script. Remember, it was the first thing I ever wrote for the Arduino, and I have done more to it since then. You will also see I have a part that checks the serial input for “?” to be typed, then prints out the current pin status. A nice thing for debugging.

Hope this helps… The next version will add Analog inputs (for reading laser tag light sensors), and then this code morphs in to an Atari joystick converter.

Until then…

/*-----------------------------------------------------------------------------

Arduino Digital Input

Monitor digital inputs, then emit a serial character depending on the pin
status. The character will be uppercase for pin connected (N.O. button push)
and lowercase for pin disconnected (N.O. button released). It will begin with
"A" for the first I/O pin, "B" for the next, and so on. Currently, with pins
0 and 1 used for serial TX/RF, this leaves pins 2-12 available (10), with pin
13 reserved for blinking the onboard LED as a heartbeat "we are alive"
indicator.

This software was written to allow an Arduino act as a cheap input/trigger
interface to software such as VenueMagic. As I do not own a copy of this
software, I could only test it under the 15 day trial. There may be other
issues...

2012-10-09 0.0 allenh - Initial version.
2012-10-11 0.1 allenh - Updated debounce to work with timing rollover, via:
                        http://www.arduino.cc/playground/Code/TimingRollover
                        Fixed bug where last DI pin was not being used.

-----------------------------------------------------------------------------*/
//#include
#include

/* TX/RX pins are needed to USB/serial. */
#define DI_PIN_START  2
#define DI_PIN_END    12
#define DI_PIN_COUNT  (DI_PIN_END-DI_PIN_START+1)

#define LED_PIN 13
#define LEDBLINK_MS 1000
#define DEBOUNCE_MS 100 // 100ms (1/10th second)

/*---------------------------------------------------------------------------*/
/*
 * Some sanity checks to make sure the #defines are reasonable.
 */
#if (DI_PIN_END >= LED_PIN)
#error PIN CONFLICT: PIN END goes past LED pin.
#endif

#if (DI_PIN_START < 2)
#error PIN CONFLICT: PIN START covers 0-TX and 1-RX pins.
#endif

#if (DI_PIN_START > DI_PIN_END)
#error PIN CONFLICT: PIN START and END should be a range.
#endif
/*---------------------------------------------------------------------------*/

/* For I/O pin status and debounce. */
unsigned int  pinStatus[DI_PIN_COUNT];      // Last set PIN mode.
unsigned long pinDebounce[DI_PIN_COUNT];    // Debounce time.
unsigned int  debounceRate = DEBOUNCE_MS;   // Debounce rate.
unsigned long pinCounter[DI_PIN_COUNT];

/* For the blinking LED (heartbeat). */
unsigned int  ledStatus = LOW;             // Last set LED mode.
unsigned long ledBlinkTime = 0;            // LED blink time.
unsigned int  ledBlinkRate = LEDBLINK_MS;  // LED blink rate.

unsigned int pinsOn = 0;

/*---------------------------------------------------------------------------*/

void setup()
{
  // Just in case it was left on...
  wdt_disable();
  // Initialize watchdog timer for 2 seconds.
  wdt_enable(WDTO_4S);

  // Initialize the pins and pinStatus array.
  for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ )
  {
    // Set pin to be digital input using pullup resistor.
    pinMode(thisPin+DI_PIN_START, INPUT_PULLUP);
    // Read and save the current pin status.
    pinStatus[thisPin] = HIGH; //digitalRead(thisPin+DI_PIN_START);
    // Clear debounce time.
    pinDebounce[thisPin] = 0;

    pinCounter[thisPin] = 0;
  }

  // Set pin 13 to output, since it has an LED we can use.
  pinMode(LED_PIN, OUTPUT);

  // Initialize the serial port.
  Serial.begin(9600);

  // Docs say this isn't necessary for Uno.
  while(!Serial) {
    ;
  }

  // Emit some startup stuff to the serial port.
  Serial.println("ArduinoDI by Allen C. Huffman (alsplace@pobox.com)");
  Serial.print("Configured for: ");
  Serial.print(debounceRate);
  Serial.print("ms Debounce, ");
  Serial.print(DI_PIN_COUNT);
  Serial.print(" DI Pins (");
  Serial.print(DI_PIN_START);
  Serial.print("-");
  Serial.print(DI_PIN_END);
  Serial.println(").");
  Serial.println("(Nathaniel is a jerk.)");
}

/*---------------------------------------------------------------------------*/

void loop()
{
  // Tell the watchdog timer we are still alive.
  wdt_reset();

  // LED blinking heartbeat. Yes, we are alive.
  if ( (long)(millis()-ledBlinkTime) >= 0 )
  {
    // Toggle LED.
    if (ledStatus==LOW)  // If LED is LOW...
    {
      ledStatus = HIGH;  // ...make it HIGH.
    } else {
      ledStatus = LOW;   // ...else, make it LOW.
    }
    // Set LED pin status.
    if (pinsOn==0) digitalWrite(LED_PIN, ledStatus);
    // Reset "next time to toggle" time.
    ledBlinkTime = millis()+ledBlinkRate;
  }

  // Check for serial data.
  if (Serial.available() > 0) {
    // If data ready, read a byte.
    int incomingByte = Serial.read();
    // Parse the byte we read.
    switch(incomingByte)
    {
      case '?':
        showStatus();
        break;
      default:
        break;
    }
  }

  // Loop through each Digital Input pin.
  for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ )
  {
    // Read the pin's current status.
    int status = digitalRead(thisPin+DI_PIN_START);

    // In pin status has changed from our last toggle...
    if (status != pinStatus[thisPin])
    {
      // Remember when it changed, starting debounce mode.
      // If not currently in debounce mode,
      if (pinDebounce[thisPin]==0)
      {
        // Set when we can accept this as valid (debounce is considered
        // done if the time gets to this point with the status still the same).
        pinDebounce[thisPin] = millis()+debounceRate;
      }

      // Check to see if we are in debounce detect mode.
      if (pinDebounce[thisPin]>0)
      {
        // Yes we are. Have we delayed long enough yet?
        if ( (long)(millis()-pinDebounce[thisPin]) >= 0 )
        {
            // Yes, so consider it switched.
            // If pin is Active LOW,
            if (status==LOW)
            {
              // Emit UPPERCASE "On" character.
              Serial.println(char(65+thisPin));
              pinCounter[thisPin]++;
              pinsOn++;
              digitalWrite(LED_PIN, HIGH);
            } else {
              // Emit lowercase "Off" character.
              Serial.println(char(97+thisPin));
              if (pinsOn>0) pinsOn--;
              if (pinsOn==0) digitalWrite(LED_PIN, LOW);
            }
            // Remember current (last set) status for this pin.
            pinStatus[thisPin] = status;
            // Reset debounce time (disable, not looking any more).
            pinDebounce[thisPin] = 0;
        } // End of if ( (long)(millis()-pinDebounce[thisPin]) >= 0 )

      } // End of if (pinDebounce[thisPin]>0)
    }
    else // No change? Flag no change. if (status != pinStatus[thisPin])
    {
      // If we were debouncing, we are no longer debouncing.
      pinDebounce[thisPin] = 0;
    }
  } // End of for()
}

void showStatus()
{
  int status = 0;

  Serial.print("DI: ");

  for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ )
  {
    // Read the pin's current status.
    int status = digitalRead(thisPin+DI_PIN_START);
    Serial.print(thisPin+DI_PIN_START);
    Serial.print("=");
    Serial.print(digitalRead(thisPin+DI_PIN_START));
    Serial.print(" ");
  }
  Serial.println("");

  for (int thisPin=0; thisPin < DI_PIN_COUNT; thisPin++ )
  {
    Serial.print(thisPin+DI_PIN_START);
    Serial.print(":");
    Serial.print(pinCounter[thisPin]);
    Serial.print(" ");
  }
  Serial.println("");

  //Serial.print("millis() = ");
  //Serial.println(millis());
}
/*---------------------------------------------------------------------------*/
// End of file.

P.S. – Don’t mind my rude comment to Nathaniel in the status display. He’s the effects designer out at the Sleepy Hollow Haunted Scream Park I was doing this project for, and I only kid him because he’s such a great guy. He’s really not a jerk. Honest.

2 thoughts on “Part 3: Debouncing digital inputs.

  1. Pingback: Part 2: From pressure mats to serial output. | Sub-Etha Software

  2. Pingback: Part 1: How I got started with Arduino. | Sub-Etha Software

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.