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
- Use the buffered-mode send API matching the configured TxMode, or configure the channel for non-buffered TX.
- Audit which TxMode the TxRing was constructed with and call only the corresponding mode's methods.
- 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
- Keep TX mode configuration and TX API usage in one place so they cannot diverge
- Prefer mode-specific wrapper types over panicking default arms
- Review CAN peripheral config when migrating async TX code between boards
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)