1. Today's topic

ESP32/ESP32-C3 GPIO constraints and pin-selection rules: strapping pins, UART0, Flash/PSRAM pins, JTAG/SWD, input-only GPIO, ADC2 with Wi-Fi and board_pins.h organisation. The main idea: in embedded systems, GPIO cannot be selected merely because a pin is “free”. Each pin has a history: boot, Flash, debugging, analogue constraints, pull resistors, sleep/wakeup and peripheral functions.

2. Why this matters

Important project uses:

  • UART2 for EC25: TX17/RX16 on ESP32-WROOM-32U;
  • UART0 for CLI/flashing;
  • traffic-light inputs;
  • GPIO for EC25 PWRKEY/RESET;
  • GPIO for camera/radar/Jetson power;
  • 1-Wire DS18B20;
  • PPS/20 Hz synchronisation signals;
  • HIL through Raspberry Pi;
  • porting to STM32.

Incorrect GPIO choices produce strange symptoms:

  • The board sometimes fails to start;
  • firmware cannot be flashed;
  • ESP32 enters download mode;
  • UART logging is silent;
  • Wi-Fi breaks ADC operation;
  • JTAG/SWD does not work;
  • an external input changes the boot mode.

3. Strapping pins

Strapping pins are pins the microcontroller reads at reset/startup to select boot parameters. For the classic ESP32:

text
GPIO0
GPIO2
GPIO5
GPIO12 / MTDI
GPIO15 / MTDO

For ESP32-C3:

text
GPIO2
GPIO8
GPIO9

After startup, they may work as ordinary GPIO, but their level at reset has already affected boot.

4. GPIO0

text
GPIO0 = LOW at reset  -> serial bootloader / download mode
GPIO0 = HIGH at reset -> normal execution from flash

Practical conclusion: do not casually connect GPIO0 to an external signal that may be LOW at power-up. If an optocoupler input is connected to GPIO0, ESP32 may enter the bootloader instead of running the firmware.

5. GPIO2

For flashing, GPIO2 must be in a correct state. It is better not to use it for an input that may be strongly pulled to the wrong level at reset.

6. GPIO12 / MTDI

GPIO12 is particularly risky: it is related to selection of the Flash VDD_SDIO voltage. If GPIO12 is high at reset, ESP32 may select an unsuitable Flash voltage. Practical conclusion: avoid GPIO12 for external signals that may be HIGH at startup.

7. GPIO15 / MTDO

GPIO15 may affect boot logging. If it is in the wrong state, UART logging may appear silent.

8. Flash/PSRAM pins

For ESP32:

text
GPIO6-GPIO11 - normally SPI flash, do not use
GPIO16/GPIO17 - may be occupied by PSRAM on some modules

On ESP32-WROOM-32U without PSRAM, GPIO16/GPIO17 are often available, so UART2 TX17/RX16 may be acceptable. Recheck when changing to a WROVER/PSRAM module. On ESP32-C3, GPIO12-GPIO17 are often associated with Flash.

9. UART0

GPIO1/GPIO3 are normally used for flashing/debugging. Recommended policy:

text
UART0:
  flashing, monitor, CLI, HIL-debug
UART2:
  EC25 modem
other GPIO:
  inputs, power control, synchronisation signals

10. Input-only GPIO34-GPIO39

On ESP32, GPIO34-GPIO39 are input-only. They have no software-enabled pull-up/pull-down. Suitable for:

  • Discrete inputs;
  • ADC;
  • status signals.

Unsuitable for:

  • Relay control;
  • EC25 PWRKEY/RESET;
  • LED;
  • 1-Wire;
  • I2C;
  • lines requiring an internal pull resistor.

11. ADC2 and Wi-Fi

On ESP32, ADC2 conflicts with Wi-Fi. If Wi-Fi is used, it is better to plan analogue measurements on ADC1. Initial policy:

text
analogue measurements + Wi-Fi -> ADC1
use ADC2 only with a full understanding of the restriction

12. The STM32 parallel: preserve SWD

STM32 has a similar issue with debug pins:

text
PA13 / SWDIO
PA14 / SWCLK
NRST
BOOT0

Recommendations:

  • Do not allocate PA13/PA14 at an early stage;
  • bring NRST out to a connector;
  • do not leave BOOT0 floating;
  • keep the SWD connector accessible.

13. Common mistakes

  • Connecting an external input to GPIO0;
  • using GPIO12 as an ordinary input with an external pull-up;
  • allocating UART0 to operational peripherals;
  • using GPIO34-GPIO39 as outputs;
  • relying on an internal pull resistor for an important signal;
  • using ADC2 together with Wi-Fi;
  • disabling STM32 SWD in CubeMX.

14. Practical task

Create PIN_AUDIT.md:

markdown
# Pin audit
## Target MCU/module
- MCU: ESP32-WROOM-32U
- Framework: ESP-IDF 6.x
- Board revision: ...
## Reserved / dangerous pins
### ESP32 strapping pins
- GPIO0: BOOT / download mode
- GPIO2: boot mode related
- GPIO5: strapping
- GPIO12 / MTDI: flash voltage strap
- GPIO15 / MTDO: boot log strap
Rule:
Do not connect external uncontrolled signals to strapping pins.
### Flash / PSRAM
- GPIO6-GPIO11: do not use
- GPIO16/GPIO17: check exact module
### UART0
- GPIO1 TX0: console/flashing/debug
- GPIO3 RX0: console/flashing/debug
### Input-only
- GPIO34-GPIO39: input only, no internal pull-up/down
### ADC
- Prefer ADC1 when Wi-Fi is used.
- Avoid ADC2 for measurements that must work during Wi-Fi operation.

A table:

markdown
| Signal | GPIO | Direction | Boot-sensitive? | Pull | Owner component | Risk | Decision |
|---|---:|---|---|---|---|---|---|
| EC25_TXD | 17 | ESP32 TX | check module | external/device | modem_service | PSRAM conflict on some modules | OK for WROOM-32U |
| EC25_RXD | 16 | ESP32 RX | check module | external/device | modem_service | PSRAM conflict on some modules | OK for WROOM-32U |
| CLI_TXD | 1 | TX | UART0 | USB-UART | cli_service | boot/debug conflict | keep |
| CLI_RXD | 3 | RX | UART0 | USB-UART | cli_service | boot/debug conflict | keep |

15. A short recap

Design pin assignments before code. A basic ESP32 policy:

text
UART0 GPIO1/GPIO3:
  reserve for flashing, CLI, monitor, HIL recovery.
GPIO0/GPIO2/GPIO5/GPIO12/GPIO15:
  do not use for external signals with uncertain levels.
GPIO6-GPIO11:
  do not use, flash.
GPIO34-GPIO39:
  input only, external pulls are required when needed.
ADC:
  with Wi-Fi, prefer ADC1.
EC25 UART:
  UART2 TX17/RX16 is acceptable for the current ESP32-WROOM-32U,
  but recheck when changing the module.
A proposed output uses GPIO34 on the classic ESP32 described in the lesson. Which audit finding blocks this assignment?

Exercise

A UART2 assignment uses TX17/RX16 on ESP32-WROOM-32U without PSRAM. The design changes to a PSRAM module. List the assumptions that must be rechecked before reusing the old board_pins.h.

Self-check criteria: Name module-specific memory reservations and boot/debug checks; do not transfer the old pin map blindly.

Show the supplied answer

Recheck the exact module’s Flash/PSRAM pin reservations, whether GPIO16/GPIO17 are available, boot/strapping behaviour and debugging/flashing access. An assignment acceptable for the old module is not automatically acceptable for the new one. Record the selected module and external reset-time levels in the pin audit.