08. Digital Inputs, Pull-Ups & Debouncing
Read stable button and switch signals.
Learning outcomes
- Explain floating inputs
- Wire an internal pull-up input
- Detect a press edge
- Debounce a mechanical button without blocking
Parts and preparation
Uno, push button, jumper wires and onboard LED.
Before power: inspect wiring, confirm supply voltage and ensure all connected circuits share GND.
Floating inputs
An unconnected high-impedance input can randomly read HIGH or LOW. A pull-up or pull-down resistor establishes a default level so the pin is defined when the button is open.
INPUT_PULLUP
The Uno has internal pull-up resistors. Wire the button from the pin to GND: open reads HIGH and pressed reads LOW. That active-low wiring is the usual course pattern for push buttons.
Contact bounce
A mechanical button does not snap cleanly between open and closed. As the contacts meet, they bounce for a few milliseconds and the pin voltage chatters between HIGH and LOW.
If your sketch reacts to every raw edge, one physical press can look like many presses. That is why counts race, LEDs flicker, and menus jump.
Software debounce
Debouncing accepts a new state only after the raw reading stays unchanged for a short time (debounceMs, often about 20-50 ms).
In this lesson's sketch, every change to the raw reading restarts a millis() timer. When the timer reaches debounceMs and the reading still differs from stableState, the sketch updates stableState. Bounce edges never last long enough to be accepted.
Edge detection
Acting while the button is held (level detection) fires again every loop pass. An event should happen when the stable state changes - for an INPUT_PULLUP button, that is usually the falling edge HIGH to LOW.
This sketch toggles the LED only when stableState becomes LOW, so one press equals one toggle.
Wiring and safe build sequence

- One button side -> D2
- Opposite button side -> GND
- Configure D2 as INPUT_PULLUP
Worked sketch
Download .ino sketchconst byte buttonPin = 2;
bool stableState = HIGH;
bool lastReading = HIGH;
unsigned long changedAt = 0;
const unsigned long debounceMs = 30;
void setup() {
pinMode(buttonPin, INPUT_PULLUP);
pinMode(LED_BUILTIN, OUTPUT);
}
void loop() {
bool reading = digitalRead(buttonPin);
if (reading != lastReading) {
changedAt = millis();
lastReading = reading;
}
if (millis() - changedAt >= debounceMs && reading != stableState) {
stableState = reading;
if (stableState == LOW) {
digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));
}
}
}How the code works
- The timer restarts whenever the raw reading changes.
- Unsigned subtraction remains safe when millis rolls over.
- The LED toggles only on the stable pressed edge.
Test and record evidence
Practical evidence checklist
Common faults and checks
- Four-leg buttons connect pairs of legs internally; rotate the button if it always reads pressed.
- Print reading and stableState for diagnosis.
Check your understanding
Q1. Why must an input not float?
Show answer
Its logic level would be undefined and noise-sensitive.
Q2. What is button bounce?
Show answer
Rapid unintended transitions as mechanical contacts settle.