2.2 Button Debounce
The goal of this example is to show you how to make button presses more reliable and consistent by using a technique called debouncing.
When we press a physical button, its contacts can bounce rapidly, causing the microcontroller to detect multiple presses instead of one.
In this example, we'll use software debouncing with the millis() function to ensure that one press equals one toggle of the LED.
In this documentation you will learn:
- How to read a button state with
digitalRead(). - What is button debouncing and how to handle it with
millis(). - How to toggle an LED on each button press.
Hardware required:
- 1x Soldered NULA MINI board
- 1x Breadboard
- 1x Push button
- 1x LED (any color)
- 1x 330 ohm resistor
- 5x Jumper wires
- 1x USB-C cable
Putting the components together
Follow the eight steps below. Each photo is taken from the same position, so you can compare it with the previous one and see exactly what changed.
1. Insert the NULA MINI board on the breadboard
Push the board into one end of the breadboard so that its two rows of pins sit on either side of the centre channel, with the chip facing down. Press it in evenly until all the pins are seated.

This example uses three pins. The names are printed along both edges of the board, and each pin sits in its own numbered row. In the photo the board occupies rows 25 to 30, which puts IO19 in row 27 on the a–e side, and IO4 in row 27 and GND in row 30 on the f–j side.
2. Place the push button
Push the button into the middle of the breadboard so that it straddles the centre channel, a few rows clear of the board. In the photo its legs are in rows 15 and 17.

3. Connect GND to the button
Ground goes across in two hops, using the blue − rail along the edge of the breadboard as a shared ground line.
- First jumper: from the row holding GND (row 30 in the photo) out to the blue − rail.
- Second jumper: from that same blue − rail back to row 15, on the f–j side of the button.

4. Connect IO19 to the button
Run a single jumper from the row holding IO19 (row 27 on the a–e side) to row 17, on the a–e side of the button.

The two wires reach the button from opposite sides: row 15 on the f–j side, row 17 on the a–e side. That is the diagonal pair, and it is what makes the button actually switch something. The button is now finished; the rest of this page is the LED.
5. Bring IO4 out and add the resistor
The LED is driven by IO4, which sits in row 27 on the f–j side of the board, the same side as GND and the opposite side from IO19.
- The board covers most of that row, so use the outermost hole, j27, and run a jumper along to row 7.
- Then place the 330 Ω resistor so it bridges row 7 to row 5.

6. Add the LED
The LED goes in next, continuing the chain: long leg (anode) into row 5, the same row the resistor ends in, and short leg (cathode) into row 3.

7. Connect the LED to ground
One jumper closes the circuit: from row 3, where the LED's short leg sits, across to the blue − rail, the same rail the button already uses for ground.

The full path is now IO4 → 330 Ω → LED → − rail → GND. Because the rail is shared, the button and the LED both take their ground from the same place.
8. Connect the board to your computer

Reading the button state
The function digitalRead(pin) reads the value from a specified digital pin and returns either HIGH or LOW as result. We call this function inside the loop():
void loop() {
//digitalRead() is a function that reads the value from a specified digital pin, either HIGH or LOW.
bool reading = digitalRead(BUTTON_PIN);
}
INPUT_PULLUP switches on a resistor inside the chip that holds the pin at 3.3 V, so the pin reads HIGH while the button is released. Pressing the button connects the pin to GND, so it reads LOW. A button wired this way is called active low, and it is why the code below watches for a change from HIGH to LOW.Button debouncing
When you press a button, it changes from an open circuit to a closed circuit, which we treat as a single press. For us, it feels like one clean action because we react slowly compared to electronics. But to a microcontroller, which checks the button state thousands of times in that short moment, it looks very different.
As the button is pushed, two metal contacts inside touch each other. Since they're not perfectly smooth or aligned, they can quickly connect and disconnect a few times before settling. To the microcontroller, that looks like the button was pressed several times in a row, even though you only pressed it once.

The simplest software-based strategy to handle this is the delay-based debouncer.
The whole point of it is to wait out the debouncing window. This is where the millis() comes in handy.
The function millis() returns the number of milliseconds passed since the board began running the current program. We can use it to check if enough time has passed between last button state change and current time, if it has, we are prepared for the next press, if not, wait a bit more.
Toggling the LED
Each time a valid press is detected, we flip the LED's state with a simple toggle. This code is located inside the debouncer filter.
if (lastState == HIGH && reading == LOW) {
//We toggle the ledState
ledState = !ledState;
//We turn the LED on or off depending on the current ledState
if (ledState == true) {
digitalWrite(LED_PIN, HIGH);
} else {
digitalWrite(LED_PIN, LOW);
}
}
lastState = reading;
Code
Below is the full example code for toggling an LED with a debounced button. It uses the millis() function to check if enough time has passed between button state changes.
/*
This is a variable to which we pass the number of pin that we had connected the BUTTON to.
The NULA board has a pin naming logic as follows: IO19, where 19 is the number that we give to the variable.
If you wish to use a different pin, make sure you are using a IO__ marked pin.
*/
const int BUTTON_PIN = 19;
/*
This is a variable to which we pass the number of pin that we had connected the LED to.
The NULA board has a pin naming logic as follows: IO4, where 4 is the number that we give to the variable.
If you wish to use a different pin, make sure you are using a IO__ marked pin.
Remember that the LED needs a 330 Ohm resistor in series with it. That resistor limits how much current flows, and
without it the LED draws more than either it or the pin is built for, so both can be damaged.
*/
const int LED_PIN = 4;
/*
Those are the variables used for button debouncing.
*/
bool ledState = false;
bool lastState = HIGH;
unsigned long lastChangeMs = 0;
const unsigned long debounceMs = 25;
void setup() {
/*
pinMode() is a function that configures the specified pin to behave either as an input or in this case as an output.
This simply means that the pin "listens" for the available data when in input mode, and "writes" data when in output mode.
As our pin needs to detect if the button has been pressed, we will put the pin in INPUT_PULLUP mode.
INPUT_PULLUP switches on a small resistor inside the chip that gently ties the pin to 3.3V, so the pin reads HIGH
while the button is released and the button only has to connect it to GND to read LOW.
*/
pinMode(BUTTON_PIN, INPUT_PULLUP);
/*
pinMode() is a function that configures the specified pin to behave either as an input or in this case as an output.
This simply means that the pin "listens" for available data when in input mode, and "writes" data when in output mode.
As our pin needs to turn on the LED, we will put the pin in OUTPUT mode.
*/
pinMode(LED_PIN, OUTPUT);
}
void loop() {
//digitalRead() is a function that reads the value from a specified digital pin, either HIGH or LOW.
bool reading = digitalRead(BUTTON_PIN);
/*
millis() is a function that returns the number of milliseconds passed since the board began running the current program.
In this example, we use this function to check out if the debouncing period has finished. This number will overflow
(go back to zero), after approximately 50 days.
*/
unsigned long now = millis();
/*
This is our debouncing logic, we check if the button has been pressed and if enough time has passed so that we don't get
false readings because of the noise in the signal.
*/
if (reading != lastState && (now - lastChangeMs) > debounceMs) {
lastChangeMs = now;
/*
As we are using the pull-up method for connecting the button, the readings are the other way around from what you
might expect: the pin reads HIGH while the button is released and LOW while it is pressed. So we need to toggle the
LED when the button state goes from HIGH to LOW.
*/
if (lastState == HIGH && reading == LOW) {
//We toggle the ledState
ledState = !ledState;
//We turn the LED on or off depending on the current ledState
if (ledState == true) {
digitalWrite(LED_PIN, HIGH);
} else {
digitalWrite(LED_PIN, LOW);
}
}
lastState = reading;
}
}
What you should see
Upload the sketch. Nothing happens at first. The LED starts off. Press the button once and it turns on, press it again and it turns off.

Full example
Check out the full example code on the link below:
2.2_Button_Debounce.ino
Example that toggles an LED with a debounced button press.