diff --git a/embassy-stm32/CHANGELOG.md b/embassy-stm32/CHANGELOG.md index dfb8ca066a..b97ec6195a 100644 --- a/embassy-stm32/CHANGELOG.md +++ b/embassy-stm32/CHANGELOG.md @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased - ReleaseDate -- fix: Fixed STM32H5 builds requiring time feature +- feat: Add blocking read/write methods for STM32 CAN (bxCAN and FDCAN) ## 0.4.0 - 2025-08-26 diff --git a/embassy-stm32/src/can/bxcan/mod.rs b/embassy-stm32/src/can/bxcan/mod.rs index 4c0795a2a5..2023bc7c22 100644 --- a/embassy-stm32/src/can/bxcan/mod.rs +++ b/embassy-stm32/src/can/bxcan/mod.rs @@ -273,6 +273,17 @@ impl<'d> Can<'d> { } } + /// Blocking version of enable. Enables the peripheral and synchronizes with the bus. + /// + /// This will wait for 11 consecutive recessive bits (bus idle state). + /// In loopback or internal modes, this typically completes very quickly. + pub fn blocking_enable(&mut self) { + while self.info.regs.enable_non_blocking().is_err() { + // CAN initialization is usually quick, especially in loopback mode + // Simple busy wait is acceptable for this one-time initialization + } + } + /// Enables or disables the peripheral from automatically wakeup when a SOF is detected on the bus /// while the peripheral is in sleep mode pub fn set_automatic_wakeup(&mut self, enabled: bool) { @@ -343,6 +354,18 @@ impl<'d> Can<'d> { self.split().0.write(frame).await } + /// Blocking write frame. + /// + /// If the TX queue is full, this will wait until there is space. + pub fn blocking_write(&mut self, frame: &Frame) -> TransmitStatus { + loop { + match self.try_write(frame) { + Ok(status) => return status, + Err(TryWriteError::Full) => continue, + } + } + } + /// Attempts to transmit a frame without blocking. /// /// Returns [Err(TryWriteError::Full)] if the frame can not be queued for transmission now. @@ -416,6 +439,19 @@ impl<'d> Can<'d> { RxMode::read(&self.info).await } + /// Blocking read frame. + /// + /// If no CAN frame is in the RX buffer, this will wait until there is one. + pub fn blocking_read(&mut self) -> Result { + loop { + match self.try_read() { + Ok(envelope) => return Ok(envelope), + Err(TryReadError::Empty) => continue, + Err(TryReadError::BusError(e)) => return Err(e), + } + } + } + /// Attempts to read a CAN frame without blocking. /// /// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue. @@ -480,6 +516,11 @@ impl<'d, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> BufferedCan<'d, TX_ self.tx.write(frame).await } + /// Blocking write frame to TX buffer. + pub fn blocking_write(&mut self, frame: &Frame) { + self.tx.blocking_write(frame) + } + /// Returns a sender that can be used for sending CAN frames. pub fn writer(&self) -> BufferedCanSender { self.tx.writer() @@ -490,6 +531,11 @@ impl<'d, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> BufferedCan<'d, TX_ self.rx.read().await } + /// Blocking read frame from RX buffer. + pub fn blocking_read(&mut self) -> Result { + self.rx.blocking_read() + } + /// Attempts to read a CAN frame without blocking. /// /// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue. @@ -540,6 +586,18 @@ impl<'d> CanTx<'d> { .await } + /// Blocking write frame. + /// + /// If the TX queue is full, this will wait until there is space. + pub fn blocking_write(&mut self, frame: &Frame) -> TransmitStatus { + loop { + match self.try_write(frame) { + Ok(status) => return status, + Err(TryWriteError::Full) => continue, + } + } + } + /// Attempts to transmit a frame without blocking. /// /// Returns [Err(TryWriteError::Full)] if the frame can not be queued for transmission now. @@ -690,6 +748,20 @@ impl<'d, const TX_BUF_SIZE: usize> BufferedCanTx<'d, TX_BUF_SIZE> { waker(); // Wake for Tx } + /// Blocking write frame to TX buffer. + pub fn blocking_write(&mut self, frame: &Frame) { + loop { + match self.tx_buf.try_send(*frame) { + Ok(()) => { + let waker = self.info.tx_waker; + waker(); + return; + } + Err(_) => continue, // Channel full, continue waiting + } + } + } + /// Returns a sender that can be used for sending CAN frames. pub fn writer(&self) -> BufferedCanSender { BufferedCanSender { @@ -716,6 +788,19 @@ impl<'d> CanRx<'d> { RxMode::read(&self.info).await } + /// Blocking read frame. + /// + /// If no CAN frame is in the RX buffer, this will wait until there is one. + pub fn blocking_read(&mut self) -> Result { + loop { + match self.try_read() { + Ok(envelope) => return Ok(envelope), + Err(TryReadError::Empty) => continue, + Err(TryReadError::BusError(e)) => return Err(e), + } + } + } + /// Attempts to read a CAN frame without blocking. /// /// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue. @@ -783,6 +868,17 @@ impl<'d, const RX_BUF_SIZE: usize> BufferedCanRx<'d, RX_BUF_SIZE> { self.rx_buf.receive().await } + /// Blocking read frame from RX buffer. + pub fn blocking_read(&mut self) -> Result { + loop { + match self.try_read() { + Ok(envelope) => return Ok(envelope), + Err(TryReadError::Empty) => continue, + Err(TryReadError::BusError(e)) => return Err(e), + } + } + } + /// Attempts to read a CAN frame without blocking. /// /// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue. diff --git a/embassy-stm32/src/can/fdcan.rs b/embassy-stm32/src/can/fdcan.rs index 99e40ba629..b1834abd68 100644 --- a/embassy-stm32/src/can/fdcan.rs +++ b/embassy-stm32/src/can/fdcan.rs @@ -334,11 +334,45 @@ impl<'d> Can<'d> { TxMode::write(&self.info, frame).await } + /// Blocking write frame. + /// + /// If the TX queue is full, this will wait until there is space. + pub fn blocking_write(&mut self, frame: &Frame) -> Option { + loop { + match self.info.regs.write(frame) { + Ok(dropped) => return dropped, + Err(nb::Error::WouldBlock) => continue, + Err(nb::Error::Other(_)) => unreachable!(), // Infallible + } + } + } + /// Returns the next received message frame pub async fn read(&mut self) -> Result { RxMode::read_classic(&self.info).await } + /// Blocking read frame. + /// + /// If no CAN frame is in the RX buffer, this will wait until there is one. + pub fn blocking_read(&mut self) -> Result { + let ns_per_timer_tick = self.info.state.lock(|s| s.borrow().ns_per_timer_tick); + + loop { + if let Some(result) = self.info.regs.read::(0) { + let ts = self.info.regs.calc_timestamp(ns_per_timer_tick, result.1); + return Ok(Envelope { ts, frame: result.0 }); + } + if let Some(result) = self.info.regs.read::(1) { + let ts = self.info.regs.calc_timestamp(ns_per_timer_tick, result.1); + return Ok(Envelope { ts, frame: result.0 }); + } + if let Some(err) = self.info.regs.curr_error() { + return Err(err); + } + } + } + /// 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 @@ -347,11 +381,45 @@ impl<'d> Can<'d> { TxMode::write_fd(&self.info, frame).await } + /// Blocking write FD frame. + /// + /// If the TX queue is full, this will wait until there is space. + pub fn blocking_write_fd(&mut self, frame: &FdFrame) -> Option { + loop { + match self.info.regs.write(frame) { + Ok(dropped) => return dropped, + Err(nb::Error::WouldBlock) => continue, + Err(nb::Error::Other(_)) => unreachable!(), // Infallible + } + } + } + /// Returns the next received message frame pub async fn read_fd(&mut self) -> Result { RxMode::read_fd(&self.info).await } + /// Blocking read FD frame. + /// + /// If no CAN FD frame is in the RX buffer, this will wait until there is one. + pub fn blocking_read_fd(&mut self) -> Result { + let ns_per_timer_tick = self.info.state.lock(|s| s.borrow().ns_per_timer_tick); + + loop { + if let Some(result) = self.info.regs.read::(0) { + let ts = self.info.regs.calc_timestamp(ns_per_timer_tick, result.1); + return Ok(FdEnvelope { ts, frame: result.0 }); + } + if let Some(result) = self.info.regs.read::(1) { + let ts = self.info.regs.calc_timestamp(ns_per_timer_tick, result.1); + return Ok(FdEnvelope { ts, frame: result.0 }); + } + if let Some(err) = self.info.regs.curr_error() { + return Err(err); + } + } + } + /// Split instance into separate portions: Tx(write), Rx(read), common properties pub fn split(self) -> (CanTx<'d>, CanRx<'d>, Properties) { ( @@ -467,6 +535,29 @@ impl<'c, 'd, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> BufferedCan<'d, self.rx_buf.receive().await } + /// Blocking write frame to TX buffer. + pub fn blocking_write(&mut self, frame: Frame) { + loop { + match self.tx_buf.try_send(frame) { + Ok(()) => { + self.info.interrupt0.pend(); // Wake for Tx + return; + } + Err(_) => continue, + } + } + } + + /// Blocking read frame from RX buffer. + pub fn blocking_read(&mut self) -> Result { + loop { + match self.rx_buf.try_receive() { + Ok(result) => return result, + Err(_) => continue, + } + } + } + /// Returns a sender that can be used for sending CAN frames. pub fn writer(&self) -> BufferedCanSender { BufferedCanSender { @@ -556,6 +647,29 @@ impl<'c, 'd, const TX_BUF_SIZE: usize, const RX_BUF_SIZE: usize> BufferedCanFd<' self.rx_buf.receive().await } + /// Blocking write frame to TX buffer. + pub fn blocking_write(&mut self, frame: FdFrame) { + loop { + match self.tx_buf.try_send(frame) { + Ok(()) => { + self.info.interrupt0.pend(); // Wake for Tx + return; + } + Err(_) => continue, + } + } + } + + /// Blocking read frame from RX buffer. + pub fn blocking_read(&mut self) -> Result { + loop { + match self.rx_buf.try_receive() { + Ok(result) => return result, + Err(_) => continue, + } + } + } + /// Returns a sender that can be used for sending CAN frames. pub fn writer(&self) -> BufferedFdCanSender { BufferedFdCanSender { @@ -586,10 +700,52 @@ impl<'d> CanRx<'d> { RxMode::read_classic(&self.info).await } + /// Blocking read frame. + /// + /// If no CAN frame is in the RX buffer, this will wait until there is one. + pub fn blocking_read(&mut self) -> Result { + let ns_per_timer_tick = self.info.state.lock(|s| s.borrow().ns_per_timer_tick); + + loop { + if let Some(result) = self.info.regs.read::(0) { + let ts = self.info.regs.calc_timestamp(ns_per_timer_tick, result.1); + return Ok(Envelope { ts, frame: result.0 }); + } + if let Some(result) = self.info.regs.read::(1) { + let ts = self.info.regs.calc_timestamp(ns_per_timer_tick, result.1); + return Ok(Envelope { ts, frame: result.0 }); + } + if let Some(err) = self.info.regs.curr_error() { + return Err(err); + } + } + } + /// Returns the next received message frame pub async fn read_fd(&mut self) -> Result { RxMode::read_fd(&self.info).await } + + /// Blocking read FD frame. + /// + /// If no CAN FD frame is in the RX buffer, this will wait until there is one. + pub fn blocking_read_fd(&mut self) -> Result { + let ns_per_timer_tick = self.info.state.lock(|s| s.borrow().ns_per_timer_tick); + + loop { + if let Some(result) = self.info.regs.read::(0) { + let ts = self.info.regs.calc_timestamp(ns_per_timer_tick, result.1); + return Ok(FdEnvelope { ts, frame: result.0 }); + } + if let Some(result) = self.info.regs.read::(1) { + let ts = self.info.regs.calc_timestamp(ns_per_timer_tick, result.1); + return Ok(FdEnvelope { ts, frame: result.0 }); + } + if let Some(err) = self.info.regs.curr_error() { + return Err(err); + } + } + } } /// FDCAN Tx only Instance @@ -609,6 +765,19 @@ impl<'c, 'd> CanTx<'d> { TxMode::write(&self.info, frame).await } + /// Blocking write frame. + /// + /// If the TX queue is full, this will wait until there is space. + pub fn blocking_write(&mut self, frame: &Frame) -> Option { + loop { + match self.info.regs.write(frame) { + Ok(dropped) => return dropped, + Err(nb::Error::WouldBlock) => continue, + Err(nb::Error::Other(_)) => unreachable!(), // Infallible + } + } + } + /// 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 @@ -616,6 +785,19 @@ impl<'c, 'd> CanTx<'d> { pub async fn write_fd(&mut self, frame: &FdFrame) -> Option { TxMode::write_fd(&self.info, frame).await } + + /// Blocking write FD frame. + /// + /// If the TX queue is full, this will wait until there is space. + pub fn blocking_write_fd(&mut self, frame: &FdFrame) -> Option { + loop { + match self.info.regs.write(frame) { + Ok(dropped) => return dropped, + Err(nb::Error::WouldBlock) => continue, + Err(nb::Error::Other(_)) => unreachable!(), // Infallible + } + } + } } enum RxMode { diff --git a/examples/stm32f1/src/bin/can_blocking.rs b/examples/stm32f1/src/bin/can_blocking.rs new file mode 100644 index 0000000000..3ab6d1b4e5 --- /dev/null +++ b/examples/stm32f1/src/bin/can_blocking.rs @@ -0,0 +1,120 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::can::frame::Envelope; +use embassy_stm32::can::{ + filter, Can, Fifo, Frame, Id, Rx0InterruptHandler, Rx1InterruptHandler, SceInterruptHandler, StandardId, + TxInterruptHandler, +}; +use embassy_stm32::peripherals::CAN; +use embassy_stm32::{bind_interrupts, Config}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + USB_LP_CAN1_RX0 => Rx0InterruptHandler; + CAN1_RX1 => Rx1InterruptHandler; + CAN1_SCE => SceInterruptHandler; + USB_HP_CAN1_TX => TxInterruptHandler; +}); + +// This example demonstrates blocking CAN operations on STM32F1 +// Configured to work with real CAN transceivers on B8/B9. + +fn handle_frame(env: Envelope, read_mode: &str) { + match env.frame.id() { + Id::Extended(id) => { + defmt::println!( + "{} Extended Frame id={:x} {:02x}", + read_mode, + id.as_raw(), + env.frame.data() + ); + } + Id::Standard(id) => { + defmt::println!( + "{} Standard Frame id={:x} {:02x}", + read_mode, + id.as_raw(), + env.frame.data() + ); + } + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Config::default()); + + // Set alternate pin mapping to B8/B9 + embassy_stm32::pac::AFIO.mapr().modify(|w| w.set_can1_remap(2)); + + static RX_BUF: StaticCell> = StaticCell::new(); + static TX_BUF: StaticCell> = StaticCell::new(); + + let mut can = Can::new(p.CAN, p.PB8, p.PB9, Irqs); + + can.modify_filters() + .enable_bank(0, Fifo::Fifo0, filter::Mask32::accept_all()); + + can.modify_config() + .set_loopback(true) // Enable loopback for testing + .set_silent(false) + .set_bitrate(250_000); + + can.blocking_enable(); + + info!("CAN Blocking Example Started"); + + // Example 1: Basic blocking operations with split CAN + let (mut tx, mut rx) = can.split(); + + for i in 0..3 { + let tx_frame = Frame::new_data(unwrap!(StandardId::new(0x123 + i)), &[i as u8, 0x01, 0x02, 0x03]).unwrap(); + + // Blocking write + info!("Blocking write frame {}", i); + tx.blocking_write(&tx_frame); + + // Blocking read + match rx.blocking_read() { + Ok(env) => { + handle_frame(env, "Blocking"); + } + Err(err) => { + error!("Blocking read error: {}", err); + } + } + } + + // Example 2: Buffered blocking operations + let mut rx = rx.buffered(RX_BUF.init(embassy_stm32::can::RxBuf::<10>::new())); + let mut tx = tx.buffered(TX_BUF.init(embassy_stm32::can::TxBuf::<10>::new())); + + for i in 3..6 { + let tx_frame = Frame::new_data(unwrap!(StandardId::new(0x200 + i)), &[i as u8, 0x10, 0x20, 0x30]).unwrap(); + + // Buffered blocking write + info!("Buffered blocking write frame {}", i); + tx.blocking_write(&tx_frame); + + // Buffered blocking read + match rx.blocking_read() { + Ok(envelope) => { + handle_frame(envelope, "BufferedBlocking"); + } + Err(err) => { + error!("Buffered blocking read error: {}", err); + } + } + } + + info!("CAN Blocking Example Completed - all CAN operations were blocking!"); + + // Keep the program running (Embassy executor still needed for interrupt handling) + loop { + embassy_time::Timer::after_secs(1).await; + } +} diff --git a/examples/stm32g4/src/bin/can_blocking.rs b/examples/stm32g4/src/bin/can_blocking.rs new file mode 100644 index 0000000000..193589f0c7 --- /dev/null +++ b/examples/stm32g4/src/bin/can_blocking.rs @@ -0,0 +1,192 @@ +#![no_std] +#![no_main] +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::peripherals::*; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, can, Config}; +use static_cell::StaticCell; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + FDCAN1_IT0 => can::IT0InterruptHandler; + FDCAN1_IT1 => can::IT1InterruptHandler; +}); + +// This example demonstrates blocking CAN operations on STM32G4 + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hse = Some(Hse { + freq: Hertz(24_000_000), + mode: HseMode::Oscillator, + }); + config.rcc.pll = Some(Pll { + source: PllSource::HSE, + prediv: PllPreDiv::DIV6, + mul: PllMul::MUL85, + divp: None, + divq: Some(PllQDiv::DIV8), // 42.5 Mhz for fdcan. + divr: Some(PllRDiv::DIV2), // Main system clock at 170 MHz + }); + config.rcc.mux.fdcansel = mux::Fdcansel::PLL1_Q; + config.rcc.sys = Sysclk::PLL1_R; + } + let peripherals = embassy_stm32::init(config); + + let mut can = can::CanConfigurator::new(peripherals.FDCAN1, peripherals.PA11, peripherals.PA12, Irqs); + + can.properties().set_extended_filter( + can::filter::ExtendedFilterSlot::_0, + can::filter::ExtendedFilter::accept_all_into_fifo1(), + ); + + // 250k bps + can.set_bitrate(250_000); + + info!("Configured CAN for blocking operations"); + + // Start in internal loopback mode for testing + let mut can = can.start(can::OperatingMode::InternalLoopbackMode); + + info!("CAN Blocking Example Started"); + + // Example 1: Basic blocking operations + for i in 0..3 { + let frame = can::frame::Frame::new_extended(0x123456F + i as u32, &[i, 0x01, 0x02, 0x03]).unwrap(); + + // Blocking write + info!("Blocking write frame {}", i); + match can.blocking_write(&frame) { + Some(_dropped_frame) => { + warn!("Frame was dropped"); + } + None => { + info!("Frame sent successfully"); + } + } + + // Blocking read + match can.blocking_read() { + Ok(envelope) => { + let (_ts, rx_frame) = (envelope.ts, envelope.frame); + info!( + "Blocking read - Rx: {} {:02x}", + rx_frame.header().len(), + rx_frame.data()[0..rx_frame.header().len() as usize], + ); + } + Err(err) => { + error!("Blocking read error: {}", err); + } + } + } + + // Example 2: Using FD blocking operations + for i in 3..6 { + let frame = can::frame::FdFrame::new_extended(0x200000 + i as u32, &[i, 0x10, 0x20, 0x30, 0x40]).unwrap(); + + // Blocking write FD + info!("Blocking write FD frame {}", i); + match can.blocking_write_fd(&frame) { + Some(_dropped_frame) => { + warn!("FD Frame was dropped"); + } + None => { + info!("FD Frame sent successfully"); + } + } + + // Blocking read FD + match can.blocking_read_fd() { + Ok(envelope) => { + let (_ts, rx_frame) = (envelope.ts, envelope.frame); + info!( + "Blocking read FD - Rx: {} {:02x}", + rx_frame.header().len(), + rx_frame.data()[0..rx_frame.header().len() as usize], + ); + } + Err(err) => { + error!("Blocking read FD error: {}", err); + } + } + } + + // Example 3: Split CAN blocking operations + let (mut tx, mut rx, _props) = can.split(); + + for i in 6..9 { + let frame = can::frame::Frame::new_extended(0x300000 + i as u32, &[i, 0xAA, 0xBB, 0xCC]).unwrap(); + + // Split blocking write + info!("Split blocking write frame {}", i); + match tx.blocking_write(&frame) { + Some(_dropped_frame) => { + warn!("Split frame was dropped"); + } + None => { + info!("Split frame sent successfully"); + } + } + + // Split blocking read + match rx.blocking_read() { + Ok(envelope) => { + let (_ts, rx_frame) = (envelope.ts, envelope.frame); + info!( + "Split blocking read - Rx: {} {:02x}", + rx_frame.header().len(), + rx_frame.data()[0..rx_frame.header().len() as usize], + ); + } + Err(err) => { + error!("Split blocking read error: {}", err); + } + } + } + + // Example 4: Buffered blocking operations + let can = can::Can::join(tx, rx); + + static TX_BUF: StaticCell> = StaticCell::new(); + static RX_BUF: StaticCell> = StaticCell::new(); + + let mut buffered_can = can.buffered( + TX_BUF.init(can::TxBuf::<8>::new()), + RX_BUF.init(can::RxBuf::<10>::new()), + ); + + for i in 9..12 { + let frame = can::frame::Frame::new_extended(0x400000 + i as u32, &[i, 0xFF, 0xEE, 0xDD]).unwrap(); + + // Buffered blocking write + info!("Buffered blocking write frame {}", i); + buffered_can.blocking_write(frame); + + // Buffered blocking read + match buffered_can.blocking_read() { + Ok(envelope) => { + let (_ts, rx_frame) = (envelope.ts, envelope.frame); + info!( + "Buffered blocking read - Rx: {} {:02x}", + rx_frame.header().len(), + rx_frame.data()[0..rx_frame.header().len() as usize], + ); + } + Err(err) => { + error!("Buffered blocking read error: {}", err); + } + } + } + + info!("CAN Blocking Example Completed - all CAN operations were blocking!"); + + // Keep the program running (Embassy executor still needed for interrupt handling) + loop { + embassy_time::Timer::after_secs(1).await; + } +}