Robotics · Microcontrollers · Grade 7
Button and digital input
One button should switch an LED on and off. Why does the pin “float” and the button seem to be “pressed twice”?
The situation
When you press the “stop” button on a bus, a light comes on in front of the driver. You want to do the same: one button — the LED comes on, press again — it goes off. You connect the button to Arduino pin 2 and the LED to pin 13, and… the LED blinks by itself — even though you have not touched the button! A resistor calms it down. Then you write a toggle program — and sometimes one press switches the LED twice and it stays off. Why does the pin “float”? Why is the button “pressed twice”? With an oscilloscope and `millis()` we find both the cause and the cure.
Topic
Button and digital input: INPUT_PULLUP, bounce (debounce). Turning a lamp on and off with one button: why a button seems to be pressed “twice”
Key ideas
- digitalRead and pull-up
- INPUT_PULLUP
- Bounce (debounce)
Formulas
`digitalRead()` thresholds (ATmega328P, 5 V): above 3 V — HIGH, below 1.5 V — LOW, in between — undefined (the input keeps its previous state with ≈ 0.5 V of hysteresis).
The current through the pull-up (or pull-down) resistor when the button is pressed is negligible. Internal pull-up 20–50 kΩ: 0.1–0.25 mA.
RC filter: if the contact opens for an instant, the capacitor does not have time to charge to 3 V — the bounce does not reach the pin.
Software debounce: after an accepted change, changes inside the window are ignored; the window must be longer than the bounce (usually 20–50 ms).
Mistake: the pin is set to OUTPUT HIGH and the button shorted it to ground — five times the limit current, the chip burns out.
Theory
What a digital input is
`pinMode(2, INPUT)` makes the pin an input: now it does not supply a voltage but reads it. `digitalRead(2)` reduces the voltage to two states: close to 5 V — HIGH (1), close to 0 V — LOW (0). The thresholds are definite: at 5 V the ATmega328P reads anything above 3 V as HIGH and anything below 1.5 V as LOW; the range 1.5–3 V is “nobody knows”. An input pin draws almost no current (less than 1 µA): it only “looks”.
A floating input
If you connect the button between pin 2 and GND and add no resistor: when the button is pressed the pin is connected to ground — LOW, for certain. But when the button is not pressed, the pin is connected to nothing — it “floats” in the air. A high-resistance input turns into an antenna: 50 Hz mains noise, nearby wires and your hand swing it between 0 and 5 V. `digitalRead` gives random HIGH and LOW — the LED blinks by itself, and more strongly if you bring your hand close. This is not a “broken Arduino” but an undefined input.
Pull-down, pull-up and INPUT_PULLUP
The cure is to “pull” the pin to a known voltage through a resistor. Pull-down: 10 kΩ between pin and GND, button between pin and 5V: LOW at rest, HIGH when pressed (0.5 mA flows through the resistor). Pull-up: 10 kΩ between pin and 5V, button between pin and GND: HIGH at rest, LOW when pressed — the logic is inverted, but one leg of the button is at ground, which is safe and convenient. In the ATmega328P this resistor is already inside the chip (20–50 kΩ): `pinMode(2, INPUT_PULLUP)` switches it on — no external resistor is needed. That is why in Arduino programs “pressed = LOW” is the usual thing.
How a button works
Inside a tactile button is a metal dome: when pressed it bends and connects two contacts, and springs back when released. Four legs — two pairs: within each pair the legs are always connected, and when pressed the pairs connect to each other. On a breadboard the button sits across the central channel; so as not to confuse which pair is where, wires go to diagonal legs — then the connection is right for any button.
Bounce and its cure
When the dome touches the contact it does not settle at once — for 1–10 ms it touches and separates several times. To the eye it is one press, on the oscilloscope — several edges. `loop()` runs hundreds of thousands of times a second and takes each edge for a “new press”: the toggle switches twice and stays off, and the Serial Monitor prints “pressed” twice. The cures: software — a window with `millis()`: after a change is accepted, new ones are ignored for OYNA ms (the window must be longer than the bounce: 20–50 ms); hardware — an RC filter: 10 kΩ + 1 µF, = 10 ms, the capacitor does not have time to charge to 3 V during short openings, and the Schmitt trigger of the input gives one clean edge.
About the model
The pin voltage is calculated in 50 µs steps; the input is a Schmitt trigger (3 V / 1.5 V). Bounce: 2 or 4 extra make-and-break events at each press and release, 2–12 ms in total (random, repeated on replay). The noise of a floating pin is 50 Hz + a slow random drift (illustrative). The program advances every millisecond; each `digitalRead()` call sees the next point ≈ 60 µs later — a real `loop()` also “looks” that often. In a short circuit the chip accumulates over time the current above the 40 mA limit and burns out.
Did you know?
- Every input pin of the ATmega328P has an internal pull-up resistor that can be switched on from the program — 20–50 kΩ according to the datasheet. So in Arduino an external resistor is not required for a button: `pinMode(pin, INPUT_PULLUP)` is enough.
- The microcontroller engineer Jack Ganssle in 2004 measured 18 different buttons and switches with an oscilloscope: contact bounce lasted 1–2 ms in most and more than 6 ms in the worst; that is why most programs take a debounce window of 20–50 ms.
Inside the experiment
A real board in a hand-inked isometric drawing: turn it, place the parts, wire the circuit, write and upload the code — current and voltage are computed at once, and a wrong connection burns a part. Inside are missions, theory, a quiz and a guided tour.
More experiments in this section
- Arduino Uno: the first program — Grade 7
- Analogue input and the Serial Monitor — Grade 7
- PWM: brightness and sound — Grade 8
- millis() and multitasking — Grade 8