embassy-rs/embassy · error

Bad mode

Error message

Bad mode

What it means

This panic comes from TxMode::register in the FDCAN driver's TX ring. The ring's waker slot is being registered while its TxMode variant is not NonBuffered (e.g. a buffered or classic mode), so the NonBuffered-specific register() path is invoked on an incompatible mode. The library treats this as a programmer error: the channel was configured with a different TX mode than the one whose API is being called.

Solutions

  1. Use the buffered-mode send API matching the configured TxMode, or configure the channel for non-buffered TX.
  2. Audit which TxMode the TxRing was constructed with and call only the corresponding mode's methods.
  3. Handle all TxMode variants in the match if both modes must share the registration code.

Example fix

// before
match self {
    TxMode::NonBuffered(w) => w.register(arg),
    _ => panic!("Bad mode"),
}
// after
match self {
    TxMode::NonBuffered(w) => w.register(arg),
    TxMode::Buffered(w) => w.register(arg), // or construct ring with TxMode::NonBuffered
    _ => panic!("Bad mode"),
}
Defensive patterns

Strategy: type-guard

Validate before calling

if let TxMode::NonBuffered(_) = tx_ring.mode() { /* safe to use non-buffered API */ }

Type guard

fn is_non_buffered(mode: &TxMode) -> bool { matches!(mode, TxMode::NonBuffered(_)) }

Prevention

When it happens

Trigger: Calling the non-buffered async TX registration/send path on a TxRing whose TxMode is TxMode::Buffered or another non-non-buffered variant — i.e. the CAN instance is set up in buffered TX mode but non-buffered waker registration is invoked.

Common situations: Reusing non-buffered TX code on a peripheral configured with buffered TX; changing the CAN TX mode in the peripheral config without updating async send code that assumes NonBuffered; mixing buffered and non-buffered send APIs on the same channel.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of embassy-rs/embassy@463a07b963 (2026-09-10). Data as JSON: /api/errors/bfd203d7f4846d78. Report an issue: GitHub.

Appendix: source

Thrown at embassy-stm32/src/can/fdcan.rs:975

            Err(e) => Err(e),
        }
    }
}

enum TxMode {
    NonBuffered(AtomicWaker),
    ClassicBuffered(super::common::ClassicBufferedTxInner),
    FdBuffered(super::common::FdBufferedTxInner),
}

impl TxMode {
    fn register(&self, arg: &core::task::Waker) {
        match self {
            TxMode::NonBuffered(waker) => {
                waker.register(arg);
            }
            _ => {
                panic!("Bad mode");
            }
        }
    }

    /// Queues the message to be sent but exerts backpressure.  If a lower-priority
    /// frame is dropped from the mailbox, it is returned.  If no lower-priority frames
    /// can be replaced, this call asynchronously waits for a frame to be successfully
    /// transmitted, then tries again.
    async fn write_generic<F: embedded_can::Frame + CanHeader>(info: &'static Info, frame: &F) -> Option<F> {
        poll_fn(|cx| {
            info.state.lock(|s| {
                s.borrow_mut().tx_mode.register(cx.waker());
            });

            if let Ok(dropped) = info.regs.write(frame) {
                return Poll::Ready(dropped);
            }

View on GitHub (pinned to 463a07b963)