diff options
| author | Dario Nieuwenhuis <[email protected]> | 2023-07-31 10:28:05 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-07-31 10:28:05 +0000 |
| commit | 2568c714c80267503554bcf090a2dbcd80b41e08 (patch) | |
| tree | 10725ca58f3b846c24b11ebf9414c2326bfbaff0 | |
| parent | eff2d71f28b44ad71256cba645ce25e56ab88f9c (diff) | |
| parent | 83ab8e057a759ac76c8cddeec1ebdc28c4516b2b (diff) | |
Merge pull request #1687 from chemicstry/bxcan_timestamp
stm32/can: implement proper RX timestamps
| -rw-r--r-- | embassy-stm32/Cargo.toml | 2 | ||||
| -rw-r--r-- | embassy-stm32/src/can/bxcan.rs | 39 | ||||
| -rw-r--r-- | examples/stm32f4/src/bin/can.rs | 18 | ||||
| -rw-r--r-- | examples/stm32f7/src/bin/can.rs | 4 | ||||
| -rw-r--r-- | tests/stm32/src/bin/can.rs | 27 |
5 files changed, 71 insertions, 19 deletions
diff --git a/embassy-stm32/Cargo.toml b/embassy-stm32/Cargo.toml index a1323e85b..8c7dd38c2 100644 --- a/embassy-stm32/Cargo.toml +++ b/embassy-stm32/Cargo.toml | |||
| @@ -84,7 +84,7 @@ default = ["rt"] | |||
| 84 | rt = ["stm32-metapac/rt"] | 84 | rt = ["stm32-metapac/rt"] |
| 85 | 85 | ||
| 86 | ## Use [`defmt`](https://docs.rs/defmt/latest/defmt/) for logging | 86 | ## Use [`defmt`](https://docs.rs/defmt/latest/defmt/) for logging |
| 87 | defmt = ["dep:defmt", "bxcan/unstable-defmt", "embassy-sync/defmt", "embassy-embedded-hal/defmt", "embassy-hal-internal/defmt", "embedded-io?/defmt", "embassy-usb-driver?/defmt", "embassy-net-driver/defmt"] | 87 | defmt = ["dep:defmt", "bxcan/unstable-defmt", "embassy-sync/defmt", "embassy-embedded-hal/defmt", "embassy-hal-internal/defmt", "embedded-io?/defmt", "embassy-usb-driver?/defmt", "embassy-net-driver/defmt", "embassy-time?/defmt"] |
| 88 | 88 | ||
| 89 | exti = [] | 89 | exti = [] |
| 90 | 90 | ||
diff --git a/embassy-stm32/src/can/bxcan.rs b/embassy-stm32/src/can/bxcan.rs index 1d0fdd532..a3e3ec860 100644 --- a/embassy-stm32/src/can/bxcan.rs +++ b/embassy-stm32/src/can/bxcan.rs | |||
| @@ -16,6 +16,17 @@ use crate::rcc::RccPeripheral; | |||
| 16 | use crate::time::Hertz; | 16 | use crate::time::Hertz; |
| 17 | use crate::{interrupt, peripherals, Peripheral}; | 17 | use crate::{interrupt, peripherals, Peripheral}; |
| 18 | 18 | ||
| 19 | /// Contains CAN frame and additional metadata. | ||
| 20 | /// | ||
| 21 | /// Timestamp is available if `time` feature is enabled. | ||
| 22 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
| 23 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 24 | pub struct Envelope { | ||
| 25 | #[cfg(feature = "time")] | ||
| 26 | pub ts: embassy_time::Instant, | ||
| 27 | pub frame: bxcan::Frame, | ||
| 28 | } | ||
| 29 | |||
| 19 | /// Interrupt handler. | 30 | /// Interrupt handler. |
| 20 | pub struct TxInterruptHandler<T: Instance> { | 31 | pub struct TxInterruptHandler<T: Instance> { |
| 21 | _phantom: PhantomData<T>, | 32 | _phantom: PhantomData<T>, |
| @@ -218,14 +229,14 @@ impl<'d, T: Instance> Can<'d, T> { | |||
| 218 | } | 229 | } |
| 219 | 230 | ||
| 220 | /// Returns a tuple of the time the message was received and the message frame | 231 | /// Returns a tuple of the time the message was received and the message frame |
| 221 | pub async fn read(&mut self) -> Result<(u16, bxcan::Frame), BusError> { | 232 | pub async fn read(&mut self) -> Result<Envelope, BusError> { |
| 222 | CanRx { can: &self.can }.read().await | 233 | CanRx { can: &self.can }.read().await |
| 223 | } | 234 | } |
| 224 | 235 | ||
| 225 | /// Attempts to read a can frame without blocking. | 236 | /// Attempts to read a can frame without blocking. |
| 226 | /// | 237 | /// |
| 227 | /// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue. | 238 | /// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue. |
| 228 | pub fn try_read(&mut self) -> Result<(u16, bxcan::Frame), TryReadError> { | 239 | pub fn try_read(&mut self) -> Result<Envelope, TryReadError> { |
| 229 | CanRx { can: &self.can }.try_read() | 240 | CanRx { can: &self.can }.try_read() |
| 230 | } | 241 | } |
| 231 | 242 | ||
| @@ -235,6 +246,10 @@ impl<'d, T: Instance> Can<'d, T> { | |||
| 235 | } | 246 | } |
| 236 | 247 | ||
| 237 | unsafe fn receive_fifo(fifo: RxFifo) { | 248 | unsafe fn receive_fifo(fifo: RxFifo) { |
| 249 | // Generate timestamp as early as possible | ||
| 250 | #[cfg(feature = "time")] | ||
| 251 | let ts = embassy_time::Instant::now(); | ||
| 252 | |||
| 238 | let state = T::state(); | 253 | let state = T::state(); |
| 239 | let regs = T::regs(); | 254 | let regs = T::regs(); |
| 240 | let fifo_idx = match fifo { | 255 | let fifo_idx = match fifo { |
| @@ -264,15 +279,19 @@ impl<'d, T: Instance> Can<'d, T> { | |||
| 264 | data[0..4].copy_from_slice(&fifo.rdlr().read().0.to_ne_bytes()); | 279 | data[0..4].copy_from_slice(&fifo.rdlr().read().0.to_ne_bytes()); |
| 265 | data[4..8].copy_from_slice(&fifo.rdhr().read().0.to_ne_bytes()); | 280 | data[4..8].copy_from_slice(&fifo.rdhr().read().0.to_ne_bytes()); |
| 266 | 281 | ||
| 267 | let time = fifo.rdtr().read().time(); | ||
| 268 | let frame = Frame::new_data(id, Data::new(&data[0..data_len]).unwrap()); | 282 | let frame = Frame::new_data(id, Data::new(&data[0..data_len]).unwrap()); |
| 283 | let envelope = Envelope { | ||
| 284 | #[cfg(feature = "time")] | ||
| 285 | ts, | ||
| 286 | frame, | ||
| 287 | }; | ||
| 269 | 288 | ||
| 270 | rfr.modify(|v| v.set_rfom(true)); | 289 | rfr.modify(|v| v.set_rfom(true)); |
| 271 | 290 | ||
| 272 | /* | 291 | /* |
| 273 | NOTE: consensus was reached that if rx_queue is full, packets should be dropped | 292 | NOTE: consensus was reached that if rx_queue is full, packets should be dropped |
| 274 | */ | 293 | */ |
| 275 | let _ = state.rx_queue.try_send((time, frame)); | 294 | let _ = state.rx_queue.try_send(envelope); |
| 276 | } | 295 | } |
| 277 | } | 296 | } |
| 278 | 297 | ||
| @@ -456,11 +475,11 @@ pub struct CanRx<'c, 'd, T: Instance> { | |||
| 456 | } | 475 | } |
| 457 | 476 | ||
| 458 | impl<'c, 'd, T: Instance> CanRx<'c, 'd, T> { | 477 | impl<'c, 'd, T: Instance> CanRx<'c, 'd, T> { |
| 459 | pub async fn read(&mut self) -> Result<(u16, bxcan::Frame), BusError> { | 478 | pub async fn read(&mut self) -> Result<Envelope, BusError> { |
| 460 | poll_fn(|cx| { | 479 | poll_fn(|cx| { |
| 461 | T::state().err_waker.register(cx.waker()); | 480 | T::state().err_waker.register(cx.waker()); |
| 462 | if let Poll::Ready((time, frame)) = T::state().rx_queue.recv().poll_unpin(cx) { | 481 | if let Poll::Ready(envelope) = T::state().rx_queue.recv().poll_unpin(cx) { |
| 463 | return Poll::Ready(Ok((time, frame))); | 482 | return Poll::Ready(Ok(envelope)); |
| 464 | } else if let Some(err) = self.curr_error() { | 483 | } else if let Some(err) = self.curr_error() { |
| 465 | return Poll::Ready(Err(err)); | 484 | return Poll::Ready(Err(err)); |
| 466 | } | 485 | } |
| @@ -473,7 +492,7 @@ impl<'c, 'd, T: Instance> CanRx<'c, 'd, T> { | |||
| 473 | /// Attempts to read a CAN frame without blocking. | 492 | /// Attempts to read a CAN frame without blocking. |
| 474 | /// | 493 | /// |
| 475 | /// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue. | 494 | /// Returns [Err(TryReadError::Empty)] if there are no frames in the rx queue. |
| 476 | pub fn try_read(&mut self) -> Result<(u16, bxcan::Frame), TryReadError> { | 495 | pub fn try_read(&mut self) -> Result<Envelope, TryReadError> { |
| 477 | if let Ok(envelope) = T::state().rx_queue.try_recv() { | 496 | if let Ok(envelope) = T::state().rx_queue.try_recv() { |
| 478 | return Ok(envelope); | 497 | return Ok(envelope); |
| 479 | } | 498 | } |
| @@ -545,10 +564,12 @@ pub(crate) mod sealed { | |||
| 545 | use embassy_sync::channel::Channel; | 564 | use embassy_sync::channel::Channel; |
| 546 | use embassy_sync::waitqueue::AtomicWaker; | 565 | use embassy_sync::waitqueue::AtomicWaker; |
| 547 | 566 | ||
| 567 | use super::Envelope; | ||
| 568 | |||
| 548 | pub struct State { | 569 | pub struct State { |
| 549 | pub tx_waker: AtomicWaker, | 570 | pub tx_waker: AtomicWaker, |
| 550 | pub err_waker: AtomicWaker, | 571 | pub err_waker: AtomicWaker, |
| 551 | pub rx_queue: Channel<CriticalSectionRawMutex, (u16, bxcan::Frame), 32>, | 572 | pub rx_queue: Channel<CriticalSectionRawMutex, Envelope, 32>, |
| 552 | } | 573 | } |
| 553 | 574 | ||
| 554 | impl State { | 575 | impl State { |
diff --git a/examples/stm32f4/src/bin/can.rs b/examples/stm32f4/src/bin/can.rs index f84f74d30..20ce4edce 100644 --- a/examples/stm32f4/src/bin/can.rs +++ b/examples/stm32f4/src/bin/can.rs | |||
| @@ -10,6 +10,7 @@ use embassy_stm32::can::bxcan::{Fifo, Frame, StandardId}; | |||
| 10 | use embassy_stm32::can::{Can, Rx0InterruptHandler, Rx1InterruptHandler, SceInterruptHandler, TxInterruptHandler}; | 10 | use embassy_stm32::can::{Can, Rx0InterruptHandler, Rx1InterruptHandler, SceInterruptHandler, TxInterruptHandler}; |
| 11 | use embassy_stm32::gpio::{Input, Pull}; | 11 | use embassy_stm32::gpio::{Input, Pull}; |
| 12 | use embassy_stm32::peripherals::CAN1; | 12 | use embassy_stm32::peripherals::CAN1; |
| 13 | use embassy_time::Instant; | ||
| 13 | use {defmt_rtt as _, panic_probe as _}; | 14 | use {defmt_rtt as _, panic_probe as _}; |
| 14 | 15 | ||
| 15 | bind_interrupts!(struct Irqs { | 16 | bind_interrupts!(struct Irqs { |
| @@ -51,9 +52,22 @@ async fn main(_spawner: Spawner) { | |||
| 51 | let mut i: u8 = 0; | 52 | let mut i: u8 = 0; |
| 52 | loop { | 53 | loop { |
| 53 | let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), [i]); | 54 | let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), [i]); |
| 55 | let tx_ts = Instant::now(); | ||
| 54 | can.write(&tx_frame).await; | 56 | can.write(&tx_frame).await; |
| 55 | let (_, rx_frame) = can.read().await.unwrap(); | 57 | |
| 56 | info!("loopback frame {=u8}", unwrap!(rx_frame.data())[0]); | 58 | let envelope = can.read().await.unwrap(); |
| 59 | |||
| 60 | // We can measure loopback latency by using receive timestamp in the `Envelope`. | ||
| 61 | // Our frame is ~55 bits long (exlcuding bit stuffing), so at 1mbps loopback delay is at least 55 us. | ||
| 62 | // When measured with `tick-hz-1_000_000` actual latency is 80~83 us, giving a combined hardware and software | ||
| 63 | // overhead of ~25 us. Note that CPU frequency can greatly affect the result. | ||
| 64 | let latency = envelope.ts.saturating_duration_since(tx_ts); | ||
| 65 | |||
| 66 | info!( | ||
| 67 | "loopback frame {=u8}, latency: {} us", | ||
| 68 | unwrap!(envelope.frame.data())[0], | ||
| 69 | latency.as_micros() | ||
| 70 | ); | ||
| 57 | i += 1; | 71 | i += 1; |
| 58 | } | 72 | } |
| 59 | } | 73 | } |
diff --git a/examples/stm32f7/src/bin/can.rs b/examples/stm32f7/src/bin/can.rs index 1b5b377ea..e9650f23a 100644 --- a/examples/stm32f7/src/bin/can.rs +++ b/examples/stm32f7/src/bin/can.rs | |||
| @@ -60,7 +60,7 @@ async fn main(spawner: Spawner) { | |||
| 60 | spawner.spawn(send_can_message(tx)).unwrap(); | 60 | spawner.spawn(send_can_message(tx)).unwrap(); |
| 61 | 61 | ||
| 62 | loop { | 62 | loop { |
| 63 | let frame = rx.read().await.unwrap(); | 63 | let envelope = rx.read().await.unwrap(); |
| 64 | println!("Received: {:?}", frame); | 64 | println!("Received: {:?}", envelope); |
| 65 | } | 65 | } |
| 66 | } | 66 | } |
diff --git a/tests/stm32/src/bin/can.rs b/tests/stm32/src/bin/can.rs index 8bdd3c24f..8737ca8ee 100644 --- a/tests/stm32/src/bin/can.rs +++ b/tests/stm32/src/bin/can.rs | |||
| @@ -7,6 +7,7 @@ | |||
| 7 | #[path = "../common.rs"] | 7 | #[path = "../common.rs"] |
| 8 | mod common; | 8 | mod common; |
| 9 | use common::*; | 9 | use common::*; |
| 10 | use defmt::assert; | ||
| 10 | use embassy_executor::Spawner; | 11 | use embassy_executor::Spawner; |
| 11 | use embassy_stm32::bind_interrupts; | 12 | use embassy_stm32::bind_interrupts; |
| 12 | use embassy_stm32::can::bxcan::filter::Mask32; | 13 | use embassy_stm32::can::bxcan::filter::Mask32; |
| @@ -14,6 +15,7 @@ use embassy_stm32::can::bxcan::{Fifo, Frame, StandardId}; | |||
| 14 | use embassy_stm32::can::{Can, Rx0InterruptHandler, Rx1InterruptHandler, SceInterruptHandler, TxInterruptHandler}; | 15 | use embassy_stm32::can::{Can, Rx0InterruptHandler, Rx1InterruptHandler, SceInterruptHandler, TxInterruptHandler}; |
| 15 | use embassy_stm32::gpio::{Input, Pull}; | 16 | use embassy_stm32::gpio::{Input, Pull}; |
| 16 | use embassy_stm32::peripherals::CAN1; | 17 | use embassy_stm32::peripherals::CAN1; |
| 18 | use embassy_time::{Duration, Instant}; | ||
| 17 | use {defmt_rtt as _, panic_probe as _}; | 19 | use {defmt_rtt as _, panic_probe as _}; |
| 18 | 20 | ||
| 19 | bind_interrupts!(struct Irqs { | 21 | bind_interrupts!(struct Irqs { |
| @@ -62,13 +64,28 @@ async fn main(_spawner: Spawner) { | |||
| 62 | let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), [i]); | 64 | let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), [i]); |
| 63 | 65 | ||
| 64 | info!("Transmitting frame..."); | 66 | info!("Transmitting frame..."); |
| 67 | let tx_ts = Instant::now(); | ||
| 65 | can.write(&tx_frame).await; | 68 | can.write(&tx_frame).await; |
| 66 | 69 | ||
| 67 | info!("Receiving frame..."); | 70 | let envelope = can.read().await.unwrap(); |
| 68 | let (time, rx_frame) = can.read().await.unwrap(); | 71 | info!("Frame received!"); |
| 69 | 72 | ||
| 70 | info!("loopback time {}", time); | 73 | info!("loopback time {}", envelope.ts); |
| 71 | info!("loopback frame {=u8}", rx_frame.data().unwrap()[0]); | 74 | info!("loopback frame {=u8}", envelope.frame.data().unwrap()[0]); |
| 75 | |||
| 76 | let latency = envelope.ts.saturating_duration_since(tx_ts); | ||
| 77 | info!("loopback latency {} us", latency.as_micros()); | ||
| 78 | |||
| 79 | // Theoretical minimum latency is 55us, actual is usually ~80us | ||
| 80 | const MIN_LATENCY: Duration = Duration::from_micros(50); | ||
| 81 | const MAX_LATENCY: Duration = Duration::from_micros(150); | ||
| 82 | assert!( | ||
| 83 | MIN_LATENCY < latency && latency < MAX_LATENCY, | ||
| 84 | "{} < {} < {}", | ||
| 85 | MIN_LATENCY, | ||
| 86 | latency, | ||
| 87 | MAX_LATENCY | ||
| 88 | ); | ||
| 72 | 89 | ||
| 73 | i += 1; | 90 | i += 1; |
| 74 | if i > 10 { | 91 | if i > 10 { |
