GPIO & PWM on ESP32

GPIO & PWM on ESP32

Control outputs, read inputs, and use LEDC for smooth PWM control.

GPIO on the ESP32

The ESP32 has up to 34 GPIO pins, though the exact number depends on the package and module. Not all pins are equal ߀” some have restrictions:

Pin range Notes
GPIO 0 Boot mode select ߀” avoid as output; has pull-up
GPIO 1, 3 UART0 TX/RX ߀” used for USB serial
GPIO 6߀“11 Connected to internal SPI flash ߀” DO NOT USE
GPIO 34߀“39 Input only ߀” no internal pull-up/down
GPIO 2 Built-in LED on most dev boards

Basic Digital I/O

Works the same as Arduino:

void setup() {
  pinMode(2,  OUTPUT);
  pinMode(15, INPUT_PULLUP);
}

void loop() {
  if (digitalRead(15) == LOW) {   // Button pressed (active-low)
    digitalWrite(2, HIGH);
  } else {
    digitalWrite(2, LOW);
  }
}

Analog Input ߀” ADC

The ESP32 has two ADCs. ADC1 (GPIO 32߀“39) is reliable. ADC2 (GPIO 0, 2, 4, 12߀“15, 25߀“27) conflicts with WiFi ߀” ADC2 cannot be used when WiFi is active.

int raw = analogRead(34);         // 0߀“4095 (12-bit ADC)
float voltage = raw * (3.3 / 4095.0);

The ESP32 runs at 3.3 V ߀” never connect inputs above 3.3 V.

ADC Non-linearity

The ESP32 ADC has known non-linearity at the extremes (below ~100 mV and above ~3.1 V). Use analogSetAttenuation() to shift the input range:

Attenuation Useful range
ADC_0db 0 ߀“ 1.1 V
ADC_2_5db 0 ߀“ 1.5 V
ADC_6db 0 ߀“ 2.2 V
ADC_11db 0 ߀“ 3.9 V (default)

DAC Output

GPIO 25 and 26 have true 8-bit DAC outputs (0߀“3.3 V):

dacWrite(25, 128);   // ~1.65 V (half of 3.3 V)
dacWrite(25, 0);     // 0 V
dacWrite(25, 255);   // 3.3 V

PWM with LEDC

The ESP32 does not use analogWrite(). Instead it has the LEDC (LED Control) peripheral ߀” 16 independent PWM channels with configurable frequency and resolution.

const int ledPin   = 2;
const int channel  = 0;
const int freq     = 5000;     // Hz
const int resolution = 8;     // bits (0߀“255)

void setup() {
  ledcSetup(channel, freq, resolution);
  ledcAttachPin(ledPin, channel);
}

void loop() {
  for (int duty = 0; duty <= 255; duty++) {
    ledcWrite(channel, duty);
    delay(10);
  }
  for (int duty = 255; duty >= 0; duty--) {
    ledcWrite(channel, duty);
    delay(10);
  }
}

Interrupts

Any GPIO can trigger an interrupt on RISING, FALLING, or CHANGE:

volatile bool triggered = false;

void IRAM_ATTR onButton() {
  triggered = true;
}

void setup() {
  pinMode(15, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(15), onButton, FALLING);
}

void loop() {
  if (triggered) {
    triggered = false;
    Serial.println("Button pressed!");
  }
}

The IRAM_ATTR attribute places the ISR in IRAM so it runs correctly even if the flash cache is busy.

Touch Pins

GPIO 4, 12, 13, 14, 15, 27, 32, 33 have capacitive touch sensing:

int touchValue = touchRead(4);   // Lower value = finger touching
if (touchValue < 40) {
  Serial.println("Touch detected!");
}