This is a summarized and simplified on-page guide based on the PlatformIO documentation, intended for use as context for LLM-assisted coding. It was created with a summarization workflow created in n8n and then summarized with Genimi 2.0 Flash, and the final document restructured and reformatted using Gemini 2.5-pro.
Chapter 1: Introduction to PlatformIO
PlatformIO is a collaborative, open-source ecosystem for embedded software development. It provides a consistent development environment across various platforms and architectures, emphasizing declarative principles, test-driven methodologies, and modern toolchains. Key components include a cross-platform IDE, Unified Debugger, Static Code Analyzer, Remote Unit Testing capabilities, a multi-platform Build System, and tools for firmware exploration and memory inspection.
The ecosystem consists of:
- PlatformIO IDE: An integrated development environment, typically an extension for popular editors like VSCode.
- PlatformIO Core (CLI): The command-line heart of PlatformIO, providing all its core functionalities.
- PlatformIO Home: A graphical interface for managing projects, libraries, devices, and your PlatformIO Account.
- PlatformIO Account: Used for accessing services like Remote Development and the PlatformIO Registry.
Chapter 2: Installation & Setup
System Requirements:
- Operating Systems: Windows, macOS, Linux, FreeBSD, Linux ARMv6+ (e.g., Raspberry Pi).
- Python Interpreter: Python 3.6 or newer. Most OSs (except Windows) include Python. If not, download from https://www.python.org/downloads/.
- Linux: Ensure
python3-venv
(or equivalent) is installed (e.g.,sudo apt install python3-venv
on Debian/Ubuntu). - macOS: During Python installation, run the
Install Certificates.command
to enable SSL/TLS for package downloads. - Windows: During Python installation, select "Add Python to PATH".
- Linux: Ensure
- Terminal Application: Standard terminal for macOS/Linux;
cmd.exe
or PowerShell for Windows. - Serial Port Access:
- Windows: Install USB drivers from the board manufacturer.
- Linux: Install
99-platformio-udev.rules
(see below). For Raspberry Pi, ensure the serial port is enabled.
Installing PlatformIO Core (CLI):
PlatformIO Core is the command-line tool that powers the ecosystem. While PlatformIO IDE often manages its own instance, you might install it separately. The recommended method is using the official installer script. Alternatively, Python's package manager (pip
) or Homebrew (macOS) can be used.
- Refer to the official PlatformIO documentation for detailed, up-to-date installation instructions for your specific OS and preferred method.
Install Shell Commands (for CLI usage outside IDE):
To use pio
and platformio
commands directly in your system terminal:
- Unix/Unix-like (Bash/Zsh):
- Ensure
$HOME/.local/bin
is in yourPATH
. Add to~/.profile
(or~/.bash_profile
) for Bash:For Zsh, add toexport PATH=$PATH:$HOME/.local/bin
~/.zprofile
:emulate sh -c '. ~/.profile'
- Create symbolic links (adjust
~/.platformio/penv/bin
if your Core installation path differs):ln -s ~/.platformio/penv/bin/platformio ~/.local/bin/platformio ln -s ~/.platformio/penv/bin/pio ~/.local/bin/pio ln -s ~/.platformio/penv/bin/piodebuggdb ~/.local/bin/piodebuggdb
- Restart your terminal session or source your profile.
- Ensure
- Windows:
- Add the PlatformIO Core scripts directory to your system's
Path
environment variable. This is typically%USERPROFILE%\.platformio\penv\Scripts\
.
- Add the PlatformIO Core scripts directory to your system's
99-platformio-udev.rules (Linux):
For Linux users to have non-root access to serial ports and debug tools:
- Download the rules file:
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
- Reload udev rules:
Or
sudo udevadm control --reload-rules sudo udevadm trigger
sudo service udev restart
. - Re-plug your development board.
Alternatively, add your user to the group that owns the serial port (commonly dialout
, plugdev
, or uucp
):
# Find group with: ls -l /dev/ttyUSB0 (or your port)
sudo usermod -a -G <group_name> $USER
# Log out and log back in
Proxy Configuration:
PlatformIO Core uses standard environment variables for proxy settings: HTTP_PROXY
, HTTPS_PROXY
, ALL_PROXY
(for SOCKS).
- Example (Unix):
export HTTP_PROXY="http://user:[email protected]:3128/" export HTTPS_PROXY="http://10.10.1.10:1080"
- Example (Windows):
set HTTP_PROXY=http://user:[email protected]:3128/
- To disable proxy server certificate verification (if necessary):
pio settings set enable_proxy_strict_ssl false # Or via environment variable: # export PLATFORMIO_SETTING_ENABLE_PROXY_STRICT_SSL="false"
Updating PlatformIO:
- Stable Version:
pio upgrade
- Development Version:
Or, within PlatformIO IDE for VSCode, set
pio upgrade --dev
platformio-ide.useDevelopmentPIOCore
totrue
. - Reverting to Stable from Development:
python -m pip uninstall platformio python -m pip install -U platformio
Chapter 3: Getting Started with PlatformIO Projects
Project Initialization:
To start a new PlatformIO project, use the pio project init
command. You typically specify the board you are targeting.
- Example: Initialize a project for the Arduino Uno.
This creates a
pio project init --board uno
platformio.ini
file and standard project directories.
Understanding the Project Structure:
A typical PlatformIO project has the following key directories:
platformio.ini
: The main configuration file for your project.src/
: (Default:src_dir
) Your project's source code (e.g.,.c
,.cpp
,.ino
files).include/
: (Default:include_dir
) Project-specific header files. This directory is automatically added to the include path.lib/
: (Default:lib_dir
) For project-specific ("private") libraries. Each library should be in its own sub-directory.test/
: (Default:test_dir
) For unit tests..pio/
: (Default:workspace_dir
) PlatformIO's working directory. It stores compiled object files, firmware, library dependencies, and other temporary build files. You generally don't need to edit files here, and it can be safely deleted (PlatformIO will regenerate it).
Basic Workflow:
-
Building:
pio run
This command compiles your project for the environments defined in
platformio.ini
. -
Uploading Firmware:
pio run --target upload
This builds the project (if necessary) and uploads the firmware to the target board.
-
Serial Monitor:
pio device monitor
This opens a serial monitor to communicate with your board. Default baud rate is 9600. You can configure port, baud rate, and other settings via command-line options or in
platformio.ini
.- Example: Monitor on
/dev/ttyUSB0
at 115200 baud.pio device monitor --port /dev/ttyUSB0 --baud 115200
- Exit the monitor typically with
Ctrl+C
.
- Example: Monitor on
Board Identifiers:
PlatformIO uses unique identifiers for development boards. To find the identifier for your board:
pio boards <search_query>
# Example: pio boards esp32
This command lists matching boards along with their identifiers, platform, and framework compatibility. Use the "ID" column value in your platformio.ini
's board
option.
Chapter 4: Project Configuration (platformio.ini
)
The platformio.ini
file is the heart of a PlatformIO project, defining how it's built, for which environments, and with what dependencies. It uses a standard INI file format.
File Structure & Syntax:
- Sections: Defined by
[header]
. For example,[platformio]
or[env:myenvironment]
. - Key/Value Pairs: Within each section, options are specified as
key = value
. - Comments: Lines starting with a semicolon (
;
) are comments and are ignored. - Multiple Values: Options that accept multiple values can be specified in two ways:
- Comma-separated on a single line:
option = value1, value2
- Multi-line, with subsequent values indented (at least two spaces):
option = value1 value2
- Comma-separated on a single line:
[platformio]
Section:
This section defines global project settings and PlatformIO Core behavior for this project.
-
default_envs
:- Type:
String
, Multiple:Yes
- Description: Specifies which environments (defined in
[env:NAME]
sections) are processed by default whenpio run
is executed without the--environment
option. Also used by debugging tools to locate the default debug environment. If not set, all environments are processed. - Example:
default_envs = uno, nodemcu
- Type:
-
Project Directory Options: These options define default paths for various project components. Paths can be relative to the project root or absolute.
~
expands to the user's home directory. Values can be interpolated.core_dir
: Path to PlatformIO Core's data directory (packages, global libraries, etc.). Default:~/.platformio
(Unix),%HOMEPATH%\.platformio
(Windows). Can be set byPLATFORMIO_CORE_DIR
env var.workspace_dir
: Project workspace directory for compiled objects, libraries, firmware. Default:<Project>/.pio
. Can be set byPLATFORMIO_WORKSPACE_DIR
env var.build_dir
: Directory withinworkspace_dir
for compiled objects and firmware per environment. Default:<workspace_dir>/build
. Can be set byPLATFORMIO_BUILD_DIR
env var. It's a cache and can be safely deleted.libdeps_dir
: Internal storage for project library dependencies installed bylib_deps
. Default:<workspace_dir>/libdeps
. Can be set byPLATFORMIO_LIBDEPS_DIR
env var.include_dir
: Default directory for project header files, automatically added toCPPPATH
. Default:<Project>/include
. Can be set byPLATFORMIO_INCLUDE_DIR
env var.src_dir
: Project's source code directory. Default:<Project>/src
. Can be set byPLATFORMIO_SRC_DIR
env var.lib_dir
: Directory for user-defined ("private") libraries. Highest priority for LDF. Default:<Project>/lib
. Can be set byPLATFORMIO_LIB_DIR
env var.data_dir
: Directory for files to be included in a SPIFFS or LittleFS filesystem image. Default:<Project>/data
. Can be set byPLATFORMIO_DATA_DIR
env var.test_dir
: Directory where unit tests are located. Default:<Project>/test
. Can be set byPLATFORMIO_TEST_DIR
env var.boards_dir
: Location for project-specific custom board definitions. Default:<Project>/boards
. Can be set byPLATFORMIO_BOARDS_DIR
env var.monitor_dir
: Directory wherepio device monitor
searches for custom filters. Default:<Project>/monitor
. Can be set byPLATFORMIO_MONITOR_DIR
env var.shared_dir
: Directory for files synchronized during Remote Development. Default:<Project>/shared
. Can be set byPLATFORMIO_SHARED_DIR
env var.globallib_dir
: (DEPRECATED) Global library storage. Default:<core_dir>/lib
. Use project-specificlib_deps
instead.platforms_dir
: Global storage for development platforms. Default:<core_dir>/platforms
.packages_dir
: Global storage for PlatformIO Package Manager (toolchains, frameworks, SDKs). Default:<core_dir>/packages
.cache_dir
: Cache for PlatformIO Registry requests, downloaded packages. Default:<core_dir>/cache
. Reset withpio update
.build_cache_dir
: (Disabled by default) Shares compiled object files between all build environments to speed up builds. Set path to enable. Example:build_cache_dir = .pio/build-cache
.
[env]
and [env:NAME]
Sections:
-
Common
[env]
: An optional section defining options shared across all specific[env:NAME]
environments. -
Working
[env:NAME]
: Defines a specific build/run environment.NAME
is a unique identifier (lowercase letters, numbers,_
,-
). At least one[env:NAME]
is required.- Example:
Here,
[env] ; Common settings framework = arduino monitor_speed = 115200 [env:uno_release] platform = atmelavr board = uno build_flags = -D RELEASE=1 [env:esp32_debug] platform = espressif32 board = esp32dev build_type = debug lib_deps = bblanchon/ArduinoJson@^6.0
[env:uno_release]
and[env:esp32_debug]
will inheritframework = arduino
andmonitor_speed = 115200
unless they override them.
- Example:
Key Environment Options (for [env]
or [env:NAME]
):
-
Platform & Framework:
platform
: Specifies the development platform (e.g.,atmelavr
,espressif32
).- Can specify version:
platform = [email protected]
.
- Can specify version:
framework
: Specifies the programming framework (e.g.,arduino
,espidf
,zephyr
). Multiple can be listed.board
: The target board ID (e.g.,uno
,esp32dev
). Find IDs withpio boards <query>
.
-
Build Control:
board_build.mcu
: Overrides the default MCU for the board (e.g.,atmega328p
).board_build.f_cpu
: Overrides the default CPU frequency (e.g.,16000000L
for 16MHz).board_build.ldscript
: Specifies a custom linker script. Path is relative to the project directory or board's manifest.build_flags
: Specifies flags passed to preprocessor, compilers (C/C++), assembler, and linker.- Type:
String
, Multiple:Yes
- Common Flags and their SCons Scopes:
-D NAME
or-D NAME=VALUE
: Define preprocessor macro (CPPDEFINES
).-U NAME
: Undefine macro (CPPDEFINES
).-I path/to/include
: Add include path (CPPPATH
).-L path/to/lib
: Add library search path (LIBPATH
).-l library_name
: Link against library (LIBS
).-Wl,<linker_option>
: Pass option to linker (LINKFLAGS
).-Wa,<asm_option>
: Pass option to assembler (ASFLAGS
).- Other compiler flags like
-Wall
,-O2
, etc. (CCFLAGS
).
- Built-in Variables for Flags:
$PYTHONEXE
,$UNIX_TIME
,$PIOENV
,$PIOPLATFORM
,$PIOFRAMEWORK
,$PROJECT_DIR
,$PROJECT_CORE_DIR
,$PROJECT_BUILD_DIR
,$BUILD_DIR
. - Stringification: For complex string macros:
build_flags = '-D MYSTRING="Text is \\"Quoted\\""'
or useenv.StringifyMacro()
inextra_scripts
. - Dynamic Flags: Prepend command with
!
. Output of command becomes flags.build_flags = !echo -DMY_DYNAMIC_FLAG=123
Example:build_flags = !python get_git_rev.py
(whereget_git_rev.py
prints a-DGIT_REV=...
flag). - Environment Variable:
PLATFORMIO_BUILD_FLAGS
.
- Type:
build_src_flags
: Same asbuild_flags
but applies only to sources insrc_dir
.- Environment Variable:
PLATFORMIO_BUILD_SRC_FLAGS
.
- Environment Variable:
build_unflags
: Removes specified flags previously set (e.g., by framework or common[env]
).build_src_filter
: Filters source files fromsrc_dir
for compilation.- Type:
String (Templates)
, Multiple:Yes
- Templates:
+<PATH_PATTERN>
(include),-<PATH_PATTERN>
(exclude). Patterns are glob-like and relative tosrc_dir
. - Default:
+<*> -<.git/> -<.svn/>
(include all, exclude .git and .svn). - Example:
build_src_filter = +<*.c> +<*.cpp> -<secret_feature/>
- Environment Variable:
PLATFORMIO_BUILD_SRC_FILTER
.
- Type:
targets
: Specifies custom build targets (fromextra_scripts
) to be processed bypio run
by default for this environment.
-
Upload Options:
upload_port
: Port for uploading firmware (e.g.,/dev/ttyUSB0
,COM3
, IP address for OTA). PlatformIO attempts auto-detection if not set. Patterns (*
,?
,[]
) can be used.- Environment Variable:
PLATFORMIO_UPLOAD_PORT
.
- Environment Variable:
upload_protocol
: Specifies the upload method/tool (e.g.,stlink
,esptool
,jlink
).upload_speed
: Baud rate for serial uploading (if applicable).upload_flags
: Extra flags passed to the uploader tool.- Environment Variable:
PLATFORMIO_UPLOAD_FLAGS
.
- Environment Variable:
upload_command
: Overrides the entire upload command. Use with caution. PlatformIO variables like$UPLOAD_PORT
,$PROJECT_DIR
,$PROGPATH
(firmware path) can be used.- Example for custom avrdude command:
upload_command = avrdude -v -p atmega328p -c arduino -P $UPLOAD_PORT -b 115200 -U flash:w:$SOURCE:i
- Example for custom avrdude command:
-
Monitor Options (for
pio device monitor
):monitor_port
: Serial port for monitoring. Auto-detected if not set.monitor_speed
: Baud rate. Default:9600
.monitor_parity
: Parity (N
,E
,O
,S
,M
). Default:N
.monitor_filters
: Comma-separated list of filters (e.g.,time, log2file, esp32_exception_decoder
).monitor_rts
: Initial RTS line state (0
or1
).monitor_dtr
: Initial DTR line state (0
or1
).monitor_eol
: End-of-line characters for sent data (CR
,LF
,CRLF
). Default:CRLF
.monitor_raw
: Enable raw mode (no encoding/transformations). Default:no
.monitor_echo
: Enable local echo. Default:no
.monitor_encoding
: Default encoding for serial port. Default:UTF-8
.
-
Advanced Options:
extra_scripts
: List of PRE and POST Python scripts to extend the build process. Paths relative to project folder.- Type:
FilePath
, Multiple:Yes
- Usage:
extra_scripts = pre:script_before.py, script_after.py
- Environment Variable:
PLATFORMIO_EXTRA_SCRIPTS
.
- Type:
extends
: Inherit options from one or more other[env:NAME]
sections.- Example:
extends = env:common_debug_settings, env:esp_base
- Example:
Interpolation of Values:
PlatformIO supports referencing values from other options or sections using ${...}
syntax.
-
${<variable>}
: Built-in variable (e.g.,${PROJECT_DIR}
,${UNIX_TIME}
). -
${sysenv.<name>}
: Operating system environment variable (e.g.,${sysenv.HOME}
). -
${platformio.<option>}
: Value from the[platformio]
section (e.g.,${platformio.build_dir}
). -
${env.<option>}
: Value from the common[env]
section (e.g.,${env.build_flags}
). -
${<section>.<option>}
: Value from another custom section (e.g.,${my_custom_settings.api_key}
). -
${this.<option>}
: Value from the current[env:NAME]
section (e.g.,${this.board}
). -
${this.__env__}
: The name of the current environment (e.g., if in[env:foo]
, it'sfoo
). -
Example:
[platformio] default_envs = uno_debug [common_settings] base_flags = -Wall -Wextra common_libs = ArduinoJson Adafruit GFX Library [env:uno_debug] platform = atmelavr board = uno build_type = debug build_flags = ${common_settings.base_flags} -D DEBUG_LEVEL=2 -D FIRMWARE_NAME="${this.__env__}" lib_deps = ${common_settings.common_libs}
Build Configurations (build_type
):
The build_type
option controls optimization and debug information.
-
release
(Default): Optimized for size or speed, no symbolic debug information. -
debug
: Compiled with full symbolic debug information and no optimization. Essential forpio debug
. -
test
: Used for unit testing. Extends the environment withPIO_UNIT_TESTING
macro and specific flags. -
Usage:
[env:mydebugenv] platform = ... board = ... build_type = debug
To build in
debug
configuration:- Set
build_type = debug
inplatformio.ini
. - Use the "Pre-Debug" task in PlatformIO IDE.
- Run
pio debug
(which implicitly uses a debug build).
- Set
Chapter 5: Advanced Scripting & Build Customization
PlatformIO's build process can be extended and customized using Python scripts and the SCons construction tool. This requires knowledge of Python and SCons concepts.
Overview:
- SCons: The underlying build tool. Scripts interact with SCons construction environments.
- Construction Environments: SCons data structures (
env
,projenv
) holding build flags, toolchain data, targets, etc., which can be modified by scripts. extra_scripts
: Theplatformio.ini
option used to include custom Python scripts.
Using extra_scripts
:
Custom scripts listed in extra_scripts
are automatically loaded when pio run
(or related commands like pio test
, pio debug
) processes an environment.
- Syntax in
platformio.ini
:Paths are relative to the project folder.[env:myenv] extra_scripts = pre:my_pre_script.py, post:my_post_script.py, another_post_script.py
Launch Types (PRE & POST):
Scripts can be designated to run at different stages:
pre:
: Scripts prefixed withpre:
execute before the main build scripts of the development platform.- The project-specific construction environment (
projenv
) is not available. Only the globalDefaultEnvironment()
(aliased asenv
) is accessible.
- The project-specific construction environment (
post:
(Default if no prefix): Scripts execute after the main platform scripts and after anypre:
scripts.- The full build environment, including
projenv
(for project sources),env
(global/default), build flags, and targets, is available.
- The full build environment, including
Accessing Construction Environments in Scripts:
SCons environments are imported into your Python scripts:
-
Import("env")
: Imports the current "working" construction environment.- In
extra_scripts
(fromplatformio.ini
): This refers toDefaultEnvironment()
, which is the global environment used for platforms, frameworks, tools, LDF, etc. - In
library.json
'sextraScript
: This refers to the library's isolated environment. Useenv_global = DefaultEnvironment()
to access the global one.
- In
-
Import("projenv")
: Imports the isolated construction environment specific to the project's source code insrc_dir
.- Available only for
post:
scripts andextraScript
inlibrary.json
. build_src_flags
fromplatformio.ini
are applied toprojenv
.
- Available only for
-
DefaultEnvironment()
: Directly access the global SCons construction environment. -
Example PRE script (
my_pre_script.py
):# my_pre_script.py Import("env") # This is DefaultEnvironment() # print(env.Dump()) # For debugging env.Append(CPPDEFINES=["GLOBAL_MACRO_PRE_SCRIPT"])
-
Example POST script (
my_post_script.py
):# my_post_script.py Import("env", "projenv") # env is DefaultEnvironment(), projenv is for src_dir # print("Global env:", env.Dump()) # print("Project env:", projenv.Dump()) env.Append(CPPDEFINES=["GLOBAL_MACRO_POST_SCRIPT"]) projenv.Append(CFLAGS=["-Wno-unused-variable"]) # Only for project sources
Common Scripting Tasks and SCons API Usage:
-
Modifying Build Flags:
env.Append(**kwargs)
: Appends values to environment variables (e.g.,CPPDEFINES
,CCFLAGS
,LINKFLAGS
).env.Append(CPPDEFINES=["MY_DEFINE", ("VALUE_DEFINE", "123")]) env.Append(CCFLAGS=["-mthumb"]) projenv.Append(LINKFLAGS=["-custom-linker-script.ld"])
env.Prepend(**kwargs)
: Prepends values.env.Replace(**kwargs)
: Replaces existing values.env.Replace(PROGNAME="my_custom_firmware_name")
env.ParseFlags("string_of_flags")
: Parses a string of GCC-style flags and updates the environment.# Example: updates CPPDEFINES, CPPPATH, CCFLAGS, etc. custom_flags = env.ParseFlags("-DSOME_FLAG -Iinclude/private -O3") env.MergeFlags(custom_flags)
- SCons variables for flags:
CCFLAGS
(C and C++),CFLAGS
(C only),CXXFLAGS
(C++ only),LINKFLAGS
(linker),ASFLAGS
(assembler),ASPPFLAGS
(assembler preprocessor),CPPDEFINES
(macros),CPPPATH
(include paths),LIBPATH
(library search paths),LIBS
(libraries to link).
-
Accessing Project Configuration (
platformio.ini
values):env.GetProjectOption("custom_option_name", default_value)
: Retrieves an option from the current[env:NAME]
section inplatformio.ini
. Custom options should typically start withcustom_
.# platformio.ini: # [env:myenv] # custom_api_key = "abcdef" # extra_script.py: api_key = env.GetProjectOption("custom_api_key") if api_key: env.Append(CPPDEFINES=[("API_KEY", env.StringifyMacro(api_key))])
config = env.GetProjectConfig()
: Returns aProjectConfig
object (compatible with Python'sConfigParser
).config = env.GetProjectConfig() value = config.get("section_name", "option_name", "default_if_not_found") # Example: read from a custom section # [my_settings] # server_url = "http://localhost" server_url = config.get("my_settings", "server_url")
-
Conditional Execution in Scripts:
Import("env") # Only run for a specific platform if env.PioPlatform().name == "espressif32": env.Append(CPPDEFINES=["IS_ESP32"]) # Check if building for debug if env.GetBuildType() == "debug": print("This is a debug build.") # Conditionally skip script execution for certain targets (e.g., IDE data dump) if env.IsIntegrationDump(): Return() # Stop script execution # Code below only runs for actual build/upload etc.
-
Executing Shell Commands:
env.Execute(command_string)
: Runs a shell command.env.Execute("$PYTHONEXE setup.py build") # Using f-strings for variables: version = env.GetProjectOption("custom_firmware_version", "1.0.0") env.Execute(f"echo Firmware version: {version} > version.txt")
Pre & Post Actions (AddPreAction
, AddPostAction
):
Attach custom Python callback functions or shell commands to be executed before or after specific SCons targets (e.g., building the program, uploading, or processing a specific file). Actions must be applied to the global construction environment (env
).
-
API:
env.AddPreAction(target, callback_or_command)
env.AddPostAction(target, callback_or_command)
- Can take a list of callbacks/commands.
-
target
:- Name of a target (e.g.,
upload
,buildprog
,size
,program
). - Path to a file (e.g.,
$PROGPATH
(firmware),$BUILD_DIR/${PROGNAME}.elf
,$BUILD_DIR/src/main.cpp.o
).
- Name of a target (e.g.,
-
callback
Function Signature:def my_callback(source, target, env): ...
source
: SCons Node object(s) for the source.target
: SCons Node object(s) for the target.env
: The construction environment.
-
Example (
extra_script.py
):Import("env") def before_upload_callback(source, target, env): print("About to upload:", target[0].path) # Example: version_tool = env.GetProjectOption("custom_version_tool") # env.Execute(f"{version_tool} --pre-upload-check {target[0].path}") def after_build_program(source, target, env): program_path = target[0].get_abspath() print(f"Program built at: {program_path}") # Example: env.Execute(f"my_signing_tool {program_path}") env.AddPreAction("upload", before_upload_callback) env.AddPostAction("buildprog", after_build_program) # env.AddPostAction("$PROGPATH", after_build_program) # Alternative for firmware file # Action for a specific object file # env.AddPostAction("$BUILD_DIR/src/main.cpp.o", lambda s, t, e: print("main.o built!")) # Custom HEX from ELF (using VerboseAction for cleaner output) env.AddPostAction( "$BUILD_DIR/${PROGNAME}.elf", env.VerboseAction( env. কম্যান্ড Line Generator ( "$OBJCOPY", ["-O", "ihex", "-R", ".eeprom", "$SOURCES", "$TARGET"], ), "Building $TARGET from $SOURCES" ) )
Build Middlewares (AddBuildMiddleware
):
Modify SCons File System Nodes (source files) during the build process or exclude them. Middlewares can only be added in PRE
scripts.
-
API:
env.AddBuildMiddleware(callback, pattern=None)
callback
: Middleware function.pattern
: Optional Unix shell-style wildcard. Callback runs if node path matches. If omitted, runs for every node.
-
Middleware Function Signature:
def my_middleware(env, node): ...
node
: SCons File System Node object (node.name
,node.get_path()
,node.get_abspath()
).
-
Return Values from Callback:
None
: Excludes the node from the build.- Modified
node
(e.g.,env.Object(...)
,env.File(...)
): Replaces original node. - Original
node
: No change.
-
Example (
pre_extra_script.py
):Import("env") def custom_defines_for_http_files(env, node): if "http_client" in node.name: # Check node name (e.g. http_client.c) # Create a new object node with additional defines return env.Object( node, # Original source node CPPDEFINES=env["CPPDEFINES"] + [("HTTP_TIMEOUT", 5000)], CCFLAGS=env["CCFLAGS"] + ["-Wno-unused-function"] ) return node # Pass through other nodes unchanged def skip_assembly_files(env, node): if node.name.endswith(".S"): return None # Exclude from build return node env.AddBuildMiddleware(custom_defines_for_http_files) env.AddBuildMiddleware(skip_assembly_files, "*.S") # Apply only to .S files # env.AddBuildMiddleware(skip_assembly_files) # Apply to all, then filter by name
Custom Targets (AddCustomTarget
):
Define new targets that can be executed with pio run -t <target_name>
.
-
API:
env.AddCustomTarget(name, dependencies, actions, title=None, description=None, always_build=True)
name
(str): Target name (e.g., "ota_package", "format_code").dependencies
(str, list, None): Files or other targets that must be built before this target's actions run (e.g.,"$PROGPATH"
or["check_headers", "$BUILD_DIR/firmware.bin"]
).actions
(str, list): Shell commands or Python callback functions to execute.- Python callback:
def my_target_action(target, source, env): ...
or simplydef my_target_action(*args, **kwargs): ...
- Python callback:
title
(str, optional): Short display title.description
(str, optional): Detailed description.always_build
(bool, optional, default:True
): IfFalse
, target might not run if dependencies are up-to-date.
-
Example (
extra_script.py
):Import("env") # Simple command shortcut env.AddCustomTarget( name="hello", dependencies=None, actions='echo "Hello from custom target!"', title="Say Hello", description="Prints a greeting message." ) # Target dependent on firmware, running a script env.AddCustomTarget( name="package_firmware", dependencies="$PROGPATH", # Depends on the main program/firmware actions=[ # $SOURCE will be the path to $PROGPATH (the dependency) 'echo "Packaging $SOURCE..."', # Assuming package_script.py is in the project root '"$PYTHONEXE" package_script.py --firmware $SOURCE --output ${PROGNAME}.zip' ], title="Package Firmware", description="Creates a ZIP archive of the firmware." ) # Target with a Python callback and custom option from platformio.ini # platformio.ini: # [env:myenv] # custom_ping_host = platformio.org # extra_scripts = extra_script.py def ping_host_action(source, target, env): host = env.GetProjectOption("custom_ping_host", "google.com") print(f"Pinging host: {host}") return env.Execute(f"ping -c 4 {host}") # For Linux/macOS; use "ping" for Windows env.AddCustomTarget( name="ping_custom_host", dependencies=None, actions=ping_host_action, title="Ping Host", description="Pings the host defined in custom_ping_host." )
Specific Scripting Examples:
- Custom Options in
platformio.ini
: Covered under "Accessing Project Configuration". Custom options should start withcustom_
(orboard_
for board manifest overrides). - Split C/C++ Build Flags:
# extra_script.py Import("env") env.Append(CCFLAGS=["-O2"]) # For both C and C++ env.Append(CFLAGS=["-std=c11"]) # C only env.Append(CXXFLAGS=["-std=c++17", "-fno-rtti"]) # C++ only
- Extra Linker Flags (without
-Wl,
prefix): Directly append toLINKFLAGS
.# extra_script.py Import("env") env.Append(LINKFLAGS=["-static", "-static-libgcc"])
- Custom Firmware/Program Name:
Use a
pre:
script to modifyPROGNAME
.; platformio.ini [env:myenv] extra_scripts = pre:set_progname.py custom_fw_version = 1.2.3
# set_progname.py Import("env") version = env.GetProjectOption("custom_fw_version", "unknown") env.Replace(PROGNAME=f"firmware_v{version}") # Also update PROG_PATH if it's used by other logic (less common) # env.Replace(PROGPATH=env.subst(f"$BUILD_DIR/${env['PROGNAME']}${PROGSUFFIX}"))
- Asking for Input (Prompts):
Use Python's
input()
. Ensure it doesn't block IDE integration.# prompt_script.py Import("env") if env.IsIntegrationDump(): # Avoid blocking IDE tools Return() user_ssid = input("Enter WiFi SSID: ") if user_ssid: env.Append(CPPDEFINES=[("WIFI_SSID", env.StringifyMacro(user_ssid))])
- Override Package Files (e.g., framework files):
Use a
pre:
script withpatch
utility or a Python patching library. Manage a.patching-done
flag file to patch only once.# apply_patches.py (PRE script) from os.path import join, isfile Import("env") FRAMEWORK_DIR = env.PioPlatform().get_package_dir("framework-arduinoavr") # Example patch_flag_file = join(FRAMEWORK_DIR, ".my_custom_patch_applied") if not isfile(patch_flag_file): original_file = join(FRAMEWORK_DIR, "variants/standard/pins_arduino.h") patch_file = join(env.subst("$PROJECT_DIR"), "patches/my_pins_patch.patch") # Patch in project if isfile(original_file) and isfile(patch_file): print("Applying custom patch to pins_arduino.h...") env.Execute(f"patch {original_file} {patch_file}") # Requires 'patch' command # Create the flag file with open(patch_flag_file, "w") as fp: fp.write("patched") else: print("Error: Original file or patch file not found for patching.")
- Override Board Configuration (e.g., VID/PID):
Use a
pre:
script to modifyenv.BoardConfig()
.; platformio.ini [env:mycustomboard] extra_scripts = pre:override_board.py board = uno # Base board
# override_board.py Import("env") board_config = env.BoardConfig() # board_config.update("build.mcu", "atmega328pb") board_config.update("build.hwids", [["0xNEWVID", "0xNEWPID"]]) # For debugging available board config fields: # print(board_config.items()) # print(board_config.get("upload.maximum_size"))
- Extra Python Packages for Scripts:
Install packages using
pip
viaenv.Execute()
.# extra_script.py Import("env") try: import requests except ImportError: print("Installing 'requests' Python package...") env.Execute(f'"{env.subst("$PYTHONEXE")}" -m pip install requests') # Might require re-running the pio command after installation for the import to succeed
- Build External Sources:
Use
env.BuildSources(target_dir, source_dir)
in apre:
script.; platformio.ini [env:myenv] extra_scripts = pre:build_external.py
# build_external.py import os Import("env") # Build sources from project_dir/external_lib/ into .pio/build/myenv/external_lib_out/ env.BuildSources( os.path.join("$BUILD_DIR", "external_lib_out"), # Target for .o files os.path.join("$PROJECT_DIR", "external_lib") # Source directory )
PlatformIO provides a comprehensive Library Manager for finding, installing, and managing dependencies from the PlatformIO Registry, Version Control Systems (VCS), and local sources.
Chapter 6: Managing Libraries
Overview of PlatformIO Library Manager:
The Library Manager automates the handling of library dependencies. Key to this is the Library Dependency Finder (LDF), which analyzes your project's source code to determine which libraries are needed.
Declaring Dependencies (lib_deps
):
Dependencies are declared in the platformio.ini
file using the lib_deps
option within an [env]
or [env:NAME]
section.
- Type:
String
, Multiple:Yes
- Description: Specifies the libraries your project depends on. PlatformIO will attempt to install these automatically.
- Syntax:
- List multiple dependencies on new lines (indented) or comma-separated.
- Package Specifications:
- Name:
MyLibrary
(Can be ambiguous if multiple libraries share a name) - Owner/Name:
ownername/MyLibrary
(Recommended for clarity, uses PlatformIO Registry) - Version:
[email protected]
(exact version),MyLibrary@^1.2.0
(semantic versioning, >=1.2.0 and <2.0.0),MyLibrary@~1.2.0
(>=1.2.0 and <1.3.0) - VCS URL:
https://github.com/user/repo.git
,https://github.com/user/repo.git#tag_or_branch
- Local Path:
symlink:///path/to/local/library
(creates a symlink, recommended for local development),/path/to/local/library
(copies) - Archive URL/Path:
http://example.com/lib.zip
,file:///path/to/lib.tar.gz
- Name:
- Best Practices for Declaring
lib_deps
:- Use Owner/Name: Prefer
ownername/LibraryName
for libraries from the PlatformIO Registry to avoid ambiguity. - Pin Versions: For production or critical projects, specify exact versions (
[email protected]
) or use semantic versioning with a well-defined range (LibraryName@^1.2.0
) to prevent unexpected breaking changes from library updates. - Avoid Latest/Development Branches for Production: Using just
LibraryName
(latest) or pointing to a development branch of a Git repository can introduce untested or breaking changes.
- Use Owner/Name: Prefer
- Example:
[env:myenv] lib_deps = ; From PlatformIO Registry with semantic versioning bblanchon/ArduinoJson @ ^6.19.4 ; From PlatformIO Registry with exact version adafruit/Adafruit GFX Library @ 1.10.13 ; From a Git repository, specific tag https://github.com/knolleary/pubsubclient.git#v2.8 ; Local library (symlinked) symlink:///Users/me/Development/MyCustomSensorLib ; Another library from registry by name (less specific) OneWire
- Merging Dependencies: Use
${env.lib_deps}
to include dependencies from the common[env]
section in a specific[env:NAME]
section.[env] lib_deps = CommonLib1 [env:specific_build] lib_deps = ${env.lib_deps} ; Includes CommonLib1 SpecificLib2
Library Dependency Finder (LDF):
The LDF is a core component that automatically analyzes #include
directives in your C/C++ source files to determine which libraries are actually used and need to be compiled and linked.
-
LDF Modes (
lib_ldf_mode
):- Type:
String
, Values:off
,chain
,deep
,chain+
,deep+
- Default:
chain
- Controls how LDF scans for dependencies:
off
: Disables LDF. Only libraries specified inlib_deps
or library manifests are considered. You must manually ensure all includes are resolvable.chain
: (Default) LDF parses project source files (src_dir
) and follows#include
directives. If an included header belongs to a library, that library is added. It then scans the headers of that library for further dependencies but does not deeply parse the library's source files. Does NOT evaluate C/C++ preprocessor conditionals by default.deep
: Similar tochain
, but LDF recursively parses all C/C++ source files of each discovered dependency. More thorough but slower. Does NOT evaluate C/C++ preprocessor conditionals by default.chain+
: Same aschain
, but LDF attempts to evaluate C/C++ preprocessor conditionals (#ifdef
,#if defined()
,#else
,#elif
) to resolve includes within conditional blocks. This mode is more accurate if your includes are within such blocks.deep+
: Same asdeep
, but with preprocessor conditional evaluation. Most thorough, potentially slowest.
- Configuration:
[env:myenv] lib_ldf_mode = deep+
- Type:
-
Compatibility Modes (
lib_compat_mode
):- Type:
String
, Values:off
,soft
,strict
- Default:
soft
- Controls how strictly LDF checks if a found library is compatible with the current build environment's framework and platform.
off
: Disables compatibility checking. LDF will link any library that provides a required header, regardless of its declared compatibility. (Not Recommended)soft
: (Default) Checks if the library is compatible with theframework
(e.g.,arduino
) specified in the[env:NAME]
section.strict
: Checks compatibility with both theframework
and theplatform
(e.g.,espressif32
) of the build environment.
- Configuration:
[env:myenv] lib_compat_mode = strict
- Type:
-
How LDF Handles Includes & Preprocessor Directives: When
chain+
ordeep+
mode is active, LDF parses C/C++ preprocessor directives like#if
,#ifdef
,#ifndef
,#else
,#elif
, anddefined()
. It uses theCPPDEFINES
(macros defined viabuild_flags
or by frameworks) from the build environment to evaluate these conditions and decide whether to follow an#include
within such a block. This allows for more accurate dependency resolution when conditional compilation is used for including headers.- Example:
; platformio.ini [env:myenv] lib_ldf_mode = chain+ build_flags = -D USE_ADVANCED_FEATURE=1
// in main.c or a library #include "core_feature.h" // Always included #ifdef USE_ADVANCED_FEATURE #if USE_ADVANCED_FEATURE == 1 #include "advanced_feature_v1.h" // LDF will pick this up #else #include "advanced_feature_v2.h" // LDF will ignore this #endif #endif
- Example:
Library Storage & Search Order:
LDF searches for libraries in the following locations, in order of precedence:
lib_dir
(Project-Specific Libraries): Directory within your project (default:<Project>/lib/
). Each library should be in its own subdirectory. This location has the highest priority.libdeps_dir
(Project Dependencies): Directory where PlatformIO installs libraries specified inlib_deps
for the current build environment (default:<Project>/.pio/libdeps/<env_name>/
).core_dir/lib
(Global Libraries - Deprecated for new projects): PlatformIO's global library storage (default:~/.platformio/lib/
). Use is generally discouraged in favor of project-local dependencies.- Framework & SDK Libraries: Libraries bundled with the selected development platform and framework (e.g., standard Arduino libraries if
framework = arduino
).
Other Library Options (in platformio.ini
):
-
lib_ignore
:- Type:
String (Library Name)
, Multiple:Yes
- Description: A list of library names that the LDF should ignore, even if they are found in standard library storages or frameworks. Useful for resolving conflicts or excluding unused bundled libraries.
- Example:
lib_ignore = SPI, Wire
- Type:
-
lib_extra_dirs
:- Type:
DirPath
, Multiple:Yes
- Description: Specifies additional directories where LDF should search for libraries. Useful for monorepos or externally located shared libraries.
- Example:
lib_extra_dirs = ../common_libs, ~/my_global_pio_libs
- Type:
-
lib_archive
:- Type:
Bool (yes/no)
, Default:yes
for library projects,no
for firmware/application projects. - Description: Controls whether the LDF should build a library as a static archive (
.a
file).lib_archive = no
will compile library sources directly with the project's source files. This can sometimes help with resolving complex linking issues or when specific compiler options need to affect library code directly, but generallyyes
(the default for libraries) is preferred for better build organization and caching. - Example:
lib_archive = no
- Type:
Creating & Publishing Libraries:
To create a shareable PlatformIO library, it needs a manifest file, typically library.json
, at its root.
-
Recommended Library Structure:
MyAwesomeLibrary/ ├── library.json (Manifest file) ├── src/ (Source files: .c, .cpp, .h) │ └── MyAwesomeLibrary.cpp │ └── MyAwesomeLibrary.h ├── examples/ (Optional: usage examples) │ └── BasicUsage/ │ └── BasicUsage.ino ├── include/ (Optional: public headers if different from src/) └── README.md
The
src
directory (andinclude
if present) is automatically added to theCPPPATH
. -
library.json
Manifest: This JSON file describes the library. Key fields:-
name
(String, required): The unique name of the library. -
version
(String, required): Semantic version (e.g., "1.0.0"). Increment this before publishing updates. -
description
(String, required): A brief description. -
keywords
(Array of Strings or Comma-separated String): Helps users find your library. -
repository
(Object): VCS repository details.type
(String): e.g., "git"url
(String): e.g., "https://github.com/username/reponame.git"
-
authors
(Array of Objects): Information about authors.name
(String),email
(String),url
(String),maintainer
(Bool, optional: true if this person is the maintainer).
-
license
(String): SPDX license identifier (e.g., "MIT", "GPL-3.0-or-later"). -
homepage
(String): URL to the library's homepage. -
dependencies
(Object, optional): Key-value pairs where key isownername/libname
orlibname
and value is the version specification (e.g.,"bblanchon/ArduinoJson": "^6.0"
). -
frameworks
(Array of Strings or Comma-separated String or*
): Compatible frameworks (e.g.,"arduino"
,"espidf"
).*
means all. -
platforms
(Array of Strings or Comma-separated String or*
): Compatible platforms (e.g.,"espressif32"
,"atmelavr"
).*
means all. -
headers
(Array of Strings or String, optional): List of public header files. If not specified, LDF scans for headers. -
export
(Object, optional): Specifies which folders from the library should be added to theCPPPATH
of projects that use this library.- Example:
"export": { "include": ["src/util", "another_include_dir"] }
would add<lib_root>/src/util
and<lib_root>/another_include_dir
to the includepath. By default,src
andinclude
(if it exists) are exported.
- Example:
-
build
(Object, optional): Build options specific to the library.flags
(String or Array of Strings): Build flags for the library.srcFilter
(String or Array of Strings): Source filter for the library's own files.libArchive
(Bool): Override globallib_archive
setting for this library.extraScript
(String): Path to a SCons script (relative to library root) to be executed for this library's build.
-
Example
library.json
snippet:{ "name": "MySensorLib", "version": "1.2.0", "description": "Library for the XYZ sensor.", "keywords": ["sensor", "i2c", "temperature"], "repository": { "type": "git", "url": "https://github.com/myname/MySensorLib.git" }, "authors": [{ "name": "My Name", "email": "[email protected]", "maintainer": true }], "license": "MIT", "frameworks": ["arduino"], "platforms": ["espressif32", "atmelavr"], "dependencies": { "Wire": "*" // Standard Arduino Wire library }, "build": { "flags": "-D SENSOR_LIB_VERSION=2" } }
-
-
Publishing to PlatformIO Registry:
- Ensure your library has a
library.json
and is committed to a VCS (Git, Hg). Push your changes, including any tags for the version. - Increment the
version
inlibrary.json
for new releases. - Use the
pio pkg publish
command from the library's root directory. You'll need to be logged into your PlatformIO account (pio account login
).cd path/to/MyAwesomeLibrary pio pkg publish
- To remove a published library (use with caution):
pio pkg unpublish ownername/libraryname
- Ensure your library has a
Chapter 7: Debugging
PlatformIO offers integrated debugging capabilities for a wide range of microcontrollers and development boards, often with a "1-click" setup.
Introduction to PlatformIO Debugging:
Debugging allows you to step through your code line by line, inspect variables, examine memory, and understand the program's execution flow on the actual hardware. PlatformIO unifies the debugging experience across different platforms and debug probes.
pio debug
Command:
The primary command for initiating a debugging session.
- Usage:
pio debug [OPTIONS]
- Description: Prepares a PlatformIO project for debugging (ensuring it's built with
build_type = debug
) and can launch a debug server or a GDB client. - Key Options:
-e, --environment TEXT
: Specify the environment to debug. If not set, usesdefault_envs
fromplatformio.ini
.-d, --project-dir DIRECTORY
: Path to the project directory. Default: current working directory.--interface gdb
: Launches GDB directly (if supported by the debug tool).--load-mode MODE
: Overridesdebug_load_mode
(e.g.,manual
,modified
,always
).-v, --verbose
: Show detailed information.
- Typical Workflow:
- PlatformIO IDE typically handles
pio debug
invocation when you start a debug session. - Manually,
pio debug
might be used to start a debug server, and then a GDB client connects to it. For instance, to just prepare and start the server:pio debug
- To launch GDB with project-specific GDB init scripts (often in
.pio/debug/<env>/pioinit.gdb
):pio debug --interface=gdb -- -x .pio/debug/myenv/pioinit.gdb
- PlatformIO IDE typically handles
Configuring Debug Tools (platformio.ini
):
Debugging behavior is configured within the [env:NAME]
section of your platformio.ini
.
-
debug_tool
:- Type:
String
- Description: Specifies the debugging tool/probe (e.g.,
stlink
,jlink
,cmsis-dap
,esp-prog
,simavr
). The available tools depend on the platform and board. - Example:
debug_tool = stlink
- Type:
-
debug_build_flags
:- Type:
String
, Multiple:Yes
- Description: Extra build flags specifically for debug builds. These are appended to other build flags when
build_type = debug
or whenpio debug
is run. - Default: Usually includes flags like
-Og -ggdb3 -g3
. - Example:
debug_build_flags = -Og -ggdb3 -DCORE_DEBUG_LEVEL=5
- Type:
-
debug_init_break
:- Type:
String
- Description: Sets the initial breakpoint location when the debugger starts (e.g.,
tbreak main
,break setup
). - Example:
debug_init_break = tbreak app_main
- Type:
-
debug_init_cmds
:- Type:
String
, Multiple:Yes
- Description: GDB commands to execute after the GDB server connection is established but before firmware loading.
- Example:
debug_init_cmds = define pio_reset_halt echo Resetting target... monitor reset halt end pio_reset_halt set mem inaccessible-by-default off
- Type:
-
debug_extra_cmds
:- Type:
String
, Multiple:Yes
- Description: Extra GDB commands to execute after the debugger is fully initialized.
- Type:
-
debug_load_cmds
:- Type:
String
, Multiple:Yes
- Description: Commands used for loading the firmware onto the target. Default is often
load
. - Example:
debug_load_cmds = load my_firmware.elf
- Type:
-
debug_load_mode
:- Type:
String
, Values:always
,modified
,manual
- Description: Controls when firmware is loaded:
always
: Load firmware every time a debug session starts.modified
: Load firmware only if it has changed since the last session.manual
: Do not load firmware automatically (user must load via GDB command).
- Example:
debug_load_mode = modified
- Type:
-
debug_server
:- Type:
String
, Multiple:Yes
- Description: Specifies the command and arguments to start the debug server (e.g., OpenOCD, J-Link GDB Server). PlatformIO often configures this automatically based on
debug_tool
. - Example (custom OpenOCD):
debug_server = ${platformio.packages_dir}/tool-openocd/bin/openocd -f interface/stlink.cfg -f target/stm32f4x.cfg
- Type:
-
debug_port
:- Type:
String
- Description: The GDB server port (e.g.,
localhost:3333
for OpenOCD,localhost:2331
for J-Link). Often auto-configured.
- Type:
-
debug_speed
:- Type:
String
- Description: Debugging adapter speed (e.g., JTAG/SWD clock speed).
- Type:
-
debug_svd_path
:- Type:
FilePath
- Description: Path to an SVD (System View Description) file for viewing peripheral registers in the debugger.
- Type:
-
debug_server_ready_pattern
:- Type:
String (Regex)
- Description: A regular expression pattern that PlatformIO uses to detect when the
debug_server
is ready to accept connections.
- Type:
Debug Build Configuration:
For debugging, your project must be compiled with debugging symbols and minimal optimization. PlatformIO handles this automatically if:
build_type = debug
is set in the[env:NAME]
section ofplatformio.ini
.- You initiate debugging via
pio debug
or the PlatformIO IDE's debug functionality (which implicitly uses a debug build configuration).
Framework-Specific Debugging Notes:
While PlatformIO aims for a unified experience, some framework-specific considerations exist:
- Arduino: Debugging typically works well with common debug probes. Ensure your board has debug PINS (SWD/JTAG) accessible.
- ESP-IDF (Espressif 32/S2/S3/C3): Requires a JTAG adapter like ESP-PROG or a board with built-in JTAG (e.g., ESP-WROVER-KIT). Configuration involves
debug_tool = esp-prog
(or similar) and sometimes specific OpenOCD scripts. - Zephyr: Debugging is well-supported. Configuration depends on the board and chosen debug probe.
- Mbed OS: Debugging is generally supported through CMSIS-DAP or other compatible probes.
For specific board and framework debugging setups, refer to the board's documentation within PlatformIO or the platform's documentation.
Chapter 8: Unit Testing
PlatformIO provides a robust Unit Testing solution to test firmware components directly on the target hardware or natively on the host machine.
Introduction to Unit Testing in PlatformIO:
Unit testing involves breaking down your project into smaller, testable parts (units) and verifying their correctness independently. This helps catch bugs early and ensures code quality.
pio test
Command:
The command to run unit tests.
- Usage:
pio test [OPTIONS]
- Key Options:
-e, --environment TEXT
: Process tests for specified environments.-f, --filter PATTERN
: Run only test suites whose path (relative totest_dir
) matches the glob pattern. Multiple filters allowed. Example:pio test -f "test_module_a/*" -f "common_tests/*"
.-i, --ignore PATTERN
: Ignore test suites matching the pattern. Example:pio test -i "embedded/*"
.--upload-port PORT
: Specify upload port for on-target tests.--test-port PORT
: Serial port for communication with the target during testing.--without-building
: Skip the building stage.--without-uploading
: Skip the uploading stage (useful for host-based tests or simulators).--without-testing
: Build and upload but do not run tests.--list-tests
: List project tests without running them.--json-output-path FILEPATH
: Generate a JSON report of test results.--junit-output-path FILEPATH
: Generate a JUnit XML report (for CI systems).-a, --program-arg ARG
: Pass an argument to the native testing program. Multiple allowed.-v, --verbose
: Show raw output from the testing framework.-vv
,-vvv
increase build/upload verbosity.
- Remote Testing:
pio remote test
(see Chapter 9).
Project Structure for Tests:
test_dir
(Default:<Project>/test/
): The root directory for all your tests.- Test Hierarchy:
- Each subfolder within
test_dir
whose name starts withtest_
(e.g.,test/test_component_A
,test/test_another_feature
) is considered an independent test suite or application. - Each test suite is compiled as a separate executable.
- Source files (
.c
,.cpp
) directly in atest_
subfolder are compiled for that test suite. - The root
test_dir
and the current test suite's folder are automatically added toCPPPATH
. Common utility files for tests can be placed in the roottest_dir
.
- Each subfolder within
- Example Structure:
my_project/ ├── platformio.ini ├── src/ │ └── main.c │ └── calculator.c │ └── calculator.h └── test/ ├── test_common/ (Common test utilities or shared test code) │ └── test_utils.h ├── test_calculator_native/ (Native tests for calculator) │ └── test_main.c └── test_calculator_embedded/ (Embedded tests for calculator) └── test_on_board.c
Shared Code between src/
and test/
:
By default, the main application code from src_dir
is not compiled with test suites to ensure test independence and avoid multiple main()
definitions.
- Recommended: Place shared code into local libraries within the
lib_dir
of your project. Both your main application (src/
) and your tests (test/
) can then#include
these libraries. LDF will handle linking. - Alternative (Less Recommended -
test_build_src = true
): If you must test code directly fromsrc_dir
(e.g., for a library project wheresrc/
is the library itself), you can settest_build_src = true
inplatformio.ini
.- Warning: If
src_dir
contains amain()
function (orsetup()
/loop()
for Arduino,app_main()
for ESP-IDF), you must guard it with#ifndef PIO_UNIT_TESTING ... #endif
to prevent it from being compiled with your tests, which will have their own entry points.// src/main.c #include <stdio.h> #include "my_lib_functions.h" // Functions to be tested #ifndef PIO_UNIT_TESTING int main() { // Application main logic printf("Result: %d\n", add_numbers(2, 3)); return 0; } #endif
- Warning: If
Test Configuration (platformio.ini
options in [env:NAME]
):
test_framework
: Specifies the unit testing framework.- Type:
String
, Multiple:Yes
(can list multiple, PIO will try to use them) - Values:
unity
(default if not set and Unity headers found),googletest
,doctest
,custom
. - Example:
test_framework = unity, googletest
- Type:
test_filter
: Glob pattern(s) to select specific test suites to run. Overrides CLI-f
.test_ignore
: Glob pattern(s) to ignore specific test suites. Overrides CLI-i
.test_port
: Serial port for communication during on-target testing.test_speed
: Baud rate fortest_port
.test_build_src
: (Bool,yes/no
, defaultno
). Ifyes
, compiles sources fromsrc_dir
with the test suite. See "Shared Code" section above.test_testing_command
: Custom command to execute the test program, especially for native tests or simulators.- Can use variables like
${platformio.build_dir}/${this.__env__}/program
(path to test executable). - Example for a native test with arguments:
test_testing_command = ${platformio.build_dir}/${this.__env__}/program --my-custom-arg
- Can use variables like
Test Types:
- Native: Tests compiled and run on the host machine (where
pio test
is executed).- Typically uses a
native
platform inplatformio.ini
(e.g.,platform = native
). - Requires a system C/C++ compiler (GCC, Clang, MSVC).
- Useful for testing logic independent of hardware, often with mocking.
- Typically uses a
- Embedded: Tests compiled for and run on the target embedded device.
- PlatformIO builds the test firmware, uploads it, communicates via
test_port
to gather results.
- PlatformIO builds the test firmware, uploads it, communicates via
- Hybrid: Test code written to be compilable and runnable on both native and embedded targets.
Supported Testing Frameworks:
-
Unity:
- Config:
test_framework = unity
- Lightweight C-based framework, suitable for embedded.
- Requires test functions like
void test_my_function(void)
,setUp()
,tearDown()
. - Main entry point:
UNITY_BEGIN()
,RUN_TEST(test_function)
,UNITY_END()
. - Configuration via
#define
s inbuild_flags
or a customunity_config.h
placed in the test suite's folder hierarchy.
// test/test_sample/test_main.c #include "unity.h" // #include "path/to/your/code.h" // Include the code to test void setUp(void) { /* Executed before each test */ } void tearDown(void) { /* Executed after each test */ } void test_addition(void) { TEST_ASSERT_EQUAL_INT(5, 2 + 3); } int main(void) { // Or setup()/loop() for Arduino, app_main() for ESP-IDF UNITY_BEGIN(); RUN_TEST(test_addition); return UNITY_END(); }
- Config:
-
GoogleTest (GMock):
- Config:
test_framework = googletest
- Powerful C++ framework, supports mocking (GMock).
- Works natively and on some embedded platforms (e.g., ESP32).
- Requires a
main()
function that initializes GoogleTest:::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS();
. (For Arduino, this is done insetup()
andloop()
). - Configuration via environment variables or command-line arguments passed to the test program (using
pio test -a "--gtest_filter=MyTestSuite.*"
ortest_testing_command
).
// test/test_gtest_example/test_main.cpp #include <gtest/gtest.h> // #include <gmock/gmock.h> // For GMock // Your code to test (e.g., in a header or linked library) int add(int a, int b) { return a + b; } TEST(MyTestSuite, AdditionTest) { ASSERT_EQ(add(2, 3), 5); EXPECT_EQ(add(-1, 1), 0); } #if defined(ARDUINO) // Example for Arduino #include <Arduino.h> void setup() { Serial.begin(115200); ::testing::InitGoogleTest(); } void loop() { RUN_ALL_TESTS(); delay(1000); // Prevent re-running too fast } #else // For native int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } #endif
- Config:
-
Doctest:
- Config:
test_framework = doctest
- Fast, header-only C++ framework, primarily for native tests.
- Requires a
main()
function that creates and runs adoctest::Context
. - PlatformIO requires specific options to be set on the context for correct reporting.
- Configuration via C++ preprocessor defines or
doctest::Context
options.
// test/test_doctest_example/test_main.cpp #define DOCTEST_CONFIG_IMPLEMENT // Required for custom main #include <doctest.h> int factorial(int number) { return number <= 1 ? number : factorial(number - 1) * number; } TEST_CASE("testing the factorial function") { CHECK(factorial(1) == 1); CHECK(factorial(2) == 2); CHECK(factorial(3) == 6); } int main(int argc, char** argv) { doctest::Context context; // PlatformIO required options: context.setOption("success", true); // Report successful tests context.setOption("no-exitcode", true); // Do not return non-zero on failure for PIO parsing // Your custom options: // context.setOption("abort-after", 5); context.applyCommandLine(argc, argv); return context.run(); }
- Config:
-
Custom Testing Frameworks:
- Config:
test_framework = custom
- Allows integration of any testing framework.
- Requires you to provide a custom Python test runner script that inherits from PlatformIO's
CustomTestRunner
base class. - The runner script is responsible for discovering tests, executing them, and parsing their output to report status (PASS, FAIL, SKIP) for each test case.
- This is an advanced topic; refer to PlatformIO's documentation on custom test runners for API details.
- Config:
Using Simulators (QEMU, Renode, SimAVR):
Run unit tests in a simulated hardware environment instead of physical hardware. This is useful for CI or when hardware is unavailable.
-
Configuration:
- Ensure the simulator toolchain is available (e.g., via
platform_packages
). - Set
test_testing_command
to invoke the simulator with the compiled test firmware. - Use
pio test --without-uploading
as the uploading stage is not needed.
- Ensure the simulator toolchain is available (e.g., via
-
Example (SimAVR for Uno):
[env:uno_simavr] platform = atmelavr board = uno framework = arduino platform_packages = platformio/tool-simavr ; Ensure simavr is available test_speed = 9600 ; For simulator's UART output if test framework uses it test_testing_command = ${platformio.packages_dir}/tool-simavr/bin/simavr -m atmega328p -f 16000000L ; CPU frequency ${platformio.build_dir}/${this.__env__}/firmware.elf ; Path to test firmware
Then run:
pio test -e uno_simavr --without-uploading
-
QEMU Example (HiFive1):
[env:hifive1_qemu] platform = sifive framework = freedom-e-sdk board = hifive1 platform_packages = platformio/tool-qemu-riscv test_testing_command = ${platformio.packages_dir}/tool-qemu-riscv/bin/qemu-system-riscv32 -nographic -machine sifive_e -kernel ${platformio.build_dir}/${this.__env__}/firmware.elf
Semihosting:
A mechanism allowing code running on an embedded target to use the I/O capabilities of the host computer (e.g., printf
to host console, file access). This is often used with debug probes.
- Configuration:
- Modify linker flags: Remove
nosys
specs (e.g.,-lnosys
,-specs=nosys.specs
) and add semihosting specs (e.g.,--specs=rdimon.specs
,-lrdimon
). This is highly toolchain-specific. - The debug tool and GDB server must support semihosting (e.g., OpenOCD often requires
monitor arm semihosting enable
). - May need
test_testing_command
to configure the GDB client or debug server appropriately.
- Modify linker flags: Remove
Chapter 9: Remote Development
PlatformIO Remote allows you to work with devices connected to a remote machine as if they were local. This requires a free PlatformIO Account.
Overview of PlatformIO Remote:
Enables remote firmware updates, device monitoring, unit testing, and sharing devices with team members.
Installation & Quick Start:
- Remote Machine (Agent - where physical devices are):
- Install PlatformIO Core.
- Log in:
pio account login
- Start the agent:
pio remote agent start
- To share with others:
pio remote agent start --share [email protected] --share [email protected]
(Agent name defaults to hostname; use-n <custom_agent_name>
if needed).
- Host Machine (Client - where you develop):
- Install PlatformIO Core.
- Log in:
pio account login
(use the same PlatformIO account). - Alternatively, for CI or non-interactive environments, set the
PLATFORMIO_AUTH_TOKEN
environment variable with a token obtained frompio account token
.
PlatformIO Remote CLI Commands:
-
Agent Management:
pio remote agent list
: Lists active agents associated with your account.pio remote agent start [OPTIONS]
: Starts the agent on the current (remote) machine.-n, --name NAME
: Custom name for this agent.-s, --share EMAIL_OR_ORG_ID
: Share agent with specified PIO accounts or organization.-d, --working-dir PATH
: Directory for agent's data.
-
Device Interaction (run these on the Client machine): To target a specific agent if multiple are running or shared, use:
pio remote --agent <AGENT_NAME_OR_ID> <COMMAND>
pio remote device list
: Lists devices connected to the remote agent(s).pio remote device monitor [OPTIONS]
: Starts a serial monitor for a remote device. Options are similar to localpio device monitor
(--port
,--baud
, etc.). The port should be the name/path as seen by the remote agent.
-
Remote Project Operations (run these on the Client machine): When running these commands, PlatformIO synchronizes your project from the client to the specified remote agent, builds it there, and then performs the action (upload, test).
pio remote run [OPTIONS]
: Similar to localpio run
, but executes on the remote agent.-e, --environment ENV
: Specify project environment.-t, --target TARGET
: Specify target (e.g.,upload
,clean
).--upload-port PORT
: Specify the upload port as seen by the remote agent. For OTA updates, this can be an IP address or.local
hostname on the remote agent's network.--force-remote
: (Usually implied bypio remote run
) Ensure processing happens remotely.
pio remote test [OPTIONS]
: Similar to localpio test
, but executes on the remote agent with devices attached to it.--upload-port PORT
,--test-port PORT
: Ports as seen by the remote agent.
pio remote update [PROJECT_OPTIONS]
: Updates platforms and libraries for a project on a remote agent.
Sharing Devices:
When starting an agent with pio remote agent start --share <email>
, the specified PlatformIO user(s) will be able to see and use the devices connected to that agent via their own pio remote ...
commands.
PlatformIO supports a wide array of microcontroller platforms and development frameworks. This section outlines how to configure and use some of the most common ones within a PlatformIO project.
Chapter 10: Working with Development Platforms
A development platform in PlatformIO represents a family of microcontrollers (e.g., Atmel AVR, Espressif ESP32) and includes the necessary toolchains, SDKs, and board definitions.
-
Selecting a Platform: Specified using the
platform
option in yourplatformio.ini
's[env:NAME]
section.[env:my_avr_project] platform = atmelavr ; Use the latest stable version [env:my_esp32_project] platform = [email protected] ; Pin to a specific version
-
Listing Available Platforms and Boards:
pio platform list
: Shows installed platforms.pio platform search <query>
: Searches the PlatformIO Registry for platforms.pio boards <query>
: Searches for boards and shows their ID, platform, and compatible frameworks. Use the "ID" for theboard
option inplatformio.ini
.
-
Platform Packages (
platform_packages
): This option allows you to specify custom versions of packages (toolchains, frameworks, tools) for a platform, or add extra packages.- Type:
String
, Multiple:Yes
- Syntax:
owner/package_name @ version_constraint
orowner/package_name = VCS_URL#commit_or_branch
. - Example:
[env:custom_toolchain_esp32] platform = espressif32 board = esp32dev platform_packages = platformio/toolchain-xtensa-esp32 @ ~8.4.0 ; Use specific toolchain version platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2.0.5 ; Use git version
- Type:
Chapter 11: Framework Integration Guides
General Concepts:
The framework
option in platformio.ini
declares which software framework(s) to use for your project. PlatformIO then configures the build system accordingly, linking the necessary libraries and setting up the correct build process.
[env:myenv]
platform = ...
board = ...
framework = arduino ; Single framework
; framework = arduino, espidf ; Multiple frameworks (if supported by platform)
Arduino:
The Arduino framework provides a simplified C++ based environment for a vast number of boards.
- Configuration:
framework = arduino
- MCUdude's Cores (MiniCore, MightyCore, MegaCore, etc.):
These popular cores for AVR microcontrollers often require specific
board_build.variant
,board_build.core
, and sometimesboard_build.f_cpu
or fuse settings inplatformio.ini
. Refer to the official documentation for each MCUdude core on GitHub for PlatformIO-specific configuration instructions (they usually provideplatformio.ini
snippets).- Example (conceptual for MiniCore):
[env:atmega328_minicore] platform = atmelavr board = ATmega328P ; This might be a generic board ID framework = arduino board_build.core = minicore board_build.variant = 328p ; Or other variant board_build.f_cpu = 16000000L ; Fuses might be set via upload_flags or custom targets upload_flags = -P$UPLOAD_PORT -b$UPLOAD_SPEED -Ulock:w:0xCF:m ; ... other fuse flags
- Example (conceptual for MiniCore):
- Debugging: Generally supported with standard debug probes (ST-Link, J-Link, CMSIS-DAP) if the board exposes debug pins (SWD/JTAG). For AVRs (like Uno), debugging is more limited, often relying on
debugWIRE
(via tools likedwire-debug
) or simulators likesimavr
.
ESP-IDF (Espressif IoT Development Framework):
The official development framework for ESP32, ESP32-S2, ESP32-S3, and ESP32-C series SoCs.
- Configuration:
framework = espidf
- Project Structure:
PlatformIO expects a specific project structure when using ESP-IDF. The main application code and its
CMakeLists.txt
are typically placed in a subdirectory (oftenmain/
or the directory specified bysrc_dir
inplatformio.ini
).project_dir/ ├── platformio.ini ├── CMakeLists.txt (Root CMakeLists for ESP-IDF project) └── main/ (Or your src_dir) ├── CMakeLists.txt (Component CMakeLists) └── main.c (Application entry point, e.g., app_main) ├── components/ (Optional: custom ESP-IDF components) │ └── my_component/ │ ├── CMakeLists.txt │ └── my_component.c └── ulp/ (Optional: for ULP coprocessor code) └── ulp_assembly_code.S
- Root
CMakeLists.txt
:cmake_minimum_required(VERSION 3.16.0) # Or higher as required by ESP-IDF version include($ENV{IDF_PATH}/tools/cmake/project.cmake) # list(APPEND EXTRA_COMPONENT_DIRS components/my_component) # If using custom components dir project(my_esp_idf_project)
- Component
CMakeLists.txt
(e.g.,main/CMakeLists.txt
):idf_component_register(SRCS "main.c" "another_file.c" INCLUDE_DIRS "." REQUIRES mbedtls # Example of requiring another component # PRIV_REQUIRES other_private_component )
- ULP (Ultra Low Power) Coprocessor Programming:
- Add ULP assembly files (e.g.,
ulp/ulp_code.S
). - In the component's
CMakeLists.txt
that uses ULP:# ... other idf_component_register parts ... set(ulp_app_name ulp_main) # Must be ulp_main set(ulp_s_sources "../ulp/ulp_code.S") # Path relative to this CMakeLists.txt set(ulp_exp_dep_srcs "main_file_that_includes_ulp_header.c") # .c file that includes generated ulp_main.h ulp_embed_binary(${ulp_app_name} ${ulp_s_sources} ${ulp_exp_dep_srcs})
- Add ULP assembly files (e.g.,
- Limitations:
- Project paths should not contain whitespace characters. Workaround: set
core_dir
inplatformio.ini
to a path without spaces (e.g.,C:/.platformio
). - The
src_filter
option inplatformio.ini
has limited or no effect with ESP-IDF due to its CMake-based build system. Component sources are managed byCMakeLists.txt
files.
- Project paths should not contain whitespace characters. Workaround: set
- Debugging: Requires a JTAG adapter (e.g., ESP-PROG) or a board with on-board JTAG. Set
debug_tool = esp-prog
or similar inplatformio.ini
. OpenOCD is typically used as the GDB server.
Zephyr RTOS:
A scalable real-time operating system (RTOS) for embedded devices.
- Configuration:
framework = zephyr
- Project Structure:
PlatformIO expects Zephyr-specific configuration files (like
prj.conf
and aCMakeLists.txt
for the app) to be in a subdirectory, typically namedzephyr/
or<src_dir>/zephyr/
or justsrc/
ifsrc_dir
points there.project_dir/ ├── platformio.ini ├── src/ (Contains main.c and Zephyr config) │ ├── prj.conf (Zephyr Kconfig settings for the application) │ ├── CMakeLists.txt (Zephyr application CMakeLists.txt) │ └── main.c └── boards/ (Optional: for custom board overlays) └── my_custom_board.overlay
- Application
CMakeLists.txt
(e.g.,src/CMakeLists.txt
):cmake_minimum_required(VERSION 3.20.0) # Or higher as required by Zephyr version find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) project(my_zephyr_app) # Add your application's source files target_sources(app PRIVATE main.c) # target_link_libraries(app PRIVATE some_interface_library)
prj.conf
(e.g.,src/prj.conf
): Contains Kconfig settings to enable/disable Zephyr features and configure modules.CONFIG_PRINTK=y CONFIG_GPIO=y CONFIG_MAIN_STACK_SIZE=2048
- Devicetree Overlays (
.overlay
files): To customize board hardware configurations.- Recommended: Set
DTC_OVERLAY_FILE
in your application'sCMakeLists.txt
beforefind_package(Zephyr...)
:set(DTC_OVERLAY_FILE ${CMAKE_CURRENT_SOURCE_DIR}/my_specific_overlay.overlay) # or for multiple: # set(DTC_OVERLAY_FILE # ${CMAKE_CURRENT_SOURCE_DIR}/overlay1.overlay # ${CMAKE_CURRENT_SOURCE_DIR}/overlay2.overlay # ) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) # ...
- Place a
<board_name>.overlay
file in the same directory as yourprj.conf
and applicationCMakeLists.txt
(e.g.,src/nrf52840dk_nrf52840.overlay
).<board_name>
must match the official Zephyr board name. - Place board-specific overlays in a
boards/
subdirectory next toprj.conf
(e.g.,src/boards/nrf52840dk_nrf52840.overlay
).
- Recommended: Set
- Zephyr Modules (Out-of-Tree):
To include external Zephyr modules, set
ZEPHYR_EXTRA_MODULES
in your application'sCMakeLists.txt
beforefind_package(Zephyr...)
.set(ZEPHYR_EXTRA_MODULES ${CMAKE_CURRENT_SOURCE_DIR}/../modules/my_custom_module ${CMAKE_CURRENT_SOURCE_DIR}/../external_libs/another_zephyr_module ) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) # ...
- Embedding Files: Use
board_build.embed_files
inplatformio.ini
to embed binary files into your firmware.These files will be accessible in your C code via auto-generated symbols.[env:my_zephyr_env] board_build.embed_files = src/my_certificate.der src/data_blob.bin
- Limitations: Minimum Python 3.6 required. Project paths should not contain whitespace.
- Debugging: Well-supported. Configure
debug_tool
based on your board and probe.
Mbed OS:
An open-source embedded operating system designed for IoT devices.
- Configuration:
framework = mbed
mbed_app.json
: Mbed OS uses ambed_app.json
file in the project root (same level asplatformio.ini
) for compile-time configuration of Mbed OS parameters.// mbed_app.json { "target_overrides": { "*": { "platform.stdio-baud-rate": 115200, "target.printf_lib": "minimal-printf", "target.c_lib": "small" }, "NUCLEO_F401RE": { "target.extra_labels_add": ["CUSTOM_FEATURE"] } }, "config": { "main-stack-size": { "help": "Configuration options for main stack size", "value": 4096 } }, "macros": ["MY_GLOBAL_APP_MACRO"] }
- Build Profiles: Mbed OS has build profiles (e.g., release, debug, develop). PlatformIO typically manages this based on
build_type
. You can force specific Mbed build flags if needed:MBED_BUILD_PROFILE_DEVELOPMENT
(default by Mbed OS for its tools)MBED_BUILD_PROFILE_DEBUG
MBED_BUILD_PROFILE_RELEASE
You can add these tobuild_flags
inplatformio.ini
:build_flags = -DMBED_BUILD_PROFILE_RELEASE
.mbedignore
: Create this file in your project root to exclude specific Mbed OS folders/components from the build, potentially reducing compile time and firmware size.# .mbedignore example mbed-os/features/cellular/* mbed-os/connectivity/drivers/wifi/COMPONENT_MYUNUSEDWIFI/*
- Custom Targets: If you have a custom board not directly supported, you can define it by:
- Creating a
custom_targets.json
in your project root. - Adding target-specific code (e.g., in
src/TARGET_MYCUSTOMBOARD/
). - Referencing this in
platformio.ini
viabuild_flags = -I$PROJECT_SRC_DIR/TARGET_MYCUSTOMBOARD
and potentially a custom board manifest. This is an advanced use case.
- Creating a
- Windows Path Length: For Windows, if you encounter path length issues, set
core_dir
inplatformio.ini
to a short path (e.g.,C:/.platformio
).
Other Frameworks:
PlatformIO supports many other frameworks. The general approach is:
- Set
framework = <framework_name>
inplatformio.ini
. - Refer to the specific framework's documentation page on the PlatformIO website for any unique configuration options or project structure requirements.
- CMSIS:
framework = cmsis
. Often used as a base layer. - Freedom E SDK (SiFive):
framework = freedom-e-sdk
. Can be combined with FreeRTOS:framework = freedom-e-sdk, freertos
. Configure FreeRTOS options viaboard_build.freertos.*
inplatformio.ini
(e.g.,board_build.freertos.heap_model = heap_4
). - LibOpenCM3:
framework = libopencm3
. A free/libre/open-source firmware library for various ARM Cortex-M MCUs. - PULP OS / SDK / Runtime (Greenwaves, OpenHW Group):
framework = pulp-os
,framework = pulp-sdk
,framework = pulp-runtime
. - Shakti SDK:
framework = shakti-sdk
. - SPL (Standard Peripheral Library - ST):
framework = spl
. For older STM32/STM8 development. - STM32Cube:
framework = stm32cube
. The official STMicroelectronics HAL and LL libraries.- PlatformIO provides several
board_build.stm32cube.*
options for customization:board_build.stm32cube.custom_config_header = yes
: Use your ownstm32XXxx_hal_conf.h
instead of the template.board_build.stm32cube.custom_system_setup = yes
: Skip default startup code if you provide your own.board_build.stm32cube.system_file
: Specify a non-default system initialization C file.board_build.stm32cube.startup_file
: Specify a non-default startup assembly file.board_build.stm32cube.custom_dsp_library = yes
: Disable default DSP library.board_build.stm32cube.variant
: Select target-specific macros for BSPs (e.g.,STM32G4xx_Nucleo
).
- PlatformIO provides several
- CMSIS:
Always consult the PlatformIO documentation for the specific platform and framework combination you are using, as configuration details can vary.
Chapter 12: Continuous Integration (CI)
Continuous Integration is a development practice where developers frequently merge their code changes into a central repository, after which automated builds and tests are run. PlatformIO is well-suited for CI workflows.
Overview (pio ci
command):
The pio ci
command is designed for use in CI environments. It allows you to build a project, process examples, and specify input source locations without needing a full project checkout in some cases. However, more commonly, CI setups will directly use pio run
, pio test
, etc., after checking out the project.
- Basic Usage:
# Build a project in the current directory pio ci --board=uno --lib="." examples/arduino-blink/arduino-blink.ino # Build specific examples from a given path pio ci path/to/project/examples --board=nodemcuv2 --board=esp32dev
- Options:
SRC...
: Source files, project, or examples directory.-l, --lib LIB_DIR...
: Library storage directory.-b, --board ID...
: Board ID(s) fromplatformio.ini
or PlatformIO registry.--build-dir DIR
: Custom build directory.--project-conf FILE
: Customplatformio.ini
.-O, --project-option OPTION...
: Pass options toplatformio.ini
(e.g.,framework=arduino
).-v, --verbose
: Verbose output.
While pio ci
exists, most modern CI setups involve:
- Checking out the full project repository.
- Installing PlatformIO Core.
- Running standard commands like
pio run -e <environment_name>
andpio test -e <environment_name>
.
Integrating with Common CI/CD Services:
The general approach for integrating PlatformIO with CI services involves:
- Setting up the CI environment: Configure the CI runner/virtual machine to have Python and install PlatformIO Core.
- Checkout: Fetch the project code from your version control system.
- Build & Test: Execute PlatformIO commands (
pio run
,pio test
). - Artifacts/Reporting: Collect build artifacts (firmware files) and test reports (e.g., JUnit XML).
-
GitHub Actions:
- Create a workflow YAML file in
.github/workflows/
. - Use existing actions like
actions/setup-python
and then install PlatformIO Core viapip
. - Example
.github/workflows/main.yml
:name: PlatformIO CI on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: submodules: true # If you use git submodules - name: Set up Python uses: actions/setup-python@v3 with: python-version: '3.9' - name: Install PlatformIO Core run: pip install -U platformio - name: Build project run: pio run -e uno_release # Replace with your environment - name: Run tests run: pio test -e native_tests # Replace with your test environment # Optional: Upload firmware artifact # - name: Upload Firmware # uses: actions/upload-artifact@v3 # with: # name: firmware-${{ github.run_id }} # path: .pio/build/uno_release/firmware.hex # Adjust path
- Create a workflow YAML file in
-
GitLab CI:
- Create a
.gitlab-ci.yml
file in your project root. - Use a Docker image with Python pre-installed or install Python and PlatformIO in a
before_script
section. - Example
.gitlab-ci.yml
:image: python:3.9-slim # Or your preferred Python image stages: - build - test before_script: - pip install -U platformio build_job: stage: build script: - pio run -e esp32_firmware # Replace with your environment artifacts: paths: - .pio/build/esp32_firmware/firmware.bin # Adjust path test_job: stage: test script: - pio test -e native_tests # Replace with your test environment # For JUnit reports: # - pio test -e native_tests --junit-output-path report.xml # artifacts: # when: always # reports: # junit: report.xml
- Create a
-
Travis CI (Legacy, but still seen):
- Create a
.travis.yml
file. - Specify Python version and install PlatformIO in
install
step. - Example
.travis.yml
:language: python python: - "3.9" cache: directories: - "~/.platformio" # Cache PlatformIO packages install: - pip install -U platformio script: - pio run -e my_build_env - pio test -e my_test_env
- Create a
-
Other CI Services (AppVeyor, CircleCI, Drone, etc.): The principles are similar: set up Python, install PlatformIO, and execute
pio
commands. Refer to the specific CI service's documentation for their configuration file syntax. PlatformIO's own documentation often has example configurations for these.
Key Considerations for CI:
- Caching: Cache PlatformIO's package directory (usually
~/.platformio/packages
or content ofcore_dir
) to speed up subsequent builds by avoiding re-downloading toolchains and frameworks. - Test Reporting: Use
pio test --json-output-path
orpio test --junit-output-path
to generate reports that can be consumed by CI systems for displaying test results. - Artifacts: Configure your CI job to save compiled firmware files (
.bin
,.hex
) as build artifacts. - Secrets Management: For operations requiring authentication (like
pio account token
for remote operations or publishing), use the CI service's secure secret management features to store tokens as environment variables. - Matrix Builds: Many CI systems allow you to define a build matrix to test against multiple environments, platforms, or configurations simultaneously.
Chapter 13: PlatformIO Account & Services
PlatformIO Account:
A PlatformIO Account is used to access various PlatformIO services.
- Required for:
- PlatformIO Community Forums.
- Remote Development (Chapter 9).
- Managing organizations, teams, and package ownership in the PlatformIO Registry.
- Publishing libraries and platforms to the PlatformIO Registry.
- Management:
- PlatformIO IDE: Provides a UI in PlatformIO Home for account creation, login, password reset, profile updates, and fetching authentication tokens.
- CLI (
pio account
):pio account login
: Log in to your PlatformIO account.pio account logout
: Log out.pio account register
: Create a new account.pio account password
: Change your password.pio account forgot
: Request a password reset.pio account show
: Display current account information.pio account update
: Update profile information.pio account token [--regenerate] [--json-output]
: Get or regenerate your Personal Authentication Token. This token can be used as thePLATFORMIO_AUTH_TOKEN
environment variable for non-interactive authentication (e.g., in CI).pio account destroy
: Delete your PlatformIO account.
PlatformIO Home:
PlatformIO Home is a graphical user interface, typically accessed within PlatformIO IDE (e.g., VSCode) or by running pio home
from the CLI. It provides a visual way to:
- Manage projects (create, import).
- Inspect boards and platforms.
- Search and manage libraries.
- Monitor devices.
- Manage your PlatformIO Account.
PlatformIO Registry & Package Management (pio pkg
):
The PlatformIO Registry is a central repository for libraries, development platforms, and tools. The pio pkg
command suite is used to interact with it and manage installed packages. (Note: pio lib
and pio platform
are older, more specific commands, while pio pkg
is the more unified, newer interface).
pio pkg install <package_spec...>
: Install packages.--global
: Install to global storage.--library <name_or_id>
/--platform <name_or_id>
/--tool <name_or_id>
: Specify type for ambiguous names.- See
lib_deps
for package specification syntax (e.g.,owner/name@version
, URL).
pio pkg uninstall <package_spec...>
: Uninstall packages.pio pkg update [--all] [<package_spec...>]
: Update installed packages to their latest compatible versions.pio pkg outdated [--all] [<package_spec...>]
: Check for outdated packages.pio pkg list
: List installed packages in the project or globally.pio pkg search <query>
: Search the PlatformIO Registry.--type [library|platform|tool]
: Filter by package type.
pio pkg show <package_spec>
: Show detailed information about a package.pio pkg publish [--owner <owner_username_or_org>] [<path_to_package>]
: Publish a library or platform to the Registry. Requires login.pio pkg unpublish <owner>/<name>[@version]
: Remove a specific version of your package from the Registry.pio pkg pack [<path_to_package>]
: Create a distributable archive of a library or platform.pio pkg exec --package <owner>/<name>[@version] -- <command_args...>
: Execute a command from a specified tool package.
Okay, let's complete the guide with Part VII: Appendix. This part will serve as a quick reference and a place for common troubleshooting tips.
Chapter 14: Command-Line Interface (CLI) Reference Summary
PlatformIO Core (CLI) is the backbone of the ecosystem. The main command is pio
or platformio
.
General Usage:
pio [GLOBAL_OPTIONS] COMMAND [COMMAND_OPTIONS] [ARGUMENTS]
- Getting Help:
pio --help
orpio -h
: General help and list of commands.pio COMMAND --help
: Help for a specific command.
- Global Options:
--no-ansi
: Disable ANSI color codes in output.--version
: Show PlatformIO version.
Summary of Key Commands (Organized by Function):
This is not an exhaustive list but covers frequently used commands. Refer to pio COMMAND --help
for full details.
-
Project Management:
pio project init --board <ID> [OPTIONS]
: Initialize a new PlatformIO project or update an existing one.--project-option "framework=arduino"
: Set project options.
pio project config [--json-output]
: Show project configuration (resolvedplatformio.ini
).pio project metadata --json-output --env <ENV>
: Dump build system metadata for an environment.
-
Building & Running:
pio run [OPTIONS]
: Build project, process environments.-e, --environment <ENV>
: Specify environment(s).-t, --target <TARGET>
: Specify target(s) (e.g.,upload
,clean
,monitor
, custom targets).--upload-port <PORT>
: Specify upload port.--monitor-port <PORT>
: Specify monitor port.-d, --project-dir <DIR>
: Specify project directory.-s, --silent
: Suppress progress.-v, --verbose
: Verbose output.
pio boards <QUERY>
: Search for board IDs.
-
Device Interaction:
pio device list [--json-output] [--serial] [--logical] [--mdns]
: List connected devices/ports.pio device monitor [OPTIONS]
: Start serial port monitor.-p, --port <PORT>
-b, --baud <RATE>
-f, --filter <FILTER_NAME>
: Apply monitor filters (e.g.,time
,log2file
,esp32_exception_decoder
).--eol <CR|LF|CRLF>
--echo
-
Package & Library Management (
pio pkg
, olderpio lib
,pio platform
):pio pkg install <PACKAGE_SPEC...>
: Install library, platform, or tool.--global
: Install globally.--env <ENV>
: Install for a specific project environment.- Examples:
pio pkg install "bblanchon/ArduinoJson@^6.0"
,pio pkg install espressif32
pio pkg uninstall <PACKAGE_SPEC...>
: Uninstall package(s).pio pkg update [<PACKAGE_SPEC...>] [--all]
: Update installed package(s).pio pkg outdated [<PACKAGE_SPEC...>] [--all]
: Check for outdated packages.pio pkg list [--global] [--env <ENV>]
: List installed packages.pio pkg search <QUERY> [--type <library|platform|tool>]
: Search PlatformIO Registry.pio pkg show <PACKAGE_SPEC>
: Show details about a package.pio pkg publish [<PATH_TO_PACKAGE>]
: Publish a library/platform.pio pkg unpublish <OWNER>/<NAME>[@<VERSION>]
: Unpublish a package version.pio lib install/uninstall/update/search/list/show
: Older, library-specific commands.pio platform install/uninstall/update/search/list/show
: Older, platform-specific commands.
-
Debugging:
pio debug [OPTIONS]
: Start a debugging session or debug server.-e, --environment <ENV>
--interface gdb
: Launch GDB client.
-
Unit Testing:
pio test [OPTIONS]
: Run unit tests.-e, --environment <ENV>
-f, --filter <PATTERN>
,-i, --ignore <PATTERN>
--without-uploading
,--without-building
--json-output-path <FILE>
,--junit-output-path <FILE>
-v, --verbose
-
PlatformIO Account & Services:
pio account login/logout/register/show/token/update/etc.
: Manage PlatformIO account.pio home [OPTIONS]
: Start PlatformIO Home UI.pio org create/list/add/remove/update/destroy
: Manage organizations.pio team create/list/add/remove/update/destroy
: Manage teams within organizations.pio access grant/revoke/list/public/private
: Manage package access permissions.
-
Remote Development:
pio remote agent start/list
: Manage remote agents.pio remote device list/monitor
: Interact with remote devices.pio remote run [OPTIONS]
: Run project commands on a remote agent.pio remote test [OPTIONS]
: Run tests on a remote agent.pio remote update
: Update project dependencies on a remote agent.
-
System & Configuration:
pio settings get [NAME]
: Get PlatformIO Core settings.pio settings set NAME VALUE
: Set a setting.pio settings reset
: Reset settings to default.pio system info
: Display system-wide information.pio system prune
: Remove unused/cached data (packages, build cache).pio system completion install/uninstall <SHELL>
: Install shell command completion.pio update
: Update installed platforms, libraries, and tools.pio upgrade [--dev]
: Upgrade PlatformIO Core itself.
Chapter 15: Troubleshooting & FAQ
This section covers common issues and frequently asked questions.
-
Installation & Core Issues:
- Multiple PlatformIO Cores: Having multiple installations (e.g., global pip, IDE-managed) can cause conflicts. It's best to use the one managed by your PlatformIO IDE (if applicable) or maintain a single, consistent installation.
- To uninstall a pip-installed core:
pip uninstall platformio
. - In VSCode, ensure
platformio-ide.useBuiltinPIOCore
is typicallytrue
.
- To uninstall a pip-installed core:
'platformio' is not recognized...
: The directory containingpio.exe
(Windows) or thepio
symlink (Unix) is not in your system'sPATH
environment variable. Revisit "Install Shell Commands" (Chapter 2).ImportError: cannot import name _remove_dead_weakref
(Windows): Often due to conflicts with multiple Python installations. Try uninstalling all Pythons, reinstalling one (latest 3.x recommended), removing~/.platformio
andC:/.platformio
(if exists), and restarting PlatformIO IDE or reinstalling Core.- Proxy Issues: Refer to "Proxy Configuration" in Chapter 2. Ensure
HTTP_PROXY
/HTTPS_PROXY
are set correctly if you are behind a proxy. Checkpio settings get enable_proxy_strict_ssl
.
- Multiple PlatformIO Cores: Having multiple installations (e.g., global pip, IDE-managed) can cause conflicts. It's best to use the one managed by your PlatformIO IDE (if applicable) or maintain a single, consistent installation.
-
Build Errors:
- Unicode Errors (
UnicodeWarning
,UnicodeDecodeError
): Project paths or library names containing non-ASCII characters can cause issues. Move your project to a path with only ASCII characters. On Windows, you might need to setcore_dir
to a simple path likeC:/.platformio
. Error: Access is denied
(Package Manager / Build):- PlatformIO lacks write permission to
core_dir
(usually~/.platformio
) or project's.pio
directory. Check folder permissions. - Antivirus software might be interfering. Try temporarily disabling it or adding PlatformIO's directories to its exclusion list.
- Run PlatformIO commands from a terminal with appropriate privileges if necessary (though usually not required).
- PlatformIO lacks write permission to
- Linker Errors (
undefined reference to...
):- Library not found or not linked: Check
lib_deps
, LDF mode (lib_ldf_mode
), and include paths. - Mismatched function signatures or C vs. C++ linkage issues (
extern "C"
needed). - Forgetting to implement a declared function.
- Library not found or not linked: Check
- Toolchain Errors (
cc1plus: error while loading shared libraries
,libncurses.so.5: cannot open
): These are often Linux-specific.- Ensure all system dependencies for the toolchain are installed. Search the error message online along with "PlatformIO" and your Linux distribution for specific solutions.
- For
libncurses.so.5
, you might need to install anncurses5-compat-libs
package or symlink an existinglibncurses.so.6
tolibncurses.so.5
(use with caution).
- Unicode Errors (
-
Upload & Monitor Issues:
- Port Not Found / Access Denied:
- Board not connected or not recognized by OS. Check
pio device list
. - Incorrect
upload_port
ormonitor_port
inplatformio.ini
or command. - Driver issues (Windows).
- Permissions issues (Linux:
99-platformio-udev.rules
not applied, or user not indialout
/plugdev
group). - Another application (including a previous
pio device monitor
instance or Arduino IDE's Serial Monitor) is using the port. Close other applications.
- Board not connected or not recognized by OS. Check
- Upload Fails / Times Out:
- Incorrect board selected in
platformio.ini
. - Board not in bootloader mode (some boards require manual reset or button press sequence).
- Wrong
upload_protocol
orupload_speed
. - Faulty USB cable or hardware issue.
- Firewall blocking network uploads (OTA).
- Incorrect board selected in
- Serial Monitor Shows Gibberish: Incorrect
monitor_speed
(baud rate) ormonitor_encoding
. Ensure it matches the rate set in your firmware (Serial.begin(rate)
).
- Port Not Found / Access Denied:
-
General Advice:
- Verbose Output: Use
pio run -v
(or-vv
,-vvv
) orpio test -v
to get more detailed logs, which can help pinpoint errors. - Clean Build: If you suspect stale build artifacts, run
pio run -t clean
and then rebuild. You can also manually delete the.pio
directory in your project. - Check
platformio.ini
: Typos or incorrect configurations are common sources of problems. - PlatformIO Community Forums: A great resource for searching for solutions or asking for help: https://community.platformio.org
- Verbose Output: Use
-
Converting Arduino
.ino
/.pde
to.cpp
: Arduino.ino
files are preprocessed before C++ compilation. For use in standard C++ environments or for better IDE support:- Rename the file to
.cpp
. - Add
#include <Arduino.h>
at the top. - Manually add forward declarations for all your custom functions before they are called (e.g.,
void myFunction(int arg);
beforesetup()
orloop()
calls it).setup()
andloop()
themselves do not need forward declarations.
- Rename the file to
-
Understanding Program Memory Usage (
.text
,.data
,.bss
): After a successful build, PlatformIO often prints a summary of Flash (Program) and RAM (Data) usage..text
: Compiled code (instructions). Stored in Flash..data
: Initialized global and static variables. Values are stored in Flash and copied to RAM at startup. Consumes both Flash (for initial values) and RAM..bss
: Uninitialized global and static variables (zero-initialized by C runtime). Consumes RAM only (no space in Flash as they are zeroed at runtime).- Calculations:
- Flash Usage (approx.):
.text
size +.data
size - RAM Usage (at startup, approx.):
.data
size +.bss
size
- Flash Usage (approx.):
- Use
pio run --verbose
or inspect the map file generated during linking for detailed section sizes.
-
Using Custom SCons Command-Line Options: To pass options directly to SCons (the underlying build system), set the
SCONSFLAGS
environment variable before running PlatformIO commands.- POSIX:
export SCONSFLAGS="--tree=all --debug=explain"
- Windows CMD:
set SCONSFLAGS=--dry-run
Common SCons flags:--dry-run
(show commands, don't execute),--tree=all
(show dependency tree),--debug=explain
(why SCons rebuilds files).
- POSIX:
SECTION: Project Configuration (platformio.ini
) Analysis
FILE_PATH:: platformio.ini
(Project Root)
CONCEPT:: Main configuration file for PlatformIO projects.
-
STRUCTURE:: INI Format
- Sections:
[section_name]
- Key-Value Pairs:
key = value
- Comments: Lines starting with
;
- Multi-line values: Indented on new lines.
- Sections:
-
SECTION_TYPE::
[platformio]
(Global Project Settings)- CONFIG_KEY::
default_envs
- DESCRIPTION:: Comma-separated list of environment names (from
[env:NAME]
) to process by default. - IMPACT::
pio run
without-e
; Debugger target selection. - EXAMPLE::
default_envs = uno_debug, esp32_release
- DESCRIPTION:: Comma-separated list of environment names (from
- CONFIG_KEY::
core_dir
- DESCRIPTION:: Path to PlatformIO Core's data (packages, global libs).
- DEFAULT_UNIX::
~/.platformio
- DEFAULT_WINDOWS::
%HOMEPATH%\.platformio
- ENVIRONMENT_VARIABLE::
PLATFORMIO_CORE_DIR
- CONFIG_KEY::
workspace_dir
- DESCRIPTION:: Project's working directory for build outputs, libdeps.
- DEFAULT::
<PROJECT_ROOT>/.pio
- ENVIRONMENT_VARIABLE::
PLATFORMIO_WORKSPACE_DIR
- CONFIG_KEY::
src_dir
- DESCRIPTION:: Directory containing project source files.
- DEFAULT::
<PROJECT_ROOT>/src
- ENVIRONMENT_VARIABLE::
PLATFORMIO_SRC_DIR
- CONFIG_KEY::
include_dir
- DESCRIPTION:: Directory for project-specific header files. Added to
CPPPATH
. - DEFAULT::
<PROJECT_ROOT>/include
- ENVIRONMENT_VARIABLE::
PLATFORMIO_INCLUDE_DIR
- DESCRIPTION:: Directory for project-specific header files. Added to
- CONFIG_KEY::
lib_dir
- DESCRIPTION:: Directory for private/local libraries. Highest LDF priority.
- DEFAULT::
<PROJECT_ROOT>/lib
- ENVIRONMENT_VARIABLE::
PLATFORMIO_LIB_DIR
- CONFIG_KEY::
test_dir
- DESCRIPTION:: Directory containing unit tests.
- DEFAULT::
<PROJECT_ROOT>/test
- ENVIRONMENT_VARIABLE::
PLATFORMIO_TEST_DIR
- CONFIG_KEY::
build_dir
- DESCRIPTION:: Cache directory for compiled objects within
workspace_dir
. - DEFAULT::
<workspace_dir>/build
- ENVIRONMENT_VARIABLE::
PLATFORMIO_BUILD_DIR
- DESCRIPTION:: Cache directory for compiled objects within
- (Other directory options like
libdeps_dir
,data_dir
,boards_dir
,monitor_dir
,shared_dir
follow similar pattern)
- CONFIG_KEY::
-
SECTION_TYPE::
[env:NAME]
(Specific Build Environment)- IDENTIFIER_FORMAT::
[env:<lowercase_alphanumeric_hyphen_underscore>]
- INHERITANCE:: Implicitly inherits from common
[env]
section if present. - CONFIG_KEY::
platform
- DESCRIPTION:: Development platform (e.g.,
atmelavr
,espressif32
). Can include version (espressif32@~5.0.0
). - ACTION_FIND_BOARDS:: Use with
board
to determine toolchain/SDK.
- DESCRIPTION:: Development platform (e.g.,
- CONFIG_KEY::
board
- DESCRIPTION:: Target board ID (e.g.,
uno
,esp32dev
). - COMMAND_FIND_ID::
pio boards <search_term>
- DESCRIPTION:: Target board ID (e.g.,
- CONFIG_KEY::
framework
- DESCRIPTION:: Programming framework(s) (e.g.,
arduino
,espidf
,zephyr
). Comma-separated for multiple.
- DESCRIPTION:: Programming framework(s) (e.g.,
- CONFIG_KEY::
build_flags
- DESCRIPTION:: Flags for preprocessor, compiler, linker. Space-separated.
- IMPACT::
CPPDEFINES
,CPPPATH
,CCFLAGS
,LINKFLAGS
, etc. - SYNTAX_DEFINE::
-DNAME
or-DNAME=VALUE
- SYNTAX_INCLUDE_PATH::
-Ipath/to/include
- SYNTAX_LIB_PATH::
-Lpath/to/lib
- SYNTAX_LINK_LIB::
-llibname
- DYNAMIC_EXECUTION::
!command_to_generate_flags
- VARIABLE_SUBSTITUTION::
${section.option}
,${sysenv.VAR}
,${this.__env__}
- CONFIG_KEY::
build_src_flags
- DESCRIPTION:: Like
build_flags
, but only for files insrc_dir
.
- DESCRIPTION:: Like
- CONFIG_KEY::
build_src_filter
- DESCRIPTION:: Include/exclude patterns for files in
src_dir
. - SYNTAX_INCLUDE::
+<pattern>
- SYNTAX_EXCLUDE::
-<pattern>
(glob patterns allowed) - DEFAULT::
+<*> -<.git/> -<.svn/>
- DESCRIPTION:: Include/exclude patterns for files in
- CONFIG_KEY::
lib_deps
- DESCRIPTION:: Project library dependencies. See
SECTION: Library Dependencies
.
- DESCRIPTION:: Project library dependencies. See
- CONFIG_KEY::
lib_ignore
- DESCRIPTION:: List of library names for LDF to ignore.
- CONFIG_KEY::
lib_ldf_mode
- DESCRIPTION:: Library Dependency Finder mode.
- VALUES::
off
,chain
(default),deep
,chain+
,deep+
- IMPACT:: How
#include
directives are resolved.+
modes evaluate preprocessor conditions.
- CONFIG_KEY::
lib_compat_mode
- DESCRIPTION:: Library compatibility check strictness.
- VALUES::
off
,soft
(default),strict
- CONFIG_KEY::
upload_port
- DESCRIPTION:: Target port for firmware upload.
- EXAMPLE_SERIAL::
/dev/ttyUSB0
,COM3
- EXAMPLE_OTA::
192.168.1.100
- CONFIG_KEY::
upload_protocol
- DESCRIPTION:: Uploader tool/method (e.g.,
esptool
,stlink
,cmsis-dap
).
- DESCRIPTION:: Uploader tool/method (e.g.,
- CONFIG_KEY::
monitor_port
- DESCRIPTION:: Serial port for
pio device monitor
.
- DESCRIPTION:: Serial port for
- CONFIG_KEY::
monitor_speed
- DESCRIPTION:: Baud rate for serial monitor. Default:
9600
.
- DESCRIPTION:: Baud rate for serial monitor. Default:
- CONFIG_KEY::
monitor_filters
- DESCRIPTION:: Comma-separated list of monitor filters (e.g.,
time, log2file, esp32_exception_decoder
).
- DESCRIPTION:: Comma-separated list of monitor filters (e.g.,
- CONFIG_KEY::
extra_scripts
- DESCRIPTION:: List of PRE/POST Python scripts for build customization. Paths relative to project root.
- SYNTAX_PRE_SCRIPT::
pre:script_name.py
- SYNTAX_POST_SCRIPT::
post:script_name.py
orscript_name.py
- CONFIG_KEY::
build_type
- DESCRIPTION:: Build configuration.
- VALUES::
release
(default),debug
,test
- IMPACT:: Optimization, debug symbols.
debug
is crucial forpio debug
.
- CONFIG_KEY::
debug_tool
- DESCRIPTION:: Debugger probe/tool to use (e.g.,
stlink
,jlink
,esp-prog
).
- DESCRIPTION:: Debugger probe/tool to use (e.g.,
- (Other env options like
targets
,board_build.mcu
,board_build.f_cpu
, test options, specific debug options follow similar pattern)
- IDENTIFIER_FORMAT::
-
CONCEPT:: Value Interpolation
- SYNTAX::
${section.option}
,${sysenv.VAR}
,${this.option}
,${this.__env__}
- DESCRIPTION:: Allows referencing other values within
platformio.ini
.
- SYNTAX::
SECTION: Library Dependencies
-
LOCATION::
platformio.ini
->[env:NAME]
->lib_deps
-
PURPOSE:: Declare libraries required by the project.
-
SPECIFICATION_FORMATS::
- REGISTRY_NAME_ONLY::
LibraryName
(Can be ambiguous) - REGISTRY_OWNER_NAME::
ownername/LibraryName
(Recommended) - REGISTRY_VERSIONED::
ownername/LibraryName@version_spec
(e.g.,^1.2.0
,~1.2.0
,1.2.3
,>=1.2.0,<2.0.0
) - VCS_URL::
https://github.com/user/repo.git[#branch_or_tag_or_commit]
- LOCAL_PATH_SYMLINK::
symlink:///path/to/library
(Recommended for local libs) - LOCAL_PATH_COPY::
/path/to/library
- ARCHIVE_URL::
http://domain.com/lib.zip
,file:///path/to/lib.tar.gz
- REGISTRY_NAME_ONLY::
-
COMMAND_INSTALL::
pio pkg install
(usually automatic on build) -
COMMAND_UPDATE::
pio pkg update
-
STORAGE_PROJECT_LIBS::
<PROJECT_ROOT>/.pio/libdeps/<env_name>/
-
STORAGE_LOCAL_DEV_LIBS:: Path specified in
lib_dir
(default:<PROJECT_ROOT>/lib/
) -
FILE_PATH::
library.json
(Library Root)- CONCEPT:: Manifest file for a library. Crucial for LDF and PlatformIO Registry.
- KEY_CONFIG::
name
(string, required) - KEY_CONFIG::
version
(string, SemVer, required) - KEY_CONFIG::
frameworks
(string/array, e.g.,"arduino"
,"*"
for all) - KEY_CONFIG::
platforms
(string/array, e.g.,"espressif32"
,"*"
for all) - KEY_CONFIG::
dependencies
(object, e.g.,{ "owner/somelib": "^1.0" }
) - KEY_CONFIG::
build.extraScript
(string, path to SCons script for this library) - (Other
library.json
fields:description
,keywords
,repository
,authors
,license
,headers
,export
,build.flags
,build.srcFilter
)
SECTION: Build Process & Customization
-
CONCEPT:: SCons - Underlying build system.
-
CONCEPT:: Advanced Scripting - Extending build with Python scripts via
extra_scripts
.- SCRIPT_TYPE:: PRE - Runs before platform build scripts.
projenv
NOT available.- ACCESS_ENV::
Import("env")
(this isDefaultEnvironment()
)
- ACCESS_ENV::
- SCRIPT_TYPE:: POST - Runs after platform build scripts.
projenv
IS available.- ACCESS_ENV_GLOBAL::
Import("env")
(this isDefaultEnvironment()
) - ACCESS_ENV_PROJECT_SRC::
Import("projenv")
- ACCESS_ENV_GLOBAL::
- API_MODIFY_FLAGS::
env.Append(CPPDEFINES=["DEF"])
,env.Replace(PROGNAME="new_name")
,env.ParseFlags("-DFOO")
,env.MergeFlags(flags_obj)
- API_PROJECT_OPTIONS::
env.GetProjectOption("custom_key", "default")
,env.GetProjectConfig()
- API_EXECUTE_COMMAND::
env.Execute("command string")
- API_ADD_ACTION::
env.AddPreAction(target, callback_or_cmd)
,env.AddPostAction(target, callback_or_cmd)
- TARGETS_COMMON::
buildprog
(program),$PROGPATH
(firmware file),upload
,size
.
- TARGETS_COMMON::
- API_BUILD_MIDDLEWARE::
env.AddBuildMiddleware(callback, pattern)
(PRE scripts only)- CALLBACK_SIGNATURE::
my_middleware(env, node)
- NODE_ACTIONS:: Return
None
(skip),env.Object(node, ...)
(modify),node
(no change).
- CALLBACK_SIGNATURE::
- API_CUSTOM_TARGET::
env.AddCustomTarget(name, dependencies, actions, title, description)
- ACTION_EXECUTE_TARGET::
pio run -t <name>
- ACTION_EXECUTE_TARGET::
- SCRIPT_TYPE:: PRE - Runs before platform build scripts.
-
FILE_PATH_FIRMWARE_DEFAULT::
<PROJECT_ROOT>/.pio/build/<env_name>/firmware.bin
(or.elf
,.hex
depending on platform)- SCons_VARIABLE::
$PROGPATH
(in scripts, path to main program artifact) - SCons_VARIABLE::
$SOURCE
(inAddPostAction
for$PROGPATH
, refers to the ELF usually) - SCons_VARIABLE::
$BUILD_DIR
(path to current environment's build directory) - SCons_VARIABLE::
$PROJECT_DIR
(project root)
- SCons_VARIABLE::
SECTION: Unit Testing
- COMMAND_RUN_TESTS::
pio test [-e <env_name>] [-f <filter>] [-i <ignore>]
- DIRECTORY_TESTS:: Path specified by
test_dir
(default:<PROJECT_ROOT>/test/
) - TEST_SUITE_STRUCTURE:: Subfolders in
test_dir
starting withtest_
(e.g.,test/test_feature_foo/
) - CONFIG_KEY::
test_framework
(inplatformio.ini
->[env:NAME]
)- VALUES::
unity
(default-ish),googletest
,doctest
,custom
- VALUES::
- CONFIG_KEY::
test_build_src
(boolean, default:no
)- DESCRIPTION:: If
yes
, compilessrc_dir
with test code. - CAUTION:: Guard main application entry points (
main
,setup/loop
) with#ifndef PIO_UNIT_TESTING
.
- DESCRIPTION:: If
- SIMULATORS:: Configure via
test_testing_command
and run withpio test --without-uploading
.- COMMON_SIMULATORS::
simavr
,qemu
,renode
- COMMON_SIMULATORS::
SECTION: Debugging
- COMMAND_START_DEBUG::
pio debug [-e <env_name>]
- CONFIG_KEY::
build_type = debug
(inplatformio.ini
->[env:NAME]
) - Essential for debug symbols. - CONFIG_KEY::
debug_tool
(e.g.,stlink
,jlink
,esp-prog
,cmsis-dap
) - CONFIG_KEY::
debug_init_break
(e.g.,tbreak main
) - CONFIG_KEY::
debug_server
(command to start GDB server, often auto-configured) - CONFIG_KEY::
upload_protocol
(Sometimes used by debugger ifdebug_tool
implies a specific uploader, e.g. somecmsis-dap
setups).
SECTION: Key Files and Directories (Summary for Quick Location)
- PRIMARY_CONFIG::
<PROJECT_ROOT>/platformio.ini
- SOURCE_CODE_APP::
<PROJECT_ROOT>/src/
(or as persrc_dir
) - HEADER_FILES_APP::
<PROJECT_ROOT>/include/
(or as perinclude_dir
) - LOCAL_LIBRARIES::
<PROJECT_ROOT>/lib/
(or as perlib_dir
) - DOWNLOADED_LIBRARIES::
<PROJECT_ROOT>/.pio/libdeps/<env_name>/
- UNIT_TESTS::
<PROJECT_ROOT>/test/
(or as pertest_dir
) - BUILD_OUTPUT_TEMP::
<PROJECT_ROOT>/.pio/build/<env_name>/
- FIRMWARE_OUTPUT::
<PROJECT_ROOT>/.pio/build/<env_name>/firmware.(bin|hex|elf)
- CUSTOM_BUILD_SCRIPTS:: Referenced by
extra_scripts
inplatformio.ini
, typically in project root or ascripts/
subfolder. - LIBRARY_MANIFEST::
<LIBRARY_ROOT>/library.json
- FRAMEWORK_SPECIFIC_CONFIG (Examples):
- ESP-IDF_APP_CMAKE::
<src_dir>/CMakeLists.txt
- ESP-IDF_PROJECT_CMAKE::
<PROJECT_ROOT>/CMakeLists.txt
- ZEPHYR_APP_CONFIG::
<src_dir_or_zephyr_subdir>/prj.conf
- ZEPHYR_APP_CMAKE::
<src_dir_or_zephyr_subdir>/CMakeLists.txt
- MBED_APP_CONFIG::
<PROJECT_ROOT>/mbed_app.json
- ESP-IDF_APP_CMAKE::