Your First Sketch

Write and upload your first ESP32 program to blink an LED using PlatformIO

The "blink" sketch is the "Hello World" of microcontroller programming. You will make an LED turn on and off repeatedly, and along the way, learn how PlatformIO's build-and-upload workflow compares to the Arduino IDE. If you have already read the Arduino IDE version of this tutorial, the code is identical -- the only differences are the #include <Arduino.h> header, the file location (src/main.cpp), and the buttons you click.

🔗How an Arduino Sketch Works

Every Arduino sketch has two required functions:

#include <Arduino.h>  // Required in PlatformIO

void setup() {
  // Runs once when the board powers on or resets
}

void loop() {
  // Runs over and over, forever
}
  • setup() is where you configure things: set pin modes, start serial communication, initialize sensors. It runs once.
  • loop() is where your main program logic goes. It runs continuously after setup() finishes -- thousands of times per second if your code is short.

This two-function structure is the same for every Arduino-compatible board, including the ESP32. The only PlatformIO-specific requirement is the #include <Arduino.h> line at the top. The Arduino IDE inserts this automatically behind the scenes; PlatformIO does not.

Most ESP32 development boards have a small LED soldered onto the board that is connected to a GPIO pin. On the majority of ESP32-WROOM-32 DevKit boards, this is GPIO 2.

Important: The built-in LED pin varies between boards. GPIO 2 is the most common, but some boards use GPIO 5, GPIO 22, or have no built-in LED at all. Check your specific board's documentation if GPIO 2 does not work. You can always connect an external LED to any available GPIO pin instead.

If you do not already have a PlatformIO project, create one now (see PlatformIO Setup). Open src/main.cpp and replace its contents with:

#include <Arduino.h>

#define LED_PIN 2  // Built-in LED on most ESP32 DevKit boards

void setup() {
  pinMode(LED_PIN, OUTPUT);  // Set the LED pin as an output
}

void loop() {
  digitalWrite(LED_PIN, HIGH);  // Turn LED on
  delay(1000);                  // Wait 1 second (1000 milliseconds)
  digitalWrite(LED_PIN, LOW);   // Turn LED off
  delay(1000);                  // Wait 1 second
}

🔗Building and Uploading

With the file saved, look at the PlatformIO toolbar at the bottom of the VS Code window:

  1. Build -- Click the checkmark icon (or press Ctrl+Alt+B). PlatformIO compiles your code and reports any errors in the terminal panel.
  2. Upload -- Click the right arrow icon (or press Ctrl+Alt+U). PlatformIO compiles (if needed) and uploads the firmware to your ESP32.

The first time you build a project, PlatformIO downloads the ESP32 toolchain automatically. You will see output like:

Resolving esp32dev dependencies...
Platform Manager: Installing espressif32
Downloading  [####################################]  100%
...
Compiling .pio/build/esp32dev/src/main.cpp.o
Linking .pio/build/esp32dev/firmware.elf
Building .pio/build/esp32dev/firmware.bin
========================= [SUCCESS] Took 45.67s =========================

Subsequent builds are much faster (typically a few seconds) because the toolchain is cached.

After a successful upload, the LED on your board should blink on and off once per second.

🔗Understanding the Code

#include <Arduino.h> -- Brings in the Arduino framework: pinMode, digitalWrite, delay, Serial, and all other Arduino functions. Required in PlatformIO; implicit in Arduino IDE.

#define LED_PIN 2 -- Creates a constant called LED_PIN with the value 2. Using a named constant instead of writing 2 everywhere makes the code easier to read and change. If your board uses a different pin, you only need to change this one line.

pinMode(LED_PIN, OUTPUT) -- Tells the ESP32 that GPIO 2 should be an output pin. This means we will send signals out of this pin to control the LED.

digitalWrite(LED_PIN, HIGH) -- Sets the pin to HIGH (3.3 V). Current flows through the LED, and it lights up.

digitalWrite(LED_PIN, LOW) -- Sets the pin to LOW (0 V). No current flows, and the LED turns off.

delay(1000) -- Pauses the program for 1000 milliseconds (1 second). During this time, the ESP32 does nothing.

Now try modifying the sketch. Change the delay values, save the file, and upload again:

void loop() {
  digitalWrite(LED_PIN, HIGH);
  delay(100);                   // On for 0.1 seconds
  digitalWrite(LED_PIN, LOW);
  delay(100);                   // Off for 0.1 seconds
}

The LED blinks much faster. Try different values:

On delayOff delayEffect
1000 ms1000 msSlow, steady blink
100 ms100 msFast blink
50 ms950 msQuick flash, long pause
500 ms100 msLong on, short off

This kind of experimentation is one of the best ways to learn. Change values, upload, and observe what happens. In PlatformIO, the upload shortcut Ctrl+Alt+U makes this cycle quick.

🔗Add Serial Output: "Hello World"

Blinking an LED is great for visual feedback, but you often need to see text output from your program -- sensor readings, status messages, or debug information. The Serial Monitor is how you do that.

Modify your src/main.cpp to include serial communication:

#include <Arduino.h>

#define LED_PIN 2

void setup() {
  pinMode(LED_PIN, OUTPUT);

  Serial.begin(115200);     // Start serial communication at 115200 baud
  delay(1000);              // Give the serial connection a moment to initialize
  Serial.println("ESP32 Blink Sketch Started");
}

void loop() {
  digitalWrite(LED_PIN, HIGH);
  Serial.println("LED is ON");
  delay(1000);

  digitalWrite(LED_PIN, LOW);
  Serial.println("LED is OFF");
  delay(1000);
}

🔗Understanding the Serial Code

Serial.begin(115200) -- Initializes serial communication at 115200 bits per second (baud). This speed must match the Serial Monitor setting. 115200 is the standard baud rate for ESP32.

Serial.println("text") -- Sends a line of text over the serial connection. println adds a newline at the end; print does not.

delay(1000) after Serial.begin() -- The ESP32's USB-serial connection can take a moment to stabilize after reset. Without this delay, the first few messages might be lost.

🔗Using the PlatformIO Serial Monitor

  1. Upload the sketch (Ctrl+Alt+U)
  2. Open the Serial Monitor by clicking the plug icon in the bottom toolbar (or press Ctrl+Alt+S)
  3. Make sure monitor_speed = 115200 is set in your platformio.ini (this is the PlatformIO equivalent of selecting the baud rate dropdown in Arduino IDE)
  4. You should see:
ESP32 Blink Sketch Started
LED is ON
LED is OFF
LED is ON
LED is OFF

If you see garbled characters, check that monitor_speed in platformio.ini matches the value in your Serial.begin() call. If you see nothing at all, press the EN (reset) button on the ESP32 to restart the sketch from the beginning.

Tip: You can also open the Serial Monitor from the terminal with pio device monitor. This is useful if you want to pass extra flags like --filter time to add timestamps.

🔗Printing Variables

Serial.println() can also print numbers and variables, which is essential for debugging:

#include <Arduino.h>

int counter = 0;

void setup() {
  Serial.begin(115200);
  delay(1000);
}

void loop() {
  counter++;
  Serial.print("Loop count: ");
  Serial.println(counter);
  delay(1000);
}

Output:

Loop count: 1
Loop count: 2
Loop count: 3
...

Notice the difference between Serial.print() (no newline) and Serial.println() (adds a newline). Using print for the label and println for the value puts them on the same line.

If your board does not have a built-in LED, or you want to control a separate one, you can connect an external LED to any available GPIO pin.

🔗What You Need

ComponentQuantity
LED (any color)1
220 $\Omega$ resistor1
Jumper wires2
Breadboard1

🔗Wiring

ESP32 pinConnects to
GPIO 13220 $\Omega$ resistor, then to LED anode (long leg)
GNDLED cathode (short leg)

The circuit is: GPIO 13 --> 220 $\Omega$ resistor --> LED anode (+) --> LED cathode (-) --> GND

Why a resistor? Without a current-limiting resistor, the LED would draw too much current from the GPIO pin. This could damage the LED, the ESP32, or both. A 220 $\Omega$ resistor limits the current to about $\frac{3.3 - 2.0}{220} \approx 6 \, \text{mA}$, which is safe and bright enough.

🔗Code for the External LED

#include <Arduino.h>

#define LED_PIN 13  // External LED on GPIO 13

void setup() {
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(115200);
  delay(1000);
  Serial.println("External LED blink started");
}

void loop() {
  digitalWrite(LED_PIN, HIGH);
  Serial.println("LED ON");
  delay(500);

  digitalWrite(LED_PIN, LOW);
  Serial.println("LED OFF");
  delay(500);
}

Change LED_PIN to whichever GPIO pin you connected your LED to.

🔗Common Issues

ProblemLikely CauseSolution
LED does not blinkWrong pin numberCheck your board's pinout; try GPIO 2 or your board's documented LED pin
LED stays on or offLED in backwardsFlip the LED (long leg to resistor side)
Serial shows garbled textBaud rate mismatchSet monitor_speed = 115200 in platformio.ini
Nothing in Serial MonitorSerial not initializedCheck Serial.begin(115200) is in setup()
Upload failsBoard in wrong stateHold BOOT button during upload
Red squiggles in editor but code compilesIntelliSense index staleBuild once, then run "PlatformIO: Rebuild IntelliSense Index"
"Port busy" on uploadSerial Monitor still openClose the Serial Monitor (Ctrl+C) before uploading

🔗Arduino IDE vs. PlatformIO: What Changed?

The code in this article is identical to the Arduino IDE version. The differences are entirely in the workflow:

AspectArduino IDEPlatformIO
File location.ino sketch filesrc/main.cpp
Arduino headerAdded automaticallyMust write #include <Arduino.h>
BuildUpload button or Ctrl+UCheckmark icon or Ctrl+Alt+B
UploadUpload button or Ctrl+UArrow icon or Ctrl+Alt+U
Serial MonitorMagnifying glass iconPlug icon or Ctrl+Alt+S
Baud rate settingDropdown in Serial Monitormonitor_speed in platformio.ini

The C++ code inside setup() and loop() does not change at all.

🔗What You Learned

In this article you:

  • Learned how the setup() / loop() structure works in PlatformIO (same as Arduino IDE, plus #include <Arduino.h>)
  • Used pinMode(), digitalWrite(), and delay() to blink an LED
  • Built and uploaded code using the PlatformIO toolbar
  • Used Serial.begin(), Serial.print(), and Serial.println() to send debug text
  • Wired an external LED with a current-limiting resistor

These are building blocks you will use in every project from here on.

🔗Next Steps

Now that you can control a digital output (on/off), the Using the Serial Monitor article goes deeper into serial debugging features specific to PlatformIO -- including monitor filters, timestamps, and logging to files.