PROJECT 1 · DIGITAL I/O

Inputs & Outputs: Button → LED

Press a pushbutton → an LED turns on; release it → the LED turns off. The "hello world" of physical computing.

New here? Start with Foundation 2: Introduction & Setup.

⬇ Download the sketch Save Project_1_ESP32_Inputs_Outputs.ino, then open it in the Arduino IDE (setup: Foundation 3).
Pushbutton pressed turns LED on; not pressed turns LED off
The whole idea: button pressed → LED on · button released → LED off.

🎯 Objective

By the end of this project, you will:

1 · What the sketch does

pinMode(buttonPin, INPUT);   // GPIO 4: read the button
pinMode(ledPin,   OUTPUT);  // GPIO 5: drive the LED

buttonState = digitalRead(buttonPin);
if (buttonState == HIGH) digitalWrite(ledPin, HIGH); // pressed  -> LED on
else                     digitalWrite(ledPin, LOW);  // released -> LED off

The LED lights when the pin reads HIGH, so the button sends 3.3 V to GPIO 4 when pressed, and a 10 kΩ pull‑down holds the pin LOW when released.

Plain INPUT has no internal pull resistor, so the external 10 kΩ pull‑down is required: otherwise the pin "floats" and reads random noise. (§7 shows a software‑only alternative.)

2 · The pins: analog or digital?

SignalGPIOUsed asHere it isAnalog capable?
ButtonGPIO 4digitalRead()DIGITAL inputYes: ADC2_CH0 + touch T0 (unused)
LEDGPIO 5digitalWrite()DIGITAL outputNo: no ADC (PWM only)

Both pins are used digitally (HIGH/LOW). Use 3V3 (never 5V) for the button's HIGH side.

3 · Parts Required

QtyPartNotes
1ESP32 DEVKIT V1 · breadboard · jumper wires-
1LEDAny color: has polarity (§5)
1220 Ω resistorLED limit · Red‑Red‑Black‑Black‑Brown
110 kΩ resistorPull‑down · Brown‑Black‑Black‑Red‑Brown
1Pushbutton4‑leg tactile

4 · Wiring the Circuit

Breadboard schematic: LED on GPIO 5, button on GPIO 4 with 10 kilo-ohm pull-down
Official kit schematic: LED on GPIO 5 (220 Ω), button on GPIO 4 (10 kΩ pull‑down to GND, 3.3 V on the other side).

LED circuit (output, GPIO 5)

  1. GPIO 5 → 220 Ω → LED anode (long leg, +)
  2. LED cathode (short leg, −)GND rail

Button circuit (input, GPIO 4)

  1. One side of the button → 3.3 V rail
  2. Other side → GPIO 4
  3. From that same GPIO 4 node → 10 kΩGND rail
Behavior: released → pulled to GND through 10 kΩ → LOW → LED off. Pressed → connected to 3.3 V → HIGH → LED on.

5 · LED polarity (don't skip)

LONG leg  = Anode  (+)  -> toward the 220 Ω / GPIO 5 side
SHORT leg = Cathode (-)  -> toward GND   (flat notch on the rim = - side)

If the LED never lights, check first that it isn't reversed.

6 · Code Walkthrough: Understanding the Sketch

Let's walk through the sketch so you understand what each line does and why.

Step 1: Pin definitions & state variables

const int buttonPin = 4;  // the pushbutton pin (digital input)
const int ledPin    = 5;  // the LED pin        (digital output)

int buttonState = 0;       // this round's reading: HIGH or LOW
int lastButtonState = -1;  // the previous reading, so we can spot changes

Naming the pins with const int means the number appears once: rewire the LED to another GPIO and you change one line. lastButtonState starts at -1, a value digitalRead() can never return, so the first real reading always counts as a change.

Step 2: one-time preparation in setup()

void setup() {
  Serial.begin(115200);        // open the USB link to the computer
  delay(500);                  // let the Serial Monitor connect

  pinMode(buttonPin, INPUT);   // GPIO 4 becomes an input
  pinMode(ledPin, OUTPUT);     // GPIO 5 becomes an output
}

setup() runs once at power-up or reset. pinMode() decides the direction of a pin: an input listens for a voltage someone else supplies, an output drives the pin to 3.3 V or 0 V itself. Every GPIO can do either, so you must say which you want.

Step 3: loop() reads → compares → acts

void loop() {
  buttonState = digitalRead(buttonPin);      // 1. READ the button

  if (buttonState != lastButtonState) {      // 2. did it CHANGE?
    if (buttonState == HIGH) {
      digitalWrite(ledPin, HIGH);            // 3. pressed  -> LED on
      Serial.println("[EVENT] Button PRESSED  -> LED is ON");
    } else {
      digitalWrite(ledPin, LOW);             //    released -> LED off
      Serial.println("[EVENT] Button RELEASED -> LED is OFF");
    }
    lastButtonState = buttonState;           // 4. remember for next time
  }

  delay(50);                                 // 5. debounce
}

loop() runs again and again forever, thousands of times per second. The if comparing this reading with the last one is what keeps the Serial Monitor usable: without it, holding the button down would print the same line endlessly.

Key concepts: what each line really does

CodeWhat it does
pinMode(pin, INPUT)Sets the pin to listen. Plain INPUT connects no internal resistor, so an unconnected pin floats and reads random noise. That is why the 10 kΩ pull-down is required here.
pinMode(pin, OUTPUT)Lets the pin drive itself to 3.3 V (HIGH) or 0 V (LOW), supplying current to whatever is attached.
digitalRead(pin)Samples the pin right now and returns HIGH or LOW. Above roughly 2.0 V reads HIGH, below about 0.8 V reads LOW.
digitalWrite(pin, HIGH)Connects the pin to 3.3 V, so current flows through the 220 Ω resistor and the LED lights. LOW connects it to 0 V and the LED goes dark.
buttonState != lastButtonStateEdge detection: acts only when the state differs from last time, turning a continuous stream of readings into discrete press and release events.
delay(50)Waits 50 ms. A mechanical button does not switch cleanly: its contacts bounce for a few milliseconds, which one press can register as several. Waiting past the bounce fixes it.

Expected output on the Serial Monitor

========================================
 Project 1: ESP32 Inputs & Outputs
========================================
Button (input)  -> GPIO 4
LED    (output) -> GPIO 5
Press the button to turn the LED ON.
Waiting for button presses...
----------------------------------------
[EVENT] Button PRESSED  -> LED is ON
[EVENT] Button RELEASED -> LED is OFF

Each press and release prints exactly one line. If a single press prints several, the debounce delay is too short for your button.

7 · Optional: no external resistor (software pull‑up)

Wire the button between GPIO 4 and GND and enable the internal pull‑up. This inverts the logic:

pinMode(buttonPin, INPUT_PULLUP);   // reads HIGH when NOT pressed
if (buttonState == LOW) digitalWrite(ledPin, HIGH); // pressed  -> LED on
else                    digitalWrite(ledPin, LOW);  // released -> LED off
Pick one approach: 10 kΩ pull‑down + original code, or INPUT_PULLUP + flipped code. Don't mix them.

8 · Demonstration

LED lit while the button is pressed
Press → LED lights and the monitor prints a PRESSED event. Release → off.

9 · Wrapping Up: What You've Learned

This is the smallest complete physical-computing program: read the world, decide, act on the world. Everything later in the kit is a variation on it. The key takeaways:

10 · Troubleshooting

SymptomLikely causeFix
LED never lightsLED reversedFlip it: long leg toward the 220 Ω side
LED always on / flickersFloating input (missing pull‑down)Add the 10 kΩ from GPIO 4 to GND
Monitor blank / garbageWrong baudSet it to 115200
Button seems invertedINPUT_PULLUP with original codeMatch code and wiring (§7)
Nothing uploadsCharge‑only cable / wrong portData cable; correct port; hold BOOT
LED very dim10 kΩ in the LED pathUse 220 Ω for the LED
← Foundation 2 · Introduction & Setup Project 2 · Analog Inputs →