no_std 应用是 bare-metal 实现,不依赖 esp-idf 及其提供的 FreeRTOS 操作系统和 Rust std 标注库,而是使用 std 的一个子集 core 库,core 库不支持 heap 内存分配和线程。当前支持 HAL/WIFI/BLE/ESP-NOW/Backtrace/Storage 等。
no_std 不依赖于
C/C++ 开发的 esp-idf 及其提供的 FreeRTOS 操作系统环境,而是基于 esp-pacs/esp-hal
开发的 xtensa-esp32s3-none-elf 应用。
esp-alloc crate 为 no_std 提供了 heap 内存分配的支持;例子:esp-examples/alloc
no_std 相关的库:
-
esp-pacs
: Peripheral access crates,是根据处理的 SVD 描述文件生成的 Peripheral Access Crates (PACs) ,它是低级的处理器寄存器定义 unsafe 封装。理论上可以直接基于 esp-pacs 开发 no_std 应用,但是因为层次太低级,开发效率不高,所以一般使用 esp-hal 等高层封装来开发。 -
esp-hal
: Hardware abstraction layer. async traits from the various packages in theembedded-hal
repository.- 相比 esp-pacs crate,esp-hal 是高层次的封装,一般实现了 embedded-hal 和 async trait,底层基于 esp-pacs;
- esp-hal 为 embassy 提供了实现,可以通过 embassy async task 来实现
并发任务
。 - 2024.03.08 发布的 0.16.0 版本开始,将以前芯片相关的 esp32-hal, esp32c3-hal 等 crate 合并为一个单独的 esp-hal crate。使用时在 features 中指定 CPU 类型,如:
esp-hal = { version = "0.17.0", features = [ "esp32s3" ] }
说明:esp-pacs 和 esp-hal 是 no_std 应用开发的基础。
-
esp-wifi
: Wi-Fi, BLE and ESP-NOW support -
esp-alloc
:Simple heap allocator
. This allocator is built on top of the phil-opp/linked-list-allocator crate, which does most of the heavy lifting. While it’s often advisable to avoid allocations in such limited environments, there are scenarios when an allocator is still required and/or desirable. -
esp-println
:print!, println!
. esp-println allows for printing over UART, USB Serial JTAG, or RTT without any required dependencies. 实现 log crate 的 trait,用于打印日志到串口; -
esp-backtrace
: Exception and panic handlers. As the name implies, esp-backtraceenables backtraces
in no_std applications. It additionally providesa panic handler and exception handler
, both behind features. This crate makes debugging issues much easier.- 为了正确显示 panic 的行号和函数符号,需要确保在 release profile 中配置
debug = true
参数(dev profile 缺省是该值)。
- 为了正确显示 panic 的行号和函数符号,需要确保在 release profile 中配置
-
esp-storage
: Embedded-storage traits to access unencrypted flash memory -
esp-ieee802154
: Low-level IEEE802.15.4 driver for the ESP32-C6 and ESP32-H2 -
esp-openthread
: A bare-metal Thread implementation using esp-ieee802154- 这里的 thread 不是线程,而是一个为低功耗物联网(IEEE 802.15.4-2006 WPAN)设备设计的基于 IPv6 的网络协议。参考:https://openthread.io/guides/thread-primer?hl=zh-cn
对比:
- std 的 esp-idf-hal 实现了 embeded-hal 和 async trait,底层基于 C/C++ esp-idf;
- no_std 的 esp-hal 实现了 embeded-hal 和 async trait,底层基于 esp-pacs;
其它开源的 no_std 库(embedded-* 是 Rust 嵌入式工作组或社区提供的 no_std 应用项目):
- embedded-graphics: Embedded-graphics is a 2D graphics library that is focused on memory constrained embedded devices.
- embedded-layout: Simple layout/alignment functions
- embedded-text: TextBox with text alignment options
embassy 是支持 async 的 no_std 库。
- embassy 为嵌入式 Rust 提供了一个 async task 的 embassy-executor 实现,有两种实现方式:
- 多线程模式:在 ESP32-S3 芯片上可以实现并发执行异步任务的多线程实现。
- 中断模式:如单核芯片上实现并发;
- thread aware executor on multicore systems;
- 并发异步任务示例:https://github.com/esp-rs/esp-hal/blob/main/examples/src/bin/embassy_multicore.rs
- embassy 开发举例:https://blog.theembeddedrustacean.com/series/rust-embassy
- https://github.com/apollolabsdev/ESP32C3/tree/41efd9d1bbdf8d2e071332af610bae0515407d07/embassy_examples
使用 esp-rs/esp-template 模板来快速创建 no_std 类型项目:
- 为了在 panic 时打印代码行和符号,需要在 release profile 中添加配置
debug = 2 # 2/full/true
:full debug info, 虽然二进制包含 debuginfo,但是烧写时会被去掉,并不会增加 flash app 体积。
创建一个 no_std Bare-Metal 项目, 自定义是否使用 WiFi/Bluetooth/ESP-NOW via the esp-wifi crate;
zj@a:~/code/esp32$ cargo generate esp-rs/esp-template
# no_std 应用需要声明 no_std 和 no_main 宏,这样编译器才不会导入 std 库。
# #![no_std] 告诉编译器不导入和链接 libstd 库。
# #![no_main] 告诉编译器不使用标准的 main 接口,而是使用 esp 提供的 main 入口。
zj@a:~/code/esp32/non_std$ cat myesp-nonstd/src/main.rs
#![no_std]
#![no_main]
# in a bare-metal environment, we need a panic handler that runs if a panic occurs in code There are
# a few different crates you can use (e.g panic-halt) but esp-backtrace provides an implementation
# that prints _the address of a backtrace_ - together with espflash these addresses can get decoded
# into source code locations
use esp_backtrace as _;
use esp_hal::{clock::ClockControl, peripherals::Peripherals, prelude::*, delay::Delay};
#[entry]
fn main() -> ! {
let peripherals = Peripherals::take();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::max(system.clock_control).freeze();
let delay = Delay::new(&clocks);
esp_println::logger::init_logger_from_env();
loop {
log::info!("Hello world!");
delay.delay(500.millis());
}
}
按需配置 Cargo.toml:
zj@a:~/code/esp32/non_std$ cat myesp-nonstd/Cargo.toml
[package]
name = "myesp-nonstd"
version = "0.1.0"
authors = ["alizj"]
edition = "2021"
license = "MIT OR Apache-2.0"
[dependencies]
# esp-has 是 no_std 类型 crate, features 中指定了 CPU 类型
esp-hal = { version = "0.17.0", features = [ "esp32s3" ] }
# bare-metal 环境下,当程序 panic 时打印调用栈
esp-backtrace = { version = "0.11.0", features = [
"esp32s3",
"exception-handler",
"panic-handler",
"println",
] }
# esp-println 启用 log feature 后,为 log 提供具体的实现
esp-println = { version = "0.9.0", features = ["esp32s3", "log"] }
# 向终端打印日志。esp_println 提供了 log 的具体实现
log = { version = "0.4.20" }
[profile.dev]
# Rust debug is too slow. For debug builds always builds with some optimization
opt-level = "s" # optimize for binary size
# dev profile 的 debug 参数默认为 2,表示 full debug info,
# release profile 的 debug 参数默认为 0,表示关闭 debug info;
[profile.release]
codegen-units = 1 # LLVM can perform better optimizations using a single thread
debug = 2 # 2/full/true:full debug info, 虽然二进制包含 debuginfo,但是烧写时会被去掉,所以不会增加 flash app 体积
debug-assertions = false
incremental = false
lto = 'fat'
opt-level = 's'
overflow-checks = false
按需配置 rust-toolchain.toml :
zj@a:~/code/esp32/myesp-nonstd$ cat rust-toolchain.toml
[toolchain]
channel = "esp" # 使用 esp channel 工具链
按需配置 .cargo/config.toml :
zj@a:~/code/esp32/myesp-nonstd$ cat .cargo/config.toml
[target.xtensa-esp32s3-none-elf]
runner = "espflash flash --monitor" # cargo run 会烧录 flash 和读终端日志
[env]
ESP_LOGLEVEL="INFO"
[build]
rustflags = [
"-C", "link-arg=-nostartfiles",
]
target = "xtensa-esp32s3-none-elf" # 使用不链接 esp-idf 的 none-elf 工具链
[unstable]
build-std = ["core"] # 使用 core 库而非 std 库!
构建和烧录:
zj@a:~/code/esp32$ cd myesp-nonstd/
zj@a:~/code/esp32/myesp-nonstd$ source ~/esp/export-esp.sh
# 构建,只使用 --release profile
zj@a:~/code/esp32/myesp-nonstd$ cargo build --release
# cargo 会运行 espflash 来烧写 binary
zj@a:~/code/esp32/myesp-nonstd$ cargo run
no_std 构建结果 只有二进制
myesp-nonstd, 不包含构建 std 应用时生成的 bootloader.bin 和
partition-table.bin:
zj@a:~/code/esp32/non_std$ ls -l target/xtensa-esp32s3-none-elf/debug/
total 2.0M
drwxr-xr-x 27 alizj 864 5 9 12:20 build/
drwxr-xr-x 338 alizj 11K 5 10 15:27 deps/
drwxr-xr-x 2 alizj 64 5 8 15:06 examples/
drwxr-xr-x 5 alizj 160 5 9 12:21 incremental/
-rwxr-xr-x 1 alizj 2.0M 5 10 15:27 myesp-nonstd*
-rw-r--r-- 1 alizj 194 5 8 15:06 myesp-nonstd.d
参考:
- 官方文档:Embedded Rust (no_std) on Espressif
- 官方 non_std 示例:https://github.com/esp-rs/no_std-training
- https://apollolabsblog.hashnode.dev/series/esp32c3-embedded-rust-hal 强烈推荐。
- https://github.com/apollolabsdev/ESP32C3
- Bare-Metal Rust on ESP32: A Brief Overview
- https://apollolabsblog.hashnode.dev/the-embedded-rust-esp-development-ecosystem
1 在 Rust no_std 应用中使用 defmt 日志框架 #
defmt 是一种 no_std 应用的 logging framework,它将 ESP32 芯片中应用打印的日志延迟到 host server 上格式化,从而降低 ESP32 芯片应用的内存开销。
ESP32 no_std book 的 defmt 例子:https://docs.esp-rs.org/no_std-training/03_7_defmt.html
对于 ESP32 no_std 应用来说, esp-println, esp-backtrace and espflash/cargo-espflash provide mechanisms to use defmt
:
- espflash has support for different logging formats, one of them being defmt.
- espflash requires framming bytes as when using defmt it also needs to print non-defmt
messages, like the bootloader prints. It’s important to note that other defmt-enabled tools
like probe-rs won’t be able to parse these messages due to the extra framing bytes. Uses
rzcobs encoding
- espflash requires framming bytes as when using defmt it also needs to print non-defmt
messages, like the bootloader prints. It’s important to note that other defmt-enabled tools
like probe-rs won’t be able to parse these messages due to the extra framing bytes. Uses
- esp-println has a defmt-espflash feature, which adds framming bytes so espflash knows that is a defmt message.
- esp-backtrace has a defmt feature that
uses defmt logging
to print panic and exception handler messages.
在代码里使用 defmt::println!() 等宏来打印日志。
If you want to use any of the logging macros like info, debug
- Enable the log feature of esp-println
- When building the app, set DEFMT_LOG level.
defmt-rtt:Transmit defmt log messages over the RTT (Real-Time Transfer) protocol https://github.com/knurling-rs/defmt/tree/main/firmware/defmt-rtt
embassy 依赖于 defmt 和 defmt-rtt: