diff options
| author | chemicstry <[email protected]> | 2023-07-25 12:07:09 +0300 |
|---|---|---|
| committer | chemicstry <[email protected]> | 2023-07-25 12:12:45 +0300 |
| commit | 62ab0bf2e75c560aa255ab51aab1f5ebf591ba97 (patch) | |
| tree | 5f1704223f27c3b40708b85fd7cac7b20fe66bac | |
| parent | 77e34c5e8a7d31eac6fdc144a329027a0ce731a5 (diff) | |
stm32/can: implement proper RX timestamps
| -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 | 13 |
4 files changed, 58 insertions, 16 deletions
diff --git a/embassy-stm32/src/can/bxcan.rs b/embassy-stm32/src/can/bxcan.rs index 8b8244d4f..795becabf 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)] | ||
| 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>, |
| @@ -199,11 +210,11 @@ impl<'d, T: Instance> Can<'d, T> { | |||
| 199 | } | 210 | } |
| 200 | 211 | ||
| 201 | /// Returns a tuple of the time the message was received and the message frame | 212 | /// Returns a tuple of the time the message was received and the message frame |
| 202 | pub async fn read(&mut self) -> Result<(u16, bxcan::Frame), BusError> { | 213 | pub async fn read(&mut self) -> Result<Envelope, BusError> { |
| 203 | poll_fn(|cx| { | 214 | poll_fn(|cx| { |
| 204 | T::state().err_waker.register(cx.waker()); | 215 | T::state().err_waker.register(cx.waker()); |
| 205 | if let Poll::Ready((time, frame)) = T::state().rx_queue.recv().poll_unpin(cx) { | 216 | if let Poll::Ready(envelope) = T::state().rx_queue.recv().poll_unpin(cx) { |
| 206 | return Poll::Ready(Ok((time, frame))); | 217 | return Poll::Ready(Ok(envelope)); |
| 207 | } else if let Some(err) = self.curr_error() { | 218 | } else if let Some(err) = self.curr_error() { |
| 208 | return Poll::Ready(Err(err)); | 219 | return Poll::Ready(Err(err)); |
| 209 | } | 220 | } |
| @@ -228,6 +239,10 @@ impl<'d, T: Instance> Can<'d, T> { | |||
| 228 | } | 239 | } |
| 229 | 240 | ||
| 230 | unsafe fn receive_fifo(fifo: RxFifo) { | 241 | unsafe fn receive_fifo(fifo: RxFifo) { |
| 242 | // Generate timestamp as early as possible | ||
| 243 | #[cfg(feature = "time")] | ||
| 244 | let ts = embassy_time::Instant::now(); | ||
| 245 | |||
| 231 | let state = T::state(); | 246 | let state = T::state(); |
| 232 | let regs = T::regs(); | 247 | let regs = T::regs(); |
| 233 | let fifo_idx = match fifo { | 248 | let fifo_idx = match fifo { |
| @@ -257,15 +272,19 @@ impl<'d, T: Instance> Can<'d, T> { | |||
| 257 | data[0..4].copy_from_slice(&fifo.rdlr().read().0.to_ne_bytes()); | 272 | data[0..4].copy_from_slice(&fifo.rdlr().read().0.to_ne_bytes()); |
| 258 | data[4..8].copy_from_slice(&fifo.rdhr().read().0.to_ne_bytes()); | 273 | data[4..8].copy_from_slice(&fifo.rdhr().read().0.to_ne_bytes()); |
| 259 | 274 | ||
| 260 | let time = fifo.rdtr().read().time(); | ||
| 261 | let frame = Frame::new_data(id, Data::new(&data[0..data_len]).unwrap()); | 275 | let frame = Frame::new_data(id, Data::new(&data[0..data_len]).unwrap()); |
| 276 | let envelope = Envelope { | ||
| 277 | #[cfg(feature = "time")] | ||
| 278 | ts, | ||
| 279 | frame, | ||
| 280 | }; | ||
| 262 | 281 | ||
| 263 | rfr.modify(|v| v.set_rfom(true)); | 282 | rfr.modify(|v| v.set_rfom(true)); |
| 264 | 283 | ||
| 265 | /* | 284 | /* |
| 266 | NOTE: consensus was reached that if rx_queue is full, packets should be dropped | 285 | NOTE: consensus was reached that if rx_queue is full, packets should be dropped |
| 267 | */ | 286 | */ |
| 268 | let _ = state.rx_queue.try_send((time, frame)); | 287 | let _ = state.rx_queue.try_send(envelope); |
| 269 | } | 288 | } |
| 270 | } | 289 | } |
| 271 | 290 | ||
| @@ -405,11 +424,11 @@ pub struct CanRx<'c, 'd, T: Instance> { | |||
| 405 | } | 424 | } |
| 406 | 425 | ||
| 407 | impl<'c, 'd, T: Instance> CanRx<'c, 'd, T> { | 426 | impl<'c, 'd, T: Instance> CanRx<'c, 'd, T> { |
| 408 | pub async fn read(&mut self) -> Result<(u16, bxcan::Frame), BusError> { | 427 | pub async fn read(&mut self) -> Result<Envelope, BusError> { |
| 409 | poll_fn(|cx| { | 428 | poll_fn(|cx| { |
| 410 | T::state().err_waker.register(cx.waker()); | 429 | T::state().err_waker.register(cx.waker()); |
| 411 | if let Poll::Ready((time, frame)) = T::state().rx_queue.recv().poll_unpin(cx) { | 430 | if let Poll::Ready(envelope) = T::state().rx_queue.recv().poll_unpin(cx) { |
| 412 | return Poll::Ready(Ok((time, frame))); | 431 | return Poll::Ready(Ok(envelope)); |
| 413 | } else if let Some(err) = self.curr_error() { | 432 | } else if let Some(err) = self.curr_error() { |
| 414 | return Poll::Ready(Err(err)); | 433 | return Poll::Ready(Err(err)); |
| 415 | } | 434 | } |
| @@ -467,10 +486,12 @@ pub(crate) mod sealed { | |||
| 467 | use embassy_sync::channel::Channel; | 486 | use embassy_sync::channel::Channel; |
| 468 | use embassy_sync::waitqueue::AtomicWaker; | 487 | use embassy_sync::waitqueue::AtomicWaker; |
| 469 | 488 | ||
| 489 | use super::Envelope; | ||
| 490 | |||
| 470 | pub struct State { | 491 | pub struct State { |
| 471 | pub tx_waker: AtomicWaker, | 492 | pub tx_waker: AtomicWaker, |
| 472 | pub err_waker: AtomicWaker, | 493 | pub err_waker: AtomicWaker, |
| 473 | pub rx_queue: Channel<CriticalSectionRawMutex, (u16, bxcan::Frame), 32>, | 494 | pub rx_queue: Channel<CriticalSectionRawMutex, Envelope, 32>, |
| 474 | } | 495 | } |
| 475 | 496 | ||
| 476 | impl State { | 497 | 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..93253ab84 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,18 @@ 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 | info!("Receiving frame..."); |
| 68 | let (time, rx_frame) = can.read().await.unwrap(); | 71 | let envelope = can.read().await.unwrap(); |
| 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 | // Theoretical minimum latency is 55us, actual is usually ~80us | ||
| 77 | let latency = envelope.ts.saturating_duration_since(tx_ts); | ||
| 78 | assert!(Duration::from_micros(50) < latency && latency < Duration::from_micros(100)); | ||
| 72 | 79 | ||
| 73 | i += 1; | 80 | i += 1; |
| 74 | if i > 10 { | 81 | if i > 10 { |
