Blinking an LED is output. The other half of every interactive project is input — and the humble pushbutton is where input begins. In this tutorial you will wire a button to an Arduino Uno the correct way, learn what “floating” means and why pull-up resistors matter, and write debounced code that reliably toggles an LED each time you press. These same techniques apply to reed switches, tilt sensors, limit switches and every other digital input you will ever use.
Why a Button Needs a Pull-Up Resistor
A pushbutton is just two contacts that touch when pressed. Wire one side to a digital pin and the other to ground, and pressing the button pulls the pin to 0 V — a clean LOW. But when the button is released, the pin is connected to nothing at all. It “floats”, picking up electrical noise from your hands, nearby cables and even radio stations, and digitalRead() returns random values. The fix is a pull-up resistor that gently holds the pin at 5 V until the button firmly drags it to ground. The Uno has one built into every pin: enable it with pinMode(pin, INPUT_PULLUP) and you need no external resistor at all. Remember the logic is now inverted — pressed reads LOW, released reads HIGH.
Parts Needed
- Arduino Uno with USB cable
- 1× tactile pushbutton (6 mm, four legs)
- 1× 5 mm LED
- 1× 220 Ω resistor
- 1× 10 kΩ resistor (optional, for the external pull-down experiment)
- Breadboard and jumper wires
Wiring the Button and LED
Tactile buttons have four legs that are internally connected in pairs across the button. Bridge the button across the breadboard’s center gap so the two switched sides are isolated, and test with a multimeter if readings seem wrong.
| Arduino Uno | Component | Notes |
|---|---|---|
| Digital pin 2 | One side of pushbutton | Other side of button goes to GND |
| GND | Pushbutton second side + LED cathode | Shared ground rail |
| Digital pin 13 | 220 Ω resistor → LED anode | LED is our output indicator |
The Debounced Toggle Sketch
Mechanical contacts do not close cleanly; they bounce open and shut for a few milliseconds, which a fast microcontroller sees as many presses. This sketch ignores changes that last less than 30 ms and toggles the LED once per genuine press.
const int BUTTON_PIN = 2;
const int LED_PIN = 13;
bool ledState = false;
int lastReading = HIGH;
int stableState = HIGH;
unsigned long lastChange = 0;
const unsigned long DEBOUNCE_MS = 30;
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP); // Button to GND, no resistor needed.
pinMode(LED_PIN, OUTPUT);
}
void loop() {
int reading = digitalRead(BUTTON_PIN);
if (reading != lastReading) {
lastChange = millis(); // Input just moved; restart the timer.
lastReading = reading;
}
if ((millis() - lastChange) > DEBOUNCE_MS && reading != stableState) {
stableState = reading;
if (stableState == LOW) { // A real, settled press.
ledState = !ledState;
digitalWrite(LED_PIN, ledState ? HIGH : LOW);
}
}
}
Walkthrough
Instead of delaying (which freezes the whole program), we time-stamp every wiggle with millis(). Only when the reading has stayed constant for 30 ms do we accept it as real. On each accepted press the LED flips state. Upload it, then press: one press on, one press off, every time.
Experiment: External Pull-Down
To see the alternative style, move the button so it connects pin 3 to 5 V, put a 10 kΩ resistor from pin 3 to GND, and use pinMode(3, INPUT) with pressed reading HIGH. This “pull-down” arrangement needs the external resistor because the Uno has no built-in pull-downs — one reason INPUT_PULLUP is the maker favorite.
Try It in the Circuit Simulator
Rebuild the button-and-LED logic in our Circuit Simulator: place a switch, a resistor and an LED, then click the switch to open and close the loop and watch the current dots start and stop. It is a great way to build intuition for open vs. closed circuits before debugging real wires.
Why We Avoid delay() for Debouncing
The classic beginner debounce is “if the pin changed, delay(30) and check again.” It works — until your project also drives a servo, blinks a status LED or listens to serial, because delay() freezes the entire processor for those 30 ms. The millis() pattern in our sketch costs a few extra variables but keeps the loop running at full speed, so debouncing never blocks anything else. This style — “remember when something happened and check how long ago” — is the single most valuable habit in Arduino programming, and it scales from one button to a dozen simultaneous tasks without any operating system at all.
Troubleshooting
- Readings change on their own: the pin is floating — confirm you used
INPUT_PULLUP, notINPUT, and the button really reaches GND. - Button seems dead: rotate it 90°; you may be using the two permanently-connected legs.
- LED toggles several times per press: increase
DEBOUNCE_MSto 50; cheap buttons bounce longer. - Always reads LOW: check for a short to ground on the pin side of the button.
Safety Notes
- Work at 5 V only; never connect buttons to mains circuits without proper isolation hardware.
- If you use an external pull-down, do not omit it — a direct 5 V-to-pin button with the pin accidentally set to OUTPUT LOW creates a short.
- Unplug USB while rewiring the breadboard.
Conclusion
You now understand floating pins, pull-ups, inverted logic and debouncing — the four ideas behind every digital input. With an LED on output and a button on input you already have a complete interactive system. Combine this with the blink tutorial and you can build reaction games, toggled night-lights, or the control panel for your next big project.