开发 Rust no_std 应用

Posted on Mon, Sep 2, 2024 Rust 单片机

no_std 应用

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 和 esp-hal 是 no_std 应用开发的基础。

对比:

  1. std 的 esp-idf-hal 实现了 embeded-hal 和 async trait,底层基于 C/C++ esp-idf;
  2. no_std 的 esp-hal 实现了 embeded-hal 和 async trait,底层基于 esp-pacs;

其它开源的 no_std 库(embedded-* 是 Rust 嵌入式工作组或社区提供的 no_std 应用项目):

  1. embedded-graphics: Embedded-graphics is a 2D graphics library that is focused on memory constrained embedded devices.
  2. embedded-layout: Simple layout/alignment functions
  3. embedded-text: TextBox with text alignment options

embassy 是支持 async 的 no_std 库。

使用 esp-rs/esp-template 模板来快速创建 no_std 类型项目:

创建一个 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

参考:

  1. 官方文档:Embedded Rust (no_std) on Espressif
  2. 官方 non_std 示例:https://github.com/esp-rs/no_std-training
  3. https://apollolabsblog.hashnode.dev/series/esp32c3-embedded-rust-hal 强烈推荐。
  4. https://github.com/apollolabsdev/ESP32C3
  5. Bare-Metal Rust on ESP32: A Brief Overview
  6. 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 :

  1. 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
  2. esp-println has a defmt-espflash feature, which adds framming bytes so espflash knows that is a defmt message.
  3. 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

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:

  1. https://embassy.dev/book/dev/project_structure.html
  2. https://embassy.dev/book/dev/basic_application.html