Author: Bex (for Pierce)
Scope: How to generate real audio (polyphony) on ESP32, what “peripherals” are, and a practical overview of ESP32 (WROOM-32 class) pins & capabilities. Includes a ready-to-run chord synth and a Mermaid pinout diagram.
⚠️ Board variance note: The mapping below targets the common ESP32-WROOM-32 / DevKit v1 family. Some boards re-label or reserve certain pins (e.g., PSRAM modules, onboard flash). Always confirm with your module’s schematic/datasheet.
tone() uses a single timer—great for one square wave. Ask for multiple different notes simultaneously and later calls override earlier ones. PWM (“LEDC”) can give you separate channels, but channels share timers so true polyphony becomes tricky and often sounds harsh (square waves + beating).
ESP32 has two 8‑bit DACs:
- DAC1 → GPIO25
- DAC2 → GPIO26
They output true analog voltage (0–3.3V), so you can mix multiple voices in software and feed the composite waveform to a small amplifier → speaker. Result: smooth, musical tones.
#include <driver/dac.h>
#include <math.h>
// --- Config ---
#define SAMPLE_RATE 44100 // Hz (try 22050 for lighter CPU)
#define VOLUME 127 // 0-127 for 8-bit DAC range
#define DAC_CHANNEL DAC_CHANNEL_1 // GPIO 25 (DAC1)
// --- Frequencies for a C major chord (C4, E4, G4) ---
float freqs[] = {261.63f, 329.63f, 392.00f};
void setup() {
dac_output_enable(DAC_CHANNEL);
}
void loop() {
static uint32_t t = 0; // sample counter
float sample = 0.0f;
// Mix 3 sines
for (int i = 0; i < 3; i++) {
sample += sinf(2.0f * M_PI * freqs[i] * (float)t / SAMPLE_RATE);
}
// Average to -1..+1 → scale to 0..255
sample = (sample / 3.0f + 1.0f) * VOLUME; // center & scale
uint8_t out = (uint8_t) sample;
dac_output_voltage(DAC_CHANNEL, out);
// ~44.1 kHz sample rate
delayMicroseconds(1000000 / SAMPLE_RATE);
t++;
}Hookup: GPIO25 → 1kΩ → small audio amp input (PAM8302/MAX98357A/etc.) → speaker. GND to GND.
(Driving a speaker directly from the pin is only for quick, very low volume tests.)
Where to go next: Use I²S in built-in DAC mode to DMA-stream audio (cleaner and non‑blocking).
They’re dedicated hardware blocks that handle time‑critical tasks so your CPU isn’t bit‑banging everything.
| Peripheral | Role | Typical Uses |
|---|---|---|
| GPIO | Digital I/O | Buttons, LEDs, relays |
| UART | Serial | Console logs, GPS modules |
| SPI | High‑speed serial | Displays, flash, SD cards |
| I²C | 2‑wire serial | Sensors, RTC, IO expanders |
| LEDC (PWM) | Timed pulses | LED dimming, motors, buzzers |
| ADC | Analog→digital | Potentiometers, sensors |
| DAC | Digital→analog | Audio/synth, CV outputs |
| I²S | Audio serial | DAC/amp streaming, DMA |
| RMT | Precise waveforms | IR, NeoPixels, custom protocols |
| Timers/Watchdog | Scheduling/safety | Periodic tasks, hang recovery |
| Wi‑Fi / BT | Radios | Connectivity, BLE sensors |
Key categories: Input‑only, ADC, DAC, Touch, PWM (LEDC), I²C/SPI/UART defaults, Strapping/boot pins. Most pins are highly multiplexed—you can remap many functions in software.
| GPIO | Notes / Special | ADC | Touch | PWM | Default Uses (common) |
|---|---|---|---|---|---|
| 0 | Strapping (boot), Touch T1 | ADC2 | T1 | ✔︎ | Often used with care; boot mode select |
| 1 (TX0) | UART0 TX (USB Prog) | — | — | ✔︎ | Serial TX to USB bridge |
| 2 | Strapping, LED on some boards, Touch T2 | ADC2 | T2 | ✔︎ | Keep low on boot for some modules |
| 3 (RX0) | UART0 RX (USB Prog) | — | — | ✔︎ | Serial RX from USB bridge |
| 4 | Touch T0 | ADC2 | T0 | ✔︎ | General I/O |
| 5 | VSPI CS (default), Strapping | — | — | ✔︎ | SPI CS or general I/O |
| 12 | Strapping (MTDI), Touch T5 | ADC2 | T5 | ✔︎ | Beware boot mode with pull‑ups/downs |
| 13 | HSPI MOSI, Touch T4 | ADC2 | T4 | ✔︎ | SPI alt or general I/O |
| 14 | HSPI SCLK, Touch T6 | ADC2 | T6 | ✔︎ | SPI alt or general I/O |
| 15 | HSPI CS, Touch T3, Strapping (MTDO) | ADC2 | T3 | ✔︎ | Boot strap pin—use carefully |
| 16 | UART2 RX (default) | — | — | ✔︎ | General I/O / UART |
| 17 | UART2 TX (default) | — | — | ✔︎ | General I/O / UART |
| 18 | VSPI SCLK (default) | — | — | ✔︎ | SPI clock |
| 19 | VSPI MISO (default) | — | — | ✔︎ | SPI MISO |
| 21 | I²C SDA (common) | — | — | ✔︎ | Any I/O; popular SDA |
| 22 | I²C SCL (common) | — | — | ✔︎ | Any I/O; popular SCL |
| 23 | VSPI MOSI (default) | — | — | ✔︎ | SPI MOSI |
| 25 | DAC1, ADC2 | ADC2 | — | ✔︎ | Analog out (DAC), also ADC |
| 26 | DAC2, ADC2 | ADC2 | — | ✔︎ | Analog out (DAC), also ADC |
| 27 | Touch T7 | ADC2 | T7 | ✔︎ | General I/O |
| 32 | Input‑only, Touch T9, RTC | ADC1 | T9 | ✖︎ | Safe input, great for sensors |
| 33 | Input‑only, Touch T8, RTC | ADC1 | T8 | ✖︎ | Safe input |
| 34 | Input‑only | ADC1 | — | ✖︎ | No output; sensor input |
| 35 | Input‑only | ADC1 | — | ✖︎ | No output; sensor input |
| 36 (SVP) | Input‑only | ADC1 | — | ✖︎ | Internal sensors, analog in |
| 39 (SVN) | Input‑only | ADC1 | — | ✖︎ | Internal sensors, analog in |
Touch map: T0=4, T1=0, T2=2, T3=15, T4=13, T5=12, T6=14, T7=27, T8=33, T9=32
ADC1: 32–39 (works even with Wi‑Fi active) • ADC2: 0, 2, 4, 12–15, 25–27 (contention with Wi‑Fi)
DAC: 25 (DAC1), 26 (DAC2)
Input‑only: 34, 35, 36 (SVP), 39 (SVN)
Strapping pins (boot‑mode sensitive): 0, 2, 4, 5, 12, 15
Rule of thumb: Prefer ADC1 for analog reads when Wi‑Fi is on. Avoid using strapping pins for outputs unless you know the boot constraints.
- UART0: TX0=GPIO1, RX0=GPIO3 (USB programming/Serial Monitor)
- UART1: TX1=GPIO10, RX1=GPIO9 (usually used by flash on many modules; avoid)
- UART2: TX2=GPIO17, RX2=GPIO16 (nice spare UART)
- I²C (soft‑assignable): common default SDA=GPIO21, SCL=GPIO22
- VSPI: SCLK=18, MISO=19, MOSI=23, CS=5
- HSPI (alt SPI): SCLK=14, MISO=12, MOSI=13, CS=15
Mermaid isn’t a PCB layout tool, but this summary graph shows categories & key purposes. (Open this file in a Mermaid‑capable viewer/editor to render.)
flowchart LR
subgraph ESP32 [ESP32 WROOM-32 Class]
A[Analog & Touch<br/>GPIO32-39 ADC1<br/>GPIO0,2,4,12-15,25-27 ADC2<br/>Touch T0-T9<br/>Input-only: 34,35,36,39]
D[Digital I/O<br/>Most GPIOs PWM -LEDC-<br/>Avoid boot straps: 0,2,4,5,12,15]
U[UART<br/>UART0 TX1/RX3<br/>UART2 TX17/RX16]
S[SPI<br/>VSPI 18/19/23/5<br/>HSPI 14/12/13/15]
I2C[I²C<br/>SDA=21<br/>SCL=22]
DAC[DAC<br/>DAC1=25<br/>DAC2=26]
RMT[RMT<br/>Precision waveforms<br/>-NeoPixels/IR-]
I2S[I²S<br/>Audio DMA → DAC/Codecs]
WIFI[Wi-Fi/BT Radios]
end
A --> DAC
D --> RMT
D --> S
D --> U
D --> I2C
DAC --> I2S
I2S --> WIFI
Although the module has a lot of pins with various functions, some of them may not be suitable for your projects. The table below shows which pins are safe to use and which pins should be used with caution.
- ✅ Your top priority pins. They are perfectly safe to use.
⚠️ Pay close attention because their behavior, particularly during boot, can be unpredictable. Use them only when absolutely necessary.- ❌ It is recommended that you avoid using these pins.
| Pin | Pin Label | GPIO | Safe to use? | Reason |
|---|---|---|---|---|
| 4 | SENSOR_VP | GPIO36 | Input only GPIO, cannot be configured as output | |
| 5 | SENSOR_VN | GPIO39 | Input only GPIO, cannot be configured as output | |
| 6 | IO34 | GPIO34 | Input only GPIO, cannot be configured as output | |
| 7 | IO35 | GPIO35 | Input only GPIO, cannot be configured as output | |
| 8 | IO32 | GPIO32 | ✅ | |
| 9 | IO33 | GPIO33 | ✅ | |
| 10 | IO25 | GPIO25 | ✅ | |
| 11 | IO26 | GPIO26 | ✅ | |
| 12 | IO27 | GPIO27 | ✅ | |
| 13 | IO14 | GPIO14 | ✅ | |
| 14 | IO12 | GPIO12 | must be LOW during boot | |
| 16 | IO13 | GPIO13 | ✅ | |
| 17 | SHD/SD2 | GPIO9 | ❌ | Connected to Flash memory |
| 18 | SWP/SD3 | GPIO10 | ❌ | Connected to Flash memory |
| 19 | SCS/CMD | GPIO11 | ❌ | Connected to Flash memory |
| 20 | SCK/CLK | GPIO6 | ❌ | Connected to Flash memory |
| 21 | SDO/SD0 | GPIO7 | ❌ | Connected to Flash memory |
| 22 | SDI/SD1 | GPIO8 | ❌ | Connected to Flash memory |
| 23 | IO15 | GPIO15 | must be HIGH during boot, prevents startup log if pulled LOW | |
| 24 | IO2 | GPIO2 | must be LOW during boot and also connected to the on-board LED | |
| 25 | IO0 | GPIO0 | must be HIGH during boot and LOW for programming | |
| 26 | IO4 | GPIO4 | ✅ | |
| 27 | IO16 | GPIO16 | ✅ | |
| 28 | IO17 | GPIO17 | ✅ | |
| 29 | IO5 | GPIO5 | must be HIGH during boot | |
| 30 | IO18 | GPIO18 | ✅ | |
| 31 | IO19 | GPIO19 | ✅ | |
| 33 | IO21 | GPIO21 | ✅ | |
| 34 | RXD0 | GPIO3 | ❌ | Rx pin, used for flashing and debugging |
| 35 | TXD0 | GPIO1 | ❌ | Tx pin, used for flashing and debugging |
| 36 | IO22 | GPIO22 | ✅ | |
| 37 | IO23 | GPIO23 | ✅ |
The ESP32-WROOM-32 module has two ADC (Analog to Digital Converter) blocks; ADC1 and ADC2. Each block has multiple channels:
- ADC1: contains 6 channels (labeled as ADC1_CH0 and ADC1_CH3 to ADC1_CH7).
- ADC2: contains 10 channels (labeled as ADC2_CH0 to ADC2_CH9).
he resolution of the ADCs on the ESP32 can be configured up to 12 bits. This means that the ADC can detect 4096 (2^12) discrete analog levels. This results in a resolution of 3.3 V (operating voltage) / 4096 units, or 0.0008 volts (0.8 mV) per unit.
⚠️ Warning:When Wi-Fi is enabled, the ADC2 pins cannot be used. If you need Wi-Fi, consider using the ADC1 pins instead.
The ESP32-WROOM-32 module contains two 8-bit Digital-to-Analog Converters (DACs). These DACs are useful for converting digital signals into analog voltages.
The ESP32-WROOM-32 module has ten capacitive touch-enabled GPIOs labeled TOUCH0 through TOUCH9. These pins work by measuring the change in capacitance when a finger or conductive object is near the surface of the pin.
They can be used for various applications, such as touch buttons, touch sliders, or even basic gesture recognition. They can also be used to wake the ESP32 from deep sleep, which is particularly useful in power-sensitive applications.
The ESP32-WROOM-32 module has two I2C bus interfaces, but no dedicated I2C pins. Instead, it allows for flexible pin assignment, meaning any GPIO pin can be configured as I2C SDA (data line) and SCL (clock line).
However, GPIO21 (SDA) and GPIO22 (SCL) are commonly used as the default I2C pins to make it easier for people using existing Arduino code, libraries, and sketches.
The ESP32-WROOM-32 module features three SPIs (SPI, HSPI, and VSPI). HSPI and VSPI are commonly used for general purposes, while the third one is used for interfacing with the SPI flash memory integrated on the module.
Similar to I2C, the ESP32 allows flexible pin assignment for SPI. This means that any GPIO pin can be configured as SPI pins.
The ESP32-WROOM-32 module has three UART interfaces: UART0, UART1, and UART2. These interfaces enable serial communication with various peripherals or for logging and debugging purposes.
⚠️ Please note that the UART1 pins (GPIO 9 and GPIO 10) are used for interfacing with the SPI flash memory integrated on the module, so you can’t use them. However, you can still use UART1 by bit-banging the UART protocol on other GPIO pins.
Besides the basic TX and RX pins, UART interfaces on the ESP32 also support RTS (Request To Send) and CTS (Clear To Send) for hardware flow control, though these are less commonly used.
Almost all GPIO pins on the module can be configured to generate PWM output.
The PWM on the ESP32 can be configured with high resolution, typically up to 16 bits, allowing for fine control over the PWM signals. PWM frequency can also be adjusted, with a typical range from a few Hz to tens of MHz, making it suitable for a wide range of applications, from controlling motors to dimming LEDs.
Some GPIOs are routed to the RTC low-power subsystem and are known as RTC GPIOs. These GPIOs can be used for waking up the ESP32 from deep sleep and for interfacing with RTC peripherals.
There are five strapping pins on the ESP32: GPIO0, GPIO2, GPIO5, GPIO12, and GPIO15. The state of these pins determines whether the ESP32 enters BOOT mode (to run the program stored in flash memory) or FLASH mode (to upload a program to flash memory) upon power-up.
Be aware that if peripherals are connected to these pins, you might face issues when trying to flash the ESP32 with new firmware. This is because these peripherals could prevent the ESP32 from entering the correct mode.
The power pins provide the necessary voltage to the module to operate:
3V3 is the main supply voltage pin. It should be provided with a stable 3.3V power supply.
GND is the ground pin.
The EN pin is the enable pin for the ESP32. When pulled HIGH, the chip is enabled and operational; when pulled LOW, the chip is disabled.
- Use DAC + I²S for clean audio playback (DMA → non‑blocking).
- Use LEDC only for simple beeps/tones (square waves).
- For analog reads with Wi‑Fi: stick to ADC1 pins.
- Treat GPIO0/2/4/5/12/15 with care (boot straps).
- Avoid using GPIO34–39 for outputs (they’re input‑only).
- For WS2812/NeoPixels under Wi‑Fi, prefer RMT‑based drivers for rock‑solid timing.
- Swap the hand‑timed loop for I²S DAC mode with a circular buffer.
- Add envelopes (ADSR) and basic filters to the synth.
- Stream WAV from SPIFFS/SD: 8‑bit @ 22 kHz is very doable.
- Try stereo (GPIO25 & GPIO26) with separate voices per channel.
Have fun. The ESP32 isn’t just microcontroller‑with‑Wi‑Fi — it’s a pocket‑sized audio workstation when you use the right peripherals. 🎛️🎶











