If you’ve spent any time doing embedded or IoT development, you know the toolchain situation is… not great. Arduino IDE works fine for blinking an LED but falls apart on any serious project. The vendor-specific IDEs (Keil, IAR, STM32CubeIDE) are powerful but tied to specific hardware families and painful to set up. And rolling your own CMake-based build system works, but it’s a lot of configuration for every new project.

PlatformIO sits in between these options in a way that’s genuinely useful. It’s an open-source ecosystem built around a consistent project structure and dependency management system that works across a huge range of hardware platforms, from Arduino to ESP32 to STM32 to Raspberry Pi RP2040 and beyond. You get it as a VS Code extension (the most common usage) or as a CLI tool.

What PlatformIO Actually Provides

The core value is dependency management and board abstraction. Traditional Arduino development involves manually downloading libraries as ZIP files, hoping version conflicts don’t cause headaches, and reconfiguring your IDE every time you switch hardware targets. PlatformIO handles this with a declarative project file.

Your platformio.ini defines the board, framework, and library dependencies for your project:

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
    bblanchon/ArduinoJson@^7.0.0
    knolleary/PubSubClient@^2.8
    adafruit/Adafruit BME280 Library@^2.2.4
monitor_speed = 115200

When you run pio run, PlatformIO downloads the correct toolchain for your target board, resolves library dependencies, and builds. When you run pio run -t upload, it flashes the firmware. The toolchain is downloaded once per platform and cached. Switch boards? Change platform and board in your INI file and rebuild.

For a project supporting multiple hardware variants — say, an ESP32 version and an STM32 version of the same firmware — you define multiple environments in the same platformio.ini:

[env:esp32]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps = ${common.lib_deps}

[env:stm32]
platform = ststm32
board = nucleo_f401re
framework = arduino
lib_deps = ${common.lib_deps}

[common]
lib_deps =
    bblanchon/ArduinoJson@^7.0.0
    knolleary/PubSubClient@^2.8

Now you can build for either target with pio run -e esp32 or pio run -e stm32. This is genuinely useful when you’re developing hardware that might ship on different chipsets, or when you have multiple products sharing a firmware codebase.

Library Management and Private Libraries

PlatformIO’s library registry has over 15,000 packages from the Arduino, Mbed, and ESP-IDF ecosystems. You specify libraries by name and version in your platformio.ini, and they’re automatically downloaded and linked. This means your project is reproducible on any machine with PlatformIO installed — no more “works on my machine” library version issues.

For private libraries or libraries not in the registry, you have a few options. Local libraries go in a lib/ directory in your project. Libraries from private Git repos can be specified as git dependencies:

lib_deps =
    https://github.com/yourorg/private-sensor-lib.git#v1.2.0

This makes it easy to share internal firmware libraries across teams using Git, with version pinning that works like any modern package manager.

The PlatformIO CLI and CI Integration

One of PlatformIO’s stronger points is its CI integration. Because everything is driven by the CLI, running firmware builds in GitHub Actions, GitLab CI, or Jenkins is straightforward:

# .github/workflows/build.yml
name: Build Firmware

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/cache@v4
        with:
          path: ~/.platformio/.cache
          key: ${{ runner.os }}-pio
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install PlatformIO
        run: pip install platformio
      - name: Build
        run: pio run
      - name: Upload firmware artifacts
        uses: actions/upload-artifact@v4
        with:
          name: firmware
          path: .pio/build/*/firmware.bin

The cache step matters — PlatformIO downloads toolchains and platforms which are large. Caching them between runs keeps CI times reasonable. With the cache, subsequent builds typically take 1-3 minutes for a typical ESP32 project, compared to 5-10 minutes without.

Unit testing is also supported through the PlatformIO test runner. You write tests using a test framework (Unity is the built-in default), and pio test runs them either on device (over serial) or natively on your host machine if you’ve structured your code to be hardware-agnostic. Native testing is faster for logic that doesn’t involve hardware peripherals.

Debugging

PlatformIO integrates with hardware debuggers through OpenOCD. If your board has a built-in debug interface (ST-LINK, JTAG, J-LINK, or similar), you can set a breakpoint in VS Code, hit F5, and step through firmware code on the device. This is proper source-level debugging — watching variables, stepping into library code — not just print statements over serial.

For ESP32 specifically, the ESP-PROG debug adapter (around £15) enables this over USB. The setup involves adding debug configuration to your platformio.ini:

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
debug_tool = esp-prog
debug_init_break = tbreak setup

It’s not zero-configuration, but it’s considerably easier than setting up OpenOCD independently.

Comparing to the Alternatives

Arduino IDE 2: Better than the original, with autocomplete and library management improvements. Still weaker than PlatformIO for large projects or multi-board targets. Good for beginners and single-platform work.

Zephyr RTOS + West: More powerful, but significantly more complex. Zephyr is the right choice for production industrial firmware that needs fine-grained control over the OS layer. PlatformIO is the right choice when you want to move quickly and the Arduino/ESP-IDF frameworks are sufficient.

vendor SDKs (STM32CubeIDE, ESP-IDF, nRF Connect): Full-featured for their respective platforms, but siloed. If you work across multiple hardware families, PlatformIO’s cross-platform approach saves significant context switching.

The honest assessment: PlatformIO is best for IoT and maker-grade embedded projects where productivity matters more than bare-metal control. For production industrial firmware at scale, you’ll likely outgrow it and move toward Zephyr or a vendor RTOS. But for prototyping, development tools integrations, and projects where the Arduino framework is adequate, PlatformIO is comfortably the best option available.

Getting Started

Install the PlatformIO IDE extension in VS Code, or install the CLI with pip install platformio. The CLI is the more reliable option for CI and headless environments. Create a new project with pio init or through the VS Code interface, select your board and framework, and you’re building in minutes.

The full platform and board list is at registry.platformio.org — over 1,500 boards across dozens of platforms. Chances are yours is there.

References