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.
Project_1_ESP32_Inputs_Outputs.ino, then open it in the Arduino IDE (setup: Foundation 3).
By the end of this project, you will:
pinMode() and know why that belongs in setup().digitalRead() and drive an LED with digitalWrite().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.
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.)| Signal | GPIO | Used as | Here it is | Analog capable? |
|---|---|---|---|---|
| Button | GPIO 4 | digitalRead() | DIGITAL input | Yes: ADC2_CH0 + touch T0 (unused) |
| LED | GPIO 5 | digitalWrite() | DIGITAL output | No: no ADC (PWM only) |
Both pins are used digitally (HIGH/LOW). Use 3V3 (never 5V) for the button's HIGH side.
| Qty | Part | Notes |
|---|---|---|
| 1 | ESP32 DEVKIT V1 · breadboard · jumper wires | - |
| 1 | LED | Any color: has polarity (§5) |
| 1 | 220 Ω resistor | LED limit · Red‑Red‑Black‑Black‑Brown |
| 1 | 10 kΩ resistor | Pull‑down · Brown‑Black‑Black‑Red‑Brown |
| 1 | Pushbutton | 4‑leg tactile |
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.
Let's walk through the sketch so you understand what each line does and why.
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.
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.
loop() reads → compares → actsvoid 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.
| Code | What 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 != lastButtonState | Edge 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. |
======================================== 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.
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
INPUT_PULLUP + flipped code. Don't mix them.
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:
pinMode() in setup() decides which.digitalRead() returns one of two values, HIGH or LOW, and digitalWrite() drives a pin to 3.3 V or 0 V. This is digital I/O, the opposite of the analog reading you meet in Project 2.INPUT_PULLUP (§7) does the same job in software, with the logic inverted.delay() after a change is the simplest cure.| Symptom | Likely cause | Fix |
|---|---|---|
| LED never lights | LED reversed | Flip it: long leg toward the 220 Ω side |
| LED always on / flickers | Floating input (missing pull‑down) | Add the 10 kΩ from GPIO 4 to GND |
| Monitor blank / garbage | Wrong baud | Set it to 115200 |
| Button seems inverted | INPUT_PULLUP with original code | Match code and wiring (§7) |
| Nothing uploads | Charge‑only cable / wrong port | Data cable; correct port; hold BOOT |
| LED very dim | 10 kΩ in the LED path | Use 220 Ω for the LED |