Rust教程06:ESP32-rust环境搭建

前五讲的语法结束后,图穷而匕首现了,其实本人学rust的目的不是想开发别的,就是想用rust开发单片机,本文记录在一台 Windows 11 + VS 2022 Community 机器上,为 ESP32-S3 N16R8(16MB Flash / 8MB PSRAM,Xtensa 架构)从零搭建 Rust 开发环境,并完成串口 demo 烧录测试的完整过程。

1. 背景知识:为什么不能只用官方 stable Rust

ESP32-S3 是 Xtensa 架构,不是 RISC-V(ESP32-C3/C6/H2 等才是)。上游 Rust 不支持 Xtensa 目标,必须使用 esp-rs 维护的 Rust fork(即 esp 工具链),它额外提供这些 target:

复制代码
xtensa-esp32-espidf      xtensa-esp32-none-elf
xtensa-esp32s2-espidf    xtensa-esp32s2-none-elf
xtensa-esp32s3-espidf    xtensa-esp32s3-none-elf   ← 本教程使用(no_std)
xtensa-esp8266-none-elf

官方安装器是 espup,但它只从 GitHub 直连下载,网络不通时会直接失败。因此本教程采用「手动下载 + 手动注册」的方式,效果与 espup 完全等价。

整体方案选型:

  • no_std + esp-hal(不用 ESP-IDF/std):体积小、编译快、生态现代
  • GNU LD 链接 (Xtensa GCC):esp-hal 官方默认方案;实测 esp 工具链自带的 rust-lld 链接 Xtensa 会出现地址符号扩展错误(section .data ... exceeds available address space),不要用 rust-lld

2. 解决网络问题(国内环境关键步骤)

先诊断本机网络:

bash 复制代码
# github.com 直连(本机超时,被墙)
curl -sI --max-time 15 https://github.com

# api.github.com(本机可直连!)
curl -s --max-time 10 -o /dev/null -w "%{http_code}\n" https://api.github.com

# crates.io 静态资源速度(本机仅 ~10KB/s)
curl -s -o /dev/null -w "%{speed_download}B/s\n" --max-time 20 \
  "https://static.crates.io/crates/esp-hal/esp-hal-1.1.2.crate" -r 0-100000

实测结论与对策:

资源 直连情况 对策
github.com 网页/Release 下载 ❌ 超时 走镜像前缀,如 https://ghfast.top/https://github.com/...(备选 gh-proxy.comghproxy.net
api.github.com(查询 release 信息) ✅ 通 直接用
static.crates.io 🐢 ~10KB/s cargo 换 rsproxy.cn 镜像(实测 ~400KB/s)

测试镜像是否可用的方法:

bash 复制代码
curl -sL -o /dev/null -w "%{http_code}\n" --max-time 15 \
  "https://ghfast.top/https://github.com/esp-rs/espup/releases/latest/download/espup-x86_64-pc-windows-msvc.zip"
# 返回 200 即可用

3. 下载并安装 esp Rust 工具链(Xtensa fork)

3.1 查询最新版本

bash 复制代码
curl -s --max-time 30 "https://api.github.com/repos/esp-rs/rust-build/releases/latest" \
  -o rustbuild.json

从返回的 JSON 中找到两个 Windows 资源(本教程时为 v1.95.0.0):

  • rust-1.95.0.0-x86_64-pc-windows-msvc.zip(约 160MB,已内置 rust-src,无需单独下载 rust-src 包)
  • rust-src-1.95.0.0.tar.xz(4MB,可忽略)

3.2 通过镜像下载

bash 复制代码
mkdir -p /e/URust/ESP32Rust/tools && cd /e/URust/ESP32Rust/tools

# -C - 支持断点续传;大文件超时后重复执行同一命令即可续传
curl -sL -C - --max-time 280 -o rust-esp.zip \
  "https://ghfast.top/https://github.com/esp-rs/rust-build/releases/download/v1.95.0.0/rust-1.95.0.0-x86_64-pc-windows-msvc.zip"

如果 280 秒没下完(curl 退出码 28),直接再跑一遍同样的命令,curl -C - 会接着下。本机第一次下了 112MB 超时,第二次续传完成。

3.3 解压并验证

bash 复制代码
unzip -o -q rust-esp.zip -d rust-esp
rust-esp/esp/bin/rustc.exe --version
# rustc 1.95.0-nightly (95e5bda86 2026-04-15) (1.95.0.0)

rust-esp/esp/bin/rustc.exe --print target-list | grep xtensa
# 应能看到 xtensa-esp32s3-none-elf 等 7 个目标

# 确认 rust-src 已内置(build-std 需要)
ls rust-esp/esp/lib/rustlib/src/rust/library

3.4 注册为 rustup 工具链

bash 复制代码
# 注意:rustup 在 ~/.cargo/bin 下,Git Bash 默认 PATH 没有它,用全路径调用
~/.cargo/bin/rustup.exe toolchain link esp "E:\URust\ESP32Rust\tools\rust-esp\esp"

~/.cargo/bin/rustup.exe toolchain list
# stable-x86_64-pc-windows-gnu (active, default)
# stable-x86_64-pc-windows-msvc
# esp          ← 出现这行即成功

之后在工程目录里放 rust-toolchain.toml(内容 channel = "esp"),cargo 就会自动切换到这个 fork。


4. 安装 espflash(烧录工具)

bash 复制代码
# 查最新版本(本教程时为 v4.5.0)
curl -s --max-time 30 "https://api.github.com/repos/esp-rs/espflash/releases/latest"

cd /e/URust/ESP32Rust/tools
curl -sL -C - -o espflash.zip \
  "https://ghfast.top/https://github.com/esp-rs/espflash/releases/download/v4.5.0/espflash-x86_64-pc-windows-msvc.zip"

unzip -o -q espflash.zip -d espflash-bin

# 复制到 cargo bin 目录(该目录通常已在用户 PATH 中)
cp espflash-bin/espflash.exe ~/.cargo/bin/
~/.cargo/bin/espflash.exe --version
# espflash 4.5.0

5. 安装 Xtensa GCC 工具链(链接器)

esp-hal 官方对 Xtensa 目标默认用 GNU LD 链接,需要乐鑫 crosstool-NG 工具链里的 xtensa-esp32s3-elf-gcc

bash 复制代码
# 查最新 release(本教程时为 esp-16.1.0_20260609)
curl -s --max-time 30 "https://api.github.com/repos/espressif/crosstool-NG/releases/latest"

cd /e/URust/ESP32Rust/tools
curl -sL -C - --max-time 280 -o gcc-xtensa.zip \
  "https://ghfast.top/https://github.com/espressif/crosstool-NG/releases/download/esp-16.1.0_20260609/xtensa-esp-elf-16.1.0_20260609-x86_64-w64-mingw32.zip"

unzip -o -q gcc-xtensa.zip
ls xtensa-esp-elf/bin | grep esp32s3-elf-gcc
# xtensa-esp32s3-elf-gcc.exe  ← 链接时会被 rustc 调用

该工具的 bin 目录需要在编译时加入 PATH(见第 7 步的 build.bat)。


6. 创建串口 demo 工程

目录结构:

复制代码
E:\URust\ESP32Rust\serial-demo\
├── .cargo\config.toml      # 目标平台 / 链接参数 / crates 镜像
├── .vscode\                # VSCode 配置(第 8 步)
├── src\main.rs             # 串口 demo
├── build.bat               # 构建环境脚本(第 7 步)
├── Cargo.toml
└── rust-toolchain.toml     # 指定 esp 工具链

6.1 rust-toolchain.toml

toml 复制代码
[toolchain]
channel = "esp"

6.2 Cargo.toml

toml 复制代码
[workspace]   # ← 重要:防止被上级目录已有的 cargo workspace 吞并

[package]
name    = "serial-demo"
version = "0.1.0"
edition = "2021"

[dependencies]
critical-section       = "1.2"
esp-backtrace          = { version = "0.19", features = ["esp32s3", "panic-handler", "println"] }
esp-bootloader-esp-idf = { version = "0.5", features = ["esp32s3"] }
esp-hal                = { version = "1.1", features = ["esp32s3", "unstable"] }
esp-println            = { version = "0.17", features = ["esp32s3"] }

[profile.dev]
opt-level = "s"

[profile.release]
opt-level        = "s"
lto              = "fat"
codegen-units    = 1
debug-assertions = true

版本号可通过 crates.io API 查询确认最新稳定版:

bash 复制代码
curl -s -A "esp32-setup" "https://crates.io/api/v1/crates/esp-hal" | python -c \
  "import json,sys; print(json.load(sys.stdin)['crate']['max_stable_version'])"

注意事项:

  • [workspace] 空表必须有------若上级目录(如 E:\URust)已存在 Cargo.toml workspace,cargo 会报错 this may be fixable by adding ... to the workspace.members array
  • esp-hal 1.1 中 delay 等模块在 unstable feature 后面,不加会报 could not find 'delay' in 'esp_hal'

6.3 .cargo/config.toml

toml 复制代码
# ESP32-S3 (Xtensa) 目标配置
# Xtensa 目标默认调用 xtensa-esp32s3-elf-gcc 链接(GNU LD),
# 需保证 tools\xtensa-esp-elf\bin 在 PATH 中(build.bat 已处理)

[build]
target = "xtensa-esp32s3-none-elf"

[target.xtensa-esp32s3-none-elf]
# cargo run 时自动调用 espflash 烧录并打开串口监视器
runner = "espflash flash --monitor"
rustflags = [
  "-C", "link-arg=-Wl,-Tlinkall.x",   # GNU LD 方式传入 esp-hal 链接脚本
  "-C", "link-arg=-nostartfiles",
  "-C", "force-frame-pointers",
]

[unstable]
build-std = ["core", "alloc"]   # Xtensa 无预编译 std 组件,需从源码构建 core/alloc

# 国内 crates 镜像(直连 crates.io 太慢)
[source.crates-io]
replace-with = "rsproxy-sparse"

[source.rsproxy]
registry = "https://rsproxy.cn/crates.io-index"

[source.rsproxy-sparse]
registry = "sparse+https://rsproxy.cn/index/"

[registries.rsproxy]
index = "https://rsproxy.cn/crates.io-index"

[net]
git-fetch-with-cli = true

6.4 src/main.rs --- 串口 demo

rust 复制代码
//! ESP32-S3 (N16R8) 串口 Demo
//!
//! 功能:
//!   1. 上电后通过 UART0 (GPIO43=TX, GPIO44=RX, 115200 8N1) 打印启动信息
//!   2. 每秒输出一次心跳计数
//!   3. 收到的串口数据原样回显(echo)

#![no_std]
#![no_main]

use core::fmt::Write;

use esp_backtrace as _;
use esp_hal::{
    delay::Delay,
    main,
    uart::{Config, Uart},
};

// 生成 ESP-IDF 格式的应用描述符(bootloader/espflash 需要)
esp_bootloader_esp_idf::esp_app_desc!();

#[main]
fn main() -> ! {
    let peripherals = esp_hal::init(esp_hal::Config::default());
    let delay = Delay::new();

    // UART0:默认 115200 8N1,TX=GPIO43,RX=GPIO44(ESP32-S3 默认串口引脚)
    let mut uart = Uart::new(peripherals.UART0, Config::default())
        .expect("UART0 初始化失败")
        .with_tx(peripherals.GPIO43)
        .with_rx(peripherals.GPIO44);

    uart.write(b"\r\n================================\r\n").ok();
    uart.write(b" ESP32-S3 Rust serial demo\r\n").ok();
    uart.write(b" type something -> echo back\r\n").ok();
    uart.write(b"================================\r\n").ok();
    uart.flush().ok();

    let mut buf = [0u8; 64];
    let mut elapsed_s: u32 = 0;
    let mut tick: u32 = 0;

    loop {
        // 1. 回显收到的数据
        if uart.read_ready() {
            match uart.read(&mut buf) {
                Ok(n) if n > 0 => {
                    uart.write(&buf[..n]).ok();
                    uart.flush().ok();
                }
                Err(e) => {
                    writeln!(uart, "\r\n[RX error: {e:?}]").ok();
                }
                _ => {}
            }
        }

        // 2. 每秒打印一次心跳
        tick += 1;
        if tick >= 100 {
            tick = 0;
            elapsed_s += 1;
            writeln!(uart, "[heartbeat] up {elapsed_s} s").ok();
        }

        delay.delay_millis(10);
    }
}

以上 API(Uart::new(...).with_tx().with_rx()read_ready()read()/write()core::fmt::Write impl)均已对照 esp-hal v1.1.2 源码确认。esp-hal 1.x API 变动较快,换版本时建议查对应版本的 docs.rs 或官方 examples


7. 关键一步:build.bat(MSVC + GCC 环境)

7.1 为什么需要它

第一次直接 cargo build 会遇到两类错误:

  1. link: extra operand ... Try 'link --help'
    esp 工具链 host 是 x86_64-pc-windows-msvc,编译 build-script / proc-macro 时调用 MSVC 的 link.exe。但 Git Bash 的 /usr/bin/link.exe(GNU coreutils)抢占了 PATH 前列,调到了错误的 link。
  2. xtensa-esp32s3-elf-gcc 找不到 (如果 PATH 没配)
    最终固件链接需要 Xtensa GCC。

解决办法:在 CMD (不是 Git Bash)里先执行 vcvars64.bat 加载 MSVC 环境,再补上 GCC 和 cargo 的 PATH。固化为脚本 build.bat

bat 复制代码
@echo off
REM 用法: build.bat cargo build --release    编译
REM       build.bat cargo run --release     编译 + 烧录 + 串口监视
REM       build.bat espflash monitor        只打开串口监视器
call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat" >nul 2>&1
set PATH=C:\Users\12518\.cargo\bin;E:\URust\ESP32Rust\tools\xtensa-esp-elf\bin;%PATH%
%*

如果你的 VS 版本/路径不同,可用 "%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 查询安装路径。

7.2 编译并生成固件

bash 复制代码
cd /e/URust/ESP32Rust/serial-demo
cmd //c build.bat cargo fetch            # 先拉依赖(Git Bash 中 cmd 用 //c)
cmd //c build.bat cargo build --release  # 编译(首次约 1-2 分钟)

验证固件镜像可以不插板子生成:

bash 复制代码
~/.cargo/bin/espflash.exe save-image --chip esp32s3 \
  target/xtensa-esp32s3-none-elf/release/serial-demo serial-demo.bin
# Image successfully saved!  (本例 93,680 字节)

8. 配置 VSCode

8.1 .vscode/extensions.json

json 复制代码
{
  "recommendations": ["rust-lang.rust-analyzer"]
}

8.2 .vscode/settings.json

json 复制代码
{
  "rust-analyzer.cargo.target": "xtensa-esp32s3-none-elf",
  "rust-analyzer.check.allTargets": false,
  "rust-analyzer.check.overrideCommand": [
    "cmd", "/c", "build.bat", "cargo", "check", "--workspace", "--message-format=json"
  ],
  "[rust]": {
    "editor.formatOnSave": true
  }
}

check.overrideCommand 是关键:rust-analyzer 默认直接跑 cargo check,会踩到和第 7 步一样的 link.exe 问题;改成走 build.bat 后代码检查完全正常。

8.3 .vscode/tasks.json

json 复制代码
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "esp32: build (release)",
      "type": "shell",
      "command": "cmd /c build.bat cargo build --release",
      "group": { "kind": "build", "isDefault": true },
      "problemMatcher": ["$rustc"]
    },
    {
      "label": "esp32: flash + monitor",
      "type": "shell",
      "command": "cmd /c build.bat cargo run --release",
      "detail": "烧录到 ESP32-S3 并打开串口监视器(Ctrl+] 退出监视器)",
      "group": { "kind": "test", "isDefault": true },
      "problemMatcher": ["$rustc"]
    },
    {
      "label": "esp32: monitor only",
      "type": "shell",
      "command": "cmd /c build.bat espflash monitor",
      "detail": "只打开串口监视器,不烧录",
      "problemMatcher": []
    }
  ]
}

使用:Ctrl+Shift+PTasks: Run Task → 选任务。


9. 烧录与验证(实测记录)

插上开发板,运行 esp32: flash + monitor (即 cargo run --release),实测输出:

复制代码
验证要点:

- espflash 正确识别 **esp32s3 + 16MB Flash**(与 N16R8 一致)✅
- bootloader 加载 Rust 应用成功 ✅
- banner + 每秒心跳正常打印 ✅
- 在监视器中直接敲键盘,字符原样回显(echo 功能)✅
- `Ctrl+C` 退出监视器,`Ctrl+R` 复位芯片

---
相关推荐
步行cgn1 小时前
MyBatis resultMap 结果映射完全指南
后端
nnerddboy1 小时前
Rust教程03:函数,控制流与所有权
开发语言·后端·rust
山荷枝1 小时前
04-框架--SpringBoot
java·spring boot·后端
linux修理工1 小时前
内存占用 99% 且 Java 程序较多的优化建议
java·开发语言
weixin_460443561 小时前
企业考试系统如何对接OA、钉钉和企业微信?SSO单点登录、组织同步与权限一致性设计
java·开发语言·数据库
nnerddboy2 小时前
Rust教程05:结构体,枚举与模式匹配
开发语言·网络·rust
小小尚@2 小时前
AE脚本-AE Actions v1.1.8 操作动作记录器
开发语言·前端·javascript·jupyter·postman
程序员雷欧2 小时前
Java 反射深度解析:从原理到源码的全面剖析
java·开发语言·python
登登登__2 小时前
春秋招笔试题总结
java·开发语言