1

Im trying to use the millis() function as a replacement for the delay() function in order to debounce a mechanical button. This is for an electric drum kit I am building. Im using a teensy Arduino to send MIDI signals from the drum pads to an external music program. The mechanical button needs to be responsible for cycling through five different states which change the MIDI signal sent from each pad, thus changing the sound, as well as lighting up one of five LEDs on my drum pad so I know which state i'm in. However I cant use delay() to debounce the button because that effects the efficiency of the drum pads, with some hits not being registered, latency etc.

Im trying to debug the code by just getting the LEDs to light up correctly when I press the button. This was the original code I wrote with the delay() function. This worked perfectly to debounce the button, but messed with my drum pads:

int but = 12;
int led0 = 7;
int led1 = 8;
int led2 = 9;
int led3 = 10;
int led4 = 11;

int ledCase = 0; 
int butState = 0; 
int oldButState = 0; 

void setup()   {                
pinMode(led0, OUTPUT);
pinMode(led1, OUTPUT);
pinMode(led2, OUTPUT);
pinMode(led3, OUTPUT);
pinMode(led4, OUTPUT);
pinMode(but, INPUT_PULLUP);
}

void loop()    { 

   oldButState = digitalRead(but);
   delay(10);
   butState = digitalRead(but); 

   if (butState != oldButState && butState == LOW) {   
     ledCase += 1;  
     oldButState = butState;
   }

   if (ledCase > 4) {
    ledCase = 0;
   }

   Serial.println(ledCase);


switch (ledCase) {
case 0:
digitalWrite(led0, HIGH);
digitalWrite(led1, LOW);
digitalWrite(led2, LOW);
digitalWrite(led3, LOW);
digitalWrite(led4, LOW);
break;
case 1:
digitalWrite(led0, LOW);
digitalWrite(led1, HIGH);
digitalWrite(led2, LOW);
digitalWrite(led3, LOW);
digitalWrite(led4, LOW);
break;
case 2:
digitalWrite(led0, LOW);
digitalWrite(led1, LOW);
digitalWrite(led2, HIGH);
digitalWrite(led3, LOW);
digitalWrite(led4, LOW);
break;
case 3:
digitalWrite(led0, LOW);
digitalWrite(led1, LOW);
digitalWrite(led2, LOW);
digitalWrite(led3, HIGH);
digitalWrite(led4, LOW);
break;
case 4:
digitalWrite(led0, LOW);
digitalWrite(led1, LOW);
digitalWrite(led2, LOW);
digitalWrite(led3, LOW);
digitalWrite(led4, HIGH);
break;
}
}

This is what I tried to use to replace the delay() function, but that hasn't worked. Note I added these lines in and removed the delay section:

unsigned long onTime;
unsigned long delayTime;

void setup() {
delayTime = millis() + 10; 
}

void loop()    { 
oldButState = digitalRead(but);
onTime = millis();
if (onTime >= delayTime) {
butState = digitalRead(but); 
delayTime = millis() + 10; 
}
}

Any pointers as to how I can make the millis() function work to debounce this would be would be very much appreciated, thank you :)

2 Answers 2

3

To better understand how you can do this it is good to understand first just what goes on when a button bounces.

It is, literally, bouncing - just like a ball when you drop it. Due to the fact that it's either connected to ground or pulled up to +5V by the pullup resistor it's either going to be LOW or HIGH with very little time between when it's not in either state, so we can pretty much ignore the rise and fall times of the signal. It's either connected to ground, or it's not connected to ground. And we're only really interested in when it's connected to ground.

More importantly we're interested in how long it's been connected to ground for.

By remembering the time it transitioned from HIGH to LOW and then comparing that time to the current time you can know how long it has been connected to ground. By noticing when it's no longer connected to ground and "forgetting" the time you remembered you can start counting afresh with the next transition from HIGH to LOW.

Take the following diagram for instance:

enter image description here

The blue line is the state of the button. As you can see it starts HIGH, then it gets pressed to be LOW and bounces between HIGH and LOW a number of times before it settles on being LOW.

The red line is the difference between a timestamp that is set to be equal to millis() at the moment the button transitions from HIGH to LOW and the current millis() value, and is ignored when the button is HIGH. As you can see the difference between that timestamp and the current millis() value steadily increases. Whenever the button bounces to the HIGH state it resets that difference, and it starts again with the next LOW transition.

When the button has finished bouncing there is nothing to change the state of the timestamp, so the difference keeps increasing. Eventually that difference will pass a trigger point (the yellow line in this diagram) and at that point you can be fairly sure that the button has finished bouncing and you should react to it.

To translate that into some code, you may end up with something like this (untested):

// Two variables to store remembered state information - the time
// of the last HIGH-LOW transition and the previous state of the
//  button.
static uint32_t debounceTime = 0;
static uint8_t buttonState = HIGH;

// Check to see if the button has changed state
if (digitalRead(buttonPin) != buttonState) {
    // Remember the new state
    buttonState = digitalRead(buttonPin);
    // If the new state is LOW ...
    if (buttonState == LOW) {
        // ... then record the time stamp
        debounceTime = millis();
    } else {
        // ... otherwise set the time stamp to 0 for "don't care".
        debounceTime = 0;
    }
}

// If the time stamp is more than 0 and the difference between now and
// then is greater than 10ms ...
if (debounceTime > 0 && (millis() - debounceTime > 10)) {
    // ... set the time stamp to 0 to say we have finished debouncing
    debounceTime = 0;
    // and do whatever it is you need to do.
    digitalWrite(ledPin, HIGH);
}

There are various tweaks and additions that could be made to that method, but that gives you the basic idea of how you split the debouncing away from the reacting, and it's the results of the debouncing that you react to, not the button state itself.

1
  • Hey sorry I never got back to this, your advice really helped! I got it working in the end. Commented May 2, 2016 at 3:06
2

Actually, Majenko's diagram assumes the interplay of the pullup resistor forces the line back to a HIGH position (as quickly as the internal transistors can overpower the drop from the switch). If the switch itself is mechanical with a pullup resistor (external or internal), then the only thing "bouncing" is the switch. If you use a digital switch, the rise is much sharper without significant bounce.

A cheap and effective debouncing tactic is to add a capacitor in parallel with the switch. The cap will drop to GND quickly as the switch drains the charge stored in the cap. But the cap will take a while to recharge since its only source is through the resistor (during which time the mechanicals settle down). (This looks much better in a diagram, but I haven't figured out how to draw on here yet.) I wish I could recommend values, but it really depends on the mechanicals of the switch and the certainty of the debounce desired--make everything HUGE and you're guaranteed no bounce, use values in between and you go on probabilities. I usually start out with 0.1uF and vary it up or down until I get a balance between reliability and speed.

The main advantage is, of course, you use absolutely no computing time to debounce any number of switches.

Potential disadvantages:

  • Added hardware cost for each switch, making this technique less desirable in production scenarios

  • 0/1 thresholds of input pins will vary across the manufacturing process, so one cap / input pin may have a different debounce time than another on an identical circuit.

  • Software debouncing schemes can note that the switch is pushed on the first drop to GND, where this hardware-only tactic relies on the last actual contact with GND. As the switch ages, I've seen this delay exceed 50 mSec.

  • Since the bounce is mechanical at its core, bounce worsens over the life of the mechanical part (in this case the switch). Typically, software can be fine-tuned easier than hardware. Often, however, swapping out a cap is easier than reflashing firmware.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.