embassy book

常见问题解答

以下是一些未排序的常见问题和解答列表。

请随时 在此页面 添加项目,特别是当聊天中的某人回答了您的问题时!

如何在没有调试探针的情况下部署到 RP2040。

安装 link:https://github.com/JoNil/elf2uf2-rs[elf2uf2-rs] 以将生成的 elf 二进制文件转换为 uf2 文件。

配置 runner 以使用此工具,将其添加到 .cargo/config.toml 中:

[target.'cfg(all(target_arch = "arm", target_os = "none"))']
runner = "elf2uf2-rs --deploy --serial --verbose"

命令行参数 --deploy 将检测您的设备并上传二进制文件,--serial 启动串行连接。 有关更多信息,请参阅文档。

缺少 main 宏

如果您看到如下错误:

#[embassy_executor::main]
|                   ^^^^ could not find `main` in `embassy_executor`

您可能缺少 embassy-executor crate 的某些功能。

对于 Cortex-M 目标,请检查您的 Cargo.toml 中是否为 embassy-executor crate 启用了以下所有功能:

  • arch-cortex-m
  • executor-thread

对于 ESP32,请考虑使用您的适当 link:https://crates.io/crates/esp-hal-common[HAL crate] 提供的执行器和 #[main] 宏。

为什么我的二进制文件这么大?

管理二进制文件大小的第一步是设置您的 link:https://doc.rust-lang.org/cargo/reference/profiles.html[profiles]。

[profile.release]
lto = true
opt-level = "s"
incremental = false
codegen-units = 1
# 注意:debug = true 是可以的 - debuginfo 不会刷入设备!
debug = true

所有这些标志都在上面链接的 Rust Book 页面中进行了详细说明。

我的二进制文件仍然很大... 充满了 std::fmt 的东西!

这意味着您的代码足够复杂,以至于 panic! 调用的格式化要求无法优化掉,即使您使用了 panic-haltpanic-reset

您可以通过将以下内容添加到您的 .cargo/config.toml 中来解决此问题:

[unstable]
build-std = ["core"]
build-std-features = ["panic_immediate_abort"]

这会将所有 panic 替换为 UDF(未定义)指令。

根据您的芯片组,这将表现出不同的行为。

请参阅您的芯片组的规范,但对于 thumbv6m,它会导致硬 fault。 可以像这样配置:

#[exception]
unsafe fn HardFault(_frame: &ExceptionFrame) -> ! {
    SCB::sys_reset() // <- 您可以执行复位以外的其他操作
}

有关更多信息,请参阅 cortex-m 的 link:https://docs.rs/cortex-m-rt/latest/cortex_m_rt/attr.exception.html[异常处理]。

embassy-time 抛出链接器错误

如果您看到如下链接器错误:

= note: rust-lld: error: undefined symbol: _embassy_time_now
          >>> referenced by driver.rs:127 (src/driver.rs:127)
          >>>               embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::now::hefb1f99d6e069842) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib

          rust-lld: error: undefined symbol: _embassy_time_schedule_wake
          >>> referenced by driver.rs:144 (src/driver.rs:144)
          >>>               embassy_time-846f66f1620ad42c.embassy_time.4f6a638abb75dd4c-cgu.0.rcgu.o:(embassy_time::driver::schedule_wake::h530a5b1f444a6d5b) in archive Devel/Embedded/pogodyna/target/thumbv7em-none-eabihf/debug/deps/libembassy_time-846f66f1620ad42c.rlib

您可能需要为您的 HAL 启用时间驱动程序(不在 embassy-time 中!)。 例如,对于 embassy-stm32,您可能需要启用 time-driver-any

[dependencies.embassy-stm32]
version = "0.1.0"
features = [
    # ...
    "time-driver-any", # 添加此行!
    # ...
]

如果您处于早期项目设置阶段,并且未使用 HAL 中的任何内容,请确保显式使用 HAL 以防止链接器将其作为死代码删除,方法是将此行添加到您的源代码中:

use embassy_stm32 as _;

您可能遇到的另一个常见错误是:

= note: rust-lld: error: undefined symbol: __pender
          >>> referenced by mod.rs:373 (src/raw/mod.rs:373)
          >>>               embassy_executor-e78174e249bca7f4.embassy_executor.1e9d60fc90940543-cgu.0.rcgu.o:(embassy_executor::raw::Pender::pend::h0f19b6e01762e4cd) in archive [...]libembassy_executor-e78174e249bca7f4.rlib

此错误可能有两种可能的原因:

  • 您正在使用 embassy-executor,但没有启用架构特定的功能之一,但您正在使用不带自己的执行器的 HAL。 例如,对于 Cortex-M(如 RP2040),您需要启用 embassy-executorarch-cortex-m 功能。
  • 您没有使用 embassy-executor。 在这种情况下,您需要启用 embassy-timegeneric-queue-X 功能之一。

错误:Only one package in the dependency graph may specify the same links value.

您的依赖树中有同一 crate 的多个版本。 这意味着您的一些 embassy crate 来自 crates.io,而另一些来自 git,它们各自引入了不同的依赖项集。

要解决此问题,请确保仅对所有 embassy crate 使用单个来源! 为此,您应该使用 [patch.crates.io] 和可能的 [patch.'https://github.com/embassy-rs/embassy.git'] 来修补您的依赖项以使用 git 来源。

例子:

[patch.crates-io]
embassy-time-queue-utils = { git = "https://github.com/embassy-rs/embassy.git", rev = "7f8af8a" }
embassy-time-driver = { git = "https://github.com/embassy-rs/embassy.git", rev = "7f8af8a" }
# embassy-time = { git = "https://github.com/embassy-rs/embassy.git", rev = "7f8af8a" }

请注意,git 修订版应与您正在使用的任何其他 embassy 补丁或 git 依赖项匹配!

如何优化我的 embassy-stm32 程序的运行速度?

如何在 stable 版本上设置任务 arena?

当您不使用 embassy-executornightly 功能时,执行器使用 bump 分配器,这可能需要配置。

如果任务 arena 对于目标的 RAM 来说 太大,则会在 编译时 发生如下错误:

rust-lld: error: section '.bss' will not fit in region 'RAM': overflowed by _ bytes
rust-lld: error: section '.uninit' will not fit in region 'RAM': overflowed by _ bytes

如果任务 arena 对于正在运行的任务来说 太小,则会在 运行时 显示此消息:

ERROR panicked at 'embassy-executor: task arena is full. You must increase the arena size, see the documentation for details: https://docs.embassy.dev/embassy-executor/'

注意:如果所有任务都在启动时生成,则此 panic 将立即发生。

查看 link:https://docs.embassy.dev/embassy-executor/git/cortex-m/index.html#task-arena[任务 Arena 文档] 了解更多详细信息。

我可以在 Embassy 中使用手动 ISR 吗?

可以! 如果您需要尽快响应事件,并且通常的"ISR,唤醒,从 ISR 返回,上下文切换到唤醒任务"流程造成的延迟对于您的应用程序来说太长,这将非常有用。 只需像在 link:https://docs.rust-embedded.org/book/start/interrupts.html[任何其他嵌入式 rust 项目中] 一样定义 #[interrupt] fn INTERRUPT_NAME() {} 处理程序。

如何测量资源使用情况(CPU,RAM 等)?

对于 CPU 使用率:

有一些已记录的技术,通常您想测量在空闲或低优先级循环中花费的时间。

我们需要记录如何在 embassy 中专门执行此操作,但 link:https://blog.japaric.io/cpu-monitor/[这篇旧帖子] 描述了一般过程。

如果您最终这样做,请使用更具体的示例更新此部分!

对于静态内存使用量

cargo sizecargo nm 等工具可以告诉您任何全局变量或其他静态使用量的大小。 具体来说,您将需要查看 .data.bss 部分的大小,它们共同构成了总的全局/静态内存使用量。

对于最大堆栈使用量

查看 link:https://github.com/Dirbaio/cargo-call-stack/[`cargo-call-stack`] 以静态计算最坏情况下的堆栈使用量。 这可能存在一些警告和不准确之处,但这对于了解总体思路来说是一个好方法。 有关更多详细信息,请参阅 link:https://github.com/dirbaio/cargo-call-stack#known-limitations[README]。

我的 STM 芯片的内存定义似乎不正确,我该如何定义 memory.x 文件?

您的项目可能会编译,刷写但无法运行。 以下情况可能适用于您的设置:

当在 Cargo.toml 文件中启用 embassy-stm32 crate 上的 memory-x 功能时,会自动生成 memory.x。 反过来,这使用 stm32-metapac 为您生成 memory.x 文件。 不幸的是,通常情况下,此内存定义不正确。

您可以通过添加自己的 memory.x 文件来覆盖它。 这样的文件可能如下所示:

MEMORY
{
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
  RAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 320K
}

_stack_start = ORIGIN(RAM) + LENGTH(RAM);

请参阅 STM32 文档,了解适合您的板和设置的特定值。 STM32 Cube 示例通常包含链接器脚本 .ld 文件。 查找 MEMORY 部分,并尝试确定 FLASH 和 RAM 大小以及节起始位置。

如果您发现 memory.x 错误的情况,请在 此 Github issue 上报告,以便其他用户不会感到意外。

USB 示例在我的板上不起作用,我还需要配置其他内容吗?

如果您正在尝试 USB 示例,并且您的设备未连接,则最常见的问题如下所列。

不正确的 RCC 配置

检查您的板和晶振/振荡器,特别是确保 HSE 设置为正确的值,例如,如果您的板确实在 8 MHz 振荡器上运行,则为 8_000_000 赫兹。

STM32 平台上的 VBUS 检测

USB 规范要求所有 USB 设备监视总线以检测插入/拔出操作。 设备必须在主机提供 VBUS 后立即上拉 D+ 或 D- 通道。

有关如何启用/禁用 vbus_detection 的信息,请参阅文档,例如 link:https://docs.embassy.dev/embassy-stm32/git/stm32f401vc/usb/struct.Config.html[`usb/struct.Config.html`]。

当设备仅由同时充当数据连接的 USB 总线供电时,这是可选的。 (如果 VBUS 中没有电源,设备将无论如何都会关闭,因此始终假设 VBUS 中有电源是安全的,即 USB 电缆始终已插入)。 如果您的设备没有所需的连接来允许 VBUS 感应(见下文),则需要将此选项设置为 false 才能工作。

当设备由另一个电源供电,因此可以通过 USB 电缆插拔事件保持供电时,则必须实现此功能,并且 vbus_detection 必须设置为 true

如果您的板由 USB 供电,并且您不确定它是否支持 vbus_detection,请查阅您的板的原理图,以查看 VBUS 是否连接到用于 USB 全速的 PA9 或用于 USB 高速的 PB13,反之亦然,可能带有分压器。 在设计自己的硬件时,请参阅 ST 应用笔记 AN4879(特别是第 2.6 节)和您的特定芯片的参考手册,了解更多详细信息。

已知问题(详细信息和/或缓解措施)

这些是常见的报告问题。 希望能帮助修复它们,或在可能的情况下改善 UX!

STM32H5 和 STM32H7 电源问题

当配置与板的设计方式不匹配时,具有内置电源管理(SMPS 和 LDO)设置的 STM32 芯片通常会导致用户出现问题。

来自示例的设置,甚至来自其他工作板的设置,可能在您的板上不起作用,因为它们的接线方式不同。

此外,某些 PWR 设置需要完全设备重启(以及足够的时间来释放任何电源电容器!),这使得难以进行故障排除。 此外,一些"错误"的电源设置几乎可以工作,这意味着它有时会在某些启动时或一段时间内工作,但会意外崩溃。

目前还没有针对此问题的修复程序,因为它取决于板/硬件。 有关更多详细信息,请参阅 link:https://github.com/embassy-rs/embassy/issues/2806[此跟踪 issue]。

STM32 BDMA 仅在某些 RAM 区域外工作

某些 STM32H7 芯片中包含的 STM32 BDMA 控制器必须配置为仅使用 RAM 的某些区域,否则传输将失败。

如果您看到如下错误:

DMA: error on BDMA@1234ABCD channel 4

您需要设置您的链接器脚本以为此区域定义一个特殊区域,并在与 BDMA 一起使用之前将数据复制到该区域。

一般步骤:

  1. 找出 BDMA 可以访问的内存区域。 您可以从 STM32 数据手册中的总线矩阵和内存映射表中获取此信息。
  2. 将内存区域添加到 memory.x,您可以修改从 https://github.com/embassy-rs/stm32-data-generated/tree/main/data/chips 生成的内存区域。
  3. 您可能需要修改 build.rs 以使 cargo 拾取修改后的 memory.x
  4. 在您的代码中,使用 #[link_section = ".xxx"] 访问定义的内存区域
  5. 在使用 BDMA 之前将数据复制到该区域。

有关更多详细信息,请参阅 link:https://github.com/embassy-rs/embassy/blob/main/examples/stm32h7/src/bin/spi_bdma.rs[SMT32H7 SPI BDMA 示例]。

如何切换到 main 分支?

有时,为了测试新的更改或修复,您需要将您的项目切换为使用来自 GitHub 的版本。

您可以将如下部分添加到您的 Cargo.toml 文件中,您需要将所有 embassy crate 补丁到相同的修订版:

使用 patch 将替换所有直接和间接依赖项。

有关此方法的更多详细信息,请参阅 link:https://embassy.dev/book/#_starting_a_new_project[新项目文档]。

[patch.crates-io]
# 确保从 github 获取最新的 git rev,您可以在此处查看最新的 git rev:
# https://github.com/embassy-rs/embassy/commits/main/
embassy-embedded-hal = { git = "https://github.com/embassy-rs/embassy",     rev = "4cade64ebd34bf93458f17cfe85c5f710d0ff13c" }
embassy-executor     = { git = "https://github.com/embassy-rs/embassy",     rev = "4cade64ebd34bf93458f17cfe85c5f710d0ff13c" }
embassy-rp           = { git = "https://github.com/embassy-rs/embassy",     rev = "4cade64ebd34bf93458f17cfe85c5f710d0ff13c" }
embassy-sync         = { git = "https://github.com/embassy-rs/embassy",     rev = "4cade64ebd34bf93458f17cfe85c5f710d0ff13c" }
embassy-time         = { git = "https://github.com/embassy-rs/embassy",     rev = "4cade64ebd34bf93458f17cfe85c5f710d0ff13c" }
embassy-usb          = { git = "https://github.com/embassy-rs/embassy",     rev = "4cade64ebd34bf93458f17cfe85c5f710d0ff13c" }
embassy-usb-driver   = { git = "https://github.com/embassy-rs/embassy",     rev = "4cade64ebd34bf93458f17cfe85c5f710d0ff13c" }

如何为 embassy 添加对新微控制器的支持?

这尤其适用于 cortex-m,以及潜在的 risc-v,其中已经支持中断处理等基本功能,甚至已经为您的架构提供了 embassy-executor 支持。

这比仅仅在已支持的芯片上使用 Embassy 困难得多。 如果您是初学者,请考虑在现有的、良好支持的芯片上使用 embassy 一段时间,然后再决定从头开始编写驱动程序。 还值得阅读受支持的 Embassy HAL 的现有源代码,以了解如何为各种芯片实现驱动程序。 您应该已经可以自如地阅读和编写不安全的代码,并了解为您的 HAL 用户编写安全抽象的责任。

这不是唯一可能的方法,但如果您正在寻找从哪里开始,这是一种处理任务的合理方法:

  1. 首先,访问 Matrix 房间或四处搜索,看看是否有人已经开始编写驱动程序,无论是在 Embassy 中还是在其他 Rust 项目中。 您可能不必从头开始!
  2. 确保 probe-rs 支持该目标,它很可能支持,如果不支持,则可能有一个 cmsis-pack 可以用来添加支持,以便可以进行刷写和调试。 在编写驱动程序时,您肯定会喜欢能够使用 SWD 或 JTAG 进行调试!
  3. 查看是否有 SVD(或 SVD,如果是一个系列)可用,如果有,请通过 chiptool 运行它以创建用于低级寄存器访问的 PAC。 如果没有,还有其他方法(例如,抓取 PDF 数据表或现有的 C 头文件),但这些方法比从 SVD 文件开始定义编写驱动程序所需的外部设备内存位置要花费更多的工作。
  4. 要么 fork embassy repo,并在那里添加您的目标,要么创建一个仅包含 PAC 和空 HAL 的 repo。 它不一定必须首先存在于 embassy repo 中。
  5. 在您的芯片上运行 hello world 二进制文件,可以使用最少的 HAL 或仅 PAC 访问,使用延迟并闪烁灯或在某些接口上发送一些原始数据,确保它可以工作,并且您可以刷写,使用 defmt + RTT 调试,编写正确的链接器脚本等。
  6. 使基本计时器操作和计时器中断工作,升级您的闪烁应用程序以使用硬件计时器和中断,并确保它们是准确的(如果可能,使用逻辑分析仪或示波器)。
  7. 使用您的计时器和计时器中断代码实现 embassy-time 驱动程序 API,以便您可以在驱动程序和应用程序中使用 embassy-time 操作。
  8. 然后开始实现您需要的任何外围设备,例如 GPIO,UART,SPI,I2C 等。 这是工作量最大的部分,并且可能会持续一段时间! 不要觉得您一开始就需要 100% 覆盖所有外围设备,这很可能是一个随着时间推移而持续进行的过程。
  9. 一旦您开始完成更多功能,就开始在您的 HAL 驱动程序之上实现 embedded-hal,embedded-io 和 embedded-hal-async traits。 这将允许用户将标准外部设备驱动程序(例如传感器,执行器,显示器等)与您的 HAL 一起使用。
  10. 讨论将 PAC/HAL 上游以获得 embassy 支持,或确保将您的驱动程序添加到 awesome-embedded-rust 列表中,以便人们可以找到它。

多个任务,还是一个具有多个 future 的任务?

一些示例在 main 中像这样结束:

// 并发运行所有内容。
// 如果我们在上面将所有内容都设为 `'static`,我们可以使用单独的任务来完成此操作。
join(usb_fut, join(echo_fut, log_fut)).await;

Embassy 中处理并发性主要有两种方法:

  1. 生成多个任务,例如使用 #[embassy_executor::task]
  2. 使用 join()select()(如上所示)在一个任务内管理多个 future

通常,这些方法中的任何一种都可以工作。 这些方法的主要区别在于:

当使用 单独的任务 时,每个任务都需要自己的 RAM 分配,因此每个任务都会有一些开销,因此执行三件事的一个任务可能比执行一件事的三个任务略小(不多,可能几十个字节)。 相比之下,对于 一个任务中的多个 future,您不需要多个任务分配,并且通常更容易在一个任务内共享数据或使用借用的资源。 link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/sharing.rs[此处可以找到] 一个展示在任务之间共享事物的一些方法的示例。

但是当涉及到"唤醒"任务时,例如当数据传输完成或按下按钮时,唤醒专用任务会更快,因为该任务不需要检查哪个 future 实际上已准备就绪。 joinselect 必须检查它们管理的所有 future,以查看哪个(或哪些)已准备好执行更多工作。 这是因为所有 Rust 执行器(如 Embassy 或 Tokio)都只能唤醒任务,而不能唤醒特定的 future。 这意味着当使用专用任务时,您将使用稍微少的 CPU 时间来处理 future。

实际上,无论哪种方式,差异都不大 - 因此首先选择对您和您的代码来说更容易的方式,但在每种情况下都会有一些细节略有不同。

在任务之间拆分外围设备资源

有两种方法可以在任务之间拆分资源,一种是手动分配,另一种是通过方便的宏。 请参阅 link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/assign_resources.rs[此示例]

我的代码/驱动程序在 debug 模式下工作,但在 release 模式下(或使用 LTO)不工作

在实现驱动程序时,此类问题通常属于以下一般原因之一,这些原因是要检查的常见错误列表:

  1. 某种竞争条件 - 更快的代码意味着您错过了中断或类似的东西
  2. 某种 UB,如果您有不安全的代码,或者缺少 fence 的 DMA 之类的东西
  3. 某种硬件勘误表,或某些硬件配置错误,例如时钟速度错误
  4. 中断处理程序中的某些问题,例如在必要时启用,禁用或重新启用中断
  5. 某种异步问题,例如在检查标志之前未完全注册唤醒器,或者未在正确的时间注册或挂起唤醒器

如何防止线程模式执行器进入睡眠状态? ==

在某些情况下,您可能希望防止线程模式执行器进入睡眠状态,例如,当这样做会导致电流尖峰从而降低模拟性能时。 作为一种解决方法,您可以生成一个在循环中 yield 的任务,以防止执行器进入睡眠状态。 请注意,这可能会增加功耗。

#[embassy_executor::task]
async fn idle() {
    loop { embassy_futures::yield_now().await; }
}

为什么我的引导加载程序在循环中重启?

引导加载程序重启循环故障排除

如果您的引导加载程序在循环中重启,则可能有多种原因。 以下是一些需要检查的事项:

验证 memory.x 文件

引导加载程序在使用 memory.x 中定义的地址创建分区时执行关键检查。 确保以下断言成立:

const {
    core::assert!(Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32 == 0);
    core::assert!(Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32 == 0);
    core::assert!(Self::PAGE_SIZE % DFU::WRITE_SIZE as u32 == 0);
    core::assert!(Self::PAGE_SIZE % DFU::ERASE_SIZE as u32 == 0);
}

// 确保有足够的进度页面来存储复制进度
assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32);
assert!(aligned_buf.len() >= STATE::WRITE_SIZE);
assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE);
assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE);

如果这些断言中的任何一个失败,引导加载程序很可能会在循环中重启。 此失败可能不会记录任何消息(例如,当使用 defmt 时)。 确认您的 memory.x 文件和 flash 内存符合这些要求。

处理 Panic 日志记录

某些 panic 错误可能会记录消息,但某些微控制器会在消息完全打印之前复位。 为了确保记录 panic 消息,请在复位之前添加使用空操作 (NOP) 指令的延迟:

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
    for _ in 0..10_000_000 {
        cortex_m::asm::nop();
    }
    cortex_m::asm::udf();
}

喂狗

一些 embassy-boot 实现(如 embassy-boot-nrfembassy-boot-rp)依赖于看门狗定时器来检测应用程序故障。 如果您的应用程序代码未正确喂狗看门狗定时器,则引导加载程序将重启。 确保正确喂狗。