$linuxjunkies
>

Use PlatformIO for Embedded Development

Install PlatformIO on Linux, integrate it with VS Code, choose between Arduino and ESP-IDF frameworks, run on-device unit tests, and attach a hardware debugger.

IntermediateUbuntuDebianFedoraArch9 min readUpdated June 7, 2026

Before you start

  • Python 3.6 or later installed
  • VS Code installed (for IDE path) or curl/git for CLI-only install
  • A supported microcontroller board with a USB connection
  • A JTAG/SWD probe (ST-Link, J-Link, or CMSIS-DAP) for the debugging section

PlatformIO turns embedded development from a toolchain-wrangling exercise into something manageable. It handles compiler installation, board definitions, library dependencies, and even on-chip debugging — all from a single CLI or from inside VS Code. This guide covers installing PlatformIO, wiring it into VS Code, picking the right framework for your target, running unit tests on-device, and attaching a hardware debugger.

Install PlatformIO

PlatformIO's primary interface is a VS Code extension that bundles the core CLI automatically.

  1. Open VS Code and go to the Extensions panel (Ctrl+Shift+X).
  2. Search for PlatformIO IDE (publisher: PlatformIO).
  3. Click Install. VS Code will install platformio-ide and, on first launch, download PlatformIO Core into ~/.platformio/.

You must have Python 3.6+ on the system — PlatformIO Core is a Python application. Check your version:

python3 --version

Install PlatformIO Core standalone (CLI-only / headless servers)

If you prefer the CLI or are setting up CI/CD without VS Code:

# Debian/Ubuntu
sudo apt install python3-pip curl git
curl -fsSL https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py -o get-platformio.py
python3 get-platformio.py
# Fedora / RHEL family
sudo dnf install python3-pip curl git
curl -fsSL https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py -o get-platformio.py
python3 get-platformio.py
# Arch
sudo pacman -S python-pip curl git
curl -fsSL https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py -o get-platformio.py
python3 get-platformio.py

After installation, add the PlatformIO binary to your shell path:

echo 'export PATH="$HOME/.platformio/penv/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
pio --version

USB device permissions (Linux-specific, important)

Without udev rules, regular users cannot access USB-serial adapters or JTAG probes. PlatformIO ships a udev rule set:

curl -fsSL https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/system/99-platformio-udev.rules \
  | sudo tee /etc/udev/rules.d/99-platformio-udev.rules
sudo udevadm control --reload-rules
sudo udevadm trigger
sudo usermod -aG dialout,plugdev $USER

Log out and back in (or run newgrp dialout) for group changes to take effect.

Create a Project

From VS Code

Click the PlatformIO icon in the Activity Bar, then PlatformIO Home → New Project. Fill in:

  • Name: project directory name
  • Board: type to search — e.g., esp32dev, uno, bluepill_f103c8
  • Framework: see the next section

From the CLI

mkdir my_project && cd my_project
pio project init --board esp32dev --project-option "framework=arduino"

This creates platformio.ini (the project manifest), src/, include/, lib/, and test/.

Choosing a Framework

The framework key in platformio.ini determines which SDK your code compiles against. The two you'll use most often:

FrameworkBest forBoards
arduino Rapid prototyping, huge library ecosystem, familiar API AVR, ESP8266, ESP32, STM32, RP2040, and more
espidf Production ESP32 work — FreeRTOS tasks, Bluetooth, fine-grained power control ESP32 family only

A minimal platformio.ini for an ESP32 using ESP-IDF looks like this:

cat platformio.ini
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = espidf
monitor_speed = 115200

For an Arduino-framework project targeting the same board, change framework = arduino. You can even stack both in the same project by listing framework = arduino, espidf — useful when you want ESP-IDF components alongside Arduino convenience functions.

Add library dependencies by their PlatformIO Registry ID or GitHub URL:

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
    knolleary/PubSubClient @ ^2.8
    adafruit/Adafruit GFX Library @ ^1.11.9
monitor_speed = 115200

Build, Upload, and Monitor

# Build
pio run

# Upload to connected board
pio run --target upload

# Open serial monitor
pio device monitor

In VS Code these map to the toolbar buttons at the bottom of the window (checkmark = build, right-arrow = upload, plug = monitor).

Unit Testing on Device

PlatformIO's built-in test runner, Unity-based, runs tests directly on the microcontroller over the serial port — not just on the host. Tests live in test/.

Write a test

mkdir -p test/test_math
cat > test/test_math/test_main.cpp << 'EOF'
#include 
#include 

void test_addition(void) {
    TEST_ASSERT_EQUAL(4, 2 + 2);
}

void test_subtraction(void) {
    TEST_ASSERT_EQUAL(0, 5 - 5);
}

void setup() {
    delay(2000);   // give monitor time to connect
    UNITY_BEGIN();
    RUN_TEST(test_addition);
    RUN_TEST(test_subtraction);
    UNITY_END();
}

void loop() {}
EOF

Run the tests

pio test --environment esp32dev

PlatformIO compiles a separate firmware image with the test suite, flashes it, captures serial output, and parses pass/fail results. Expect output similar to:

# Output will vary; illustrative only
test/test_math/test_main.cpp:6:test_addition:PASS
test/test_math/test_main.cpp:10:test_subtraction:PASS
-----------------------
2 Tests 0 Failures 0 Ignored

For host-side (native) unit tests that run on your Linux machine without hardware, add a [env:native] section and use platform = native. This is ideal for testing pure logic functions.

Hardware Debugging with GDB

PlatformIO supports on-chip debugging via OpenOCD for most ARM Cortex targets and JTAG/SWD probes. Common probes: ST-Link, J-Link, CMSIS-DAP (including the cheap DAPLink clones).

Configure the debug probe in platformio.ini

[env:bluepill_f103c8]
platform = ststm32
board = bluepill_f103c8
framework = arduino
debug_tool = stlink
debug_build_flags = -O0 -g3 -ggdb3
# Optional: program via the debugger too
upload_protocol = stlink

Launch the debugger in VS Code

  1. Open the Run and Debug panel (Ctrl+Shift+D).
  2. Select PlatformIO Debugger from the dropdown and press F5.
  3. PlatformIO builds with debug symbols, flashes via the probe, and attaches GDB. Set breakpoints by clicking the gutter in the source editor.

CLI debugging

pio debug --interface=gdb -x .pioinit

The -O0 -g3 flags are critical — without them the compiler optimises away variables and breakpoints land on the wrong lines. Always set debug_build_flags explicitly.

Verification

# Confirm PlatformIO version and core path
pio --version
pio system info

# List detected connected boards
pio device list

# Verify a clean build
pio run --verbose 2>&1 | tail -20

Troubleshooting

Permission denied on /dev/ttyUSB0 or /dev/ttyACM0

You skipped the udev step or haven't re-logged-in. Verify your groups with groups; you need dialout (Debian/Ubuntu/Fedora) or uucp (Arch). Alternatively, for a single session: sudo chmod a+rw /dev/ttyUSB0.

Board not found in PlatformIO registry

Run pio boards | grep -i <keyword> to search locally. Custom or very new boards need a manual boards/ JSON definition in the project root — see the PlatformIO documentation on custom board manifests.

Debugger connects but breakpoints don't halt

Check that debug_build_flags = -O0 -g3 is set and that you're not accidentally uploading a release build. Also confirm the reset strategy: some STM32 Blue Pill clones require debug_init_cmds to issue monitor reset halt before running.

ESP32 upload fails with "A fatal error occurred"

Hold the BOOT button on the ESP32 board during the upload phase, then release it. Some boards lack the auto-reset circuit. Setting upload_speed = 115200 in platformio.ini also reduces errors on marginal USB cables.

tested on:Ubuntu 24.04Fedora 40Arch rollingDebian 12

Frequently asked questions

Can PlatformIO manage multiple target environments in one project?
Yes. Define multiple [env:name] sections in platformio.ini — each with its own board, framework, and flags. Use 'pio run --environment <name>' to target a specific one.
Does PlatformIO work in CI/CD pipelines without VS Code?
Fully. The CLI is the foundation; install PlatformIO Core via the installer script in your pipeline, then call 'pio run', 'pio test', and 'pio check' as regular shell commands.
How do I run unit tests without physical hardware?
Add a [env:native] section with 'platform = native' and no board or framework. PlatformIO compiles and runs the test binary directly on your Linux host, ideal for pure logic tests.
Can I use ESP-IDF components (like nvs_flash) in an Arduino-framework project?
Yes, for ESP32 targets you can set 'framework = arduino, espidf' to access IDF components directly from C++ code while retaining the Arduino setup()/loop() structure.
How do I update PlatformIO Core and installed platforms?
Run 'pio upgrade' to update Core itself, and 'pio platform update' to pull in the latest toolchain and board definition packages for all installed platforms.

Related guides