Learning Goals 5 min
By the end of this lesson you will be able to:
- Wire a push button using only two wires — no external resistor, no 5 V wire — by leaning on a resistor that is already inside the Arduino chip.
- Set a pin with
pinMode(pin, INPUT_PULLUP)and explain how the on-chip pull-up resistor changes the pin's default state from "floating" toHIGH. - Read the inverted button signal (
LOWwhen pressed,HIGHwhen released) and flip it back into a normal mirror with the!(NOT) operator, so that pressing still lights the onboard LED.
Warm-Up 10 min
Last lesson you built the canonical pull-down circuit. Today is short and sweet — it's the same circuit, except a piece you used yesterday turns out to be hiding inside the Arduino itself.
Quick-fire puzzle
Count the components in your L01-16 button circuit:
- How many wires ran from the Arduino to the breadboard? (Hint: 5 V, GND, signal.)
- How many resistors sat on the breadboard?
- If the Arduino chip already had a 10 kΩ-ish resistor hidden inside, connecting every digital pin to 5 V — and you could switch it on with a single line of code — which of those wires/components could you delete?
Reveal the answer
- Three wires: 5 V to A11 (red), D7 to H11 (yellow), and H16 back to Arduino GND (black).
- One resistor: the 10 kΩ pull-down at G11–G16.
- If the chip already had a built-in pull-up, you'd no longer need the external resistor or the 5 V wire. You'd be down to two wires total: GND to the button, and signal from the button to a digital pin.
This isn't a hypothesis. Every digital pin on the ATmega328P (the chip on your UNO) really does have a built-in pull-up resistor — about 20–50 kΩ — that you can switch on with the single word INPUT_PULLUP. Today's whole lesson is about flipping that switch.
New Concept 20 min
The big idea — the resistor was inside the chip the whole time
Inside the ATmega328P chip on your Arduino UNO is a network of tiny resistors — one per digital pin — connecting each pin to the chip's internal 5 V rail. They're switched off by default. When you call pinMode(pin, INPUT_PULLUP) instead of pinMode(pin, INPUT), you turn that pin's internal resistor on. From that moment, the pin is "pulled up" to HIGH whenever nothing outside is dragging it down.
The internal pull-up is roughly 20–50 kΩ — a little weaker than the external 10 kΩ you used yesterday, but plenty strong for buttons, switches and most other digital inputs.
The wiring flip — GND replaces 5 V
Yesterday's pull-down circuit had the button supply 5 V when pressed. Today's pull-up circuit is the mirror image: the button supplies GND when pressed. The chip's internal resistor handles the default.
| State | L01-16 (external pull-down) | L01-17 (internal pull-up) |
|---|---|---|
| Button released | Pull-down resistor drags pin to GND → reads LOW | Internal pull-up drags pin to 5 V → reads HIGH |
| Button pressed | Button connects pin to 5 V → reads HIGH | Button connects pin to GND → reads LOW |
| Logic | Pressed = HIGH (positive logic) | Pressed = LOW (negative / inverted logic) |
Two new pieces of code
| Function / operator | What it does |
|---|---|
pinMode(pin, INPUT_PULLUP) | Declares the pin as input and switches on the chip's built-in pull-up resistor. Replaces pinMode(pin, INPUT) entirely. |
!value | The NOT operator. Flips HIGH ↔ LOW. So !digitalRead(pin) reads the pin and then inverts the answer — handy because pull-up logic is inverted. |
Wiring map — only two wires now
| From | To | Wire colour |
|---|---|---|
Arduino GND | − rail → A15 | Black — supplies GND to the button’s top side |
| Button top pair | Breadboard E15 | (component) |
| Button bottom pair | Breadboard F15 | (component — signal side) |
Arduino D7 | Breadboard J15 | Yellow — reads the button’s bottom side |
The button still straddles the trough (same as L01-16). What’s gone: the 5 V wire, the 10 kΩ resistor, and its GND-return wire. From a five-thing circuit (button + resistor + 3 wires) down to a three-thing circuit (button + 2 wires).
Why it matters
For a single button you save one wire and one resistor. For a project with ten buttons (a keypad, a game controller, a control panel) you save twenty components. Real makers always use INPUT_PULLUP for buttons, switches, and any input that wants a sensible default — only reaching for an external resistor in the rare cases where the internal one isn't strong enough or fast enough.
Worked Example 20 min
Goal: rebuild the L01-16 mirror circuit using only two wires and no external resistor — then write the new sketch.
Step 1 — strip down the L01-16 wiring
Start from your L01-16 build:
- Remove the 10 kΩ pull-down resistor at H15–H20.
- Remove the 5 V wire from Arduino
5Vto the + rail andA15. - Remove the GND wire from
J20to the − rail. - Add a new black wire from
A15up to the − rail — where the red 5 V wire used to reach the + rail. - Keep the yellow signal wire from
D7toJ15and the button itself.
You should now have a much emptier-looking breadboard.
The new wiring — and the resistor you cannot see
Two drawings again. On the breadboard, the resistor has simply vanished; on the schematic it is still there, drawn inside a dashed box marked inside the ATmega328P chip. That box is the whole lesson: nothing was removed from the circuit, it just moved indoors.
Press and hold the button in the left-hand picture and watch the current appear.
On the breadboard what you build
On paper what engineers draw
digitalRead returns HIGH. Held, the button gives the current a much easier path straight to ground, the pin is dragged down to 0 V, and the reading flips to LOW. That is why the logic is inverted — pressed now means LOW. Compare the two schematics side by side with L01-16's: same three things in a line, but the resistor has swapped places with the button.| From | To | Wire colour |
|---|---|---|
| Button top pair | Breadboard E15 (rows A–E strip) | (component) |
| Button bottom pair | Breadboard F15 (rows F–J strip) | (component — signal side) |
Breadboard A15 | − rail → Arduino GND | Black |
Arduino D7 | Breadboard J15 | Yellow (signal) |
Four rows, and two of them are the button. Compare that with L01-16's seven.
Step 3 — write the sketch
Two small changes from L01-16's sketch: INPUT becomes INPUT_PULLUP, and the line that drives the LED gains a ! to flip the inverted reading back to a normal mirror.
const int BUTTON_PIN = 7;
const int LED_PIN = 13; // onboard L LED
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
digitalWrite(LED_PIN, !digitalRead(BUTTON_PIN));
}Expected behaviour
- Plug in the Arduino. The onboard
LLED stays dark — same as L01-16. - Press the button. The LED lights up immediately.
- Release. The LED goes off immediately.
- The visible behaviour is identical to yesterday's lesson. The internal pull-up does its work silently.
Step 4 — trace the inverted logic
Look at the one-line loop() body. Three things happen, right-to-left:
digitalRead(BUTTON_PIN)returnsHIGHif the button is released,LOWif pressed. (Inverted from yesterday.)- The
!in front flips it.!HIGHbecomesLOW;!LOWbecomesHIGH. digitalWrite(LED_PIN, …)sends that flipped value to the onboard LED.
Net effect: pressed button → !LOW = HIGH → LED on. Released button → !HIGH = LOW → LED off. Same outcome as L01-16, fewer components.
Basic 7 min
Goal: Remove the ! and see the raw, un-inverted INPUT_PULLUP behaviour. Same hardware, slightly different code.
void loop() {
digitalWrite(LED_PIN, digitalRead(BUTTON_PIN));
}Questions:
- Without the
!, what is the onboard LED doing when you don't press the button? ____ - What happens when you do press it? ____
- Is this useful behaviour or weird behaviour? (Hint: think "active-low" indicator — some warning lights actually do this. The light is on by default and goes off when everything is fine.) ____
Challenge 1 7 min
Goal: Wire a second button at cols 17/20 (the gap between yesterday's button position and where the green LED used to sit). Pin it to D6. The onboard LED should light up if either button is pressed.
You don't know || (OR) yet — that's L01-20. But you can use a clever trick: combine both reads with && (which you also don't know yet, technically) or by chaining !s. Or even simpler: both buttons' bottom-pair pins can be wired to the SAME D7-style signal column! Because both buttons connect to GND when pressed, plugging both of them into the same signal column means pressing either one drives the signal LOW.
So the simplest version: don't add a second digital pin. Just plug button 2's signal-side leg into the same col 11 tie strip as button 1. One signal pin, two buttons, both pull it LOW.
// No code change at all from the Worked Example! Both buttons share D7.
void loop() {
digitalWrite(LED_PIN, !digitalRead(BUTTON_PIN));
}Questions:
- Why does this trick work with
INPUT_PULLUPbut not with the pull-down circuit from L01-16? ____ (Hint: with pull-down, you'd need two separate 10 kΩ resistors and two separate signals. With pull-up, all the buttons share one default state.) - If you wanted only ONE specific button to light the LED, would this shared-pin trick still work? Why not? ____
Challenge 2 6 min
Goal: Two classmates converted their L01-16 circuit to INPUT_PULLUP. Neither works. Find three faults in total.
Classmate A built today's two-wire circuit correctly. Their onboard LED flickers on and off by itself, even when nobody touches the button. Their sketch has two faults:
const int BUTTON_PIN = 7;
const int LED_PIN = 13;
void setup() {
pinMode(BUTTON_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
digitalWrite(LED_PIN, digitalRead(BUTTON_PIN));
}Classmate B uploaded the Worked Example sketch exactly. Their LED never lights at all, pressed or not. They kept one habit from L01-16. The wire from A15 goes to the + rail, which is still wired to Arduino 5V.
For each fault, write what is wrong and the fix. Then fix A's sketch and upload it to your own two-wire circuit.
It works if your fixed sketch lights the onboard LED only while the button is held. It must not flicker when you wave a hand near the board.
Challenge 3 15 min
The "any-key wake" circuit
Many real devices have a "press any key to wake" feature — laptops, microwaves, alarm clocks. Build the simplest version: three buttons, all wired in parallel to the same digital input. Pressing any one of them wakes the onboard LED.
Your task:
- Place three buttons across the breadboard. Each must straddle the trough so its top pair and bottom pair are on different tie strips.
- Wire each button's top-pair side to GND (via the breadboard's − rail if you like).
- Wire each button's bottom-pair side to the same shared signal column on the breadboard. From that column, run one yellow wire to
D7. - Use the exact Worked Example sketch — no code changes at all.
It works if:
- Pressing button 1 lights the onboard LED.
- Pressing button 2 lights it.
- Pressing button 3 lights it.
- Pressing any two together lights it (no different from pressing one).
- Releasing all three turns the LED off.
This Mini-Challenge has no wiring-reveal — the building is the lesson. If you get stuck, the trick is that all three buttons share one signal column and one GND. The internal pull-up handles the default state for free.
Then test the shared column. Move button 3's signal-side wire into an empty column that nothing else uses. Predict what pressing button 3 does now, then press it and check. Put the wire back and confirm all three buttons work again.
Recap 5 min
Every digital pin on the Arduino has a built-in 20–50 kΩ pull-up resistor that you can switch on with pinMode(pin, INPUT_PULLUP). With it on, the pin's default state is HIGH — so you wire the button to GND, and a press pulls the pin LOW. Use the ! operator on the read value if your code still expects "HIGH means pressed". One keyword saves one resistor and one wire per button — for free.
- INPUT_PULLUP
- A
pinModemode that sets a pin as input and activates the chip's built-in pull-up resistor. The replacement forINPUTin 99% of button circuits. - Internal pull-up
- A 20–50 kΩ resistor permanently fabricated inside the ATmega328P chip, connecting a digital pin to the chip's 5 V rail. Off by default; switched on by
INPUT_PULLUP. - Active-low
- A circuit where the "active" state is
LOWrather thanHIGH. INPUT_PULLUP buttons are active-low — pressing them drives the pin to LOW. The opposite (L01-16's pull-down) is active-high. - ! (NOT operator)
- Flips
HIGHtoLOWandLOWtoHIGH. Place it in front of any expression to invert it:!digitalRead(pin). - Pull-up
- A resistor (internal or external) connecting a pin to
5Vso that the pin defaults toHIGHwhen nothing else drives it. The partner of "pull-down".
Extra Mission 5 min
Part 1 — Design a button panel
A games controller, a TV remote and a microwave all have a panel of buttons. Invent a device with a button panel. On paper, design its panel and wiring.
Your design must include:
- A name and a one-sentence job for the device.
- At least four buttons, each with a job.
- Which buttons need their own pin, and which could share one pin like today's "any-key wake".
- The Arduino pin for each button, all using
INPUT_PULLUP. - How many resistors and wires you save compared with L01-16's pull-down wiring.
Part 2 — Make it
Build the core of your panel: two buttons on their own pins, D7 and D6, both with INPUT_PULLUP. Button 1 lights the onboard LED while held. Button 2 lights the red LED module on D9 while held. Use !digitalRead() for each one — no if needed.
Bring back next class: your panel design, the saved .ino file, and a short video pressing each button in turn, then both together.
Heads up for next class: L01-18 "Debouncing a Button" explains why one press can count as several. Then it gives you a tiny fix.