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 | Breadboard A11 | Black — supplies GND to the button's top pair |
| Button top-left | Breadboard D11 | (component pin) |
| Button bottom-left | Breadboard F11 | (component pin — signal side) |
Arduino D7 | Breadboard H11 | Yellow — reads the signal from 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 the GND-return wire from col 16. 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 G11–G16.
- Remove the 5 V wire from Arduino
5VtoA11. - Remove the GND wire from
H16to ArduinoGND. - Add a new black wire from Arduino
GNDtoA11— where the red 5 V wire used to be. - Keep the yellow signal wire from
D7toH11and the button itself.
You should now have a much emptier-looking breadboard.
The new wiring
5V as a red wire and now comes from GND instead. The yellow signal wire didn't move at all. The 10 kΩ resistor that used to live at G11–G16 is gone; its replacement is invisible because it sits inside the Arduino chip itself.Step 2 — see what's invisible: the schematic
The internal pull-up isn't physically on your breadboard, but it is in the circuit. A complete schematic shows it inside a dashed "this lives inside the chip" boundary so you can think about how the circuit actually works.
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.
Try It Yourself 20 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.) ____
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? ____
Goal: Compare the two circuits — L01-16 (external pull-down) and today's L01-17 (internal pull-up) — by filling in this table on paper. No new building required; just count the parts.
| Component / wire | L01-16 count | L01-17 count | Savings |
|---|---|---|---|
| Push button | 1 | ____ | ____ |
| 10 kΩ resistor | 1 | ____ | ____ |
| Wires from Arduino to breadboard | 3 (5V, signal, GND) | ____ | ____ |
Lines of setup() code | 2 | ____ | ____ |
Lines of loop() code | 1 | ____ | ____ |
Questions:
- For a project with 10 buttons, how many components do you save by using
INPUT_PULLUPinstead of external pull-downs? ____ - Can you think of any reason a real engineer might still prefer an external pull-down? (Hint: the internal pull-up is fixed at 20–50 kΩ. What if you needed a different value, or a stronger pull?) ____
Mini-Challenge 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.
When you're done, reflect on a notebook page: why is the equivalent project impossible with L01-16's pull-down approach without three separate signal pins or three separate resistors? ____
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".
Homework 5 min
Convert L01-15's doorbell to take input. Take the Musical Doorbell project from L01-15 (3 LEDs + buzzer, rings on a loop) and turn it into a real doorbell that rings only when a button is pressed. Use INPUT_PULLUP on the button.
- Re-plug your three LEDs and your buzzer onto the breadboard exactly as in L01-15.
- Add a push button to a spare patch of breadboard — e.g. cols 17/20 (between the yellow and green LEDs), straddling the trough. Use
D7for its signal pin. - Wire the button's top pair to GND (via the − rail). Two wires total for the button: GND-side and signal-side.
- In your sketch, declare the button pin and set its mode with
INPUT_PULLUP. - Move the entire doorbell sequence from
loop()into the body of a new helper function calledringDoorbell(). - In
loop(), use the trick from today's Worked Example to callringDoorbell()only when the button reads pressed. (You don't haveif/elseyet, but you havedigitalReadand!. Combined cleverly, that's enough — see the next paragraph.)
One pattern that almost works without if: at the top of loop(), just call ringDoorbell() regardless, and add a small "stay quiet until next press" delay at the end. The honest answer: the truly clean version waits for if in L01-20. For homework, just get the ringDoorbell helper written and call it once per loop pass — then leave a note in your sketch's comments saying "TODO: only ring when pressed (needs if from L01-20)".
Also: a design reflection on paper.
- For tomorrow's lesson (L01-18 "Debouncing a Button"), you'll learn that buttons aren't perfect — pressing one can register as several presses in quick succession. Why is that, and what kind of fix do you think would help? (Hint: think about the actual mechanical contact inside the button.) ____
- In real consumer electronics — your TV remote, your phone's volume buttons — are the buttons more likely to be wired with internal pull-up or external pull-down? Why? ____
Bring back next class:
- The saved
.inofile (call itdoorbell-with-button). - A short phone video showing the doorbell hardware and your button — even if pressing doesn't yet make a difference, take the video.
- Your two reflection answers on a notebook page.
Heads up for next class: L01-18 "Debouncing a Button" explains why a single press sometimes registers as several presses, and gives you a tiny one-line fix that every Arduino sketch in the world ends up using.