Overview
The ESP32-WROVER is Espressif's classic dual-core WiFi + Bluetooth module with a superpower the plain WROOM doesn't have: 4MB of external PSRAM alongside its 4MB of flash. That extra RAM is what makes camera streaming, large JSON/TLS buffers, audio processing, JPEG handling, and MicroPython with room to breathe practical on an ESP32. If a project has ever thrown "out of memory" at you on a WROOM, the WROVER is the fix.
This is the raw solder-down module — castellated pads, onboard PCB antenna, RF shield — not a dev board. That's exactly what you want when a breadboard prototype graduates to a custom PCB: you control the regulator, the USB (or lack of it), and the pin breakout. It runs from a single 3.3V supply, and programming happens over TXD0/RXD0 with a USB-UART adapter using the standard two-signal auto-reset arrangement (EN + IO0).
Everything the ESP32 ecosystem offers applies: Arduino IDE, ESP-IDF, MicroPython, PlatformIO. A few pins carry rules worth knowing before you route a board — IO34/IO35 and SENSOR_VP/VN are input-only, the SD0-SD3/CLK/CMD pads belong to the internal flash and must stay unconnected, and because PSRAM claims IO16/IO17 internally, those pins aren't available on WROVER designs. The pinout diagram below is the map.
At a Glance
Specifications
| Parameter | Value |
| Module | ESP32-WROVER (B revision), PCB antenna |
| CPU | Xtensa LX6 dual-core, up to 240 MHz |
| SRAM / PSRAM | 520 KB internal + 4 MB external PSRAM |
| Flash | 4 MB |
| WiFi | 802.11 b/g/n, 2.4 GHz |
| Bluetooth | v4.2 BR/EDR + BLE |
| Supply Voltage | 3.0 - 3.6V (3.3V nominal) |
| Peak Current | ~500 mA during WiFi TX bursts |
| Input-Only Pins | IO34, IO35, SENSOR_VP (IO36), SENSOR_VN (IO39) |
| Reserved Pads | SD0-SD3, CLK, CMD (internal flash) — leave unconnected; IO16/IO17 used by PSRAM |
| Strapping Pins | IO0 (boot mode), IO2, IO12 (flash voltage), IO15 |
| Operating Temperature | -40°C to +85°C |
Pinout Diagram
Viewed from above with the antenna at the top: the left column runs GND, IO23, IO22, TXD0, RXD0, IO21 down through the SD pads; the right column runs GND, 3V3, EN, the sensor/input-only pins, then the general-purpose IOs. TXD0/RXD0 are the flashing UART, EN is reset (active low), and IO0 held low at reset selects download mode.
Wiring Guide
Minimal Boot Circuit
The bare minimum for the module to run your firmware:
| Module Pad | Connect To | Details |
|---|---|---|
| 3V3 | 3.3V supply | + 10uF and 100nF close to the pad |
| GND (all three) | Ground plane | Tie every GND pad |
| EN | 10k to 3.3V + 1uF to GND | Power-on reset RC |
| IO0 | 10k to 3.3V | Boots from flash by default |
Flashing With a USB-UART Adapter
| Module Pad | USB-UART Pin | Details |
|---|---|---|
| TXD0 | RXD | Crossed over |
| RXD0 | TXD | Crossed over |
| GND | GND | |
| IO0 | Hold LOW during reset | Enters download mode |
| EN | Pulse LOW | Reset |
Strapping Pins — What Not To Load at Boot
| Pin | Role at Reset | Rule of Thumb |
|---|---|---|
| IO0 | Boot mode select | Pull up; low at reset = download mode |
| IO2 | Must be low/floating to flash | Fine as an output (LED) after boot |
| IO12 | Flash voltage select | Keep LOW/floating at reset — high bricks boot on 3.3V flash |
| IO15 | Boot log silencing | Pull low to mute ROM messages if desired |
| IO34/IO35, VP/VN | Input-only | Perfect for ADC and buttons; no pull-ups inside |
Power Supply Design
| Requirement | Recommendation |
|---|---|
| Regulator | 3.3V LDO rated 600 mA+ (AMS1117-3.3, ME6211, or buck) |
| Bulk capacitance | 10-22 uF at the module's 3V3 pad |
| Decoupling | 100 nF ceramic right at the pad |
| Brownout symptoms | Reboots when WiFi starts = undersized supply |
| Antenna keep-out | No copper/ground under the antenna end of the module |
Code Examples
The first two sketches run in the Arduino IDE (board: "ESP32 Wrover Module", PSRAM: enabled). The MicroPython examples show flashing the interpreter and proving the PSRAM is alive.
Arduino IDE — PSRAM check + WiFi scan
// ESP32-WROVER - PSRAM check + WiFi scan
// Arduino IDE: Tools -> Board -> "ESP32 Wrover Module", PSRAM: "Enabled"
#include <WiFi.h>
void setup() {
Serial.begin(115200);
delay(500);
Serial.printf("Chip: %s, %d cores @ %d MHz\n",
ESP.getChipModel(), ESP.getChipCores(), ESP.getCpuFreqMHz());
Serial.printf("Flash: %u bytes\n", ESP.getFlashChipSize());
Serial.printf("PSRAM: %u bytes (%s)\n", ESP.getPsramSize(),
psramFound() ? "FOUND" : "NOT FOUND");
// Allocate 1MB in PSRAM to prove it's usable
uint8_t *big = (uint8_t *)ps_malloc(1024 * 1024);
Serial.printf("1MB PSRAM alloc: %s\n", big ? "OK" : "FAILED");
if (big) free(big);
WiFi.mode(WIFI_STA);
Serial.println("\nScanning WiFi...");
int n = WiFi.scanNetworks();
for (int i = 0; i < n; i++) {
Serial.printf("%2d: %-24s %d dBm\n",
i + 1, WiFi.SSID(i).c_str(), WiFi.RSSI(i));
}
}
void loop() {}
Arduino IDE — Big buffer web fetch (PSRAM in action)
// ESP32-WROVER - download into a PSRAM buffer
// Fill in your WiFi credentials before uploading.
#include <WiFi.h>
#include <HTTPClient.h>
const char *SSID = "YOUR_WIFI";
const char *PASS = "YOUR_PASSWORD";
void setup() {
Serial.begin(115200);
WiFi.begin(SSID, PASS);
while (WiFi.status() != WL_CONNECTED) { delay(300); Serial.print("."); }
Serial.printf("\nConnected: %s\n", WiFi.localIP().toString().c_str());
// 2MB buffer - impossible on a WROOM, trivial on a WROVER
size_t cap = 2 * 1024 * 1024;
uint8_t *buf = (uint8_t *)ps_malloc(cap);
Serial.printf("PSRAM buffer: %s\n", buf ? "allocated" : "failed");
HTTPClient http;
http.begin("http://example.com/");
int code = http.GET();
if (code == 200) {
String body = http.getString();
size_t n = min((size_t)body.length(), cap);
memcpy(buf, body.c_str(), n);
Serial.printf("Fetched %u bytes into PSRAM. First line:\n", n);
Serial.println(body.substring(0, body.indexOf('\n')));
} else {
Serial.printf("HTTP error: %d\n", code);
}
http.end();
free(buf);
}
void loop() {}
MicroPython — flash + PSRAM proof
# Flash MicroPython (SPIRAM build!) onto the WROVER over UART:
# pip install esptool
# esptool --port /dev/ttyUSB0 erase_flash
# esptool --port /dev/ttyUSB0 --baud 460800 write_flash 0x1000 \
# ESP32_GENERIC-SPIRAM-latest.bin
# (download the GENERIC-SPIRAM firmware from micropython.org)
# Then at the REPL:
import gc, esp, machine
print("CPU MHz:", machine.freq() // 1_000_000)
print("Flash size:", esp.flash_size())
gc.collect()
print("Free RAM:", gc.mem_free()) # ~4MB free = PSRAM active
# Allocate a 2MB bytearray - only possible with PSRAM
big = bytearray(2 * 1024 * 1024)
print("2MB bytearray OK, length:", len(big))
MicroPython — WiFi scan on the module
# ESP32-WROVER - MicroPython WiFi scan
import network
import time
sta = network.WLAN(network.STA_IF)
sta.active(True)
print("Scanning...")
for ssid, bssid, ch, rssi, sec, hidden in sta.scan():
print("{:24s} ch{:2d} {:4d} dBm".format(ssid.decode(), ch, rssi))
# Simple connect helper
def connect(ssid, password, timeout=15):
sta.connect(ssid, password)
t0 = time.time()
while not sta.isconnected():
if time.time() - t0 > timeout:
raise RuntimeError("WiFi connect timeout")
time.sleep(0.5)
print("Connected:", sta.ifconfig()[0])
# connect("YOUR_WIFI", "YOUR_PASSWORD")