忘了 / 绑错 bind_interrupts!:E0277;symbol EXTI15_10 is already defined
主线位置:中断主线第 1 步,
bind_interrupts!把向量接到驱动的处理函数。宏展开成什么、Binding为什么是编译期证明,见中断绑定。
flowchart TB
d["ExtiInput::new 等"] --> need["要 Binding I H"]
need --> q{"Irqs 里有吗"}
q -->|没有这一行| e1["E0277 漏绑"]
q -->|向量不对| e2["E0277 绑错"]
q -->|有| ok["编译通过"]
own["自写同名向量函数"] --> dup["symbol already defined"]
实验 crate exp-embassy/pitb-qemu(thumbv7em-none-eabihf,embassy-stm32 stm32l475vg,release)。[patch.crates-io] 指向本目录里 exp-embassy/hal-qemu-patched 的副本:和 0.6.0 相比,rcc/bd.rs 注释掉一行 while !cr.read().dbp() {},USART 的 TDR/RDR 改成 32 位访问、clear_interrupt_flags 只写 ICR 定义的位;本页涉及的 exti/、lib.rs、buffered.rs 的构造函数没有改动。报错文件在 pitb-qemu/errs/。运行的部分是 QEMU 11.1.2 b-l475e-iot01a。
现象
漏绑。 按钮先接上了,后来加了串口,Irqs 里忘了加 USART1(errs/e20_missing.txt):
bind_interrupts!(struct Irqs {
EXTI15_10 => exti::InterruptHandler<interrupt::typelevel::EXTI15_10>;
});
// ...
let _uart = BufferedUart::new(p.USART1, p.PB7, p.PB6, TX.init([0; 32]), RX.init([0; 32]), Irqs, Config::default());
error[E0277]: the trait bound `Irqs: Binding<USART1, BufferedInterruptHandler<USART1>>` is not satisfied
--> src/bin/e20_missing.rs:24:93
|
24 | let _uart = BufferedUart::new(p.USART1, p.PB7, p.PB6, TX.init([0; 32]), RX.init([0; 32]), Irqs, Config::default());
| ----------------- required by a bound introduced by this call ^^^^ unsatisfied trait bound
|
help: the trait `Binding<embassy_stm32::interrupt::typelevel::USART1, BufferedInterruptHandler<embassy_stm32::peripherals::USART1>>` is not implemented for `Irqs`
but trait `Binding<embassy_stm32::interrupt::typelevel::EXTI15_10, embassy_stm32::exti::InterruptHandler<embassy_stm32::interrupt::typelevel::EXTI15_10>>` is implemented for it
...
绑错向量。 从一个用 PB5(EXTI 线 5)的例子抄来 EXTI9_5,自己的按钮却在 PC13(线 13)(errs/e20_wrong.txt):
error[E0277]: the trait bound `Irqs: Binding<EXTI15_10, InterruptHandler<EXTI15_10>>` is not satisfied
--> src/bin/e20_wrong.rs:19:65
|
19 | let mut button = ExtiInput::new(p.PC13, p.EXTI13, Pull::Down, Irqs);
| -------------- ^^^^ unsatisfied trait bound
| |
| required by a bound introduced by this call
|
help: the trait `Binding<embassy_stm32::interrupt::typelevel::EXTI15_10, InterruptHandler<embassy_stm32::interrupt::typelevel::EXTI15_10>>` is not implemented for `Irqs`
but trait `Binding<embassy_stm32::interrupt::typelevel::EXTI9_5, InterruptHandler<embassy_stm32::interrupt::typelevel::EXTI9_5>>` is implemented for it
...
同一个向量再写一个处理函数。 PB14 也在 EXTI15_10 上,想自己数它的边沿,于是加了一个 cortex-m-rt 的 #[interrupt] fn EXTI15_10()(errs/e20_own.txt):
error: symbol `EXTI15_10` is already defined
--> src/bin/e20_own.rs:11:1
|
11 | / bind_interrupts!(struct Irqs {
12 | | EXTI15_10 => exti::InterruptHandler<interrupt::typelevel::EXTI15_10>;
13 | | });
| |__^
|
= note: this error originates in the macro `bind_interrupts` (in Nightly builds, run with -Z macro-backtrace for more info)
另一个模块里再写一个 bind_interrupts! 绑同一个向量,报的是同一句(errs/e20_double.txt,指向第二个宏)。
出处
驱动的构造函数多一个零大小参数,例如 ExtiInput::new:
_irq: impl Binding<
<<T as ExtiPin>::ExtiChannel as Channel>::IRQ,
InterruptHandler<<<T as ExtiPin>::ExtiChannel as Channel>::IRQ>,
>,
引脚类型 PC13 经 ExtiChannel 推出它的向量是 EXTI15_10,于是要求 Irqs: Binding<EXTI15_10, InterruptHandler<EXTI15_10>>。Binding 是 unsafe trait,只有 bind_interrupts! 会去实现它。所以漏绑、绑错都在类型检查时发现,rustc 的 help 把「需要的」和「已有的」都列出来,对照着改就行。
bind_interrupts! 同时生成一个 #[unsafe(no_mangle)] unsafe extern "C" fn EXTI15_10(),cortex-m-rt 的向量表按这个符号名找处理函数。#[interrupt] fn EXTI15_10() 也生成同名的 no_mangle 符号,两个定义在同一个 crate 里,rustc 报 symbol ... is already defined。
修法
前两种照 help 改:漏绑就在 Irqs 里加一行;绑错就换成报错里「需要的」那个向量:
bind_interrupts!(struct Irqs {
EXTI15_10 => exti::InterruptHandler<interrupt::typelevel::EXTI15_10>;
USART1 => usart::BufferedInterruptHandler<peripherals::USART1>;
});
第三种:宏的语法允许一个向量后面列多个处理函数($irq:ident => $($handler:ty),*;),生成的向量函数按顺序调它们的 on_interrupt()。把自己的逻辑写成一个实现 Handler 的类型,和驱动的列在同一行(src/bin/p20_own.rs):
static CALLS: AtomicU32 = AtomicU32::new(0);
// our own work on the same vector, as a second handler type
struct CountCalls;
impl Handler<EXTI15_10> for CountCalls {
unsafe fn on_interrupt() {
CALLS.fetch_add(1, Ordering::Relaxed);
}
}
bind_interrupts!(struct Irqs {
EXTI15_10 => exti::InterruptHandler<interrupt::typelevel::EXTI15_10>, CountCalls;
});
另一个任务写 EXTI 的 SWIER 触发线 13,按钮任务等上升沿(QEMU 11.1.2 b-l475e-iot01a):
edge on PC13; EXTI15_10 ran 1 time(s), both handlers inside
驱动的处理函数照常唤醒了 ExtiInput,自己的处理函数也跑了。如果是同一个向量上的另一条 EXTI 线(PB14),更简单的做法是也用 ExtiInput:同一个 Irqs 里的那一行绑定对线 10 到 15 都有效。只有完全不用 embassy 驱动的中断,才用 #[interrupt] 自己写,并且不要在 bind_interrupts! 里再列它。
怎么确认没中招
- 报错是 E0277 且 trait 名是
Binding<..>:看help的「is not implemented」和「but ... is implemented」两行,前者就是要加的那一行。 symbol X is already defined:grep -rn 'fn X\b\|X =>' src/,同一个向量只能有一个定义。- 引脚对应哪个向量,不用查手册:写
bind_interrupts!(struct Irqs {});编译一次,E0277 会说出来。errs/e20_empty.txt里 PB14 得到的是the trait bound `Irqs: Binding<EXTI15_10, InterruptHandler<EXTI15_10>>` is not satisfied。
小结
- 驱动的
_irq: impl Binding<中断, 处理函数>参数让漏绑、绑错向量成为 E0277,报错里同时有「需要的」和「已有的」。 bind_interrupts!生成no_mangle的向量函数,再写同名的#[interrupt]或第二个bind_interrupts!就是symbol ... is already defined。- 同一个向量要加自己的逻辑:实现
Handler<I>,和驱动的处理函数列在同一行。