简介
本书的目标是提供有关在 乐鑫 设备上使用 Rust 编程语言的综合指南。
对这些设备的 Rust 支持仍在进行中,并且进展迅速。因此,本文档的某些部分可能已过时或在阅读之间发生巨大变化。
有关 ESP 上 Rust 的相关工具和库,请参阅 GitHub 上的 esp-rs 组织。该组织由乐鑫员工和社区成员管理。
关于设备支持的说明
本书内容仅适用于ESP32系列设备;这包括:
- ESP32 系列
- ESP32-C 系列
- ESP32-S 系列
- ESP32-H 系列
ESP8266 系列不在本书讨论范围之内。 ESP8266 系列的 Rust 支持有限,乐鑫未提供官方支持。
本书适合谁
本书适用于具有一定 Rust 经验的人,并且还假定读者具有嵌入式开发和电子学的基本知识。对于那些没有经验的人,我们建议先阅读假设和先决条件以及其他资源部分以加快速度。
假设和先决条件
- 您可以熟练使用 Rust 编程语言,并在桌面环境中编写和运行应用程序。
- 你应该熟悉 2021 版的习语,因为本书针对 Rust 2021。
- 您能够熟练地使用其他语言(例如 C 或 C++)开发嵌入式系统,并且熟悉以下概念:
- 交叉编译
- 常见的数字接口,如 UART、SPI、I²C 等。
- 内存映射外设
- 中断
其他资源
如果您对上述任何内容都不熟悉或经验不足,或者您只是想了解有关本书中提到的特定主题的更多信息,您可能会发现这些资源很有帮助。
资源 | 描述 |
---|---|
The Rust Programming Language | 如果您不熟悉 Rust,我们建议您先阅读本书。 |
The Embedded Rust Book | 在这里,您可以找到 Rust 的嵌入式工作组提供的其他一些资源。 |
The Embedonomicon | 在 Rust 中进行嵌入式编程时的基本细节。 |
Embedded Rust on Espressif | 与 Ferrous Systems 合作创建的培训材料。 |
翻译
这本书目前只有英文版本。一旦本书的内容稳定下来,我们计划将其翻译成其他语言。随着翻译可用,本节将更新以包含它们。
如何使用本书
本书通常假定您是从前到后阅读它;如果没有前几章的上下文,后面章节中涵盖的内容可能毫无意义。
为本书做贡献
本书的工作在这个存储库中进行协调。
如果您在遵循本书中的说明时遇到问题,或者发现本书的某些部分不够清晰或难以理解,那么这是一个错误,应该在本书的问题跟踪器中报告。
非常欢迎修复拼写错误和添加新内容的拉取请求!
重复使用该材料
本书根据以下许可分发:
- 本书中包含的代码示例和独立的 Cargo 项目是根据 MIT 许可证 和 Apache 许可证 v2.0 的条款获得许可的。
- 本书中包含的文字、图片和图表均根据 Creative Commons CC-BY-SA v4.0 许可条款获得许可。
TL;DR:如果您想在您的作品中使用我们的文字或图片,您需要:
- 给予适当的信任(即在幻灯片中提及这本书,并提供相关页面的链接)
- 提供指向 CC-BY-SA v4.0 许可证的链接
- 说明您是否以任何方式更改了材料,并根据相同的许可对我们提供的材料进行任何更改
请让我们知道如果你寻找这本书有用!
生态概述
在乐鑫芯片上使用 Rust 有以下几种方法:
- 使用 std 库,又名标准库。
- 使用核心库 (no_std),也就是裸机开发。
这两种方法各有利弊,因此您应该根据项目的需要做出决定。本章包含对这两种方法的概述:
另请参阅 The Embedded Rust Book 中不同运行时的比较。
GitHub 上的 esp-rs 组织 拥有许多与在乐鑫芯片上运行 Rust 相关的存储库。大多数所需的 crate 都在这里托管了它们的源代码。
关于存储库命名约定的注释
在 esp-rs 组织中,我们使用以下写法:
- 以
esp-idf-
开头的存储库专注于std
方法。例如:esp-idf-hal
- 以
esp-
开头的存储库专注于no_std
方法。例如:esp-hal
很容易记住如下:
no_std
在裸机之上工作,所以esp-
是乐鑫芯片std
除了裸机,还需要附加层, 则是esp-idf-
支持的乐鑫产品
笔记:
- ✅ - 该功能已实施或支持
- ⏳ - 该功能正在开发中
- ❌ - 不支持该功能
芯片 | std | no_std |
---|---|---|
ESP32 | ✅ | ✅ |
ESP32-C2 | ⏳ | ✅ |
ESP32-C3 | ✅ | ✅ |
ESP32-C6 | ⏳ | ✅ |
ESP32-S2 | ✅ | ✅ |
ESP32-S3 | ✅ | ✅ |
ESP32-H2 | ⏳ | ⏳ |
ESP8266 | ❌ | ✅ |
在某些情况下支持的产品将在整本书中称为支持的 乐鑫产品。
截至目前,esp-idf 框架支持的乐鑫产品是支持 Rust std
开发的产品。不同版本的esp-idf以及对乐鑫芯片的支持情况见此表。
使用标准库 (std
)
乐鑫提供了一个基于 C 的开发框架,称为 esp-idf,它已经或将支持从 ESP32 开始的所有乐鑫芯片;请注意,此框架_不支持_ ESP8266。另请参阅对 乐鑫产品的支持。
反过来,esp-idf
提供了一个新的 lib 环境,它具有足够的功能来在其上构建 Rust 标准库 (std
)。这是在 ESP 设备上启用 std
支持所采用的方法。
当前支持
截至目前,esp-idf 框架支持的乐鑫产品是支持 Rust std
开发的产品。
使用 std
时,您可以访问 esp-idf 中存在的许多功能,包括线程、互斥锁和其他同步原语、集合、随机数生成、套接字……
相关的 esp-rs
crates
仓库 | 描述 |
---|---|
esp-rs/embedded-svc | 嵌入式服务(WiFi 、网络 、Httpd 、日志记录 等)的抽象特征 |
esp-rs/esp-idf-svc | 使用 esp-idf 驱动程序的 embedded-svc 的实现。 |
esp-rs/esp-idf-hal | 使用 esp-idf 框架实现 embedded-hal 和其他特性。 |
esp-rs/esp-idf-sys | Rust 绑定到 esp-idf 开发框架。提供对驱动程序、Wi-Fi 等的原始(不安全 )访问。 |
上述 crates 具有相互依赖性,这种关系可以在下面看到。
graph TD;
esp-idf-hal --> esp-idf-sys & embedded-svc
esp-idf-svc --> esp-idf-sys & esp-idf-hal & embedded-svc
当您可能想要使用标准库 (std
)
- 丰富的功能:如果您的嵌入式系统需要大量功能,如支持网络协议、文件 I/O 或复杂的数据结构,您可能希望使用托管环境方法,因为标准库提供了广泛的功能,可以使用相对快速有效地构建复杂的应用程序
- 可移植性:
std
crate 提供了一组标准化的 API,可以跨不同的平台和架构使用,从而更容易编写可移植和可重用的代码。 - 快速开发:
std
crate 提供了一组丰富的功能,可用于快速高效地构建应用程序,而无需过多担心底层细节。
裸机开发 (no_std
)
嵌入式 Rust 开发人员可能更熟悉使用 no_std
;它不使用 std
(Rust 标准库 )而是使用一个子集,即核心库。 The Embedded Rust Book 对此有很大的介绍 。
重要的是要注意,由于 no_std
使用的 Rust 核心库
是 Rust 标准库
的一个子集,因此 no_std
crate 可以在 std
环境中编译,但反之则不然。因此,在创建 crate 时,如果它需要标准库
才能运行,则值得牢记。
当前支持
下表涵盖了目前不同乐鑫产品对 no_std
的支持情况。
HAL | Wi-Fi/BLE/ESP-NOW | Backtrace | Storage | |
---|---|---|---|---|
ESP32 | ✅ | ✅ | ✅ | ✅ |
ESP32-C2 | ✅ | ✅ | ✅ | ✅ |
ESP32-C3 | ✅ | ✅ | ✅ | ✅ |
ESP32-C6 | ✅ | ✅ | ✅ | ✅ |
ESP32-S2 | ✅ | ✅ | ✅ | ✅ |
ESP32-S3 | ✅ | ✅ | ✅ | ✅ |
ESP32-H2 | ⏳ | ⏳ | ✅ | ⏳ |
注意:
- ✅ 在 Wi-Fi/BLE/ESP-NOW 中表示目标至少支持所列技术之一。有关详细信息,请参阅 esp-wifi 存储库的当前支持表。
- ESP8266 HAL 处于维护模式,不会对该芯片进行进一步开发。
相关的 esp-rs
crates
Repository | Description |
---|---|
esp-rs/esp-hal | 硬件抽象层 |
esp-rs/esp-pacs | 外设访问 |
esp-rs/esp-wifi | Wi-Fi, BLE 和 ESP-NOW 支持 |
esp-rs/esp-alloc | 简单的堆分配器 |
esp-rs/esp-println | print! , println! |
esp-rs/esp-backtrace | 异常和 panic 处理 |
esp-rs/esp-storage | 访问未加密闪存的嵌入式存储特性 |
当您可能想使用裸机时(no_std
)
- 小内存占用:如果您的嵌入式系统资源有限并且需要小内存占用,您可能希望使用裸机,因为 std 功能会增加大量的最终二进制文件大小和编译时间。
- 直接硬件控制:如果您的嵌入式系统需要对硬件进行更直接的控制,例如低级设备驱动程序或访问专门的硬件功能,您可能希望使用裸机,因为
std
添加了抽象,这会使直接与硬件交互变得更加困难。 - 实时约束或时间关键型应用程序:如果您的嵌入式系统需要实时性能或低延迟响应时间,因为
std
会引入不可预测的延迟和开销,从而影响实时性能。 - 自定义要求:裸机允许对应用程序的行为进行更多自定义和细粒度控制,这在专用或非标准环境中很有用。
设置开发环境
目前,乐鑫 SoC 基于两种不同的架构:RISC-V
和 Xtensa
。两种架构都支持 std
和 no_std
方法。
要设置开发环境,请执行以下操作:
- 安装 Rust
- 根据您的目标安装
正如下面安装过程中提到的,对于 std
开发不要忘记安装 std
开发要求.
请注意,您可以在容器中托管开发环境。
安装 Rust
确保安装了 Rust。如果没有,请参阅 rustup 网站上的说明。
另请参阅。 替代安装方法.
注: 如果您在主机上运行 Windows,请确保您已经安装了下面列出的 ABI 之一。有关更多详细信息,请参阅 The rustup 一书中的 Windows 一章。
- MSVC: 推荐的 ABI,包含在
rustup
默认要求列表中。将其用于与 Visual Studio 生成的软件的互操作性。- GNU: GCC 工具链使用的 ABI。自行安装,以便与使用 MinGW/MSYS2 工具链构建的软件进行互操作。
仅限 RISC-V 目标
要基于 RISC-V
架构为乐鑫芯片构建 Rust 应用程序,请执行以下操作:
-
使用
rust-src
组件 安装nightly 工具链
:rustup toolchain install nightly --component rust-src
- 设定目标:
-
对于
no_std
(裸机)应用程序,运行:rustup target add riscv32imc-unknown-none-elf # 适用于 ESP32-C2 和 ESP32-C3 rustup target add riscv32imac-unknown-none-elf # 适用于 ESP32-C6 和 ESP32-H2
-
对于
std
应用程序,使用riscv32imc-esp-espidf
。由于此目标当前是 Tier 3,因此它没有通过
rustup
分发的预构建对象,并且 不需要 作为no_std
目标安装.
-
- 要构建
std
项目,您还需要安装:
现在您应该能够在乐鑫的 RISC-V
芯片上构建和运行项目。
RISC-V 和 Xtensa 目标
espup
是一个工具,可简化为 Xtensa
和 RISC-V
架构开发 Rust 应用程序所需组件的安装和维护。
要安装 espup
, 请运行:
cargo install espup
您也可以直接下载预编译的[发布二进制文件]或使用 cargo-binstall.
安装 espup
后,执行以下操作:
-
安装所有必要的工具,通过运行以下命令为所有支持的乐鑫目标开发 Rust 应用程序:
espup install
espup
将创建一个导出文件,其中包含构建项目所需的一些环境变量:- Unix 系统 -
$HOME/export-esp.sh
- Windows -
%USERPROFILE%\export-esp.ps1
- Unix 系统 -
-
在 Unix 系统上,确保在构建任何应用程序之前在每个终端中 source 此文件:
. $HOME/export-esp.sh
在 Windows 系统上,无需 source 该文件。它只是为了显示修改后的环境变量而创建的。
ß
运行 espup install
后:
no_std
(裸机)应用程序应该开箱即用std
应用程序需要额外的std
开发要求
什么是 espup 安装
为了启用对乐鑫目标的支持, espup
安装了以下工具:
分叉编译器可以与标准 Rust 编译器共存,允许两者都安装在您的系统上。使用任何可用的覆盖方法时,将调用这个分支编译器。
注: 我们正在努力将我们的分支合并到上游项目中。
LLVM
分支的变化。已经在进行中,请查看此问题跟踪中的状态。- Rust 编译器分叉。如果
LLVM
的更改被接受,我们将继续进行 Rust 编译器的修改。
Xtensa 目标的其他安装方法
- 使用 esp-rs/rust-build 安装脚本。这是过去推荐的方式,但现在安装脚本已冻结功能,所有新功能只会包含在 espup 中。有关说明,请参阅存储库自述文件。
- 使用来自源代码的
Xtensa
支持构建 Rust 编译器。此过程的计算成本很高,可能需要一个或多个小时才能完成,具体取决于您的系统。除非有采用这种方法的主要原因,否则不推荐这样做。这是从源代码构建它的存储库: [esp-rs/rust 存储库]。
std
开发要求
无论目标架构如何,请确保您安装了以下构建 std
应用程序所需的工具:
python
: ESP-IDF 要求git
: ESP-IDF 要求ldproxy
: 一种将链接器参数转发给实际链接器的工具,该链接器也作为参数提供给ldproxy
。安装方法:cargo install ldproxy
std 运行时使用 ESP-IDF (乐鑫 IoT 开发框架) 作为托管环境,但用户不需要安装它。ESP-IDF 由esp-idf-sys 自动下载和安装, 这是一个所有 std 项目在构建 std 应用程序时都需要使用的 crate。
使用容器
您可以在容器中托管开发环境,而不是直接安装在您的本地系统上。乐鑫提供 idf-rust 镜像,支持 RISC-V
和 Xtensa
目标架构,支持 std
和 no_std
开发。
您可以找到许多适用于 linux/arm64
和 linux/amd64
平台的标签。
对于每个 Rust 版本,我们使用以下命名约定生成标签:
<chip>_<rust-toolchain-version>
- 例如,
esp32_1.64.0.0
包含用于使用1.64.0.0
Xtensa Rust 工具链为 ESP32 开发std
和no_std
应用程序的生态系统。
- 例如,
有特殊情况
<chip>
可以是all
,表示与所有乐鑫目标兼容<rust-toolchain-version>
可以是latest
,表示Xtensa Rust
工具链的最新版本
根据您的操作系统,您可以选择任何容器运行时,例如 Docker、Podman 或 Lima。
工具
现在我们已经安装了所需的依赖项,我们将介绍一些工具,这些工具将使我们为 ESP 目标开发 Rust 应用程序的生活变得更加容易。
文本编辑器和 IDE
虽然这常常是一个有争议的话题,但是使用正确的开发环境可以显著提高您在某种编程语言下的生产力。以下是我们认为是最佳选项的经过精选的列表。
Visual Studio Code
其中一个比较常见的开发环境是 Microsoft 的 Visual Studio Code 文本编辑器,以及 Rust Analyzer 扩展。
Visual StudioCode 是一个开源的跨平台图形文本编辑器,具有丰富的扩展生态系统。 Rust Analyzer extension提供了 Rust language server protocol 的实现,另外还包括自动完成、转到定义等功能。
Visual Studio Code 可以通过最流行的包管理器安装,安装程序可在官方网站上获得。 Rust Analyzer extension可以通过内置的扩展管理器安装在 Visual Studio Code 中。
除了 Rust Analyzer (RA),还有其他可能非常有用的扩展:
- Even Better TOML 用于编辑基于 TOML 的配置文件
- crates 帮助管理 Rust 依赖项
技巧和窍门
如果您正在为不支持 std
的目标进行开发,Rust Analyzer 的行为可能会很奇怪,经常会报告各种错误。这可以通过在项目中创建 .vscode/settings.json
文件并使用以下内容填充来解决:
{
"rust-analyzer.checkOnSave.allTargets": false
}
如果您正在使用自定义工具链,就像您使用 Xtensa 目标一样,您可以通过 rust-toolchain.toml
文件为 cargo
提供一些提示以改善用户体验:
[toolchain]
channel = "esp"
components = ["rustfmt", "rustc-dev"]
targets = ["xtensa-esp32-none-elf"]
CLion
CLion 是 JetBrains 的 C 和 C++ 跨平台 IDE。
IntelliJ
vim
vim 是一个基于 vi 的高度可配置的文本编辑器,它还支持Rust Analyzer。
espflash
用于 ESP 设备的串行烧写工具。支持烧写 ESP32、ESP32-C2、ESP32-C3、ESP32-S2、ESP32-S3 和 ESP8266。
esp-rs/espflash 存储库包含两个 crate,cargo-espflash
和 espflash
。您可以在下面各自的部分中找到有关它们的更多信息。
cargo-espflash
为 cargo
提供一个子命令,用于处理交叉编译和烧写的操作。请注意,这需要使用 cargo
的实验性 build-std
特性。有关这方面的更多信息,请参阅 cargo documentation。
安装方法:
cargo install cargo-espflash
这个命令必须在一个 Cargo 项目中运行,也就是包含一个 Cargo.toml
文件的目录中运行。例如,要在 release
模式下构建一个名为 blinky
的示例,将生成的二进制文件烧写到设备中,然后启动串行监视器,可以执行以下操作:
cargo espflash --example=blinky --release --monitor
如需更多信息,请参阅 cargo-espflash README.
espflash
提供一个独立的命令行应用程序,用于将一个 ELF 文件烧写到设备中。
安装方法:
cargo install espflash
假设您已经通过其他方式构建了一个 ELF 二进制文件,espflash
可以用于将其下载到您的设备中。例如,如果您使用 idf.py
从 esp-idf 构建了 getting-started/blinky
示例,则可以运行以下命令:
espflash build/blinky
如需更多信息,请参阅 espflash README.
espmonitor
esp-rs/espmonitor 存储库包含两个 crate,cargo-espmonitor
和 espmonitor
。
cargo-espmonitor
cargo install cargo-espmonitor
espmonitor
cargo install espmonitor
调试
使用本章将介绍使用不同工具调试 Rust 应用程序。
probe-rs
probe-rs 项目是一组使用各种调试探针与嵌入式 MCU 交互的工具。它类似于 openOCD、PyOCD、Segger 工具等。支持 ARM 和 RISCV 架构以及一系列工具,包括但不限于:
- Debugger
- GDB 支持
- 用于交互式调试的 CLI
- VSCode 扩展
- RTT (实时传输)
- 类似于 IDF 的 app_trace 组件
- Flashing algorithms
有关 probe-rs 以及如何设置项目的更多信息,请访问 probe.rs 网站。
USB-JTAG-SERIAL
peripheral for ESP32-C3
从 probe-rs
v0.12 开始,可以使用内置的 USB-JTAG-SERIAL
外设对 ESP32-C3 进行烧写和调试,无需任何外部硬件调试器。有关配置接口的更多信息可以在官方文档中找到。
支持乐鑫 Espressif 芯片
probe-rs
目前仅支持 ARM
& RISC-V
,因此这限制了目前可以使用的 Espressif 芯片数量。
Chip | Flashing | Debugging |
---|---|---|
ESP32-C3 | ✅ | ⚠️ |
请注意: 标有 ⚠️ 的项目目前正在进行中,可用,但可能会出现错误。
权限 - Linux
在 Linux 上,您可能会在尝试与 Espressif 交互时遇到权限问题。安装以下 udev
规则并重新加载应该可以解决该问题。
# Espressif dev kit FTDI
ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6010", MODE="660", GROUP="plugdev", TAG+="uaccess"
# Espressif USB JTAG/serial debug unit
ATTRS{idVendor}=="303a", ATTRS{idProduct}=="1001", MODE="660", GROUP="plugdev", TAG+="uaccess"
# Espressif USB Bridge
ATTRS{idVendor}=="303a", ATTRS{idProduct}=="1002", MODE="660", GROUP="plugdev", TAG+="uaccess"
OpenOCD
与 probe-rs 类似,OpenOCD 不支持 Xtensa 架构。然而,乐鑫在 espressif/openocd-esp32 下维护了一个 OpenOCD 的分支,它支持 Espressif 的芯片。
有关如何为您的平台安装 openocd-esp32
的说明,请参阅 Espressif 文档。
Espressif 芯片设置
安装后,就像使用正确的脚本运行 openocd
一样简单。对于带有内置 USB JTAG 的芯片,通常有一个开箱即用的配置,例如在 ESP32-C3 上:
openocd -f board/esp32c3-builtin.cfg
对于其他配置,可能需要分别指定芯片和接口,例如,带有 J-Link 的 ESP32:
openocd -f interface/jlink.cfg -f target/esp32.cfg
在 Visual Studio Code 上调试
您可以直接在 Visual Studio Code 中使用图形输出进行调试。
ESP32
硬件设置
ESP32 没有内置 JTAG 接口,需要外接 JTAG 转接器到 ESP32 开发板,例如可以使用 ESP-Prog。
ESP32 Pin | JTAG Signal |
---|---|
MTDO/GPIO15 | TDO |
MTDI/GPIO12 | TDI |
MTCK/GPIO13 | TCK |
MTMS/GPIO14 | TMS |
3V3 | VJTAG |
GND | GND |
注意:在 Windows 上 USB 串行转换器 A 0403 6010 00
驱动程序应该是 WinUSB。
VSCode 设置
- 为 VScode 安装 Cortex-Debug 扩展。
- 在要调试的项目中创建
.vscode/launch.json
文件。可以使用这个模板文件。 - 修改 executable,svdFile,serverpath 路径和 toolchainPrefix 字段。
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
// more info at: https://github.com/Marus/cortex-debug/blob/master/package.json
"name": "Attach",
"type": "cortex-debug",
"request": "attach", // attach instead of launch, because otherwise flash write is attempted, but fails
"cwd": "${workspaceRoot}",
"executable": "target/xtensa-esp32-none-elf/debug/.....",
"servertype": "openocd",
"interface": "jtag",
"svdFile": "../../esp-pacs/esp32/svd/esp32.svd",
"toolchainPrefix": "xtensa-esp32-elf",
"openOCDPreConfigLaunchCommands": ["set ESP_RTOS none"],
"serverpath": "C:/Espressif/tools/openocd-esp32/v0.11.0-esp32-20220411/openocd-esp32/bin/openocd.exe",
"configFiles": ["board/esp32-wrover-kit-3.3v.cfg"],
"overrideAttachCommands": [
"set remote hardware-watchpoint-limit 2",
"mon halt",
"flushregs"
],
"overrideRestartCommands": ["mon reset halt", "flushregs", "c"]
}
]
}
ESP32-C3
revision < 3 没有内置 JTAG 接口。
revision 3 具有内置 JTAG 接口,您无需连接外部设备即可进行调试。要获取芯片版本,请运行 cargo espflash board-info
命令。
硬件设置
如果您的 ESP32-C3 的版本小于 3,请按照这些说明进行操作,如果您的版本为 3,则可以跳转到VSCode 设置。
ESP32-C3 版本 1 和版本 2 没有内置 JTAG 接口,因此您必须将外部 JTAG 适配器连接到 ESP32-C3 板,例如,可以使用 ESP-Prog。
ESP32-C3 Pin | JTAG Signal |
---|---|
MTDO/GPIO7 | TDO |
MTDI/GPIO5 | TDI |
MTCK/GPIO6 | TCK |
MTMS/GPIO4 | TMS |
3V3 | VJTAG |
GND | GND |
注意:在 Windows 上 USB 串行转换器 A 0403 6010 00
驱动程序应该是 WinUSB。
VSCode 设置
- 为 VScode 安装 Cortex-Debug 扩展。
- 在要调试的项目中创建
.vscode/launch.json
文件。可以使用这个模板文件。 - 修改 executable,svdFile,serverpath 路径和 toolchainPrefix 字段。
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
// more info at: https://github.com/Marus/cortex-debug/blob/master/package.json
"name": "Attach",
"type": "cortex-debug",
"request": "attach", // attach instead of launch, because otherwise flash write is attempted, but fails
"cwd": "${workspaceRoot}",
"executable": "target/riscv32imc-unknown-none-elf/debug/examples/usb_serial_jtag", //
"servertype": "openocd",
"interface": "jtag",
"svdFile": "../../esp-pacs/esp32c3/svd/esp32c3.svd",
"toolchainPrefix": "riscv32-esp-elf",
"openOCDPreConfigLaunchCommands": ["set ESP_RTOS none"],
"serverpath": "C:/Espressif/tools/openocd-esp32/v0.11.0-esp32-20220411/openocd-esp32/bin/openocd.exe",
"configFiles": ["board/esp32c3-builtin.cfg"],
"overrideAttachCommands": [
"set remote hardware-watchpoint-limit 2",
"mon halt",
"flushregs"
],
"overrideRestartCommands": ["mon reset halt", "flushregs", "c"]
}
]
}
模拟
模拟项目非常方便。它允许用户使用 CI 测试项目,在没有硬件的情况下尝试项目,以及许多其他场景。
目前,有几种模拟 Rust 项目在乐鑫芯片上的方法,它们都有一些限制,但是它们正在快速发展,并且每天变得更好。
在本章中,我们将讨论可用的不同模拟方式。
Wokwi
Wokwi 是一个在线模拟器,支持在 ESP 芯片中模拟 Rust 项目(包括 std
和 no_std
),请访问 wokwi.com/rust 查看示例列表和开始新项目的方法。
Wokwi 提供了 WiFi 模拟、虚拟逻辑分析仪和 GDB 调试等众多功能,有关更多详细信息,请参阅 Wokwi 文档。对于 ESP 芯片,有一张表格列出了当前支持的模拟功能。
使用 wokwi-server
wokwi-server 是一个命令行工具,用于启动您项目的 Wokwi 模拟。也就是说,它允许您在您的计算机或容器中构建项目并模拟生成的二进制文件。
wokwi-server 还允许您在其他 Wokwi 项目上模拟您的生成二进制文件,其中包括比芯片本身更多的硬件部件。有关详细说明,请参阅 wokwi-server 自述文件的相应部分。
QEMU
乐鑫维护了一个 QEMU 分支,位于 espressif/QEMU,其中包含必要的补丁,使其可以在乐鑫芯片上运行。请查看 QEMU wiki 以获取有关如何构建 QEMU 并使用它来模拟项目的说明。
一旦您构建了 QEMU,您应该会有 qemu-system-xtensa
可执行文件。
使用 QEMU 运行我们的项目
注意:目前只支持 ESP32,因此请确保您正在以
xtensa-esp32-espidf
为目标进行编译。
要在 QEMU 中运行我们的项目,我们需要一个固件/映像,其中包含合并的引导加载程序和分区表。我们可以使用 cargo-espflash
来生成它:
cargo espflash save-image --merge ESP32 <OUTFILE> --release
如果您喜欢使用
espflash
,则可以通过先构建项目,然后生成映像来实现相同的结果:cargo build --release espflash save-image --merge ESP32 target/xtensa-esp32-espidf/release/<NAME> <OUTFILE>
现在,在 QEMU 中运行映像:
/path/to/qemu-system-xtensa -nographic -machine esp32 -drive file=<OUTFILE>,if=mtd,format=raw
编写你自己的应用程序
当你安装了合适的Rust编译器和工具链之后,你就可以开始创建应用程序了。
基本上有两种方法可以做到这一点:从模板生成或仅使用cargo
从头开始。
我们强烈建议从模板开始项目,因为它为你配置了一个项目,节省了从头开始使用cargo
设置项目所需的所有时间。
如果你正在寻找灵感,请查看我们 Awesome ESP Rust 仓库的 Projects 部分
从模板生成项目
我们目前维护两个模板仓库:
- esp-template -
no_std
模板。 - esp-idf-template -
std
模板。
这两个模板都基于 cargo-generate,这是一个允许你基于现有模板创建新项目的工具。在我们的情况下,可以使用 esp-idf-template 或 esp-template 生成具有所有必需配置和依赖项的应用程序。
可以通过运行以下命令安装 cargo generate
:
cargo install cargo-generate
当调用 cargo generate
子命令时,你将被提示回答关于你的应用程序目标的一系列问题。完成此过程后,你将拥有一个可构建的项目,具有所有正确的配置。
使用任何一个模板,生成的应用程序可以像正常情况下一样使用适当的工具链和目标构建,只需运行 cargo build
即可。
使用 cargo run
将编译项目,将其烧录,并打开我们的芯片的串行监视器。
esp-idf-template
当使用 Rust 标准库 (std
) 时,可以使用 esp-idf-template 模板,它看起来像这样:
$ cargo generate --git https://github.com/esp-rs/esp-idf-template cargo
🤷 Project Name : esp-rust-app
🔧 Destination: /home/alice/esp-rust-app ...
🔧 Generating template ...
✔ 🤷 MCU · esp32
✔ 🤷 Configure project to use Dev Containers (VS Code, GitHub Codespaces and Gitpod)? (beware: Dev Containers not available for esp-idf v4.3.2) · false
✔ 🤷 STD support · true
✔ 🤷 ESP-IDF native build version (v4.3.2 = previous stable, v4.4 = stable, mainline = UNSTABLE) · v4.4
[ 1/10] Done: .cargo/config.toml
[ 2/10] Done: .cargo
[ 3/10] Done: .gitignore
[ 4/10] Done: .vscode
[ 5/10] Done: Cargo.toml
[ 6/10] Done: build.rs
[ 7/10] Done: rust-toolchain.toml
[ 8/10] Done: sdkconfig.defaults
[ 9/10] Done: src/main.rs
[10/10] Done: src
🔧 Moving generated files into: `/home/alice/esp-rust-app`...
💡 Initializing a fresh Git repository
✨ Done! New project created /home/alice/esp-rust-app
有关模板项目的更多详细信息,请参见 了解 esp-idf-template。
esp-template
对于裸机应用程序(no_std
),您可以使用 esp-template 模板:
cargo generate --git https://github.com/esp-rs/esp-template
🤷 Project Name : esp-rust-app
🔧 Destination: /home/alice/esp-rust-app ...
🔧 Generating template ...
✔ 🤷 Which MCU to target? · esp32c3
✔ 🤷 Configure project to use Dev Containers (VS Code, GitHub Codespaces and Gitpod)? · false
✔ 🤷 Enable allocations via the esp-alloc crate? · false
[ 1/11] Done: .cargo/config.toml
[ 2/11] Done: .cargo
[ 3/11] Done: .gitignore
[ 4/11] Done: .vscode/settings.json
[ 5/11] Done: .vscode
[ 6/11] Done: Cargo.toml
[ 7/11] Done: LICENSE-APACHE
[ 8/11] Done: LICENSE-MIT
[ 9/11] Done: rust-toolchain.toml
[10/11] Done: src/main.rs
[11/11] Done: src
🔧 Moving generated files into: `/home/alice/esp-rust-app`...
✨ Done! New project created /home/alice/esp-rust-app
有关模板项目的更多详细信息,请参见 了解 esp-template。
在模板中使用开发容器
在两个模板仓库中,都有对于开发容器支持的提示。使用开发容器可以添加对以下工具的支持:
开发容器使用 idf-rust
容器镜像,该容器镜像在 Rust 安装章节的 使用容器部分 中有介绍,并且为开发乐鑫芯片的 Rust 应用程序提供了一个无需安装的开发环境。开发容器还与 Wokwi 模拟器 集成,可以在容器内模拟项目,并且使用 Web Flash 进行烧录。
更多有关开发容器的详细信息,请参阅模板自述文件中的开发容器部分。
编写 no_std 应用程序
本章的目标是提供一个入门指南,介绍如何使用 Rust 编程语言和 esp-hal 在 Espressif SoCs 和模块上进行开发。
请注意,在每个 SoC 的
esp-hal
下的examples
文件夹中都有涵盖特定外设使用的示例。例如esp32c3-hal/examples
。
本章展示的示例通常适用于使用 ESP32-C3-DevKit-RUST-1 开发板的 ESP32-C3。
您可以使用任何其他 ESP32、ESP32-C3、ESP32-S2 或 ESP32-S3 开发板,但可能需要进行较小的代码更改和配置更改。
此外,本书的本节仅涵盖本地工作。也就是说,我们将使用本地主机进行开发,而不是使用开发容器,因此请确保您已正确设置开发环境。
了解 esp-template
现在我们已经知道了如何生成 no_std 项目,让我们检查生成的项目包含的内容并尝试了解它的每个部分。
检查生成的项目
使用以下选项从 esp-template 创建项目:
- MCU:
esp32c3
- Devcontainer 支持:
false
esp-alloc
crate 支持:false
将生成如下文件结构:
├── .cargo
│ └── config.toml
├── src
│ └── main.rs
├── .vscode
│ └── settings.json
├── .gitignore
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
└── rust-toolchain.toml
在继续之前,让我们看看这些文件的作用。
- .gitignore
- 告诉
git
忽略哪些文件和文件夹
- 告诉
- Cargo.toml
- Cargo 元数据和项目依赖声明清单
LICENSE-APACHE
,LICENSE-MIT
- 这些是 Rust 生态系统中使用最广泛的许可证
- 如果您想应用不同的许可证,可以删除这些文件并在
Cargo.toml
中更改许可证。
- rust-toolchain.toml
- 定义要使用的 Rust 工具链
- 根据您的目标,这将使用
nightly
或esp
- .cargo/config.toml
- Cargo 配置
- 这定义了几个选项以正确构建项目
- 还包含
runner = "espflash --monitor"
- 这意味着您可以使用cargo run
来烧录和监视您的代码
- .vscode/settings.json
- Visual Studio Code 的设置 - 如果您不使用 VSCode,可以删除整个文件夹
- src/main.rs
- 新创建项目的主源文件
- 我们将在下一节中分析它的内容
main.rs
#![no_std]
#![no_main]
use esp32c3_hal::{clock::ClockControl, pac::Peripherals, prelude::*, timer::TimerGroup, Rtc};
use esp_backtrace as _;
#[riscv_rt::entry]
fn main() -> ! {
let peripherals = Peripherals::take().unwrap();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
// Disable the RTC and TIMG watchdog timers
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
let mut wdt1 = timer_group1.wdt;
rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();
loop {}
}
这是相当多的代码。让我们看看它有什么用。
#![no_std]
- 这告诉Rust编译器,这段代码不使用
libstd
- 这告诉Rust编译器,这段代码不使用
#![no_main]
no_main
属性表示该程序不使用标准的 main 接口,该接口专为接收参数的命令行应用程序设计。我们将使用riscv-rt
创建的 entry 属性来定义自定义入口点,而不是标准的 main。在此程序中,我们将入口点命名为main
,但是也可以使用任何其他名称。入口点函数必须是 diverging function。即具有签名fn foo() -> !
;此类型表示该函数永远不会返回,这意味着程序永远不会终止。
use esp32c3_hal:{...}
- 我们需要引入一些要使用的类型
- 这些来自
esp-hal
use esp_backtrace as _;
- 由于我们处于裸机环境中,如果在代码中发生 panic,我们需要运行一个 panic-handler
- 有几种不同的 crate 可用(例如
panic-halt
),但esp-backtrace
提供了一个实现,该实现打印回溯的地址 - 与espflash
/espmonitor
一起,这些地址可以解码为源代码位置
let peripherals = Peripherals::take().unwrap();
- HAL 驱动程序通常接管通过 PAC 访问的外设的所有权
- 在这里,我们从 PAC 中获取所有外设,以便稍后将它们传递给 HAL 驱动程序
let system = peripherals.SYSTEM.split();
- 有时一个外设(这里是 System 外设)是粗粒度的,并且不完全适合 HAL 驱动程序 - 因此,在这里,我们将 System 外设分成更小的部分,这些部分将传递给驱动程序
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
- 在这里,我们配置系统时钟 - 在本例中,我们使用默认值
- 我们冻结时钟,这意味着我们以后无法更改它们
- 一些驱动程序需要时钟的引用,以了解如何计算速率和持续时间
- 接下来的代码块实例化了一些外设(即 RTC 和两个定时器组)以禁用启动后已启用的看门狗
- 如果没有该代码,SoC 将在一段时间后重新启动
- 还有另一种方法可以防止重新启动:feeding 看门狗
loop {}
- 因为我们的函数永远不会返回值,所以我们在一个循环中“什么也不做”。
运行代码
构建和运行代码非常简单:
cargo run
这将根据配置构建代码并执行 espflash
将代码刷入开发板。
由于我们的 runner
配置 还将 --monitor
参数传递给 espflash
,因此我们可以看到代码输出的内容。
确保你已经安装了
espflash
,否则这一步会失败。安装espflash
:cargo install espflash
你应该会看到类似以下的输出:
Connecting...
Chip type: ESP32-C3 (revision 3)
Crystal frequency: 40MHz
Flash size: 4MB
Features: WiFi
MAC address: 60:55:f9:c0:0e:ec
App/part. size: 198752/4128768 bytes, 4.81%
[00:00:00] ######################################## 12/12 segment 0x0
[00:00:00] ######################################## 1/1 segment 0x8000
[00:00:01] ######################################## 57/57 segment 0x10000
Flashing has completed!
Commands:
CTRL+R Reset chip
CTRL+C Exit
ESP-ROM:esp32c3-api1-20210207
Build:Feb 7 2021
rst:0x15 (USB_UART_CHIP_RESET),boot:0xc (SPI_FAST_FLASH_BOOT)
Saved PC:0x4004c72e
0x4004c72e - _stack_start
at ??:??
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fcd6100,len:0x172c
load:0x403ce000,len:0x928
0x403ce000 - _erwtext
at ??:??
load:0x403d0000,len:0x2ce0
0x403d0000 - _erwtext
at ??:??
entry 0x403ce000
0x403ce000 - _erwtext
at ??:??
I (24) boot: ESP-IDF v4.4-dev-2825-gb63ec47238 2nd stage bootloader
I (24) boot: compile time 12:10:40
I (25) boot: chip revision: 3
I (28) boot_comm: chip revision: 3, min. bootloader chip revision: 0
I (35) boot.esp32c3: SPI Speed : 80MHz
I (39) boot.esp32c3: SPI Mode : DIO
I (44) boot.esp32c3: SPI Flash Size : 4MB
I (49) boot: Enabling RNG early entropy source...
I (54) boot: Partition Table:
I (58) boot: ## Label Usage Type ST Offset Length
I (65) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (73) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (80) boot: 2 factory factory app 00 00 00010000 003f0000
I (88) boot: End of partition table
I (92) boot_comm: chip revision: 3, min. application chip revision: 0
I (99) esp_image: segment 0: paddr=00010020 vaddr=3c030020 size=04a6ch ( 19052) map
I (110) esp_image: segment 1: paddr=00014a94 vaddr=40380000 size=00910h ( 2320) load
I (116) esp_image: segment 2: paddr=000153ac vaddr=00000000 size=0ac6ch ( 44140)
I (131) esp_image: segment 3: paddr=00020020 vaddr=42000020 size=2081ch (133148) map
I (152) boot: Loaded app from partition at offset 0x10000
这里你看到的是来自第一阶段和第二阶段引导程序的消息,然后...什么都没有了。
这正是代码所做的。
您可以使用 CTRL+R
重新启动,或使用 CTRL+C
退出。
在下一章中,我们将添加一些更有趣的输出。
Hello World
在上一章中,您已经将第一段代码烧录到 SoC 上并运行成功 - 虽然这已经很令人兴奋,但我们可以做得更好。
传统上,在微控制器上运行的第一件事情是闪烁灯(blinky)。
但是,在这里我们将从 Hello World 开始。
添加依赖项
你可以通过以下任意一种方法添加依赖项:
- 编辑
Cargo.toml
文件。在[dependencies]
部分添加以下行:
esp-println = { version = "0.3.1", features = ["esp32c3"] }
- 使用
cargo add
命令:
cargo add esp-println --features "esp32c3"
esp-println
是一个附加的 crate,调用 ROM 函数打印文本,该文本由 espflash
(或任何其他串行监视器)显示。
我们需要传递 esp32c3
特性,因为该 crate 针对多个 SoC,需要知道它要在哪个 SoC 上运行。
请注意,可能在您阅读本文时会有新版本,请检查 crates.io。
打印一些内容
在 main.rs
的 loop {}
之前,加入以下这行代码:
esp_println::println!("Hello World");
查看结果
再次运行
cargo run
你应该能看到打印出了 Hello World 这段文字!
Panic!
当 Rust 中出现严重错误时,可能会发生panic。
让我们看看它对我们来说是什么样子。
在 main.rs
中将此行放在某处,例如在我们的 println
之前:
panic!("This is a panic");
再次运行代码。
您应该会看到类似于这样的内容:
!! A panic occured in 'src\main.rs', at line 25, column 5
PanicInfo {
payload: Any { .. },
message: Some(
This is a panic,
),
location: Location {
file: "src\\main.rs",
line: 25,
col: 5,
},
can_unwind: true,
}
Backtrace:
0x420019aa
0x420019aa - main
at C:\tmp\getting-started\src\main.rs:25
0x4200014c
0x4200014c - _start_rust
at ...\.cargo\registry\src\github.com-1ecc6299db9ec823\riscv-rt-0.9.0\src\lib.rs:389
我们看到了 panic 发生的位置,甚至看到了回溯信息!
虽然在这个例子中事情是显而易见的,但这在更复杂的代码中会派上用场。
现在尝试使用 release 模式编译并运行代码:
cargo run --release
现在情况不太好看:
!! A panic occured in 'src\main.rs', at line 25, column 5
PanicInfo {
payload: Any { .. },
message: Some(
This is a panic,
),
location: Location {
file: "src\\main.rs",
line: 25,
col: 5,
},
can_unwind: true,
}
Backtrace:
0x42000140
0x42000140 - _start_rust
at ??:??
我们仍然可以看到 panic 发生的位置,但是回溯信息不太有用了。
这是因为编译器省略了调试信息并优化了代码。
但是你可能已经注意到了烧录二进制文件大小的差异。
它从 199056 字节减少到了 86896 字节!
请注意,对于我们所得到的东西来说,这仍然很大。有许多选项可以使二进制文件更小,这超出了本书的范围。
在继续之前,请删除引起明确 panic 的行。
Blinky
让我们看看如何创建标志性的 Blinky。
将 main.rs
中的代码改成这样
#![no_std]
#![no_main]
use esp32c3_hal::{
clock::ClockControl, pac::Peripherals, prelude::*, timer::TimerGroup, Delay, Rtc, IO,
};
use esp_backtrace as _;
#[riscv_rt::entry]
fn main() -> ! {
let peripherals = Peripherals::take().unwrap();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
// Disable the RTC and TIMG watchdog timers
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
let mut wdt1 = timer_group1.wdt;
rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();
esp_println::println!("Hello World");
// Set GPIO7 as an output, and set its state high initially.
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
let mut led = io.pins.gpio7.into_push_pull_output();
led.set_high().unwrap();
// Initialize the Delay peripheral, and use it to toggle the LED state in a
// loop.
let mut delay = Delay::new(&clocks);
loop {
led.toggle().unwrap();
delay.delay_ms(500u32);
}
}
在 ESP32-C3-DevKit-RUST-1 上,有一个常规的 LED 连接到 GPIO 7。如果你使用其他开发板,请参考其数据手册。
请注意,Espressif 大多数开发板现在使用一种工作方式不同的可寻址 LED,它超出了本书的范围。在这种情况下,您也可以将常规 LED 连接到某些空闲引脚(不要忘记加电阻)。
在这里,我们可以将引脚设置为高电平、低电平或切换它。
我们还看到 HAL 提供了一种延迟执行的方法。
检测按钮按下
大多数开发板都有一个按钮,在我们的例子中,我们将使用 BOOT
on GPIO9
的按钮。让我们看看如何检查按钮的状态。
#![no_std]
#![no_main]
use esp32c3_hal::{
clock::ClockControl, pac::Peripherals, prelude::*, timer::TimerGroup, Rtc, IO,
};
use esp_backtrace as _;
#[riscv_rt::entry]
fn main() -> ! {
let peripherals = Peripherals::take().unwrap();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
// Disable the RTC and TIMG watchdog timers
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
let mut wdt1 = timer_group1.wdt;
rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();
// Set GPIO7 as an output, GPIO9 as input
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
let mut led = io.pins.gpio7.into_push_pull_output();
let button = io.pins.gpio9.into_pull_up_input();
loop {
if button.is_high().unwrap() {
led.set_high().unwrap();
} else {
led.set_low().unwrap();
}
}
}
现在,如果未按下按钮,则 LED 会亮起。如果按下按钮,则 LED 熄灭。
类似于将 GPIO
转换为 output
,我们可以将其转换为 input
。然后,我们可以使用 is_high
等函数获取 input
引脚的当前状态。
使用中断检测按钮按下
中断提供了处理器处理异步事件和致命错误的机制。
让我们添加 critical-section
crate (请参阅如何添加依赖项的说明),并将 main.rs
更改为以下内容:
#![no_std]
#![no_main]
use core::cell::RefCell;
use critical_section::Mutex;
use esp32c3_hal::{
clock::ClockControl,
gpio::Gpio9,
gpio_types::{Event, Input, Pin, PullUp},
interrupt,
pac::{self, Peripherals},
prelude::*,
timer::TimerGroup,
Rtc, IO,
};
use esp_backtrace as _;
static BUTTON: Mutex<RefCell<Option<Gpio9<Input<PullUp>>>>> = Mutex::new(RefCell::new(None));
#[riscv_rt::entry]
fn main() -> ! {
let peripherals = Peripherals::take().unwrap();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
// Disable the RTC and TIMG watchdog timers
let mut rtc = Rtc::new(peripherals.RTC_CNTL);
let timer_group0 = TimerGroup::new(peripherals.TIMG0, &clocks);
let mut wdt0 = timer_group0.wdt;
let timer_group1 = TimerGroup::new(peripherals.TIMG1, &clocks);
let mut wdt1 = timer_group1.wdt;
rtc.swd.disable();
rtc.rwdt.disable();
wdt0.disable();
wdt1.disable();
// Set GPIO9 as input
let io = IO::new(peripherals.GPIO, peripherals.IO_MUX);
let mut button = io.pins.gpio9.into_pull_up_input();
button.listen(Event::FallingEdge); // raise interrupt on falling edge
critical_section::with(|cs| BUTTON.borrow_ref_mut(cs).replace(button));
interrupt::enable(pac::Interrupt::GPIO, interrupt::Priority::Priority3).unwrap();
loop {}
}
#[interrupt]
fn GPIO() {
critical_section::with(|cs| {
esp_println::println!("GPIO interrupt");
BUTTON
.borrow_ref_mut(cs)
.as_mut()
.unwrap()
.clear_interrupt();
});
}
这里有很多新东西。
首先是 static BUTTON
。我们需要它,因为在中断处理程序中我们必须清除 button 上挂起的中断,并且我们需要以某种方式将 button 从 main 传递到中断处理程序。
由于中断处理程序不能有参数,我们需要一个静态变量来将 button 放入中断处理程序。
我们需要 Mutex
来安全地访问 button 。
请注意,这不是您可能从
libstd
中了解到的 Mutex,而是来自critical-section
的 Mutex(这就是我们需要将其添加为依赖项的原因)。
然后我们需要在输出引脚上调用 listen
以配置外设以引发中断。我们可以为不同的事件引发中断——这里我们想在 FallingEdge 引发中断。
在下一行中,我们将 button 移入到 static BUTTON
中,以便中断处理程序获取它。
我们需要做的最后一件事实际上是启用中断。
这里的第一个参数是我们想要的中断类型。有几种可能的中断。
第二个参数是中断的优先级。
中断处理程序是通过 #[interrupt]
宏定义的。这里函数的名称必须与中断匹配。
编写 std 应用程序
如果您想学习如何开发 std
应用程序,可以与 Ferrous Systems 一起开发培训:
培训基于 ESP32-C3-DevKit-RUST-1。您可以使用任何其他 ESP32、ESP32-C3、ESP32-S2 或 ESP32-S3 开发板,但可能需要更改代码和配置。
培训分为两部分:
请注意,在
esp-idf-hal
的示例文件夹下有几个示例涵盖了特定外设的使用。例如esp32-idf-hal/examples
。
了解 esp-idf-template
现在我们已经知道了如何生成 std 项目,让我们检查生成的项目包含的内容并尝试了解它的每个部分。
检查生成的项目
使用以下选项从 esp-idf-template 创建项目:
- MCU:
esp32c3
- ESP-IDF version:
v4.4
- STD support:
true
- Devcontainer support:
false
将生成如下文件结构:
├── build.rs
├── .cargo
│ └── config.toml
├── Cargo.toml
├── .gitignore
├── rust-toolchain.toml
├── sdkconfig.defaults
└── src
└── main.rs
在继续之前,让我们看看这些文件的作用。
- .gitignore
- 告诉
git
忽略哪些文件和文件夹
- 告诉
- Cargo.toml
- Cargo 元数据和项目依赖声明清单
LICENSE-APACHE
,LICENSE-MIT
- 这些是 Rust 生态系统中使用最广泛的许可证
- 如果您想应用不同的许可证,可以删除这些文件并在
Cargo.toml
中更改许可证。
- rust-toolchain.toml
- 定义要使用的 Rust 工具链
- 根据您的目标,这将使用
nightly
或esp
- .cargo/config.toml
- Cargo 配置
- 包含我们的目标
- 包含
runner = "espflash --monitor"
- 这意味着您可以使用cargo run
来烧录和监视您的代码 - 包含要使用的链接器,在我们的例子中是
ldproxy
- 包含启用的 unstable
build-std
cargo feature。 - 包含
ESP-IDF-VERSION
环境变量,该变量告诉esp-idf-sys
将使用哪个 ESP-IDF 版本。
- src/main.rs
- 新创建项目的主源文件
- 我们将在下一节中分析它的内容
- build.rs
- 传播
ldproxy
的链接器参数。
- 传播
- sdkconfig.defaults
- 包含来自 ESP-IDF 默认值的覆盖值。
main.rs
use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module imported
fn main() {
esp_idf_sys::link_patches();
println!("Hello, world!");
}
第一行是一个导入,它定义了 esp-idf 入口点,当根 crate 是一个定义了 main 函数的二进制 crate 时。
然后,我们有一个通常的 main 函数,上面有两行:
- 调用
esp_idf_sys::link_patches
函数,确保在 Rust 中实现的 ESP-IDF 的一些补丁链接到最终的可执行文件。 - 我们在控制台中打印著名的“Hello World!”。
运行代码
构建和运行代码非常简单:
cargo run
这将根据配置构建代码并执行 espflash
将代码刷入开发板。
由于我们的 runner
配置 还将 --monitor
参数传递给 espflash
,因此我们可以看到代码输出的内容。
确保你已经安装了
espflash
,否则这一步会失败。安装espflash
:cargo install espflash
你应该会看到类似以下的输出:
Connecting...
Chip type: ESP32-C3 (revision 3)
Crystal frequency: 40MHz
Flash size: 4MB
Features: WiFi
MAC address: 60:55:f9:c0:39:7c
App/part. size: 409728/4128768 bytes, 9.92%
[00:00:00] ######################################## 12/12 segment 0x0
[00:00:00] ######################################## 1/1 segment 0x8000
[00:00:04] ######################################## 210/210 segment 0x10000
Flashing has completed!
Commands:
CTRL+R Reset chip
CTRL+C Exit
ESP-ROM:esp32c3-api1-20210207
Build:Feb 7 2021
rst:0x15 (USB_UART_CHIP_RESET),boot:0xc (SPI_FAST_FLASH_BOOT)
Saved PC:0x4004c97e
0x4004c97e - chip726_phyrom_version_num
at ??:??
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fcd6100,len:0x172c
load:0x403ce000,len:0x928
0x403ce000 - _iram_text_end
at ??:??
load:0x403d0000,len:0x2ce0
0x403d0000 - _iram_text_end
at ??:??
entry 0x403ce000
0x403ce000 - _iram_text_end
at ??:??
I (24) boot: ESP-IDF v4.4-dev-2825-gb63ec47238 2nd stage bootloader
I (24) boot: compile time 12:10:40
I (24) boot: chip revision: 3
I (28) boot_comm: chip revision: 3, min. bootloader chip revision: 0
I (35) boot.esp32c3: SPI Speed : 80MHz
I (39) boot.esp32c3: SPI Mode : DIO
I (44) boot.esp32c3: SPI Flash Size : 4MB
I (49) boot: Enabling RNG early entropy source...
I (54) boot: Partition Table:
I (58) boot: ## Label Usage Type ST Offset Length
I (65) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (73) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (80) boot: 2 factory factory app 00 00 00010000 003f0000
I (88) boot: End of partition table
I (92) boot_comm: chip revision: 3, min. application chip revision: 0
I (99) esp_image: segment 0: paddr=00010020 vaddr=3c050020 size=17640h ( 95808) map
I (122) esp_image: segment 1: paddr=00027668 vaddr=3fc89c00 size=0146ch ( 5228) load
I (123) esp_image: segment 2: paddr=00028adc vaddr=40380000 size=0753ch ( 30012) load
I (133) esp_image: segment 3: paddr=00030020 vaddr=42000020 size=419d8h (268760) map
I (176) esp_image: segment 4: paddr=00071a00 vaddr=4038753c size=02644h ( 9796) load
I (178) esp_image: segment 5: paddr=0007404c vaddr=50000010 size=00010h ( 16) load
I (185) boot: Loaded app from partition at offset 0x10000
I (188) boot: Disabling RNG early entropy source...
I (205) cpu_start: Pro cpu up.
I (213) cpu_start: Pro cpu start user code
I (213) cpu_start: cpu freq: 160000000
I (213) cpu_start: Application information:
I (216) cpu_start: Project name: libespidf
I (221) cpu_start: App version: 1
I (226) cpu_start: Compile time: Nov 3 2022 13:16:23
I (232) cpu_start: ELF file SHA256: 0000000000000000...
I (238) cpu_start: ESP-IDF: 755ce10-dirty
I (243) heap_init: Initializing. RAM available for dynamic allocation:
I (250) heap_init: At 3FC8BF90 len 00050780 (321 KiB): DRAM
I (257) heap_init: At 3FCDC710 len 00002950 (10 KiB): STACK/DRAM
I (263) heap_init: At 50000020 len 00001FE0 (7 KiB): RTCRAM
I (270) spi_flash: detected chip: generic
I (274) spi_flash: flash io: dio
I (279) sleep: Configure to isolate all GPIO pins in sleep state
I (285) sleep: Enable automatic switching of GPIO sleep configuration
I (292) cpu_start: Starting scheduler.
Hello, world!
如您所见,有来自第一阶段和第二阶段引导加载程序的消息,然后是我们的打印的“Hello, world!”。
您可以使用 CTRL+R
重新启动,或使用 CTRL+C
退出。
故障排查
在这里,我们将列出在构建项目时可能出现的常见错误及其原因和解决方案。
LIBCLANG_PATH 环境变量未设置
thread 'main' panicked at 'Unable to find libclang: "couldn't find any valid shared libraries matching: ['libclang.so', 'libclang-*.so', 'libclang.so.*', 'libclang-*.so.*'], set the `LIBCLANG_PATH` environment variable to a path where one of these files can be found (invalid: [])"', /home/esp/.cargo/registry/src/github.com-1ecc6299db9ec823/bindgen-0.60.1/src/lib.rs:2172:31
我们需要 libclang
来为 bindgen
生成 ESP-IDF C 标头的 Rust 绑定。确保环境变量 LIBCLANG_PATH
已设置并指向我们的 LLVM 自定义分支:
- Unix:
export $HOME/.espressif/tools/xtensa-esp32-elf-clang/<clang_version>-<host_triple>/esp-clang/lib
- Windows:
$Env:LIBCLANG_PATH="%USERPROFILE%/.espressif/tools/xtensa-esp32-elf-clang/<clang_version>-<host_triple>/esp-clang/bin/libclang.dll" $Env:PATH+=";%USERPROFILE%/.espressif/tools/xtensa-esp32-elf-clang/<clang_version>-<host_triple>/esp-clang/bin/"
缺少 ldproxy
error: linker `ldproxy` not found
|
= note: No such file or directory (os error 2)
如果您尝试构建 std
应用程序,则必须安装 [ldproxy。
]
cargo install ldproxy
使用错误的 Rust 工具链
$ cargo build
error: failed to run `rustc` to learn about target-specific information
Caused by:
process didn't exit successfully: `rustc - --crate-name ___ --print=file-names --target xtensa-esp32-espidf --crate-type bin --crate-type rlib --crate-type dylib --crate-type cdylib --crate-type staticlib --crate-type proc-macro --print=sysroot --print=cfg` (exit status: 1)
--- stderr
error: Error loading target specification: Could not find specification for target "xtensa-esp32-espidf". Run `rustc --print target-list` for a list of built-in targets
如果您遇到之前的错误或类似错误,您可能没有使用正确的 Rust 工具链,请记住,对于 Xtensa 目标,您需要使用 Espressif Rust 分支工具链,有几种方法可以做到:
- 命令行上使用的工具链覆盖速记:
cargo +esp
。 - 将
RUSTUP_TOOLCHAIN
环境变量设置为esp
. - 设置目录覆盖:
rustup override set esp
- 将 rust-toolchain.toml 文件添加到您的项目中:
[toolchain] channel = "esp"
- 将
esp
设置为默认工具链。
有关工具链覆盖的更多信息,请参阅 The rustup book 的 Overrides 章节。
Windows
长路径名
使用 Windows 时,如果使用长路径名,您可能会在构建新项目时遇到问题。请按照以下步骤替换项目的路径:
subst r: <pathToYourProject>
cd r:\
缺少 ABI
Compiling cc v1.0.69
error: linker `link.exe` not found
|
= note: The system cannot find the file specified. (os error 2)
note: the msvc targets depend on the msvc linker but `link.exe` was not found
note: please ensure that VS 2013, VS 2015, VS 2017 or VS 2019 was installed with the Visual C++ option
error: could not compile `compiler_builtins` due to previous error
warning: build failed, waiting for other jobs to finish...
error: build failed
此错误的原因是我们缺少 MSVC C++,因此我们不满足编译时要求,请安装 Visual Studio 2013(或更高版本)或 Visual C++ Build Tools 2019。对于 Visual Studio,请务必检查“C++ 工具”和“Windows 10 SDK”选项。如果使用 GNU ABI,请安装 MinGW/MSYS2 工具链。
缺少 libtinfo.so.5
thread 'main' panicked at 'Unable to find libclang: "the `libclang` shared library at /home/user/.espressif/tools/xtensa-esp32-elf-clang/esp-15.0.0-20221014-x86_64-unknown-linux-gnu/esp-clang/lib/libclang.so.15.0.0 could not be o
pened: libtinfo.so.5: cannot open shared object file: No such file or directory"', /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/bindgen-0.60.1/src/lib.rs:2172:31
一些 LLVM 15 版本,esp-15.0.0-20220922
和 esp-15.0.0-20221014
,需要 libtinfo.so.5
。此依赖项已在 esp-15.0.0-20221201
LLVM 版本中删除。如果您使用的是任何需要它的版本,请确保安装了 libtinf5
:
- Ubuntu/Debian:
sudo apt-get install libtinfo5
- Fedora:
sudo dnf install ncurses-compat-libs
- openSUSE:
sudo dnf install libncurses5
- Arch Linux:
sudo pacman -S ncurses5-compat-libs
FAQ
我更新了我的 sdkconfig.defaults
文件,但它似乎没有任何效果
您必须清理您的项目并重新构建,以使 sdkconfig.defaults
中的更改生效:
cargo clean
cargo build
此页面上提到的 crates 的文档已过时或丢失
由于 docs.rs 施加的资源限制,在构建文档时互联网访问被阻止,因此我们无法为 esp-idf-sys
或依赖它的任何 crate 构建文档。
相反,我们正在构建文档并将其托管在 GitHub Pages 上:
***ERROR*** A stack overflow in task main has been detected.
如果第二阶段引导加载程序报告此错误,您可能需要增加主任务的堆栈大小。这可以通过将以下内容添加到 sdkconfig.defaults
文件来完成:
CONFIG_ESP_MAIN_TASK_STACK_SIZE=7000
在此示例中,我们为主任务的堆栈分配 7kB。
我怎样才能完全禁用看门狗定时器?
添加到您的 sdkconfig.defaults
文件:
CONFIG_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
回想一下,在修改这些配置文件时,您必须在重建之前清理您的项目