The ESP32 gives you a dual-core 240MHz processor, Wi-Fi, and Bluetooth on a board that costs less than a sandwich. You can program it with the same Arduino IDE you already know. This guide takes you from a blank PC to a working Wi-Fi web server that switches an LED from your phone. It covers driver installs, board selection, pinout traps, and the errors that stop most first uploads.
Quick Takeaways
- Install Arduino IDE 2.x, add the Espressif board URL, and install the esp32 by Espressif Systems package. You need no extra toolchain.
- The ESP32 runs at 3.3V logic. GPIO pins are not 5V tolerant.
- If upload fails with “Wrong boot mode,” hold the BOOT button while the IDE shows “Connecting…”.
- Your first project is a Blink sketch. Your second is a Wi-Fi web server that toggles GPIO2.
| Spec | ESP32 (classic, WROOM-32) |
|---|---|
| CPU | Dual-core Xtensa LX6, up to 240MHz |
| RAM | 520KB SRAM |
| Flash | 4MB typical (external) |
| Wireless | 802.11 b/g/n Wi-Fi, Bluetooth 4.2 + BLE |
| Logic level | 3.3V |
| GPIO | Up to 34 usable pins (varies by board) |
| Interfaces | I2C, SPI, UART, I2S, CAN, PWM |
What You Need
| Item | Notes |
|---|---|
| ESP32 DevKit board | ESP32-DevKitC, DOIT DevKit V1, or NodeMCU-32S all work |
| Data-capable USB cable | Many cheap cables carry power only |
| LED + 220Ω resistor | Optional if your board has an onboard LED |
| Breadboard + jumper wires | For the external LED |
| Computer | Windows, macOS, or Linux |
Step 1: Install Arduino IDE and USB Drivers
Download Arduino IDE 2.x from arduino.cc and install it. Then check which USB-to-serial chip your board uses. Look at the small square IC near the USB port.
| USB chip | Driver needed? | Typical boards |
|---|---|---|
| CP2102 / CP2104 (Silicon Labs) | Yes on Windows/macOS: install the CP210x VCP driver | DevKitC, many clones |
| CH340 / CH9102 (WCH) | Yes on Windows/macOS: install the CH34x driver | Budget clones |
| Native USB (ESP32-S3/C3) | No | S3, C3 boards with USB port |
Plug in the board. Windows should list a new COM port in Device Manager. Linux shows /dev/ttyUSB0 or /dev/ttyACM0. On Linux, add your user to the serial group so the IDE can open the port:
sudo usermod -a -G dialout $USER # Debian/Ubuntu; log out and back in afterward
Step 2: Add the ESP32 Board Package
The IDE does not ship with ESP32 support. Add Espressif’s package index.
- Open File → Preferences.
- Paste this URL into Additional boards manager URLs:
https://espressif.github.io/arduino-esp32/package_esp32_index.json
- Click OK.
- Open Tools → Board → Boards Manager (or press the board icon in the left sidebar).
- Search for esp32 and install esp32 by Espressif Systems.
The download is several hundred MB because it bundles compilers and tools. Let it finish. This guide targets the 3.x core. If you see older tutorials using ledcSetup(), they target the 2.x core. The 3.x API changed.
Step 3: Select Your Board and Port
Go to Tools → Board → esp32 and pick the entry that matches your hardware.
| Your board | Select this |
|---|---|
| Generic 30/38-pin DevKit (WROOM-32) | ESP32 Dev Module |
| DOIT ESP32 DevKit V1 | DOIT ESP32 DEVKIT V1 |
| ESP32-S3 DevKit | ESP32S3 Dev Module |
| ESP32-C3 DevKit | ESP32C3 Dev Module |
| Not listed | ESP32 Dev Module (safe default) |
Then set Tools → Port to the new COM or tty port. Leave Upload Speed at 921600. Drop it to 115200 if uploads fail midway.
Pinout Configuration: Read This Before Wiring
Most first-project failures come from wrong pin choices, not code. Know these rules for the classic ESP32.
| Pin group | Rule |
|---|---|
| GPIO34–GPIO39 | Input only. No output, no internal pull-ups |
| GPIO6–GPIO11 | Connected to the onboard flash. Never use them |
| GPIO0, GPIO2, GPIO5, GPIO12, GPIO15 | Strapping pins. Their level at reset sets the boot mode |
| ADC2 pins (GPIO0, 2, 4, 12–15, 25–27) | Do not work while Wi-Fi is active. Use ADC1 (GPIO32–39) instead |
| GPIO21 / GPIO22 | Default I2C SDA / SCL |
| GPIO23 / GPIO19 / GPIO18 | Default SPI (VSPI) MOSI / MISO / SCLK |
| GPIO1 / GPIO3 | UART0 TX / RX, used by the USB serial link |
Each GPIO supplies at most about 12mA safely. The absolute maximum per pin is 40mA. Always use a 220Ω–330Ω series resistor with an LED.
Project 1: Blink an LED
Wiring
Many DevKit boards have an onboard LED on GPIO2. If yours does not, wire an external one:
- GPIO2 → 220Ω resistor → LED anode (long leg)
- LED cathode (short leg) → GND
Code
// Blink: toggles an LED on GPIO2 every 500 ms
#define LED_PIN 2 // GPIO2: onboard LED on most DevKit boards
void setup() {
pinMode(LED_PIN, OUTPUT); // configure GPIO2 as a push-pull output
}
void loop() {
digitalWrite(LED_PIN, HIGH); // drive pin to 3.3V, LED on
delay(500); // wait 500 ms
digitalWrite(LED_PIN, LOW); // drive pin to 0V, LED off
delay(500);
}
Upload
- Click the Verify (checkmark) button to compile. The first build takes a minute or two.
- Click Upload (right arrow).
- When the console shows
Connecting........, press and hold BOOT if it stalls. Release it once the upload begins. - The LED should blink twice per second.
If it works, your toolchain, drivers, and board are all good.
Project 2: Wi-Fi Web Server to Control the LED
Now use the feature that sets the ESP32 apart from an Arduino Uno. This sketch connects to your 2.4GHz network and hosts a page with ON and OFF buttons. The ESP32 does not support 5GHz Wi-Fi.
Circuit
Reuse the Blink wiring: GPIO2 → 220Ω → LED → GND.
Code
// ESP32 Wi-Fi LED controller using the built-in WebServer library
#include <WiFi.h>
#include <WebServer.h>
const char* WIFI_SSID = "YOUR_SSID"; // 2.4 GHz network name
const char* WIFI_PASS = "YOUR_PASSWORD"; // network password
#define LED_PIN 2
WebServer server(80); // HTTP server on TCP port 80
bool ledState = false;
// Build the HTML page; the button label reflects the current state
String buildPage() {
String html = "<!DOCTYPE html><html><head>";
html += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
html += "<title>ESP32 LED</title></head><body style='font-family:sans-serif;text-align:center'>";
html += "<h1>ESP32 LED Control</h1>";
html += "<p>LED is <b>" + String(ledState ? "ON" : "OFF") + "</b></p>";
html += ledState ? "<a href='/off'><button>Turn OFF</button></a>"
: "<a href='/on'><button>Turn ON</button></a>";
html += "</body></html>";
return html;
}
void handleRoot() { server.send(200, "text/html", buildPage()); }
void handleOn() {
ledState = true;
digitalWrite(LED_PIN, HIGH);
server.sendHeader("Location", "/"); // redirect back to the main page
server.send(303);
}
void handleOff() {
ledState = false;
digitalWrite(LED_PIN, LOW);
server.sendHeader("Location", "/");
server.send(303);
}
void setup() {
Serial.begin(115200); // UART0 at 115200 baud for debug output
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
WiFi.mode(WIFI_STA); // station mode: join an existing network
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.print("Connecting to Wi-Fi");
while (WiFi.status() != WL_CONNECTED) { // wait for the DHCP lease
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("IP address: ");
Serial.println(WiFi.localIP()); // open this address in your browser
server.on("/", handleRoot); // register URL handlers
server.on("/on", handleOn);
server.on("/off", handleOff);
server.begin();
}
void loop() {
server.handleClient(); // process incoming HTTP requests
}
Run It
- Replace
YOUR_SSIDandYOUR_PASSWORDwith your network credentials. - Upload the sketch.
- Open Tools → Serial Monitor and set the baud rate to 115200.
- Press the EN (reset) button on the board.
- Wait for the printed IP address, such as
192.168.1.57. - Enter that address in a browser on the same network. Tap the button and watch the LED respond.
How the Code Works
WiFi.mode(WIFI_STA)makes the chip a client. UseWIFI_APif you want the board to create its own network.server.on()maps a URL path to a handler function. This is lightweight routing.- The
303redirect stops the browser from re-sending/onwhen you refresh the page. server.handleClient()must run often. Avoid longdelay()calls inloop(), or the page will feel unresponsive.
ESP32 vs. ESP8266 vs. Arduino Uno
| Feature | ESP32 | ESP8266 | Arduino Uno |
|---|---|---|---|
| CPU | Dual-core 240MHz | Single-core 80/160MHz | 16MHz AVR |
| SRAM | 520KB | ~80KB | 2KB |
| Wi-Fi | Yes (2.4GHz) | Yes (2.4GHz) | No |
| Bluetooth | Yes (Classic + BLE) | No | No |
| Logic level | 3.3V | 3.3V | 5V |
| ADC | 12-bit, up to 18 channels | 10-bit, 1 channel | 10-bit, 6 channels |
| Typical price | Low | Lowest | Moderate |
| Best for | IoT, BLE, multitasking | Simple Wi-Fi projects | Beginner logic, 5V shields |
Common Upload and Boot Errors
| Symptom | Likely cause | Fix |
|---|---|---|
| No COM port appears | Charge-only cable or missing driver | Swap the cable. Install CP210x or CH34x driver |
Failed to connect to ESP32: Wrong boot mode |
Chip is not in download mode | Hold BOOT during “Connecting…”. Release after upload starts |
A fatal error occurred: Timed out waiting for packet header |
Weak USB power or wrong port | Use a rear PC port. Try a shorter cable. Re-select the port |
| Upload stalls at a percentage | Upload speed too high | Set Upload Speed to 115200 |
Brownout detector was triggered loop |
Supply sags under Wi-Fi load | Use a 5V/1A+ supply. Add a 100µF capacitor across 3V3 and GND |
| Garbled Serial Monitor text | Baud rate mismatch | Match the monitor to Serial.begin(115200) |
| Wi-Fi never connects | 5GHz network or wrong credentials | Use a 2.4GHz SSID. Re-check the password |
ets_main.c reboot loop on boot |
A strapping pin is held at the wrong level | Disconnect wiring from GPIO0, 2, 12, 15 and retry |
Practical Next Steps
Once Blink and the web server run, pick a small build that adds one new skill each time.
| Project | New skill | Key parts |
|---|---|---|
| Temperature/humidity monitor | Read an I2C sensor and serve JSON | BME280 on GPIO21/22 |
| Wi-Fi relay switch | Switch mains loads safely | 5V relay module with optocoupler, external supply |
| BLE beacon | Use Bluetooth Low Energy | Built-in radio, BLEDevice library |
| Touch-controlled light | Use capacitive touch pins | touchRead(T0) on GPIO4 |
| Deep-sleep sensor node | Cut power use to ~10µA | esp_deep_sleep_start(), RTC wake timer |
Follow these habits from the start:
- Keep Wi-Fi credentials in a separate
secrets.hfile and exclude it from version control. - Use the ADC1 pins for analog reads whenever Wi-Fi is on.
- Add a level shifter between the ESP32 and any 5V sensor output.
- Print debug messages with
Serial.println()early. They save hours later.
FAQ
Is the ESP32 compatible with the Arduino IDE?
Yes. The esp32 by Espressif Systems board package adds full support to Arduino IDE 1.8.x and 2.x. You use standard functions like digitalWrite() and Serial.begin(), plus ESP32-specific libraries for Wi-Fi and Bluetooth.
Why won’t my ESP32 upload code?
The usual causes are a charge-only USB cable, a missing CP210x or CH340 driver, or the chip not entering download mode. Use a data cable, install the driver, and hold the BOOT button while the IDE shows “Connecting…”. Lowering Upload Speed to 115200 fixes many stalls.
Can I power the ESP32 with 5V?
Yes, through the VIN or 5V pin or the USB port. The onboard regulator drops it to 3.3V. The GPIO pins are 3.3V only. Connecting a 5V signal directly to a GPIO can damage the chip, so use a level shifter or a voltage divider.
Which ESP32 board should a beginner buy?
Buy a 30-pin or 38-pin ESP32 DevKit with a WROOM-32 module and a CP2102 USB chip. It has the most tutorials and library support. It also fits a standard breadboard. Choose the ESP32-S3 if you need native USB or more RAM, and the ESP32-C3 if you want a cheap RISC-V board.



