Recognizing and Coping with Contact Bounce on the Raspberry Pi
You may need to cope with contact bounce for your next Raspberry Pi project. Reading a switch is quite simple. It returns either a 0 or a 1, depending on whether the switch is pressed. Switches should always be connected between the general-purpose input/output (GPIO) input pin and ground and either enable the internal pull-up resistor or fix an external resistor to them. When a switch is closed, it makes a path to ground. You read a logic low or zero when this path is made.
You may see other arrangements where a switch is connected between the GPIO input pin and 3V3, but this is not recommended because switches are normally on long wires. Routing long wires connected to ground is less risky than connecting to a power voltage. This is how you wire a switch.
Although a switch to ground with a pull-up resistor to a high voltage can read only 1 or 0, most switches are a little more complex. For example, take a 1-pound bag of sugar and drop it from about 6 inches onto a hard surface like a table. It hits the surface with a thud and stops. Now try the same thing with a Ping-Pong ball, and you’ll see that it bounces. The first bounce of the Ping-Pong ball isn’t as high as the height you dropped it from, but it’s quite high. Then the ball falls and bounces again, only slightly lower. This continues with smaller and smaller bounce heights until the ball comes to rest. Notice that the lower the bounce, the shorter the time between bounces; the bounces speed up until the ball comes to rest.
Switch contacts are like that — they’re rigid surfaces coming together quickly, and they bounce. So much so that if you examine the logic levels the switch gives out, you’ll see not a simple transition from a high to a low but a succession of transitions getting closer and closer together until a steady level is reached. This is known as a dirty edge or contact bounce.
This rapid changing from 0 to 1 can be a problem if the computer is looking at the switch very rapidly. In fact, this happens much more rapidly than a user can press a button, but sometimes circumstances or code will let this happen. To illustrate the problem, look at the following code, hook up a switch between GPIO 2 (pin 3) and ground (pin 6). This pin has a pull-up resistor fitted on the board.
# Bounce Problem shows contact bounce # Author: Mike Cook import time import wiringpi2 as io io.wiringPiSetupGpio() io.pinMode(2,0) # to input lastPress = io.digitalRead(2) count = 0 pressTime = time.time() print"testing push button" while True: press = io.digitalRead(2) if press == 0 and lastPress != press: count += 1 pressTime = time.time() if count >= 5: print"five presses" count = 0 lastPress = press
The technique used here is to count five switch presses and then print out when the computer has counted them all. If you try this, you may find that it prints out that it has detected five presses after four or even three presses, as well as every five. In short, it is unreliable because contact bounce is causing erroneous counts.
To investigate this deeper, you need to measure the interval between presses. This is done with the following code.
# Bounce Measure # measures contact time interval # Author: Mike Cook import time import wiringpi2 as io io.wiringPiSetupGpio() io.pinMode(2,0) # to input lastPress = io.digitalRead(2) count = 0 startTime = time.time() interval = [0.0,0.0,0.0,0.0,0.0] print"testing push button" while True: press = io.digitalRead(2) if press == 0 and lastPress != press: interval[count] = time.time() - startTime count +=1 startTime = time.time() if count >= 5: print"five presses ",interval count = 0 startTime = time.time() lastPress = press
The interval between each switch press is recorded in a list. This is derived from the system time clock and counts the number of seconds since the system booted up. By subtracting the system time from the time of the current press, you get the time since the last press. The result of running this program is shown here:
five presses [1.2872810363769531, 0.25738978385925293, 6.198883056640625e-05, 0.27149009704589844, 6.699562072753906e-05] five presses [2.247836112976074, 0.31453490257263184, 0.32202887535095215, 0.2885620594024658, 0.33057308197021484] five presses [1.048125982284546, 3.504753112792969e-05, 0.5636959075927734, 0.43523192405700684, 0.4095041751861572] five presses [14.676625967025757, 0.24735713005065918, 0.24397802352905273, 0.33951711654663086, 0.34607601165771484]
Notice that the code makes no attempt to reduce the number of significant bits in the readings, but have a close look at some of them, and you’ll see that some end with an e-05. That is the default way of saying 10 to the power of –5; this is known as the exponent format and is the default way floating point numbers are shown in most of computing. So, forgetting the ridiculous number of places after the decimal point, this number, 6.12e–05, is interpreted as 0.0000612 or 61.2 microseconds (uS), which is way faster than any human can press anything. This is an example of contact bounce. You don’t see every bounce because the computer only samples the logic level of the line at discrete intervals.
The way to prevent this from happening is to introduce a dead time — that is, to ignore the switch for a specific time after a press has occurred. On many occasions, this will happen naturally because you might have a print statement or some other code that takes up some time before the switch is looked at again. If not, a simple solution would be to add a delay just after the switch has been detected so that nothing is done during the potential bounce time.
However, for the ultimate debounce, you need to make a note of the time a switch transition is detected. Then when another transition is detected, only treat it as real if a specific time has elapsed between the last real transition and the present time. An example of this is shown in the following code.
# Bounce Solution # How to cope with bounce # Author: Mike Cook import time import wiringpi2 as io io.wiringPiSetupGpio() io.pinMode(2,0) # to input lastPress = io.digitalRead(2) count = 0 pressTime = time.time() print"testing push button" while True: press = io.digitalRead(2) if press == 0 and lastPress != press and (time.time() - pressTime) > 0.025: count += 1 pressTime = time.time() if count >= 5: print"five presses" count = 0 lastPress = press
Here the secret is the extra test in the if statement that checks that at least 25 milliseconds (mS) have elapsed before a transition is taken as real.
Different types of mechanical switches bounce to a different degree. Depending on exactly what your code is and how fast it runs, this may or may not be a problem. If you do experience “phantom” transitions on reading a mechanical switch, it’s likely to be contact bounce. If so, you can use one of the techniques described here to eliminate the effect.