Rust in Everything
基于 embassy-time-driver 0.2.2

the name TICK_HZ is defined multiple times

主线位置:定时器主线的起点,Timer::after(100ms) 先把 100 ms 换算成 tick。tick 有多长由编译期常量 TICK_HZ 决定,它怎么从 feature 里选出来,见 Instant 与 Duration 的「TICK_HZ 从哪来」。

flowchart TB
  bin["二进制 tick-hz-32_768"] --> drv["embassy-time-driver"]
  std["std 带 1 MHz"] --> drv
  lib["库 tick-hz-1_000"] --> drv
  drv --> n{"打开几个"}
  n -->|0| def["默认 1 MHz"]
  n -->|1| one["用那一个"]
  n -->|2 个以上| err["E0428"]

实验 crate exp-embassy/pita-tick、pita-tick2、pita-tick2-fixed、pita-tick3(std,debug);M2 Max、macOS 27、rustc 1.99.0。

现象

照抄了芯片例子的频率。 仓库里大多数 stm32 例子给 embassy-time 开 tick-hz-32_768。把这一行搬进一个用 std 驱动的程序(pita-tick):

embassy-time = { version = "=0.5.1", features = ["std", "tick-hz-32_768"] }
error[E0428]: the name `TICK_HZ` is defined multiple times
  --> ~/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/embassy-time-driver-0.2.2/src/tick.rs:86:1
   |
54 | pub const TICK_HZ: u64 = 32_768;
   | -------------------------------- previous definition of the value `TICK_HZ` here
...
86 | pub const TICK_HZ: u64 = 1_000_000;
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `TICK_HZ` redefined here
   |
   = note: `TICK_HZ` must be defined only once in the value namespace of this module

For more information about this error, try `rustc --explain E0428`.
error: could not compile `embassy-time-driver` (lib) due to 1 previous error

Cargo.toml 里只写了一个频率,另一个是 std 带进来的。

一个库选了频率。 pita-tick2 的二进制只开 std,它依赖的本地库 sensor 写了:

[dependencies]
# a library that picks a tick rate itself
embassy-time = { version = "=0.5.1", features = ["tick-hz-1_000"] }

报错是同一条,只是 previous definition 那一行换成了 28 | pub const TICK_HZ: u64 = 1_000;。错误出在 embassy-time-driver 里,不提是谁开的 feature。

库选了频率,二进制没选。 这时没有任何报错,pita-tick3 是证据。二进制自带一个驱动、不开 tick-hz-*(embassy-stm32 的 TIM 驱动就是这样,按 TICK_HZ 算预分频),依赖同一个 sensor:

TICK_HZ = 1000, from_micros(1500) = Duration { ticks: 2 } = 2000 us

整个程序的时间单位变成了 1 ms,from_micros(1500) 向上取整成 2 ms。库作者在自己的测试里不会发现,因为对库来说 1 kHz 就是它想要的。

出处

TICK_HZ 转手取自 tick.rs,每个频率一行 #[cfg(feature = "tick-hz-X")] pub const TICK_HZ。cargo 把整个依赖图里对 embassy-time-driver 打开的 feature 合并成一个集合,开了两个不同的 tick-hz-*,tick.rs 里就有两行 pub const 同时生效,rustc 报 E0428。开两次同一个频率只留下一行,不报错。

embassy-time 的 Cargo.toml 里 std = ["tick-hz-1_000_000", ...],mock-driver、wasm 也一样,所以用这三个驱动时不能再选。同一个文件的 feature 说明写了规则:

#! At most 1 `tick-*` feature can be enabled. If none is enabled, a default of 1MHz is used.
#!
#! If the time driver in use supports using arbitrary tick rates, you can enable one `tick-*`
#! feature from your binary crate to set the tick rate. The driver will use configured tick rate.
#! If the time driver supports a fixed tick rate, it will enable one feature itself, so you should
#! not enable one. Check the time driver documentation for details.

「from your binary crate」是关键:频率是全程序的决定,只有二进制和固定频率的驱动有资格做。

修法

先找出是谁开的。pita-tick2 里:

$ cargo tree -e features -i embassy-time-driver
embassy-time-driver v0.2.2
├── embassy-time-driver feature "default"
│   └── embassy-time v0.5.1
...
│       ├── embassy-time feature "std"
│       │   └── pita-tick2 v0.1.0 (~/exp-embassy/pita-tick2)
│       ├── embassy-time feature "tick-hz-1_000"
│       │   └── sensor v0.1.0 (~/exp-embassy/pita-tick2/sensor)
│       └── embassy-time feature "tick-hz-1_000_000"
│           └── embassy-time feature "std" (*)
├── embassy-time-driver feature "tick-hz-1_000"
│   └── embassy-time feature "tick-hz-1_000" (*)
└── embassy-time-driver feature "tick-hz-1_000_000"
    └── embassy-time feature "tick-hz-1_000_000" (*)

最后两条分支就是两个频率,往上追:tick-hz-1_000 来自 sensor,tick-hz-1_000_000 来自 std。库里删掉 feature(pita-tick2-fixed):

[dependencies]
# a library leaves the tick rate to the binary
embassy-time = "=0.5.1"
TICK_HZ = 1000000, sensor period = Duration { ticks: 10000 }

库里的 Duration::from_millis(10) 跟着全程序的 1 MHz 变成 10000 tick,它本来就不该关心单位。第一种情况(照抄芯片例子)删掉 tick-hz-32_768:std 驱动只能是 1 MHz。

怎么确认没中招

  • 库 crate 的 Cargo.toml 里不应出现 tick-hz-:grep -rn 'tick-hz' --include=Cargo.toml .,只允许二进制 crate 有,并且用固定频率驱动(std、mock-driver、wasm)时连二进制也不该有。
  • 程序启动时打印一次 embassy_time::TICK_HZ,和驱动文档里的频率比一比。没有报错不代表频率是你选的。
  • 想让一个库在不同频率下都对,它的测试至少在两个频率下各跑一遍;换算的取整方向见 Instant 与 Duration。

小结

  • TICK_HZ 是 tick.rs 里按 feature 选中的一行常量;整个依赖图开了两个不同的 tick-hz-*,就是 E0428,报错指向 embassy-time-driver。
  • std / mock-driver / wasm 自带 1 MHz;常见的冲突是照抄芯片例子的 tick-hz-32_768,或一个库自己选了频率。
  • 库选频率、二进制没选时不报错,全程序的分辨率被改掉;用 cargo tree -e features -i embassy-time-driver 找出是谁开的。

💬 关联讨论

发起讨论
加载中...