ESP32: first program – LED, button and PWM

Just like with the Arduino, the first program on a new board is a blinking LED. On the ESP32 the program looks almost the same, but there are important differences: the pins work at 3.3 V, the analog input is 12-bit (0–4095), and LED brightness is set with the ledc functions. In this lesson we write four short programs: blink, button, a "breathing" LED and a potentiometer.

Setup: ESP32 in the Arduino IDE

Arduino IDE and an ESP32 board
Arduino IDE: choosing the board, the port and uploading to the ESP32
  1. In the Arduino IDE open Tools → Board → Boards Manager, search for esp32 and install esp32 by Espressif Systems.
  2. Choose the ESP32 Dev Module board and the port the board is connected to (Tools → Port).
  3. If no port appears, install the driver for the board's USB chip (CP2102 or CH340) – just like for Arduino clones.
  4. If uploading stops at "Connecting…", hold the BOOT button on the board until writing starts.

ESP32 board pins

Functions of the ESP32 development board pins
Functions of the ESP32 development board pins
PinsUsed forWatch out
GPIO2built-in blue LED (on most boards)–
GPIO4, 5, 13, 16–19, 21–23, 25–27, 32, 33digital inputs and outputs, PWM21 and 22 are I2C (SDA, SCL)
GPIO34, 35, 36, 39inputs only (analog input ADC1)no output and no INPUT_PULLUP
GPIO0, 2, 12, 15work, but affect bootingdo not connect buttons to GND on them at power-up
GPIO6–11used by the flash memorydo not use them
3.3 V, not 5 V! Never connect 5 V to an ESP32 pin – it will damage the chip. Sensors that output 5 V (for example Echo of an HC-SR04 powered from 5 V) are connected through a voltage divider.
Pinout of the 38-pin ESP32 DevKit board
Pinout of the 38-pin ESP32 DevKit board

Program 1: blinking LED

The built-in LED is on GPIO2, so you do not need to connect anything for the first program. For an external LED: the long leg (anode) through a 220 Ω resistor to GPIO2, the short leg to GND.

// ESP32 – first program: blinking an LED
const int LED_PIN = 2;          // built-in blue LED on most DevKit boards (GPIO2)

void setup() {
  pinMode(LED_PIN, OUTPUT);     // the pin is an output
  Serial.begin(115200);         // the ESP32 usually works at 115200 baud
  Serial.println("Blinking starts!");
}

void loop() {
  digitalWrite(LED_PIN, HIGH);  // on (3.3 V on the pin)
  delay(500);
  digitalWrite(LED_PIN, LOW);   // off (0 V)
  delay(500);
}

Open the Serial Monitor and set the speed to 115200 – the message from setup() is there. If you see "strange characters", the monitor speed is not the same as in Serial.begin().

Program 2: button

Connect the button between GPIO13 and GND, without a resistor: INPUT_PULLUP turns on a resistor inside the chip that keeps the pin HIGH. When we press the button, the pin is connected to GND and reads LOW.

// ESP32 – a button turns the LED on and off
const int LED_PIN = 2;
const int BUTTON_PIN = 13;      // button between GPIO13 and GND

bool ledOn = false;
int previous = HIGH;           // with INPUT_PULLUP: not pressed = HIGH

void setup() {
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);   // built-in resistor to 3.3 V
  Serial.begin(115200);
}

void loop() {
  int now = digitalRead(BUTTON_PIN);
  // react only at the moment of pressing (HIGH -> LOW)
  if (previous == HIGH && now == LOW) {
    ledOn = !ledOn;
    digitalWrite(LED_PIN, ledOn ? HIGH : LOW);
    Serial.println(ledOn ? "LED: on" : "LED: off");
    delay(30);                  // short pause against contact "bounce"
  }
  previous = now;
}

The program remembers the previous button state and reacts only to a change (HIGH → LOW). If we only wrote if (now == LOW), the LED would flicker while we hold the button.

Program 3: PWM – brightness

A pin can only be on or off. PWM (pulse-width modulation) switches it on and off thousands of times per second; if it is on most of the time, the LED looks brighter. On the Arduino this is analogWrite(), on the ESP32 the functions ledcAttach() and ledcWrite().

// ESP32 – PWM: the LED slowly fades in and out ("breathing")
// Arduino core 3.x: ledcAttach(pin, frequency, resolution) and ledcWrite(pin, value)
const int LED_PIN = 2;
const int FREQ = 5000;         // 5000 Hz – the eye cannot see flicker
const int RESOLUTION = 8;       // 8 bits: values from 0 to 255

void setup() {
  ledcAttach(LED_PIN, FREQ, RESOLUTION);
}

void loop() {
  for (int brightness = 0; brightness <= 255; brightness++) {   // brighter and brighter
    ledcWrite(LED_PIN, brightness);
    delay(8);
  }
  for (int brightness = 255; brightness >= 0; brightness--) {   // dimmer and dimmer
    ledcWrite(LED_PIN, brightness);
    delay(8);
  }
}
Arduino UnoESP32
functionanalogWrite(pin, 0–255)ledcWrite(pin, value)
PWM pins6 pins (~)almost all output pins
frequencyfixed (about 490 Hz)we choose it (e.g. 5000 Hz)
resolution8 bits (0–255)we choose it (1–14 bits)
Older board package (2.x): instead of ledcAttach(pin, freq, res) you write ledcSetup(channel, freq, res) and ledcAttachPin(pin, channel), and ledcWrite takes the channel number. You can see the version in the Boards Manager.

Program 4: potentiometer (analog input)

A potentiometer has three legs: the outer ones to 3V3 and GND, the middle one to GPIO34. The ESP32 has a 12-bit analog-to-digital converter: analogRead() returns a number from 0 to 4095 (on the Arduino Uno from 0 to 1023).

// ESP32 – a potentiometer sets the LED brightness
const int POT_PIN = 34;         // ADC1, input only
const int LED_PIN = 2;

void setup() {
  Serial.begin(115200);
  ledcAttach(LED_PIN, 5000, 8);
}

void loop() {
  int value = analogRead(POT_PIN);          // 0 .. 4095 (12-bit ADC)
  int brightness = map(value, 0, 4095, 0, 255); // convert to 0 .. 255
  ledcWrite(LED_PIN, brightness);
  Serial.print("ADC: ");
  Serial.print(value);
  Serial.print("  brightness: ");
  Serial.println(brightness);
  delay(100);
}

The map() function converts a number from one range to another: 0–4095 to 0–255. Note that the ESP32 analog input is not perfectly linear – near 0 V and 3.3 V the values "lag". That does not matter for this exercise.

Practice tasks

  1. Change program 1 so that the LED blinks twice quickly and then once slowly (like a heartbeat).
  2. Add a press counter to program 2 and print it in the Serial Monitor.
  3. Build a traffic light with three LEDs (GPIO 25, 26, 27, each with a 220 Ω resistor).
  4. Combine programs 2 and 3: each button press changes the LED brightness in a loop: 0, 64, 128, 255.
Solution: LED brightness in four steps
// the button changes the LED brightness: 0 -> 64 -> 128 -> 255 -> 0 ...
const int LED_PIN = 2;
const int BUTTON_PIN = 13;
const int LEVELS[4] = { 0, 64, 128, 255 };

int step = 0;
int previous = HIGH;

void setup() {
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  ledcAttach(LED_PIN, 5000, 8);
  ledcWrite(LED_PIN, LEVELS[step]);
}

void loop() {
  int now = digitalRead(BUTTON_PIN);
  if (previous == HIGH && now == LOW) {
    step = (step + 1) % 4;          // after the last comes the first
    ledcWrite(LED_PIN, LEVELS[step]);
    delay(30);
  }
  previous = now;
}

Check your knowledge

Open the quiz

Quiz: ESP32 – LED, button and PWM

1. What is the highest voltage you may connect to an ESP32 pin?




2. What range does analogRead() return on the ESP32?




3. What does INPUT_PULLUP do?




4. Which functions change LED brightness on the ESP32 (package 3.x)?