Mistakes Are Faster Than Reading Documentation
Every ESP32 developer has a list of hours lost to debugging problems that, in retrospect, had obvious causes. The frustrating reality is that many of these mistakes are not obvious at all when you first encounter them — the error messages are cryptic, the behaviour is inconsistent, and the root cause often hides beneath an assumption you did not know you were making. This guide documents the nine most common ESP32 beginner mistakes, explains exactly why each one happens, and gives you the specific fix.
Mistake 1: Using ADC2 Pins While Wi-Fi is Active
ESP32 has two ADC units. ADC2 is shared with the Wi-Fi radio hardware. When Wi-Fi is enabled and connecting (or connected), any call to analogRead() on an ADC2 pin will fail, returning -1 or a completely wrong value. The affected pins are GPIO 0, 2, 4, 12, 13, 14, 15, 25, 26, and 27.
How it manifests: Your analog sensor reads fine during setup (before Wi-Fi connects), then returns garbage or 4095 constantly after WiFi.begin() completes.
Fix: Use only ADC1 pins for analog reads in Wi-Fi projects. ADC1 pins are GPIO 32, 33, 34, 35, 36 (VP), and 39 (VN). Move your potentiometer, LDR, or analog sensor to GPIO 32 or 34 and analogRead() will return accurate values regardless of Wi-Fi state.
Mistake 2: Power Supply Brownout Under Wi-Fi Load
The ESP32’s Wi-Fi transmitter draws up to 500 mA in short bursts. Most laptop USB ports are rated at 500 mA total — shared with the USB hub chip, the keyboard, and any other devices on the same hub. Long or thin USB cables add resistance, dropping voltage. The ESP32 has an onboard brownout detector that resets the chip when 3.3 V drops below ~2.4 V, producing the telltale message: Brownout detector was triggered.
How it manifests: Random resets during Wi-Fi connection, OTA updates, or HTTP requests. The Serial Monitor prints “Brownout detector was triggered” followed by a reset reason code.
Fix: Use a dedicated 5 V / 2 A USB wall adapter (not a laptop USB port or cheap phone charger). Use a USB cable under 1 m with 28 AWG or thicker power conductors. Add a 100 µF electrolytic capacitor between the 3.3 V and GND pins on your breadboard — this buffers peak current spikes. If the problem persists, measure the 3.3 V rail with a multimeter during a Wi-Fi transmit burst; any reading below 3.0 V indicates an inadequate supply.
Mistake 3: Connecting GPIO 6–11 to External Circuits
GPIO 6, 7, 8, 9, 10, and 11 are internally connected to the SPI flash interface. Any external connection to these pins disrupts communication between the ESP32 and the flash chip holding your firmware. The result is usually an immediate boot loop, or the ESP32 refusing to accept firmware uploads.
How it manifests: The board endlessly prints boot messages and resets, or the Arduino IDE fails to upload with a “Failed to connect” error even though you can see the board in Device Manager. The problem started after you wired something to a GPIO in the 6–11 range.
Fix: Disconnect anything wired to GPIO 6–11 immediately. On the 38-pin DevKitC these pins are physically absent from the headers — if you have a different board or a custom PCB, verify the silkscreen labels carefully. Never use GPIO 6–11 in user code.
Mistake 4: String Heap Fragmentation and Memory Exhaustion
The Arduino String class is convenient but dangerous in long-running ESP32 sketches. Each String concatenation with += or + creates a new heap allocation and immediately frees the old one. With many small allocations and frees of different sizes, the heap becomes fragmented — you may have 100 KB free in total but no single contiguous block large enough to satisfy a 50 KB allocation. The symptom is a working sketch that crashes after hours or days, often in the middle of an HTTP request or JSON parse.
How it manifests: The ESP32 runs fine for hours, then crashes with a “panic” or returns a NULL from malloc. ESP.getFreeHeap() may still show a non-zero value when the crash happens.
Fix: Reserve fixed-size buffers and use snprintf() and strcat() instead of String concatenation in long-running code. Check ESP.getMaxAllocHeap() to see the largest available contiguous block. For JSON, use ArduinoJSON’s StaticJsonDocument<N> with a stack-allocated buffer of known size. For HTTP bodies, stream the response in chunks rather than buffering the entire body as a String.
Mistake 5: Wrong Baud Rate on Serial Monitor
This mistake is common enough that it deserves a section. The Arduino IDE’s Serial Monitor has a baud rate dropdown in its bottom-right corner. If it does not match the rate in Serial.begin(), the output is garbage characters or nothing at all.
How it manifests: The Serial Monitor shows random characters like ???⸮⸮⸮ or shows nothing. Your sketch uploaded successfully and the device is running (you can tell because the LED blinks), but Serial output is missing.
Fix: Set both Serial.begin(115200) in your sketch and the Serial Monitor dropdown to 115200. The ESP32 ROM bootloader also prints at 115200, so keeping everything at 115200 means you see both boot messages and sketch output in one monitor session. After changing the Serial Monitor baud rate, press Enter in the monitor to request fresh output.
Mistake 6: Not Handling Wi-Fi Disconnection in Long-Running Sketches
Wi-Fi connections are not permanent. A router restart, a signal drop, or DHCP lease expiry can disconnect an ESP32 that has been running for days. If your sketch assumes Wi-Fi is always connected and calls client.connect() or http.GET() on a disconnected socket, it will hang or return error codes silently, accumulating failures without recovering.
How it manifests: Your ESP32 works perfectly for the first day, then stops posting data. The device is running (LED blinks on), but no data arrives at the server. A reboot fixes it temporarily.
Fix: Add a connection check before every network operation:
if (WiFi.status() != WL_CONNECTED) {
Serial.println("Wi-Fi lost. Reconnecting...");
WiFi.disconnect();
WiFi.begin(SSID, PASS);
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED && millis() - start < 15000) {
delay(500);
}
}
if (WiFi.status() == WL_CONNECTED) {
// proceed with HTTP call
}
Add a hardware watchdog as a last resort: esp_task_wdt_init(30, true) resets the chip if your main loop does not call esp_task_wdt_reset() within 30 seconds — catching truly stuck states.
Mistake 7: GPIO Output State at Boot Causing Relay or Motor Triggers
Several ESP32 GPIO pins have defined states before your sketch begins: GPIO 1 and 3 toggle during the ROM boot log, GPIO 5 starts high (boot strapping pull-up), and some pins may float briefly before being driven. If you connect a relay module directly to these pins, the brief transient can trigger the relay at power-on, even before your sketch runs — an alarming behaviour for mains-controlled devices.
How it manifests: A relay or motor briefly activates the instant the ESP32 is powered, before the sketch logic could have turned it on.
Fix: Use active-low relay modules (which require a LOW signal to energise the coil) and configure the control GPIO as OUTPUT with a HIGH initial state: pinMode(relayPin, OUTPUT); digitalWrite(relayPin, HIGH);. Set this in the first line of setup() and choose a relay control pin that starts high by default (GPIO 5, for example, which has a boot-time pull-up). For motor controllers, add an enable line that your sketch explicitly raises after initialising.
Mistake 8: Forgetting to Call Wire.begin() or SPI.begin()
I²C and SPI buses must be initialised before any sensor library can use them. The Arduino ESP32 core does not automatically start Wire or SPI. If you include a sensor library that uses I²C but forget Wire.begin(), the first sensor.begin() call may appear to succeed (some libraries do not check return values) but all subsequent reads return zero or garbage.
How it manifests: BME280, MPU6050, SSD1306, and similar I²C sensors all read 0 or return NaN for every value. The sensor is wired correctly, the address matches, but data is always wrong.
Fix: Always add these lines to setup() before any sensor initialisation:
Wire.begin(); // default: SDA=GPIO21, SCL=GPIO22
// or with custom pins:
Wire.begin(sdaPin, sclPin);
For SPI sensors: SPI.begin(); or SPI.begin(sckPin, misoPin, mosiPin, csPin);. Use an I²C scanner sketch to confirm the sensor address is visible on the bus before adding it to your main project.
Mistake 9: Blocking Loops That Starve the Wi-Fi Stack
The ESP32’s Arduino core runs the Wi-Fi stack as a FreeRTOS task on Core 0 with a specific priority. If your sketch runs a long blocking loop on Core 1 (where Arduino loop() executes) that never yields — a while(true) polling loop, a delay(50000) call, or intensive computation — the FreeRTOS scheduler may not give the Wi-Fi task enough processor time. The result is Wi-Fi disconnections, HTTP timeouts, or OTA update failures mid-transfer.
How it manifests: MQTT clients disconnect during heavy computation. An HTTP POST that worked fine during idle fails when you add an FFT or image processing routine to the loop. OTA updates stall at 50% and time out.
Fix: Prefer non-blocking code patterns using millis() instead of delay() for timing. For genuinely long computations, yield the CPU periodically with yield() or vTaskDelay(1) every few thousand iterations. For very long operations (audio processing, FFT of large buffers), pin the computation to Core 1 with a FreeRTOS task and keep loop() lightweight:
void compute_task(void *params) {
while (true) {
do_heavy_computation();
vTaskDelay(10); // yield every cycle
}
}
void setup() {
xTaskCreatePinnedToCore(compute_task, "compute", 8192, NULL, 1, NULL, 1);
}
Building Good Habits Early
Most of these mistakes share a common thread: they result from assumptions carried over from simpler microcontrollers (Arduino Uno, for example) that do not apply to the ESP32’s more complex hardware. The ESP32 has a radio that shares ADC resources, a boot sequence that reads specific GPIO states, a memory model with a heap that can fragment, and an RTOS that requires cooperative scheduling. Internalising these differences early — rather than discovering them one crash at a time — is what separates an ESP32 developer who ships projects from one who spends days debugging inexplicable failures.