The pins you cannot use
Six pins run the flash chip, six more cannot output at all, four are read at boot, and six give wrong readings once Wi-Fi is on. The silkscreen mentions none of it.
A third of the header is already busy
The four categories are different kinds of problem, and only one of them is fatal:
Flash pins (6–11) are not yours at any point. They connect the CPU to the flash chip a millimetre away inside the module, and the board cannot run without them. Boards that bring them out to the header are being unhelpful.
Input-only pins (34–39) work fine as inputs and cannot be outputs. They also have no internal pull-up or pull-down, so every button on one of them needs its own resistor. They are the natural home for analog sensors.
Strapping pins (0, 2, 12, 15) are yours once the board has booted. The danger is entirely at reset: whatever is attached to them is voting on how the chip starts. Use them as outputs, or as inputs to something that idles in the harmless direction.
ADC2 pins are a trap with a delay fuse. They work perfectly until the radio starts, and then return zero — which arrives in your project weeks after the sensor did, as "my sensor broke when I added Wi-Fi".
The rule that saves the most time
Wire the awkward things first. Anything that needs a pull-up, anything on an interrupt, anything analog and anything that idles low goes on a pin chosen from the list above; LEDs and relays go wherever is left. Doing it the other way round means rewiring after the first reboot.
It is different on every chip
The numbers here are the classic ESP32. The S3 has more pins and a different flash range, the C3 has 22 pins and no ADC2 conflict, and boards with octal PSRAM lose several more. The pin selector on any page here shows the map for the chip you picked, and the sketch above asks the board rather than a diagram.
The code
Rather than trusting a diagram found online, ask the board. This prints which pins on your chip can output and which cannot.
#include <soc/gpio_num.h>
void setup() {
Serial.begin(115200);
delay(200);
for (int p = 0; p < SOC_GPIO_PIN_COUNT; p++) {
if (!GPIO_IS_VALID_GPIO(p)) continue;
Serial.printf("GPIO %-2d %s\n", p,
GPIO_IS_VALID_OUTPUT_GPIO(p) ? "in + out" : "input only");
}
}
void loop() {}GPIO_IS_VALID_OUTPUT_GPIO is a macro from the SDK, so it knows the chip you compiled for. The answer changes between an ESP32 and an S3, and this follows.
MicroPython raises rather than printing a table. Trying to make an output out of a pin that cannot be one fails immediately, which is the check.
from machine import Pin
for p in range(0, 40):
try:
Pin(p, Pin.OUT)
print(p, "in + out")
except ValueError:
print(p, "input only or unusable")This is one of the places the interpreter is genuinely better than the compiler: an illegal pin is an exception at the line that caused it, not a sketch that silently does nothing.
When it does not work
You are on a flash pin. GPIO 6 to 11 are wired to the SPI flash chip inside the module, and interfering with them stops the CPU fetching instructions. Some boards bring them out to the header anyway.
GPIO 34 to 39 have no internal pull-ups. INPUT_PULLUP compiles, does nothing, and leaves the pin floating between reads. Add a physical 10 k resistor or move to a pin that has the hardware.
That pin is on ADC2, which shares hardware with the radio. Once Wi-Fi is running, ADC2 reads return an error the Arduino wrapper turns into a plausible-looking zero. Move the sensor to an ADC1 pin.
A strapping pin. GPIO 0, 2, 12 and 15 are sampled during reset; GPIO 12 in particular selects the flash voltage, and holding it high can stop the board booting at all.
Cores, flash size, PSRAM, MAC address. Eight lines in setup that settle the arguments you would otherwise have with your own board.
What the chip says about itself →Edit this page — content/esp32/pins-you-cannot-use.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.