Phase 2: Setup & Development 17 min read read

Your First ESP32 Program: From Blink to Wi-Fi in One Session

Write your first ESP32 programs step by step — LED blink, button input, Serial debugging, analog reading, and a Wi-Fi network scan — with full annotated code for each.

Updated June 18, 2026

The Learning Path in This Guide

A great first session with a new microcontroller progresses through increasingly complex programs, with each one building on what you learned from the last. This guide takes you through five programs in order: an LED blink, button-controlled LED, serial debug output, analog sensor reading, and finally a Wi-Fi network scan. Each one introduces one or two new concepts so you are never overwhelmed, and each produces visible output so you know immediately whether your code works.

You need: Arduino IDE installed with ESP32 support, your ESP32 DevKitC plugged into USB, and optionally one LED with a 220 Ω resistor, one momentary push button, and a 10 kΩ potentiometer for the later examples. If you only have the bare ESP32 board, the onboard LED and the Serial Monitor provide enough feedback for all five programs.

Program 1: LED Blink

The blink program is the “Hello World” of microcontroller development. It proves that code compiles, uploads, and runs. Open Arduino IDE, create a new sketch (File → New), and replace all the contents with:

const int LED_PIN = 2;   // Built-in LED on the DevKitC

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_PIN, HIGH);   // LED on
  delay(500);                     // wait 500 ms
  digitalWrite(LED_PIN, LOW);    // LED off
  delay(500);                     // wait 500 ms
}

What each line does: const int LED_PIN = 2 creates a named constant for the pin number — always name your pins rather than using raw numbers, because you will refer to them many times and a name is much easier to change later. void setup() runs once at power-on and after every reset. pinMode(LED_PIN, OUTPUT) tells the ESP32 that this pin will output a signal rather than read one. void loop() runs repeatedly forever after setup() completes. digitalWrite(LED_PIN, HIGH) drives the pin to 3.3 V, turning on the LED. delay(500) pauses the program for 500 milliseconds.

Upload the sketch. After the upload completes, the blue LED on the DevKitC should blink on for half a second and off for half a second, repeating indefinitely. If it does, move on to Program 2.

Program 2: Button-Controlled LED

This program reads a physical button and turns the LED on only while the button is pressed. Wire a momentary push button between GPIO 4 and GND. No pull-up resistor is needed because we will use the ESP32’s internal pull-up in the code.

const int LED_PIN    = 2;
const int BUTTON_PIN = 4;

void setup() {
  pinMode(LED_PIN,    OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);  // internal 45kΩ pull-up to 3.3V
}

void loop() {
  // Button reads LOW when pressed (it pulls to GND, overriding the pull-up)
  if (digitalRead(BUTTON_PIN) == LOW) {
    digitalWrite(LED_PIN, HIGH);
  } else {
    digitalWrite(LED_PIN, LOW);
  }
}

Key concepts: INPUT_PULLUP enables the internal resistor that holds the pin at 3.3 V when nothing drives it. When you press the button, it connects GPIO 4 to GND, overriding the pull-up and pulling the pin to 0 V. digitalRead() returns HIGH (1) when the pin is near 3.3 V and LOW (0) when near 0 V. Because the button grounds the pin when pressed, a LOW reading means the button is held down. This “active low” logic with pull-ups is the most common button wiring pattern in electronics.

Upload and test. The LED should light only while you hold the button. Release it and the LED turns off immediately.

Program 3: Serial Debugging

The Serial Monitor is your window into what the ESP32 is doing at runtime. This program uses it to print sensor readings, timing data, and state changes — an essential technique for debugging any project.

const int LED_PIN    = 2;
const int BUTTON_PIN = 4;

int press_count = 0;
bool last_state = HIGH;   // tracks previous button state

void setup() {
  Serial.begin(115200);
  Serial.println("ESP32 started. Waiting for button press...");
  pinMode(LED_PIN,    OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}

void loop() {
  bool current_state = digitalRead(BUTTON_PIN);

  // Detect falling edge (HIGH → LOW = button just pressed)
  if (current_state == LOW && last_state == HIGH) {
    press_count++;
    Serial.print("Button pressed! Total presses: ");
    Serial.println(press_count);
    digitalWrite(LED_PIN, HIGH);
    delay(20);  // simple debounce
  }

  // Detect rising edge (LOW → HIGH = button just released)
  if (current_state == HIGH && last_state == LOW) {
    Serial.println("Button released.");
    digitalWrite(LED_PIN, LOW);
    delay(20);
  }

  last_state = current_state;
}

Upload this sketch. Open the Serial Monitor (magnifying glass icon in IDE 2.x, or Ctrl+Shift+M) and set the baud rate to 115200. Press the button several times. Each press and release prints a message, and the press count increments. This edge-detection pattern — comparing current state to previous state — is fundamental to event-driven programming on microcontrollers.

Serial.print() prints without a newline; Serial.println() adds a newline character at the end. Serial.printf("Value: %dn", variable) works like C’s printf for formatted output. Use Serial.printf() whenever you want to print a mix of text and numbers in a single call.

Program 4: Analog Sensor Reading

Connect a 10 kΩ potentiometer: outer pins to 3.3 V and GND, middle (wiper) pin to GPIO 32. GPIO 32 is on ADC1, which works correctly while Wi-Fi is active.

const int POT_PIN = 32;
const int LED_PIN = 2;

void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  analogReadResolution(12);       // 12-bit: 0–4095
  analogSetAttenuation(ADC_11db); // full range: 0–3.3V
}

void loop() {
  int raw       = analogRead(POT_PIN);           // 0–4095
  float voltage = raw * (3.3f / 4095.0f);        // convert to volts
  int blink_ms  = map(raw, 0, 4095, 100, 2000);  // 100ms to 2s blink rate

  Serial.printf("Raw: %4d | Voltage: %.2f V | Blink: %dmsn",
                raw, voltage, blink_ms);

  digitalWrite(LED_PIN, HIGH);
  delay(blink_ms / 2);
  digitalWrite(LED_PIN, LOW);
  delay(blink_ms / 2);
}

Concepts introduced: analogReadResolution(12) sets the ADC to return values from 0 to 4095 (2¹² = 4096 steps). analogSetAttenuation(ADC_11db) extends the input range to approximately 3.3 V (without attenuation, the full-scale range is only ~1 V). map(value, inMin, inMax, outMin, outMax) rescales a number from one range to another — here, pot position (0–4095) becomes a blink delay (100–2000 ms). Turn the pot and watch the LED blink rate change; watch the Serial Monitor for live voltage readings.

Program 5: Wi-Fi Network Scan

This final program uses the ESP32’s Wi-Fi radio to scan for nearby networks and print them to the Serial Monitor. No Wi-Fi credentials are needed — the scan works without connecting. This is an excellent test that the Wi-Fi hardware is working.

#include <WiFi.h>

void setup() {
  Serial.begin(115200);
  Serial.println("Starting Wi-Fi scan...");

  // Set Wi-Fi to station mode (not AP) — required before scanning
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();   // ensure we are not connected to anything
  delay(100);
}

void loop() {
  int network_count = WiFi.scanNetworks();

  if (network_count == 0) {
    Serial.println("No networks found.");
  } else {
    Serial.printf("n%d networks found:n", network_count);
    Serial.printf("%-5s %-32s %-6s %sn", "No.", "SSID", "RSSI", "Security");
    Serial.println(String('-', 60));

    for (int i = 0; i < network_count; i++) {
      String security = (WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? "Open" : "Secured";
      Serial.printf("%-5d %-32s %-6d %sn",
                    i + 1,
                    WiFi.SSID(i).c_str(),
                    WiFi.RSSI(i),
                    security.c_str());
    }
  }

  WiFi.scanDelete();   // free scan results from heap
  Serial.println("nScanning again in 10 seconds...");
  delay(10000);
}

Upload and open the Serial Monitor at 115200. After a few seconds you will see a table of all Wi-Fi networks the ESP32 can detect, with their names (SSID), signal strength (RSSI in dBm — more negative means weaker), and security type. RSSI above -60 dBm is excellent; -70 to -80 dBm is usable; below -85 dBm is unreliable. The scan repeats every 10 seconds so you can watch signal strength change as you move the board around.

Understanding the Compilation Process

When you click Upload in Arduino IDE, three things happen. First, compilation: the IDE invokes the Xtensa GCC compiler to translate your C++ sketch into machine code. The output is an ELF file containing your program plus all library code. The IDE then runs esptool.py to compute flash memory regions and addresses from the ELF file. Second, flashing: esptool.py communicates with the ESP32 bootloader over UART at 921600 baud to erase the relevant flash sectors and write the new binary. Third, reset: esptool.py pulses the RTS line to trigger a hardware reset, causing the ESP32 to exit download mode and start running the new firmware.

The compilation output in the IDE console shows binary size and the percentage of flash used. A standard ESP32 DevKitC with the default 4 MB flash and “Default 4MB with spiffs” partition scheme gives you approximately 1.5 MB for your sketch. Complex sketches with BLE, TLS, and web servers can approach this limit; if they do, switch to the “Huge APP (3MB No OTA)” partition scheme, which gives your sketch 3 MB at the cost of OTA update capability.

Next Steps After These Five Programs

These five programs cover the fundamental input and output operations every ESP32 project relies on: output (LED control), input (button and analog), debugging (Serial), and connectivity (Wi-Fi scan). From here, the natural progression is connecting to a specific Wi-Fi network with a password, sending data to a server or cloud platform, reading a real sensor (DHT22 temperature, BMP280 pressure, or MPU6050 accelerometer), and storing data in NVS or SPIFFS. Each of those topics has its own guide in this series — follow the related guides below to continue.

Frequently Asked Questions

Projects to Build

Put this knowledge to work — try one of these hands-on projects.