If you’ve spent time doing embedded development the traditional way, you know the ritual: download the vendor’s IDE (which is usually an ancient Eclipse fork), install a specific compiler toolchain that only works on certain OS versions, fight with library version conflicts, discover that the documentation refers to a version from three years ago, and repeat for every board vendor you want to support. It’s a mess that has nothing to do with writing good firmware.

PlatformIO solves most of this. It’s an open-source embedded development platform that provides a unified project structure, a cross-platform build system, a package manager for libraries and toolchains, and an IDE that runs as a VS Code extension. You define what board you’re targeting in a config file, and PlatformIO handles downloading the right toolchain, finding compatible libraries, and compiling correctly. It supports over 1,300 boards across 40+ platforms — including ESP32, ESP8266, STM32, nRF52, Raspberry Pi Pico, Arduino Mega, and dozens of less common targets.

Installation

PlatformIO runs as a VS Code extension (the most common setup) or as a standalone CLI.

VS Code extension:

  1. Open VS Code
  2. Extensions (Ctrl+Shift+X) → search “PlatformIO IDE”
  3. Install and reload

The extension downloads the PlatformIO Core (Python-based) on first launch. This takes a few minutes but is a one-time setup.

CLI only:

pip install platformio
# or
curl -fsSL https://raw.githubusercontent.com/platformio/platformio-core-installer/develop/get-platformio.py -o get-platformio.py
python3 get-platformio.py

The CLI is useful for CI pipelines and headless builds.

Project Structure

A PlatformIO project looks like this:

my-project/
├── platformio.ini      # Project configuration
├── src/
│   └── main.cpp        # Main source file
├── include/            # Header files
├── lib/                # Project-local libraries
└── test/               # Unit tests

The platformio.ini file is where everything is configured:

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
monitor_speed = 115200

[env:nano33ble]
platform = nordicnrf52
board = nano33ble
framework = arduino

One platformio.ini can define multiple environments — different boards, different build configurations — and you can switch between them with a dropdown in the VS Code status bar. This makes it practical to maintain a codebase that targets both an ESP32 development board and a custom PCB variant simultaneously.

Supported Platforms and Frameworks

PlatformIO separates “platforms” (the chip architecture and toolchain) from “frameworks” (the software abstraction layer). The most common combinations:

PlatformTypical BoardsAvailable Frameworks
espressif32ESP32, ESP32-S3, ESP32-C3Arduino, ESP-IDF
espressif8266NodeMCU, Wemos D1Arduino
ststm32STM32 Nucleo, Blue PillArduino, STM32Cube, libopencm3
nordicnrf52Arduino Nano 33 BLE, nRF52840Arduino, Zephyr
atmelavrArduino Uno, Mega, NanoArduino
raspberrypiRaspberry Pi PicoArduino, pico-sdk

Using framework = arduino on an ESP32 gives you the familiar Arduino API (setup/loop, Serial, Wire, SPI) while using the ESP-IDF under the hood. Switching to framework = espidf gives you direct access to ESP-IDF tasks, FreeRTOS primitives, and the full ESP-IDF ecosystem at the cost of more verbose setup code.

Library Management

PlatformIO’s library registry (registry.platformio.org) indexes thousands of libraries, searchable by name or platform compatibility. Adding a library to your project:

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
    bblanchon/ArduinoJson@^7.0.0
    adafruit/DHT sensor library@^1.4.6
    knolleary/PubSubClient@^2.8

PlatformIO downloads the specified versions on the next build and pins them to your project. The version specifiers follow semantic versioning (^7.0.0 means compatible with 7.x.x). Libraries are cached locally so subsequent builds don’t need network access.

For libraries not in the registry — internal libraries, modified forks — you can use a local path or a Git URL:

lib_deps =
    https://github.com/your-org/your-library.git#v1.2.0
    file:///absolute/path/to/local/library

Build Flags and Environments

Multiple build environments let you define variations without duplicating your codebase:

[platformio]
default_envs = esp32dev

[env]
# Shared config applied to all environments
build_flags =
    -DMQTT_MAX_PACKET_SIZE=1024

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
build_flags =
    ${env.build_flags}
    -DDEBUG_MODE=1
    -DBOARD_VERSION=2

[env:esp32-production]
platform = espressif32
board = esp32dev
framework = arduino
build_flags =
    ${env.build_flags}
    -DDEBUG_MODE=0
    -DBOARD_VERSION=2
    -O2

This pattern — shared config with environment-specific overrides — keeps large multi-target projects manageable without copying configuration everywhere.

The PIO Unified Debugger

PlatformIO includes hardware debugging via the PIO Unified Debugger, which abstracts over GDB and different debug probes (J-Link, ST-Link, Black Magic Probe, CMSIS-DAP). Debugging configuration in platformio.ini:

[env:stm32-debug]
platform = ststm32
board = nucleo_f446re
framework = arduino
debug_tool = stlink
debug_build_flags = -O0 -g

With an ST-Link connected, you get breakpoints, variable inspection, memory views, and a call stack directly in VS Code — without leaving the same IDE you write code in. For developers coming from web or backend development, having a proper debugger at the embedded level feels like a revelation compared to the printf-debugging that dominates a lot of embedded workflows.

CI Integration

PlatformIO’s CLI makes CI integration straightforward. A GitHub Actions example:

name: Build
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      
      - name: Install PlatformIO
        run: pip install platformio
      
      - name: Build all environments
        run: pio run
      
      - name: Run unit tests
        run: pio test -e native

The native environment is worth noting. PlatformIO supports running unit tests on your development machine via a “native” platform — no hardware required. You write tests in the PlatformIO test framework, and logic that doesn’t depend on specific hardware peripherals can be verified in CI without a connected microcontroller.

Comparison with Alternatives

The main alternatives to PlatformIO for embedded development:

Arduino IDE 2.x: Much improved from the original, but still limited to the Arduino ecosystem. Fine for Arduino-compatible boards, poor for anything else, limited CI support.

STM32CubeIDE: Eclipse-based, STM32-specific, includes powerful HAL configuration tools via CubeMX. Better than PlatformIO for complex STM32 projects that need deep HAL integration, but vendor-locked and heavy.

ESP-IDF with VS Code extension: The right choice if you’re ESP32-only and want maximum access to ESP-IDF features. More complex setup, excellent tooling once configured.

Zephyr RTOS with West: Cross-platform like PlatformIO, safety-certification focus, steep learning curve. Better for complex industrial applications; overkill for most IoT projects.

PlatformIO sits in a practical middle ground: genuinely cross-platform, low setup friction, good library management, strong VS Code integration, and works well for the range of hardware most IoT projects target. It won’t replace vendor-specific IDEs for highly specialised work, but it’s the right default for projects that span multiple board targets or where developer experience matters alongside firmware capability.

Getting started is about 20 minutes from nothing to a blinking LED on an ESP32, with the right toolchain downloaded and configured automatically. That alone is worth a lot.