Rust in Everything
基于 embassy-stm32 0.6.0

忘了 / 绑错 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>,和驱动的处理函数列在同一行。

💬 关联讨论

发起讨论
加载中...