Debouncing Buttons in MicroPython

This post may contain affiliate links. Please read my disclaimer for more info.

Ever introduce a pushbutton into your electronics project but have trouble getting a stable reading? Have you read about “debouncing” a button and want to learn how to easily accomplish this in software? This article goes over the background of switch and button debouncing and walks through my MicroPython code to accomplish this.


So what even is debouncing? Whenever you press a button or flip a switch the signal takes a little bit of time to stabilize. Take for example a simple pushbutton that is using a pull-up resistor to 3.3V. When not pressing the button the power source pulls up the microcontroller input pin to 3.3V. MicroPython reads this value as a digital 1 indicating that the button is not being pressed. When the button is pressed down the input to the microcontroller is shorted to ground so that a digital 0 can be read.

Note that lots of devices have internal pull-up resistors for use, rather than adding an external resistor to the board.

So while this is a pretty basic circuit, the debouncing issue occurs when reading the voltage at the MicroPython input continuously, you begin to see glitches in the signal. To show this, I did a simple breadboard with a pushbutton and power supply (FYI, this awesome SparkFun kit makes it really easy to add 3.3V/5V to a breadboard from a USB cable).

Now to demonstrate the glitches that occur when using a button, I used my logic analyzer (consider getting a USB logic analyzer if you don’t have one) to capture the voltage at the switch that could be fed into a microcontroller. You can see in the screenshot below that channel 0 goes low twice before stabilizing at 0V. The whole process only takes around 20 microseconds but it’s enough to cause debounce issues. When the microcontroller samples the voltage you may get an “ON, OFF, ON, OFF” pattern within that 20 microsecond period. The screenshot below captures just one demonstration of debouncing, there are lots of factors that go into a debounce period including the mechanical properties of the button and how the user presses it.

Software Debouncing

So we’ve identified the problem but how can we solve it? I’ve developed a MicroPython class that is fairly simple but reliably debounces switches and buttons. The general algorithm works like this:

  1. Wait until a switch transitions from high to low
  2. After the switch changes, start a timer to go off in 100 milliseconds
  3. When the timer expires, check that the switch is still low
  4. If the switch is still low, start the timer again to check the value in another 100 milliseconds
  5. Once 3 consecutive reads 100 milliseconds apart show the same value, the debounce period completes

Below is the class in its entirety, as you can see it’s not a ton of code, but it’s very effective for debouncing.

  • The class starts off by initializing some member variables to keep the internal state of the switch. The user must initialize a MicroPython Pin object before creating an object using the class. The user can also configure how many consecutive checks are required and their period, the defaults being 3 checks 100 milliseconds apart.
  • The pin is initialized to trigger the _switch_change method whenever the value of the switch rises or falls.
  • Once _switch_change triggers the start of debouncing begins. The device samples the current value and starts the debounce timer. Also the _switch_change callback is disabled because it is only needed to start the debouncing algorithm.
  • When the timer expires the _check_debounce callback fires
    • If the value is the same as the previous value, the debounce_checks variable is incremented, keeping track of how many consecutive checks have had the correct value
    • If the value is different, thendebounce_checks is reset
  • Once debounce_checks equals the number of checks to perform, the new_value_available variable is set to True indicating to the application that the switch has a new value to work with.

Example Usage

Now that you have some background on the class itself, let’s look at how to use it in a real application. The class provides us with two important variables:

  • new_value_available a boolean value to indicate if the switch has changed values
  • value: the value of the switch

Because the class could be updating these variables at any time, including the timer callback functions, we need to make sure that we don’t modify these variables when the switch class might be modifying them at the same time! To do this, we can disable interrupts for a short time to read and copy the switch’s value into a local variable.

  • The code begins by just creating a Pin object and then initializing the Switch object
  • The main loop disables interrupts, so we can safely read and modify the objects member variables. If there is a new value available, we copy the value from the class into the application’s local variable
  • Finally, we act on the new value of the switch, printing the appropriate message

This software demonstrates a basic debouncing technique that is still extremely effective. Adding this class adds minimal code size and processing overhead and makes it easy to debounce correctly. I’m using this technique in various electronics projects. Check out my GitHub Repo where I’ve shared the code along with the example.

Cross Compile the Code

While this class isn’t a ton of code, MicroPython could run out of memory while trying to run the module. Fortunately, there is a way to cross compile our code from Python source to bytecode. The bytecode will execute the exact same way as the original Python source but takes up less memory and can actually execute faster.

One thing to remember whenever doing a cross compile, the version of the MicroPython cross compiler must be the same as the version of MicroPython you’ll be running on the device. After cross compiling you end up with a .mpy file that is the bytecode version of the original source file. This bytecode syntax can change between MicroPython versions so it’s important that the two are in sync. My GitHub Repo currently contains a checked in bytecode version for MicroPython, check the to see what version it is cross-compiled for.

To cross compile your own version you first need to clone the MicroPython repository.

After cloning you can checkout the version of the repository that matches the MicroPython firmware version you’re running. In this example I’ll checkout version 1.9.4.

Next, you can compile the cross compiler by running

This essentially runs the Makefile in the mpy-cross directory. If everything works you should have a new executable in the mpy-cross directory called mpy-cross. You can use that to cross compile by calling it directly and giving it the path to the file you want to cross compile.

This creates the .mpy file you can upload to your device just like any other MicroPython .py file.

Next Steps

Some things I’d like to add in the future to improve the class:

  • Add ability to pass in a callback function to the class to make switch changes easier
  • Create more examples for using the class
  • Profile a variety of switches and buttons to determine better debounce periods