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.
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
Install the VS Code extension (recommended path)
PlatformIO's primary interface is a VS Code extension that bundles the core CLI automatically.
- Open VS Code and go to the Extensions panel (Ctrl+Shift+X).
- Search for PlatformIO IDE (publisher: PlatformIO).
- Click Install. VS Code will install
platformio-ideand, 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:
| Framework | Best for | Boards |
|---|---|---|
| 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
- Open the Run and Debug panel (Ctrl+Shift+D).
- Select PlatformIO Debugger from the dropdown and press F5.
- 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.
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
AI and Artificial-Life Tools on Linux
Set up open-source AI/ML and artificial-life toolkits on Linux: PyTorch, JAX, DEAP, Avida, NetLogo, and RL environments with GPU driver guidance.
Assembly Language on Linux: A Starter Guide
Write x86-64 assembly on Linux from scratch: install NASM and GAS, learn syscalls, assemble and link a working program, then inspect and debug it.
How to Benchmark Disk Performance with fio
Learn to benchmark Linux disk performance with fio: writing job files, testing latency and throughput, and interpreting IOPS and percentile output correctly.
The Linux Boot Process Explained
Trace the full Linux boot sequence from UEFI firmware through GRUB2, the kernel, initramfs, and systemd to your login prompt — with diagnostics at each stage.