WSL Ubuntu 搭建 ESP-IDF:工具链机制与串口烧录的取舍

ESP32 开发绕不开 ESP-IDF。在 Windows 上原生机装一套也能跑,但 CMake、Python、make 这些工具在 Windows 下总有些水土不服,碰到组件脚本里写死 Linux 路径就更头疼。这篇文章记录我在 WSL2 Ubuntu 下搭 ESP-IDF 的过程,顺便讲清楚工具链机制和串口烧录这块的取舍。

为什么选 WSL 而不是原生 Windows 或虚拟机

先说结论:ESP-IDF 官方提供 Windows 安装器,能用,但开发体验不如 Linux。原因有几方面。

第一,ESP-IDF 的构建系统基于 CMake 加 Ninja,大量组件脚本(Python、shell)按 POSIX 假设编写。Windows 下偶尔会遇到路径分隔符、符号链接、shell 调用之类的问题,排查起来打断节奏。

第二,ESP-IDF 的文档和社区示例默认 Linux/macOS,复制粘贴一条命令就能跑的概率更高。

虚拟机方案能解决工具链问题,但开销大:要分配独立内存和磁盘,文件在宿主机和虚拟机之间来回拷,启动慢。WSL2 给的是真正的 Linux 内核,启动几乎瞬时,和 Windows 共享剪贴板、文件系统(通过 \\wsl$ 访问),用 VS Code Remote-WSL 直接接进 WSL 里的项目,体验接近原生 Linux 开发。

不过 WSL2 不是没代价。最大的坑是 USB 和串口访问,这块后面单独说。

ESP-IDF 工具链是怎么组织的

动手装之前,先搞清楚 ESP-IDF 到底装了什么,避免后面遇到环境变量问题一头雾水。

ESP-IDF 分三部分:

  1. 框架源码esp-idf 仓库):包含组件库(WiFi、蓝牙、外设驱动等)、构建系统脚本、idf.py 命令行工具的 Python 实现。通过 git clone --recursive 拉下来。
  2. 交叉编译工具链:ESP32 是 Xtensa 架构(S2/S3 也是),C3/C6 是 RISC-V。这些交叉编译器(xtensa-esp32-elf-gccriscv32-esp-elf-gcc)不在 esp-idf 仓库里,install.sh 会单独下载到 ~/.espressif/ 下。
  3. Python 虚拟环境idf.py 及大量构建脚本用 Python 写,依赖一堆包。install.sh 会在 ~/.espressif/python_env/ 建一个独立 venv,避免污染系统 Python。

install.sh 干的事就是下载工具链、建 venv、装 Python 依赖。装完之后,工具链和 venv 都在 ~/.espressif/,和 esp-idf 源码目录分离。这意味着同一台机器可以装多个 IDF 版本,每个版本跑一次 install.sh 就行,切换时 source 对应的 export.sh

export.sh 在做什么

每次开新终端,idf.py 都找不到,原因是没有环境变量。export.sh 做三件事:

  • 设置 IDF_PATH 指向 esp-idf 源码目录
  • 把交叉编译器、CMake、Ninja 等工具路径加进 PATH
  • 激活 ~/.espressif/python_env/ 里那个 Python venv

所以 export.sh 不是”配置一次永久生效”的脚本,它是每个终端会话都要 source 一次的激活脚本。理解了这点,后面的别名方案就好懂了。

步骤一:准备 Ubuntu 环境

先更新系统并装依赖。这些包是 ESP-IDF 官方文档列出的,缺一个可能影响构建:

1
2
sudo apt update && sudo apt upgrade -y
sudo apt install -y git wget flex bison gperf python3 python3-pip python3-venv cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0

前提是已经装好 WSL2 和 Ubuntu 发行版。如果访问 GitHub 慢,提前配好代理。

步骤二:拉 ESP-IDF 源码

1
2
3
mkdir -p ~/development/esp
cd ~/development/esp
git clone -b v5.5.1 --recursive https://github.com/espressif/esp-idf.git

--recursive 必须加。ESP-IDF 的子模块(components 里的第三方库)很多,不带 recursive 会缺东西,构建时再报错更难定位。

克隆慢的话用 Gitee 镜像:

1
git clone -b v5.5.1 --recursive https://gitee.com/EspressifSystems/esp-idf.git

注意 Gitee 镜像不一定同步所有子模块,偶尔会碰到某个子模块拉不下来。遇到这种情况,可以进入对应子模块目录手动切换 remote,或者配代理直连 GitHub。

步骤三:装工具链

1
2
cd ~/development/esp/esp-idf
./install.sh esp32,esp32s2,esp32s3

install.sh 后面的参数指定要装哪些芯片的工具链。不传的话默认装所有支持的目标,体积会比较大。我常用 S3,加上 32 和 S2 备用,按需选就行。

脚本会下载交叉编译器、建 Python venv、装依赖。按网络情况,10 到 30 分钟正常。装完工具链在 ~/.espressif/,不污染 esp-idf 源码目录。

如果中途失败(常见原因是网络中断或磁盘满),清掉重来:

1
2
rm -rf ~/.espressif
./install.sh esp32,esp32s2,esp32s3

步骤四:激活环境

1
2
. ./export.sh
idf.py --version

能打印版本号就说明 PATH 和 venv 都对了。

每个终端都要 source 一次 export.sh 很烦。官方推荐的做法是加个别名,需要时再激活:

1
2
# 加到 ~/.zshrc 或 ~/.bashrc
alias get_idf=". $HOME/development/esp/esp-idf/export.sh"

然后 source ~/.zshrc,之后每个终端里敲 get_idf 就激活环境。

不要图省事把 . export.sh 直接写进 .zshrc。这样每个终端都会激活 IDF 的 Python venv,包括你写别的 Python 项目、跑别的工具的终端,会污染全局 Python 环境。用别名按需激活才符合 venv 的设计意图。

步骤五:建项目并构建

1
2
3
4
5
6
7
8
mkdir -p ~/projects/esp32
cd ~/projects/esp32
get_idf

idf.py create-project test-esp32
cd test-esp32
idf.py set-target esp32s3
idf.py build

set-target 会根据芯片配置项目(选择对应的工具链、sdkconfig 默认值)。常见目标:esp32esp32s2esp32s3esp32c3

构建成功会打印固件大小和内存占用,build/ 目录下生成 .bin 文件。

验证编译器:

1
xtensa-esp32s3-elf-gcc --version

注意编译器名字带芯片型号,换目标后名字也变(比如 C3 是 riscv32-esp-elf-gcc)。

文件系统位置影响构建速度

这是个容易被忽略的坑。WSL2 里访问 Windows 文件系统(/mnt/c/...)要走 9P 协议,IO 性能比 WSL 原生文件系统(~/,即 ext4)差很多。把项目放在 /mnt/c/ 下构建,时间可能是放在 ~/ 下的好几倍。

所以项目目录一定放在 WSL 原生文件系统里,比如 ~/projects/。需要用 Windows 工具看文件时,通过 \\wsl$\Ubuntu\home\... 访问。

串口烧录:WSL2 最大的痛点

到这一步,构建没问题,但烧录会卡住。WSL2 默认看不到 USB 设备,自然也看不到 USB 转串口芯片(CP2102、CH340 之类),/dev/ttyUSB0 不会出现。

有两条路。

方案一:从 Windows 侧烧录。 在 Windows 上单独装一套 ESP-IDF(用官方 installer),或者只装 esptool。构建在 WSL 里完成,把 build/*.bin 拷到 Windows 下用 Windows 的 esptool 烧。好处是稳定,坏处是要维护两套环境,或者手动拷文件。

方案二:用 usbipd-win 把 USB 设备桥接进 WSL。 usbipd-win 是微软官方的工具,能把 Windows 上的 USB 设备 attach 到 WSL2 里。装好后在 Windows PowerShell 里执行:

1
2
3
usbipd list                  # 列出 USB 设备,找到 ESP32 开发板的 BUSID
usbipd bind --busid <BUSID>
usbipd attach --wsl --busid <BUSID>

attach 之后 WSL 里就能看到 /dev/ttyUSB0,然后正常 idf.py -p /dev/ttyUSB0 flash

这条路能跑通,但每次插拔都要重新 attach,而且需要内核支持 usbipd(较新的 WSL2 内核自带)。调试时频繁烧录的话体验不如原生 Windows 或直接用 Linux 机器。

我的实际做法是:日常开发在 WSL(编辑、构建、菜单配置都在 WSL),烧录时如果频繁就切到 Windows 侧的 esptool,偶尔烧一次就用 usbipd。

关于版本和更新

ESP-IDF 版本迭代比较快,v5.x 是当前的稳定主线。固定一个版本(比如这里的 v5.5.1)开发是有意义的,避免中途升级引入兼容问题。需要升级时,重新 git checkout 新版本,git submodule update --init --recursive,再跑一次 install.sh(会装新版本对应的工具链),不影响旧版本。

不同项目用不同 IDF 版本时,每个版本 clone 到不同目录(比如 ~/development/esp/esp-idf-v5.5.1~/development/esp/esp-idf-v5.4.1),每个跑一次 install.sh,然后 get_idf 别名做成多版本切换:

1
2
alias get_idf_v551=". $HOME/development/esp/esp-idf-v5.5.1/export.sh"
alias get_idf_v541=". $HOME/development/esp/esp-idf-v5.4.1/export.sh"

小结

WSL2 搭 ESP-IDF 在构建和编辑体验上接近原生 Linux,比原生 Windows 顺手,比虚拟机轻量。主要代价是 USB 串口烧录要绕一层(usbipd-win 或 Windows 侧 esptool)。如果你的工作流是”WSL 构建 加 偶尔烧录”,这个组合值得用;如果每分钟都要烧一次做调试,单独弄台 Linux 机器或直接用 Windows installer 可能更省心。

参考资料