PlatformIO

技术栈
工具链
platformio嵌入式构建系统包管理VSCode跨平台

概览

PlatformIO 技术栈概览

PlatformIO 是面向嵌入式开发的跨平台、跨框架的专业级工具链,由 Ivan Kravets 于 2014 年创建。它不绑定特定 MCU 或 RTOS,统一管理 40+ 平台、1000+ 开发板、20+ 开发框架的构建、依赖、调试与测试

解决什么问题

  • 碎片化开发体验:告别多 IDE、多工具链混用,一套 CLI + VSCode 插件搞定所有
  • 依赖地狱:内置包管理器,platformio.ini 声明式管理库依赖和版本
  • 多板兼容:同一项目轻松切换目标板,无需手动改 Makefile 或 include 路径
  • CI/CD 集成:命令行友好,适合自动化构建和持续集成流水线

关键特性

  • 声明式配置platformio.ini 一行定义板子、框架、库依赖
  • 智能库管理:自动解析依赖树,从官方 registry 拉取兼容版本
  • 统一调试接口:集成 GDB + OpenOCD/J-Link/pyOCD
  • 多 IDE 支持:VSCode 原生插件、CLion、Vim 等
  • 远程开发:支持 SSH 远程构建/烧录

安装

PlatformIO 安装指南

1. 环境准备

项目 要求
操作系统 Windows 10+ / macOS 11+ / Ubuntu 20.04+
Python ≥ 3.6(PlatformIO Core 需 Python)
IDE VSCode 1.80+(推荐)、CLion 或纯 CLI 模式
依赖 Git ≥ 2.x、串口驱动(CH340/CP2102)

2. 安装步骤

方式一:VSCode 插件(推荐)

  1. 安装 Visual Studio Code
  2. 打开 VSCode → 扩展 → 搜索 PlatformIO IDE
  3. 点击安装,等待自动下载 PlatformIO Core
  4. 安装完成后,左侧栏出现 🐜 图标即就绪

方式二:命令行安装 PlatformIO Core

# 推荐使用安装脚本
curl -fsSL -o get-platformio.py https://raw.githubusercontent.com/platformio/platformio-core-installer/master/get-platformio.py
python3 get-platformio.py

# 或通过 pip
pip install -U platformio

# 验证安装
pio --version

方式三:Homebrew (macOS)

brew install platformio

Shell 补全(可选)

# Bash
pio completion bash >;> ~/.bashrc
# Zsh
pio completion zsh >;> ~/.zshrc
# Fish
pio completion fish >;> ~/.config/fish/completions/pio.fish

3. 常见安装问题

问题 解决方案
VSCode 插件安装缓慢 手动下载 .vsix 离线安装;或切换网络环境
PlatformIO Core 下载失败 设置 PLATFORMIO_CORE_DIR 国内镜像;使用 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple platformio
首次 pio run 卡住 首次需下载工具链和框架 SDK(几百 MB),耐心等待或配置代理
权限问题 (Linux) 安装 udev 规则:`curl -fsSL https://raw.githubusercontent.com/platformio/platformio-core/develop/platformio/assets/system/99-platformio-udev.rules
端口占用 关闭 Arduino IDE、串口监视器等占用端口的程序

示例

PlatformIO 多环境管理:ESP32 + Arduino Uno 双目标构建

目标

一个 PlatformIO 项目同时支持 ESP32 和 Arduino Uno,通过环境配置和条件编译实现跨平台代码复用。

项目结构

multi-target/
├── platformio.ini
├── src/
│   └── main.cpp
├── include/
│   └── board_config.h
└── lib/
    └── (自定义库)

一、platformio.ini — 多环境配置

; platformio.ini
[platformio]
default_envs = uno              ; 默认环境

; ── 公共配置(所有环境继承) ──
[common]
monitor_speed = 115200
lib_deps =
    adafruit/Adafruit Unified Sensor @ ^1.1.14

; ── Arduino Uno 环境 ──
[env:uno]
platform = atmelavr
board = uno
framework = arduino
build_flags =
    -D BOARD_UNO
    -D LED_PIN=LED_BUILTIN
lib_deps =
    ${common.lib_deps}
    adafruit/DHT sensor library @ ^1.4.4
monitor_speed = 9600

; ── ESP32 DevKit 环境 ──
[env:esp32]
platform = espressif32
board = esp32dev
framework = arduino
build_flags =
    -D BOARD_ESP32
    -D LED_PIN=2
lib_deps =
    ${common.lib_deps}
    adafruit/DHT sensor library @ ^1.4.4
    adafruit/Adafruit SSD1306 @ ^2.5.7
    adafruit/Adafruit GFX Library @ ^1.11.9
monitor_filters = esp32_exception_decoder

; ── ESP32-C3 环境 ──
[env:esp32-c3]
platform = espressif32
board = esp32-c3-devkitm-1
framework = arduino
build_flags =
    -D BOARD_ESP32_C3
    -D LED_PIN=8
lib_deps =
    ${common.lib_deps}
    adafruit/DHT sensor library @ ^1.4.4

二、board_config.h — 条件编译板级配置

// board_config.h
#pragma once
#include <Arduino.h>

// ── 板级识别 ──
#if defined(BOARD_UNO)
    #define BOARD_NAME      "Arduino Uno"
    #define CPU_SPEED_MHZ   16
    #define HAS_WIFI        0
    #define HAS_BLUETOOTH   0
    #define RAM_KB          2
    #define FLASH_KB        32

#elif defined(BOARD_ESP32)
    #define BOARD_NAME      "ESP32 DevKit"
    #define CPU_SPEED_MHZ   240
    #define HAS_WIFI        1
    #define HAS_BLUETOOTH   1
    #define RAM_KB          520
    #define FLASH_KB        4096

#elif defined(BOARD_ESP32_C3)
    #define BOARD_NAME      "ESP32-C3"
    #define CPU_SPEED_MHZ   160
    #define HAS_WIFI        1
    #define HAS_BLUETOOTH   1
    #define RAM_KB          400
    #define FLASH_KB        4096

#else
    #error "未定义目标板!请使用 -D BOARD_xxx 编译宏"
#endif

// ── 引脚映射表 ──
#if defined(BOARD_UNO)
    #define PIN_I2C_SDA      A4
    #define PIN_I2C_SCL      A5
    #define PIN_DHT          2
    #define PIN_BUTTON       3

#elif defined(BOARD_ESP32)
    #define PIN_I2C_SDA      21
    #define PIN_I2C_SCL      22
    #define PIN_DHT          4
    #define PIN_BUTTON       0      // BOOT 按键

#elif defined(BOARD_ESP32_C3)
    #define PIN_I2C_SDA      6
    #define PIN_I2C_SCL      7
    #define PIN_DHT          3
    #define PIN_BUTTON       9
#endif

三、main.cpp — 跨平台业务逻辑

#include <Arduino.h>
#include "board_config.h"

#if HAS_WIFI
    #include <WiFi.h>
#endif

// ── 启动信息 ──
void printBoardInfo() {
    Serial.println(F("========================"));
    Serial.print(F("板卡: "));
    Serial.println(BOARD_NAME);
    Serial.print(F("CPU: "));
    Serial.print(CPU_SPEED_MHZ);
    Serial.println(F(" MHz"));
    Serial.print(F("RAM: "));
    Serial.print(RAM_KB);
    Serial.print(F(" KB | Flash: "));
    Serial.print(FLASH_KB);
    Serial.println(F(" KB"));

    Serial.print(F("WiFi: "));
    Serial.println(HAS_WIFI ? F("✓") : F("✗"));
    Serial.print(F("BLE:  "));
    Serial.println(HAS_BLUETOOTH ? F("✓") : F("✗"));

    Serial.print(F("LED:  D"));
    Serial.print(LED_PIN);
    Serial.print(F(" | DHT: D"));
    Serial.print(PIN_DHT);
    Serial.println(F("\n========================"));
}

// ── 统一延时(毫秒) ──
void smartDelay(unsigned long ms) {
    unsigned long start = millis();
    while (millis() - start < ms) {
        #if HAS_WIFI
            // ESP32 还需要处理 WiFi 和其他后台任务
            // 非 ESP32 上这行在编译时被去除
        #endif
        delay(1);  // 让步给系统
    }
}

// ── 初始化 ──
void setup() {
    Serial.begin(115200);
    // 某些 ESP32-C3 需要延时等待 USB CDC 就绪
    #if defined(BOARD_ESP32_C3)
        delay(1000);
    #endif

    while (!Serial && millis() < 3000) {
        delay(10);
    }

    printBoardInfo();

    pinMode(LED_PIN, OUTPUT);
    pinMode(PIN_BUTTON, INPUT_PULLUP);

    #if HAS_WIFI
        WiFi.mode(WIFI_OFF);  // 省电
        Serial.println(F("WiFi 已关闭(省电模式)"));
    #endif

    Serial.println(F("\n系统就绪!"));
    Serial.print(F("LED 开始闪烁 | 按键引脚 D"));
    Serial.println(PIN_BUTTON);
}

// ── 主循环 ──
void loop() {
    static unsigned long lastBlink = 0;
    static unsigned long lastReport = 0;
    static uint32_t loopCount = 0;

    loopCount++;

    // LED 闪烁
    if (millis() - lastBlink >= 500) {
        lastBlink = millis();
        digitalWrite(LED_PIN, !digitalRead(LED_PIN));
    }

    // 状态报告
    if (millis() - lastReport >= 5000) {
        lastReport = millis();
        Serial.print(F("["));
        Serial.print(BOARD_NAME);
        Serial.print(F("] 运行中 | 循环 #"));
        Serial.print(loopCount);
        Serial.print(F(" | 运行时间: "));
        Serial.print(millis() / 1000);
        Serial.println(F("s"));

        #if (FLASH_KB > 100)
            Serial.print(F("  可用堆: "));
            Serial.print(ESP.getFreeHeap());
            Serial.println(F(" bytes"));
        #endif
    }

    // 按键检测(可选)
    if (digitalRead(PIN_BUTTON) == LOW) {
        delay(20);
        if (digitalRead(PIN_BUTTON) == LOW) {
            Serial.println(F("按键按下!"));
            while (digitalRead(PIN_BUTTON) == LOW) { delay(10); }
        }
    }
}

四、构建与烧录

# 列出所有环境
pio project config

# 构建指定环境
pio run -e uno
pio run -e esp32
pio run -e esp32-c3

# 一键构建全部
pio run

# 烧录(自动检测端口)
pio run -e esp32 -t upload

# 监控
pio device monitor -e esp32

关键点

  • ${common.xxx} 语法实现配置继承,避免重复
  • -D MACRO 编译标志注入板级宏,驱动 #if 条件编译
  • board_config.h 集中管理引脚映射,业务逻辑代码零 #ifdef
  • #if HAS_WIFI 这种语义化条件比 #ifdef ESP32 更易读易维护

PlatformIO 快速上手:Arduino Uno 项目搭建与构建

目标

使用 PlatformIO CLI 创建 Arduino Uno 项目,完成编译、上传、串口监控的全流程。

前提

已安装 PlatformIO Core(pio --version 验证)。

完整步骤

1. 创建项目

# 创建目录并初始化 Arduino Uno 项目
mkdir pio-hello &;& cd pio-hello
pio project init --board uno --ide vscode

生成的 platformio.ini 如下:

; platformio.ini
[env:uno]
platform = atmelavr
board = uno
framework = arduino
monitor_speed = 9600

2. 编写代码

src/main.cpp

#include <Arduino.h>

const int LED_PIN = LED_BUILTIN;
unsigned long lastToggle = 0;
bool ledState = LOW;
unsigned long loopCount = 0;

void setup() {
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(9600);
  while (!Serial) { delay(10); }

  Serial.println("\n=== PlatformIO Hello ===");
  Serial.print("Board: ");
  Serial.println(ARDUINO_BOARD);
  Serial.print("CPU Freq: ");
  Serial.print(F_CPU / 1000000);
  Serial.println(" MHz");
}

void loop() {
  loopCount++;

  if (millis() - lastToggle >= 500) {
    lastToggle = millis();
    ledState = !ledState;
    digitalWrite(LED_PIN, ledState);

    Serial.print("[");
    Serial.print(loopCount);
    Serial.print("] LED: ");
    Serial.println(ledState ? "ON" : "OFF");
  }
}

3. 编译

pio run
# 或指定环境:
pio run -e uno

成功输出:

Processing uno (platform: atmelavr; board: uno; framework: arduino)
...
RAM:   [===       ]  25.4% (used 521 bytes from 2048 bytes)
Flash: [=         ]   8.7% (used 2814 bytes from 32256 bytes)
========================= [SUCCESS] Took 3.45 seconds =========================

4. 上传

# 自动检测端口并上传
pio run --target upload

# 或指定端口
pio run --target upload --upload-port /dev/ttyUSB0

5. 串口监控

pio device monitor
# 退出:Ctrl+C

预期输出:

=== PlatformIO Hello ===
Board: Arduino Uno
CPU Freq: 16 MHz
[1] LED: ON
[2] LED: OFF
[3] LED: ON
...

6. 一键编译+上传+监控

pio run -t upload &;& pio device monitor

关键点

  • platformio.ini 是声明式配置核心,无需手写 Makefile
  • 自动管理 Arduino 框架和 AVR 工具链,首次 pio run 会自动下载
  • pio device list 可查看所有连接的串口设备
  • 配合 VSCode 使用:项目目录下 code . 即可获得完整 IDE 体验

教程

PlatformIO 开发全流程

本章目标

掌握 PlatformIO 的项目管理、库依赖、调试和 CI/CD 集成,从 Arduino IDE 用户升级为专业嵌入式开发者。


1. 为什么从 Arduino IDE 迁移到 PlatformIO?

痛点 Arduino IDE PlatformIO
代码补全 基本无 完整 IntelliSense
多文件项目 手动管理 tabs 文件夹树形结构
库版本管理 全局安装 项目级 .ini 声明
Git 友好 需要手动整理 .ini + src/ 即可
多板支持 切换板卡管理器 [env:xxx] 多环境
CI/CD 困难 命令行原生支持

2. platformio.ini 详解

[platformio]
default_envs = uno

[env:uno]
platform = atmelavr
board = uno
framework = arduino
monitor_speed = 115200

; 库依赖
lib_deps =
    adafruit/DHT sensor library @ ^1.4.4    ; 语义版本
    https://github.com/me/my-lib.git          ; Git 仓库
    symlink://../my-local-lib                 ; 本地库

; 编译标志
build_flags =
    -D DEBUG_MODE
    -D VERSION_STRING=\"1.0.0\"

; 上传配置
upload_port = /dev/ttyUSB0
upload_speed = 115200

; 自定义脚本
extra_scripts = pre:build_info.py

库依赖三种方式

语法 场景
owner/lib @ ^1.2.3 PlatformIO Registry 中的库
https://github.com/... Git 仓库(私有库用 git+ssh://
symlink://path 本地开发的库

3. 项目目录结构

my-firmware/
├── platformio.ini          ← 项目配置核心
├── src/
│   ├── main.cpp            ← 入口
│   └── modules/
│       ├── sensor.cpp
│       └── sensor.h
├── include/
│   └── config.h
├── lib/
│   └── my-custom-lib/      ← 项目私有库
├── test/
│   └── test_sensor.cpp     ← 单元测试
└── data/                   ← ESP8266/ESP32 SPIFFS 数据

4. 调试与日志

条件编译日志宏

// debug.h
#pragma once
#include <Arduino.h>

#ifdef DEBUG_MODE
  #define LOG_INFO(fmt, ...)  Serial.printf("[INFO] " fmt "\n", ##__VA_ARGS__)
  #define LOG_WARN(fmt, ...)  Serial.printf("[WARN] " fmt "\n", ##__VA_ARGS__)
  #define LOG_ERR(fmt, ...)   Serial.printf("[ERR]  " fmt "\n", ##__VA_ARGS__)
#else
  #define LOG_INFO(...)
  #define LOG_WARN(...)
  #define LOG_ERR(...)
#endif

单元测试(Unity 框架)

// test/test_sensor.cpp
#include <unity.h>
#include "sensor.h"

void test_temperature_conversion() {
    float expected = 25.0;
    float actual = raw_to_celsius(512, 10);  // 10bit ADC
    TEST_ASSERT_FLOAT_WITHIN(0.5, expected, actual);
}

int main() {
    UNITY_BEGIN();
    RUN_TEST(test_temperature_conversion);
    return UNITY_END();
}

运行:pio test -e uno


5. CI/CD 集成

GitHub Actions

# .github/workflows/build.yml
name: PlatformIO CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/cache@v3
        with:
          path: ~/.platformio
          key: ${{ runner.os }}-pio
      - uses: actions/setup-python@v4
        with: { python-version: '3.10' }
      - run: pip install platformio
      - run: pio run -e uno -e esp32

6. 实用技巧

  • pio device list — 列出串口设备
  • pio lib search "DHT22" — 搜索库
  • pio system info — 系统诊断
  • pio run -t clean — 清理构建
  • pio run --verbose — 详细输出(调试用)

思考题

  1. PlatformIO 的 ${common.xxx} 继承语法适用什么场景?
  2. 为什么 ESP32 项目建议用 monitor_filters = esp32_exception_decoder
  3. 如何为团队项目固定工具链版本?