diff options
| author | Corey Schuhen <[email protected]> | 2024-03-07 22:48:25 +1000 |
|---|---|---|
| committer | Corey Schuhen <[email protected]> | 2024-03-13 17:46:50 +1000 |
| commit | 12a3af5043fd1d9a32f57b5cab9b5729a304ffc5 (patch) | |
| tree | f98fe58851d3643363d48cf8b563a280212f0970 | |
| parent | 35f284ec22848d7085e00f377136fd66067ca756 (diff) | |
Shared frame types.
Remove BXCAN speciffic id and frame modules
Remove SizedClassicData
| -rw-r--r-- | embassy-stm32/src/can/bx/frame.rs | 248 | ||||
| -rw-r--r-- | embassy-stm32/src/can/bx/id.rs | 113 | ||||
| -rw-r--r-- | embassy-stm32/src/can/bx/mod.rs | 106 | ||||
| -rw-r--r-- | embassy-stm32/src/can/bxcan.rs | 17 | ||||
| -rw-r--r-- | embassy-stm32/src/can/fd/peripheral.rs | 2 | ||||
| -rw-r--r-- | embassy-stm32/src/can/frame.rs | 54 | ||||
| -rw-r--r-- | examples/stm32f1/src/bin/can.rs | 6 | ||||
| -rw-r--r-- | examples/stm32f4/src/bin/can.rs | 4 | ||||
| -rw-r--r-- | examples/stm32f7/src/bin/can.rs | 2 | ||||
| -rw-r--r-- | tests/stm32/src/bin/can.rs | 4 |
10 files changed, 139 insertions, 417 deletions
diff --git a/embassy-stm32/src/can/bx/frame.rs b/embassy-stm32/src/can/bx/frame.rs deleted file mode 100644 index 828f375be..000000000 --- a/embassy-stm32/src/can/bx/frame.rs +++ /dev/null | |||
| @@ -1,248 +0,0 @@ | |||
| 1 | #[cfg(test)] | ||
| 2 | use core::cmp::Ordering; | ||
| 3 | use core::ops::{Deref, DerefMut}; | ||
| 4 | |||
| 5 | use crate::can::bx::{Id, IdReg}; | ||
| 6 | |||
| 7 | /// A CAN data or remote frame. | ||
| 8 | #[derive(Clone, Debug, Eq)] | ||
| 9 | //#[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 10 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 11 | pub struct Frame { | ||
| 12 | pub(crate) id: IdReg, | ||
| 13 | pub(crate) data: Data, | ||
| 14 | } | ||
| 15 | |||
| 16 | impl Frame { | ||
| 17 | /// Creates a new data frame. | ||
| 18 | pub fn new_data(id: impl Into<Id>, data: impl Into<Data>) -> Self { | ||
| 19 | let id = match id.into() { | ||
| 20 | Id::Standard(id) => IdReg::new_standard(id), | ||
| 21 | Id::Extended(id) => IdReg::new_extended(id), | ||
| 22 | }; | ||
| 23 | |||
| 24 | Self { id, data: data.into() } | ||
| 25 | } | ||
| 26 | |||
| 27 | /// Creates a new remote frame with configurable data length code (DLC). | ||
| 28 | /// | ||
| 29 | /// # Panics | ||
| 30 | /// | ||
| 31 | /// This function will panic if `dlc` is not inside the valid range `0..=8`. | ||
| 32 | pub fn new_remote(id: impl Into<Id>, dlc: u8) -> Self { | ||
| 33 | assert!(dlc <= 8); | ||
| 34 | |||
| 35 | let mut frame = Self::new_data(id, []); | ||
| 36 | // Just extend the data length, even with no data present. The API does not hand out this | ||
| 37 | // `Data` object. | ||
| 38 | frame.data.len = dlc; | ||
| 39 | frame.id = frame.id.with_rtr(true); | ||
| 40 | frame | ||
| 41 | } | ||
| 42 | |||
| 43 | /// Returns true if this frame is an extended frame. | ||
| 44 | #[inline] | ||
| 45 | pub fn is_extended(&self) -> bool { | ||
| 46 | self.id.is_extended() | ||
| 47 | } | ||
| 48 | |||
| 49 | /// Returns true if this frame is a standard frame. | ||
| 50 | #[inline] | ||
| 51 | pub fn is_standard(&self) -> bool { | ||
| 52 | self.id.is_standard() | ||
| 53 | } | ||
| 54 | |||
| 55 | /// Returns true if this frame is a remote frame. | ||
| 56 | #[inline] | ||
| 57 | pub fn is_remote_frame(&self) -> bool { | ||
| 58 | self.id.rtr() | ||
| 59 | } | ||
| 60 | |||
| 61 | /// Returns true if this frame is a data frame. | ||
| 62 | #[inline] | ||
| 63 | pub fn is_data_frame(&self) -> bool { | ||
| 64 | !self.is_remote_frame() | ||
| 65 | } | ||
| 66 | |||
| 67 | /// Returns the frame identifier. | ||
| 68 | #[inline] | ||
| 69 | pub fn id(&self) -> Id { | ||
| 70 | self.id.to_id() | ||
| 71 | } | ||
| 72 | |||
| 73 | /// Returns the priority of this frame. | ||
| 74 | #[inline] | ||
| 75 | pub fn priority(&self) -> FramePriority { | ||
| 76 | FramePriority(self.id) | ||
| 77 | } | ||
| 78 | |||
| 79 | /// Returns the data length code (DLC) which is in the range 0..8. | ||
| 80 | /// | ||
| 81 | /// For data frames the DLC value always matches the length of the data. | ||
| 82 | /// Remote frames do not carry any data, yet the DLC can be greater than 0. | ||
| 83 | #[inline] | ||
| 84 | pub fn dlc(&self) -> u8 { | ||
| 85 | self.data.len() as u8 | ||
| 86 | } | ||
| 87 | |||
| 88 | /// Returns the frame data (0..8 bytes in length) if this is a data frame. | ||
| 89 | /// | ||
| 90 | /// If this is a remote frame, returns `None`. | ||
| 91 | pub fn data(&self) -> Option<&Data> { | ||
| 92 | if self.is_data_frame() { | ||
| 93 | Some(&self.data) | ||
| 94 | } else { | ||
| 95 | None | ||
| 96 | } | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | impl PartialEq for Frame { | ||
| 101 | fn eq(&self, other: &Self) -> bool { | ||
| 102 | match (self.data(), other.data()) { | ||
| 103 | (None, None) => self.id.eq(&other.id), | ||
| 104 | (Some(a), Some(b)) => self.id.eq(&other.id) && a.eq(b), | ||
| 105 | (None, Some(_)) | (Some(_), None) => false, | ||
| 106 | } | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | /// Priority of a CAN frame. | ||
| 111 | /// | ||
| 112 | /// Returned by [`Frame::priority`]. | ||
| 113 | /// | ||
| 114 | /// The priority of a frame is determined by the bits that are part of the *arbitration field*. | ||
| 115 | /// These consist of the frame identifier bits (including the *IDE* bit, which is 0 for extended | ||
| 116 | /// frames and 1 for standard frames), as well as the *RTR* bit, which determines whether a frame | ||
| 117 | /// is a data or remote frame. Lower values of the *arbitration field* have higher priority. | ||
| 118 | /// | ||
| 119 | /// This struct wraps the *arbitration field* and implements `PartialOrd` and `Ord` accordingly, | ||
| 120 | /// ordering higher priorities greater than lower ones. | ||
| 121 | #[derive(Debug, Copy, Clone)] | ||
| 122 | pub struct FramePriority(IdReg); | ||
| 123 | |||
| 124 | /// Ordering is based on the Identifier and frame type (data vs. remote) and can be used to sort | ||
| 125 | /// frames by priority. | ||
| 126 | impl Ord for FramePriority { | ||
| 127 | fn cmp(&self, other: &Self) -> core::cmp::Ordering { | ||
| 128 | self.0.cmp(&other.0) | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | impl PartialOrd for FramePriority { | ||
| 133 | fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> { | ||
| 134 | Some(self.cmp(other)) | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | impl PartialEq for FramePriority { | ||
| 139 | fn eq(&self, other: &Self) -> bool { | ||
| 140 | self.cmp(other) == core::cmp::Ordering::Equal | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | impl Eq for FramePriority {} | ||
| 145 | |||
| 146 | /// Payload of a CAN data frame. | ||
| 147 | /// | ||
| 148 | /// Contains 0 to 8 Bytes of data. | ||
| 149 | /// | ||
| 150 | /// `Data` implements `From<[u8; N]>` for all `N` up to 8, which provides a convenient lossless | ||
| 151 | /// conversion from fixed-length arrays. | ||
| 152 | #[derive(Debug, Copy, Clone)] | ||
| 153 | pub struct Data { | ||
| 154 | pub(crate) len: u8, | ||
| 155 | pub(crate) bytes: [u8; 8], | ||
| 156 | } | ||
| 157 | |||
| 158 | impl Data { | ||
| 159 | /// Creates a data payload from a raw byte slice. | ||
| 160 | /// | ||
| 161 | /// Returns `None` if `data` contains more than 8 Bytes (which is the maximum). | ||
| 162 | /// | ||
| 163 | /// `Data` can also be constructed from fixed-length arrays up to length 8 via `From`/`Into`. | ||
| 164 | pub fn new(data: &[u8]) -> Option<Self> { | ||
| 165 | if data.len() > 8 { | ||
| 166 | return None; | ||
| 167 | } | ||
| 168 | |||
| 169 | let mut bytes = [0; 8]; | ||
| 170 | bytes[..data.len()].copy_from_slice(data); | ||
| 171 | |||
| 172 | Some(Self { | ||
| 173 | len: data.len() as u8, | ||
| 174 | bytes, | ||
| 175 | }) | ||
| 176 | } | ||
| 177 | |||
| 178 | /// Creates an empty data payload containing 0 bytes. | ||
| 179 | #[inline] | ||
| 180 | pub const fn empty() -> Self { | ||
| 181 | Self { len: 0, bytes: [0; 8] } | ||
| 182 | } | ||
| 183 | } | ||
| 184 | |||
| 185 | impl Deref for Data { | ||
| 186 | type Target = [u8]; | ||
| 187 | |||
| 188 | #[inline] | ||
| 189 | fn deref(&self) -> &[u8] { | ||
| 190 | &self.bytes[..usize::from(self.len)] | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 194 | impl DerefMut for Data { | ||
| 195 | #[inline] | ||
| 196 | fn deref_mut(&mut self) -> &mut [u8] { | ||
| 197 | &mut self.bytes[..usize::from(self.len)] | ||
| 198 | } | ||
| 199 | } | ||
| 200 | |||
| 201 | impl AsRef<[u8]> for Data { | ||
| 202 | #[inline] | ||
| 203 | fn as_ref(&self) -> &[u8] { | ||
| 204 | self.deref() | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | impl AsMut<[u8]> for Data { | ||
| 209 | #[inline] | ||
| 210 | fn as_mut(&mut self) -> &mut [u8] { | ||
| 211 | self.deref_mut() | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | impl PartialEq for Data { | ||
| 216 | fn eq(&self, other: &Self) -> bool { | ||
| 217 | self.as_ref() == other.as_ref() | ||
| 218 | } | ||
| 219 | } | ||
| 220 | |||
| 221 | impl Eq for Data {} | ||
| 222 | |||
| 223 | #[cfg(feature = "defmt")] | ||
| 224 | impl defmt::Format for Data { | ||
| 225 | fn format(&self, fmt: defmt::Formatter<'_>) { | ||
| 226 | self.as_ref().format(fmt) | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | macro_rules! data_from_array { | ||
| 231 | ( $($len:literal),+ ) => { | ||
| 232 | $( | ||
| 233 | impl From<[u8; $len]> for Data { | ||
| 234 | #[inline] | ||
| 235 | fn from(arr: [u8; $len]) -> Self { | ||
| 236 | let mut bytes = [0; 8]; | ||
| 237 | bytes[..$len].copy_from_slice(&arr); | ||
| 238 | Self { | ||
| 239 | len: $len, | ||
| 240 | bytes, | ||
| 241 | } | ||
| 242 | } | ||
| 243 | } | ||
| 244 | )+ | ||
| 245 | }; | ||
| 246 | } | ||
| 247 | |||
| 248 | data_from_array!(0, 1, 2, 3, 4, 5, 6, 7, 8); | ||
diff --git a/embassy-stm32/src/can/bx/id.rs b/embassy-stm32/src/can/bx/id.rs deleted file mode 100644 index 9fdcd8319..000000000 --- a/embassy-stm32/src/can/bx/id.rs +++ /dev/null | |||
| @@ -1,113 +0,0 @@ | |||
| 1 | //! CAN Identifiers. | ||
| 2 | |||
| 3 | /// Standard 11-bit CAN Identifier (`0..=0x7FF`). | ||
| 4 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 5 | pub struct StandardId(u16); | ||
| 6 | |||
| 7 | impl StandardId { | ||
| 8 | /// CAN ID `0`, the highest priority. | ||
| 9 | pub const ZERO: Self = Self(0); | ||
| 10 | |||
| 11 | /// CAN ID `0x7FF`, the lowest priority. | ||
| 12 | pub const MAX: Self = Self(0x7FF); | ||
| 13 | |||
| 14 | /// Tries to create a `StandardId` from a raw 16-bit integer. | ||
| 15 | /// | ||
| 16 | /// This will return `None` if `raw` is out of range of an 11-bit integer (`> 0x7FF`). | ||
| 17 | #[inline] | ||
| 18 | pub const fn new(raw: u16) -> Option<Self> { | ||
| 19 | if raw <= 0x7FF { | ||
| 20 | Some(Self(raw)) | ||
| 21 | } else { | ||
| 22 | None | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | /// Creates a new `StandardId` without checking if it is inside the valid range. | ||
| 27 | /// | ||
| 28 | /// # Safety | ||
| 29 | /// | ||
| 30 | /// The caller must ensure that `raw` is in the valid range, otherwise the behavior is | ||
| 31 | /// undefined. | ||
| 32 | #[inline] | ||
| 33 | pub const unsafe fn new_unchecked(raw: u16) -> Self { | ||
| 34 | Self(raw) | ||
| 35 | } | ||
| 36 | |||
| 37 | /// Returns this CAN Identifier as a raw 16-bit integer. | ||
| 38 | #[inline] | ||
| 39 | pub fn as_raw(&self) -> u16 { | ||
| 40 | self.0 | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | /// Extended 29-bit CAN Identifier (`0..=1FFF_FFFF`). | ||
| 45 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 46 | pub struct ExtendedId(u32); | ||
| 47 | |||
| 48 | impl ExtendedId { | ||
| 49 | /// CAN ID `0`, the highest priority. | ||
| 50 | pub const ZERO: Self = Self(0); | ||
| 51 | |||
| 52 | /// CAN ID `0x1FFFFFFF`, the lowest priority. | ||
| 53 | pub const MAX: Self = Self(0x1FFF_FFFF); | ||
| 54 | |||
| 55 | /// Tries to create a `ExtendedId` from a raw 32-bit integer. | ||
| 56 | /// | ||
| 57 | /// This will return `None` if `raw` is out of range of an 29-bit integer (`> 0x1FFF_FFFF`). | ||
| 58 | #[inline] | ||
| 59 | pub const fn new(raw: u32) -> Option<Self> { | ||
| 60 | if raw <= 0x1FFF_FFFF { | ||
| 61 | Some(Self(raw)) | ||
| 62 | } else { | ||
| 63 | None | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | /// Creates a new `ExtendedId` without checking if it is inside the valid range. | ||
| 68 | /// | ||
| 69 | /// # Safety | ||
| 70 | /// | ||
| 71 | /// The caller must ensure that `raw` is in the valid range, otherwise the behavior is | ||
| 72 | /// undefined. | ||
| 73 | #[inline] | ||
| 74 | pub const unsafe fn new_unchecked(raw: u32) -> Self { | ||
| 75 | Self(raw) | ||
| 76 | } | ||
| 77 | |||
| 78 | /// Returns this CAN Identifier as a raw 32-bit integer. | ||
| 79 | #[inline] | ||
| 80 | pub fn as_raw(&self) -> u32 { | ||
| 81 | self.0 | ||
| 82 | } | ||
| 83 | |||
| 84 | /// Returns the Base ID part of this extended identifier. | ||
| 85 | pub fn standard_id(&self) -> StandardId { | ||
| 86 | // ID-28 to ID-18 | ||
| 87 | StandardId((self.0 >> 18) as u16) | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | /// A CAN Identifier (standard or extended). | ||
| 92 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 93 | pub enum Id { | ||
| 94 | /// Standard 11-bit Identifier (`0..=0x7FF`). | ||
| 95 | Standard(StandardId), | ||
| 96 | |||
| 97 | /// Extended 29-bit Identifier (`0..=0x1FFF_FFFF`). | ||
| 98 | Extended(ExtendedId), | ||
| 99 | } | ||
| 100 | |||
| 101 | impl From<StandardId> for Id { | ||
| 102 | #[inline] | ||
| 103 | fn from(id: StandardId) -> Self { | ||
| 104 | Id::Standard(id) | ||
| 105 | } | ||
| 106 | } | ||
| 107 | |||
| 108 | impl From<ExtendedId> for Id { | ||
| 109 | #[inline] | ||
| 110 | fn from(id: ExtendedId) -> Self { | ||
| 111 | Id::Extended(id) | ||
| 112 | } | ||
| 113 | } | ||
diff --git a/embassy-stm32/src/can/bx/mod.rs b/embassy-stm32/src/can/bx/mod.rs index f639260a1..650d4414b 100644 --- a/embassy-stm32/src/can/bx/mod.rs +++ b/embassy-stm32/src/can/bx/mod.rs | |||
| @@ -25,19 +25,26 @@ | |||
| 25 | 25 | ||
| 26 | //mod embedded_hal; | 26 | //mod embedded_hal; |
| 27 | pub mod filter; | 27 | pub mod filter; |
| 28 | mod frame; | ||
| 29 | mod id; | ||
| 30 | 28 | ||
| 31 | #[allow(clippy::all)] // generated code | 29 | #[allow(clippy::all)] // generated code |
| 32 | use core::cmp::{Ord, Ordering}; | 30 | use core::cmp::{Ord, Ordering}; |
| 33 | use core::convert::{Infallible, TryInto}; | 31 | use core::convert::{Infallible, Into, TryInto}; |
| 34 | use core::marker::PhantomData; | 32 | use core::marker::PhantomData; |
| 35 | use core::mem; | 33 | use core::mem; |
| 36 | 34 | ||
| 37 | pub use id::{ExtendedId, Id, StandardId}; | 35 | pub use embedded_can::{ExtendedId, Id, StandardId}; |
| 36 | |||
| 37 | /// CAN Header: includes ID and length | ||
| 38 | pub type Header = crate::can::frame::Header; | ||
| 39 | |||
| 40 | /// Data for a CAN Frame | ||
| 41 | pub type Data = crate::can::frame::ClassicData; | ||
| 42 | |||
| 43 | /// CAN Frame | ||
| 44 | pub type Frame = crate::can::frame::ClassicFrame; | ||
| 38 | 45 | ||
| 39 | use crate::can::bx::filter::MasterFilters; | 46 | use crate::can::bx::filter::MasterFilters; |
| 40 | pub use crate::can::bx::frame::{Data, Frame, FramePriority}; | 47 | use crate::can::frame::ClassicData; |
| 41 | 48 | ||
| 42 | /// A bxCAN peripheral instance. | 49 | /// A bxCAN peripheral instance. |
| 43 | /// | 50 | /// |
| @@ -148,13 +155,13 @@ impl IdReg { | |||
| 148 | /// Sets the remote transmission (RTR) flag. This marks the identifier as | 155 | /// Sets the remote transmission (RTR) flag. This marks the identifier as |
| 149 | /// being part of a remote frame. | 156 | /// being part of a remote frame. |
| 150 | #[must_use = "returns a new IdReg without modifying `self`"] | 157 | #[must_use = "returns a new IdReg without modifying `self`"] |
| 151 | fn with_rtr(self, rtr: bool) -> IdReg { | 158 | /*fn with_rtr(self, rtr: bool) -> IdReg { |
| 152 | if rtr { | 159 | if rtr { |
| 153 | Self(self.0 | Self::RTR_MASK) | 160 | Self(self.0 | Self::RTR_MASK) |
| 154 | } else { | 161 | } else { |
| 155 | Self(self.0 & !Self::RTR_MASK) | 162 | Self(self.0 & !Self::RTR_MASK) |
| 156 | } | 163 | } |
| 157 | } | 164 | }*/ |
| 158 | 165 | ||
| 159 | /// Returns the identifier. | 166 | /// Returns the identifier. |
| 160 | fn to_id(self) -> Id { | 167 | fn to_id(self) -> Id { |
| @@ -165,15 +172,28 @@ impl IdReg { | |||
| 165 | } | 172 | } |
| 166 | } | 173 | } |
| 167 | 174 | ||
| 175 | /// Returns the identifier. | ||
| 176 | fn id(self) -> embedded_can::Id { | ||
| 177 | if self.is_extended() { | ||
| 178 | embedded_can::ExtendedId::new(self.0 >> Self::EXTENDED_SHIFT) | ||
| 179 | .unwrap() | ||
| 180 | .into() | ||
| 181 | } else { | ||
| 182 | embedded_can::StandardId::new((self.0 >> Self::STANDARD_SHIFT) as u16) | ||
| 183 | .unwrap() | ||
| 184 | .into() | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 168 | /// Returns `true` if the identifier is an extended identifier. | 188 | /// Returns `true` if the identifier is an extended identifier. |
| 169 | fn is_extended(self) -> bool { | 189 | fn is_extended(self) -> bool { |
| 170 | self.0 & Self::IDE_MASK != 0 | 190 | self.0 & Self::IDE_MASK != 0 |
| 171 | } | 191 | } |
| 172 | 192 | ||
| 173 | /// Returns `true` if the identifier is a standard identifier. | 193 | /// Returns `true` if the identifier is a standard identifier. |
| 174 | fn is_standard(self) -> bool { | 194 | /*fn is_standard(self) -> bool { |
| 175 | !self.is_extended() | 195 | !self.is_extended() |
| 176 | } | 196 | }*/ |
| 177 | 197 | ||
| 178 | /// Returns `true` if the identifer is part of a remote frame (RTR bit set). | 198 | /// Returns `true` if the identifer is part of a remote frame (RTR bit set). |
| 179 | fn rtr(self) -> bool { | 199 | fn rtr(self) -> bool { |
| @@ -181,6 +201,21 @@ impl IdReg { | |||
| 181 | } | 201 | } |
| 182 | } | 202 | } |
| 183 | 203 | ||
| 204 | impl From<&embedded_can::Id> for IdReg { | ||
| 205 | fn from(eid: &embedded_can::Id) -> Self { | ||
| 206 | match eid { | ||
| 207 | embedded_can::Id::Standard(id) => IdReg::new_standard(StandardId::new(id.as_raw()).unwrap()), | ||
| 208 | embedded_can::Id::Extended(id) => IdReg::new_extended(ExtendedId::new(id.as_raw()).unwrap()), | ||
| 209 | } | ||
| 210 | } | ||
| 211 | } | ||
| 212 | |||
| 213 | impl From<IdReg> for embedded_can::Id { | ||
| 214 | fn from(idr: IdReg) -> Self { | ||
| 215 | idr.id() | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 184 | /// `IdReg` is ordered by priority. | 219 | /// `IdReg` is ordered by priority. |
| 185 | impl Ord for IdReg { | 220 | impl Ord for IdReg { |
| 186 | fn cmp(&self, other: &Self) -> Ordering { | 221 | fn cmp(&self, other: &Self) -> Ordering { |
| @@ -682,9 +717,9 @@ where | |||
| 682 | // The controller schedules pending frames of same priority based on the | 717 | // The controller schedules pending frames of same priority based on the |
| 683 | // mailbox index instead. As a workaround check all pending mailboxes | 718 | // mailbox index instead. As a workaround check all pending mailboxes |
| 684 | // and only accept higher priority frames. | 719 | // and only accept higher priority frames. |
| 685 | self.check_priority(0, frame.id)?; | 720 | self.check_priority(0, frame.id().into())?; |
| 686 | self.check_priority(1, frame.id)?; | 721 | self.check_priority(1, frame.id().into())?; |
| 687 | self.check_priority(2, frame.id)?; | 722 | self.check_priority(2, frame.id().into())?; |
| 688 | 723 | ||
| 689 | let all_frames_are_pending = !tsr.tme(0) && !tsr.tme(1) && !tsr.tme(2); | 724 | let all_frames_are_pending = !tsr.tme(0) && !tsr.tme(1) && !tsr.tme(2); |
| 690 | if all_frames_are_pending { | 725 | if all_frames_are_pending { |
| @@ -739,14 +774,15 @@ where | |||
| 739 | debug_assert!(idx < 3); | 774 | debug_assert!(idx < 3); |
| 740 | 775 | ||
| 741 | let mb = self.canregs.tx(idx); | 776 | let mb = self.canregs.tx(idx); |
| 742 | mb.tdtr().write(|w| w.set_dlc(frame.dlc() as u8)); | 777 | mb.tdtr().write(|w| w.set_dlc(frame.header().len() as u8)); |
| 743 | 778 | ||
| 744 | mb.tdlr() | 779 | mb.tdlr() |
| 745 | .write(|w| w.0 = u32::from_ne_bytes(frame.data.bytes[0..4].try_into().unwrap())); | 780 | .write(|w| w.0 = u32::from_ne_bytes(frame.data()[0..4].try_into().unwrap())); |
| 746 | mb.tdhr() | 781 | mb.tdhr() |
| 747 | .write(|w| w.0 = u32::from_ne_bytes(frame.data.bytes[4..8].try_into().unwrap())); | 782 | .write(|w| w.0 = u32::from_ne_bytes(frame.data()[4..8].try_into().unwrap())); |
| 783 | let id: IdReg = frame.id().into(); | ||
| 748 | mb.tir().write(|w| { | 784 | mb.tir().write(|w| { |
| 749 | w.0 = frame.id.0; | 785 | w.0 = id.0; |
| 750 | w.set_txrq(true); | 786 | w.set_txrq(true); |
| 751 | }); | 787 | }); |
| 752 | } | 788 | } |
| @@ -756,16 +792,17 @@ where | |||
| 756 | debug_assert!(idx < 3); | 792 | debug_assert!(idx < 3); |
| 757 | 793 | ||
| 758 | let mb = self.canregs.tx(idx); | 794 | let mb = self.canregs.tx(idx); |
| 759 | // Read back the pending frame. | 795 | |
| 760 | let mut pending_frame = Frame { | 796 | let id = IdReg(mb.tir().read().0).id(); |
| 761 | id: IdReg(mb.tir().read().0), | 797 | let mut data = [0xff; 8]; |
| 762 | data: Data::empty(), | 798 | data[0..4].copy_from_slice(&mb.tdlr().read().0.to_ne_bytes()); |
| 763 | }; | 799 | data[4..8].copy_from_slice(&mb.tdhr().read().0.to_ne_bytes()); |
| 764 | pending_frame.data.bytes[0..4].copy_from_slice(&mb.tdlr().read().0.to_ne_bytes()); | 800 | let len = mb.tdtr().read().dlc(); |
| 765 | pending_frame.data.bytes[4..8].copy_from_slice(&mb.tdhr().read().0.to_ne_bytes()); | 801 | |
| 766 | pending_frame.data.len = mb.tdtr().read().dlc(); | 802 | Some(Frame::new( |
| 767 | 803 | Header::new(id, len, false), | |
| 768 | Some(pending_frame) | 804 | ClassicData::new(&data).unwrap(), |
| 805 | )) | ||
| 769 | } else { | 806 | } else { |
| 770 | // Abort request failed because the frame was already sent (or being sent) on | 807 | // Abort request failed because the frame was already sent (or being sent) on |
| 771 | // the bus. All mailboxes are now free. This can happen for small prescaler | 808 | // the bus. All mailboxes are now free. This can happen for small prescaler |
| @@ -898,18 +935,19 @@ fn receive_fifo(canregs: crate::pac::can::Can, fifo_nr: usize) -> nb::Result<Fra | |||
| 898 | } | 935 | } |
| 899 | 936 | ||
| 900 | // Read the frame. | 937 | // Read the frame. |
| 901 | let mut frame = Frame { | 938 | let id = IdReg(rx.rir().read().0).id(); |
| 902 | id: IdReg(rx.rir().read().0), | 939 | let mut data = [0xff; 8]; |
| 903 | data: [0; 8].into(), | 940 | data[0..4].copy_from_slice(&rx.rdlr().read().0.to_ne_bytes()); |
| 904 | }; | 941 | data[4..8].copy_from_slice(&rx.rdhr().read().0.to_ne_bytes()); |
| 905 | frame.data[0..4].copy_from_slice(&rx.rdlr().read().0.to_ne_bytes()); | 942 | let len = rx.rdtr().read().dlc(); |
| 906 | frame.data[4..8].copy_from_slice(&rx.rdhr().read().0.to_ne_bytes()); | ||
| 907 | frame.data.len = rx.rdtr().read().dlc(); | ||
| 908 | 943 | ||
| 909 | // Release the mailbox. | 944 | // Release the mailbox. |
| 910 | rfr.write(|w| w.set_rfom(true)); | 945 | rfr.write(|w| w.set_rfom(true)); |
| 911 | 946 | ||
| 912 | Ok(frame) | 947 | Ok(Frame::new( |
| 948 | Header::new(id, len, false), | ||
| 949 | ClassicData::new(&data).unwrap(), | ||
| 950 | )) | ||
| 913 | } | 951 | } |
| 914 | 952 | ||
| 915 | /// Identifies one of the two receive FIFOs. | 953 | /// Identifies one of the two receive FIFOs. |
diff --git a/embassy-stm32/src/can/bxcan.rs b/embassy-stm32/src/can/bxcan.rs index acd831937..4d96df8aa 100644 --- a/embassy-stm32/src/can/bxcan.rs +++ b/embassy-stm32/src/can/bxcan.rs | |||
| @@ -6,7 +6,7 @@ use core::task::Poll; | |||
| 6 | 6 | ||
| 7 | pub mod bx; | 7 | pub mod bx; |
| 8 | 8 | ||
| 9 | pub use bx::{filter, Data, ExtendedId, Fifo, Frame, Id, StandardId}; | 9 | pub use bx::{filter, Data, ExtendedId, Fifo, Frame, Header, Id, StandardId}; |
| 10 | use embassy_hal_internal::{into_ref, PeripheralRef}; | 10 | use embassy_hal_internal::{into_ref, PeripheralRef}; |
| 11 | use futures::FutureExt; | 11 | use futures::FutureExt; |
| 12 | 12 | ||
| @@ -18,19 +18,20 @@ use crate::{interrupt, peripherals, Peripheral}; | |||
| 18 | 18 | ||
| 19 | pub mod enums; | 19 | pub mod enums; |
| 20 | use enums::*; | 20 | use enums::*; |
| 21 | pub mod frame; | ||
| 21 | pub mod util; | 22 | pub mod util; |
| 22 | 23 | ||
| 23 | /// Contains CAN frame and additional metadata. | 24 | /// Contains CAN frame and additional metadata. |
| 24 | /// | 25 | /// |
| 25 | /// Timestamp is available if `time` feature is enabled. | 26 | /// Timestamp is available if `time` feature is enabled. |
| 26 | #[derive(Debug, Clone, PartialEq, Eq)] | 27 | #[derive(Debug, Clone)] |
| 27 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 28 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 28 | pub struct Envelope { | 29 | pub struct Envelope { |
| 29 | /// Reception time. | 30 | /// Reception time. |
| 30 | #[cfg(feature = "time")] | 31 | #[cfg(feature = "time")] |
| 31 | pub ts: embassy_time::Instant, | 32 | pub ts: embassy_time::Instant, |
| 32 | /// The actual CAN frame. | 33 | /// The actual CAN frame. |
| 33 | pub frame: crate::can::bx::Frame, | 34 | pub frame: Frame, |
| 34 | } | 35 | } |
| 35 | 36 | ||
| 36 | /// Interrupt handler. | 37 | /// Interrupt handler. |
| @@ -260,20 +261,20 @@ impl<'d, T: Instance> Can<'d, T> { | |||
| 260 | } | 261 | } |
| 261 | 262 | ||
| 262 | let rir = fifo.rir().read(); | 263 | let rir = fifo.rir().read(); |
| 263 | let id = if rir.ide() == Ide::STANDARD { | 264 | let id: embedded_can::Id = if rir.ide() == Ide::STANDARD { |
| 264 | Id::from(StandardId::new_unchecked(rir.stid())) | 265 | embedded_can::StandardId::new(rir.stid()).unwrap().into() |
| 265 | } else { | 266 | } else { |
| 266 | let stid = (rir.stid() & 0x7FF) as u32; | 267 | let stid = (rir.stid() & 0x7FF) as u32; |
| 267 | let exid = rir.exid() & 0x3FFFF; | 268 | let exid = rir.exid() & 0x3FFFF; |
| 268 | let id = (stid << 18) | (exid); | 269 | let id = (stid << 18) | (exid); |
| 269 | Id::from(ExtendedId::new_unchecked(id)) | 270 | embedded_can::ExtendedId::new(id).unwrap().into() |
| 270 | }; | 271 | }; |
| 271 | let data_len = fifo.rdtr().read().dlc() as usize; | 272 | let data_len = fifo.rdtr().read().dlc(); |
| 272 | let mut data: [u8; 8] = [0; 8]; | 273 | let mut data: [u8; 8] = [0; 8]; |
| 273 | data[0..4].copy_from_slice(&fifo.rdlr().read().0.to_ne_bytes()); | 274 | data[0..4].copy_from_slice(&fifo.rdlr().read().0.to_ne_bytes()); |
| 274 | data[4..8].copy_from_slice(&fifo.rdhr().read().0.to_ne_bytes()); | 275 | data[4..8].copy_from_slice(&fifo.rdhr().read().0.to_ne_bytes()); |
| 275 | 276 | ||
| 276 | let frame = Frame::new_data(id, Data::new(&data[0..data_len]).unwrap()); | 277 | let frame = Frame::new(Header::new(id, data_len, false), Data::new(&data).unwrap()); |
| 277 | let envelope = Envelope { | 278 | let envelope = Envelope { |
| 278 | #[cfg(feature = "time")] | 279 | #[cfg(feature = "time")] |
| 279 | ts, | 280 | ts, |
diff --git a/embassy-stm32/src/can/fd/peripheral.rs b/embassy-stm32/src/can/fd/peripheral.rs index 8ec09ac12..cce4e5e8d 100644 --- a/embassy-stm32/src/can/fd/peripheral.rs +++ b/embassy-stm32/src/can/fd/peripheral.rs | |||
| @@ -182,7 +182,7 @@ impl Registers { | |||
| 182 | DataLength::Fdcan(len) => len, | 182 | DataLength::Fdcan(len) => len, |
| 183 | DataLength::Classic(len) => len, | 183 | DataLength::Classic(len) => len, |
| 184 | }; | 184 | }; |
| 185 | if len as usize > ClassicFrame::MAX_DATA_LEN { | 185 | if len as usize > ClassicData::MAX_DATA_LEN { |
| 186 | return None; | 186 | return None; |
| 187 | } | 187 | } |
| 188 | 188 | ||
diff --git a/embassy-stm32/src/can/frame.rs b/embassy-stm32/src/can/frame.rs index 9c293035d..0dc74d299 100644 --- a/embassy-stm32/src/can/frame.rs +++ b/embassy-stm32/src/can/frame.rs | |||
| @@ -9,6 +9,20 @@ pub struct Header { | |||
| 9 | flags: u8, | 9 | flags: u8, |
| 10 | } | 10 | } |
| 11 | 11 | ||
| 12 | #[cfg(feature = "defmt")] | ||
| 13 | impl defmt::Format for Header { | ||
| 14 | fn format(&self, fmt: defmt::Formatter<'_>) { | ||
| 15 | match self.id() { | ||
| 16 | embedded_can::Id::Standard(id) => { | ||
| 17 | defmt::write!(fmt, "Can Standard ID={:x} len={}", id.as_raw(), self.len,) | ||
| 18 | } | ||
| 19 | embedded_can::Id::Extended(id) => { | ||
| 20 | defmt::write!(fmt, "Can Extended ID={:x} len={}", id.as_raw(), self.len,) | ||
| 21 | } | ||
| 22 | } | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 12 | impl Header { | 26 | impl Header { |
| 13 | const FLAG_RTR: usize = 0; // Remote | 27 | const FLAG_RTR: usize = 0; // Remote |
| 14 | const FLAG_FDCAN: usize = 1; // FDCan vs Classic CAN | 28 | const FLAG_FDCAN: usize = 1; // FDCan vs Classic CAN |
| @@ -54,6 +68,14 @@ impl Header { | |||
| 54 | pub fn bit_rate_switching(&self) -> bool { | 68 | pub fn bit_rate_switching(&self) -> bool { |
| 55 | self.flags.get_bit(Self::FLAG_BRS) | 69 | self.flags.get_bit(Self::FLAG_BRS) |
| 56 | } | 70 | } |
| 71 | |||
| 72 | /// Get priority of frame | ||
| 73 | pub(crate) fn priority(&self) -> u32 { | ||
| 74 | match self.id() { | ||
| 75 | embedded_can::Id::Standard(id) => (id.as_raw() as u32) << 18, | ||
| 76 | embedded_can::Id::Extended(id) => id.as_raw(), | ||
| 77 | } | ||
| 78 | } | ||
| 57 | } | 79 | } |
| 58 | 80 | ||
| 59 | /// Trait for FDCAN frame types, providing ability to construct from a Header | 81 | /// Trait for FDCAN frame types, providing ability to construct from a Header |
| @@ -70,11 +92,13 @@ pub trait CanHeader: Sized { | |||
| 70 | /// | 92 | /// |
| 71 | /// Contains 0 to 8 Bytes of data. | 93 | /// Contains 0 to 8 Bytes of data. |
| 72 | #[derive(Debug, Copy, Clone)] | 94 | #[derive(Debug, Copy, Clone)] |
| 95 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 73 | pub struct ClassicData { | 96 | pub struct ClassicData { |
| 74 | pub(crate) bytes: [u8; 8], | 97 | pub(crate) bytes: [u8; Self::MAX_DATA_LEN], |
| 75 | } | 98 | } |
| 76 | 99 | ||
| 77 | impl ClassicData { | 100 | impl ClassicData { |
| 101 | pub(crate) const MAX_DATA_LEN: usize = 8; | ||
| 78 | /// Creates a data payload from a raw byte slice. | 102 | /// Creates a data payload from a raw byte slice. |
| 79 | /// | 103 | /// |
| 80 | /// Returns `None` if `data` is more than 64 bytes (which is the maximum) or | 104 | /// Returns `None` if `data` is more than 64 bytes (which is the maximum) or |
| @@ -110,19 +134,34 @@ impl ClassicData { | |||
| 110 | } | 134 | } |
| 111 | } | 135 | } |
| 112 | 136 | ||
| 137 | impl From<&[u8]> for ClassicData { | ||
| 138 | fn from(d: &[u8]) -> Self { | ||
| 139 | ClassicData::new(d).unwrap() | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
| 113 | /// Frame with up to 8 bytes of data payload as per Classic CAN | 143 | /// Frame with up to 8 bytes of data payload as per Classic CAN |
| 114 | #[derive(Debug, Copy, Clone)] | 144 | #[derive(Debug, Copy, Clone)] |
| 145 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 115 | pub struct ClassicFrame { | 146 | pub struct ClassicFrame { |
| 116 | can_header: Header, | 147 | can_header: Header, |
| 117 | data: ClassicData, | 148 | data: ClassicData, |
| 118 | } | 149 | } |
| 119 | 150 | ||
| 120 | impl ClassicFrame { | 151 | impl ClassicFrame { |
| 121 | pub(crate) const MAX_DATA_LEN: usize = 8; | ||
| 122 | |||
| 123 | /// Create a new CAN classic Frame | 152 | /// Create a new CAN classic Frame |
| 124 | pub fn new(can_header: Header, data: ClassicData) -> ClassicFrame { | 153 | pub fn new(can_header: Header, data: impl Into<ClassicData>) -> ClassicFrame { |
| 125 | ClassicFrame { can_header, data } | 154 | ClassicFrame { |
| 155 | can_header, | ||
| 156 | data: data.into(), | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | /// Creates a new data frame. | ||
| 161 | pub fn new_data(id: impl Into<embedded_can::Id>, data: &[u8]) -> Self { | ||
| 162 | let eid: embedded_can::Id = id.into(); | ||
| 163 | let header = Header::new(eid, data.len() as u8, false); | ||
| 164 | Self::new(header, data) | ||
| 126 | } | 165 | } |
| 127 | 166 | ||
| 128 | /// Create new extended frame | 167 | /// Create new extended frame |
| @@ -181,6 +220,11 @@ impl ClassicFrame { | |||
| 181 | pub fn data(&self) -> &[u8] { | 220 | pub fn data(&self) -> &[u8] { |
| 182 | &self.data.raw() | 221 | &self.data.raw() |
| 183 | } | 222 | } |
| 223 | |||
| 224 | /// Get priority of frame | ||
| 225 | pub fn priority(&self) -> u32 { | ||
| 226 | self.header().priority() | ||
| 227 | } | ||
| 184 | } | 228 | } |
| 185 | 229 | ||
| 186 | impl embedded_can::Frame for ClassicFrame { | 230 | impl embedded_can::Frame for ClassicFrame { |
diff --git a/examples/stm32f1/src/bin/can.rs b/examples/stm32f1/src/bin/can.rs index 00d61096f..a43fb4427 100644 --- a/examples/stm32f1/src/bin/can.rs +++ b/examples/stm32f1/src/bin/can.rs | |||
| @@ -46,16 +46,16 @@ async fn main(_spawner: Spawner) { | |||
| 46 | 46 | ||
| 47 | let mut i: u8 = 0; | 47 | let mut i: u8 = 0; |
| 48 | loop { | 48 | loop { |
| 49 | let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), [i, 0, 1, 2, 3, 4, 5, 6]); | 49 | let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), &[i, 0, 1, 2, 3, 4, 5, 6]); |
| 50 | can.write(&tx_frame).await; | 50 | can.write(&tx_frame).await; |
| 51 | 51 | ||
| 52 | match can.read().await { | 52 | match can.read().await { |
| 53 | Ok(env) => match env.frame.id() { | 53 | Ok(env) => match env.frame.id() { |
| 54 | Id::Extended(id) => { | 54 | Id::Extended(id) => { |
| 55 | defmt::println!("Extended Frame id={:x} {:02x}", id.as_raw(), env.frame.data().unwrap()); | 55 | defmt::println!("Extended Frame id={:x} {:02x}", id.as_raw(), env.frame.data()); |
| 56 | } | 56 | } |
| 57 | Id::Standard(id) => { | 57 | Id::Standard(id) => { |
| 58 | defmt::println!("Standard Frame id={:x} {:02x}", id.as_raw(), env.frame.data().unwrap()); | 58 | defmt::println!("Standard Frame id={:x} {:02x}", id.as_raw(), env.frame.data()); |
| 59 | } | 59 | } |
| 60 | }, | 60 | }, |
| 61 | Err(err) => { | 61 | Err(err) => { |
diff --git a/examples/stm32f4/src/bin/can.rs b/examples/stm32f4/src/bin/can.rs index b20af8cf1..2ed631a46 100644 --- a/examples/stm32f4/src/bin/can.rs +++ b/examples/stm32f4/src/bin/can.rs | |||
| @@ -51,7 +51,7 @@ async fn main(_spawner: Spawner) { | |||
| 51 | 51 | ||
| 52 | let mut i: u8 = 0; | 52 | let mut i: u8 = 0; |
| 53 | loop { | 53 | loop { |
| 54 | 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(); | 55 | let tx_ts = Instant::now(); |
| 56 | can.write(&tx_frame).await; | 56 | can.write(&tx_frame).await; |
| 57 | 57 | ||
| @@ -65,7 +65,7 @@ async fn main(_spawner: Spawner) { | |||
| 65 | 65 | ||
| 66 | info!( | 66 | info!( |
| 67 | "loopback frame {=u8}, latency: {} us", | 67 | "loopback frame {=u8}, latency: {} us", |
| 68 | unwrap!(envelope.frame.data())[0], | 68 | envelope.frame.data()[0], |
| 69 | latency.as_micros() | 69 | latency.as_micros() |
| 70 | ); | 70 | ); |
| 71 | i += 1; | 71 | i += 1; |
diff --git a/examples/stm32f7/src/bin/can.rs b/examples/stm32f7/src/bin/can.rs index c3e14bbf4..2701196ed 100644 --- a/examples/stm32f7/src/bin/can.rs +++ b/examples/stm32f7/src/bin/can.rs | |||
| @@ -26,7 +26,7 @@ bind_interrupts!(struct Irqs { | |||
| 26 | #[embassy_executor::task] | 26 | #[embassy_executor::task] |
| 27 | pub async fn send_can_message(tx: &'static mut CanTx<'static, CAN3>) { | 27 | pub async fn send_can_message(tx: &'static mut CanTx<'static, CAN3>) { |
| 28 | loop { | 28 | loop { |
| 29 | let frame = Frame::new_data(unwrap!(StandardId::new(0 as _)), [0]); | 29 | let frame = Frame::new_data(unwrap!(StandardId::new(0 as _)), &[0]); |
| 30 | tx.write(&frame).await; | 30 | tx.write(&frame).await; |
| 31 | embassy_time::Timer::after_secs(1).await; | 31 | embassy_time::Timer::after_secs(1).await; |
| 32 | } | 32 | } |
diff --git a/tests/stm32/src/bin/can.rs b/tests/stm32/src/bin/can.rs index e869e8fb9..e36137b38 100644 --- a/tests/stm32/src/bin/can.rs +++ b/tests/stm32/src/bin/can.rs | |||
| @@ -60,7 +60,7 @@ async fn main(_spawner: Spawner) { | |||
| 60 | 60 | ||
| 61 | let mut i: u8 = 0; | 61 | let mut i: u8 = 0; |
| 62 | loop { | 62 | loop { |
| 63 | let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), [i]); | 63 | let tx_frame = Frame::new_data(unwrap!(StandardId::new(i as _)), &[i]); |
| 64 | 64 | ||
| 65 | info!("Transmitting frame..."); | 65 | info!("Transmitting frame..."); |
| 66 | let tx_ts = Instant::now(); | 66 | let tx_ts = Instant::now(); |
| @@ -70,7 +70,7 @@ async fn main(_spawner: Spawner) { | |||
| 70 | info!("Frame received!"); | 70 | info!("Frame received!"); |
| 71 | 71 | ||
| 72 | info!("loopback time {}", envelope.ts); | 72 | info!("loopback time {}", envelope.ts); |
| 73 | info!("loopback frame {=u8}", envelope.frame.data().unwrap()[0]); | 73 | info!("loopback frame {=u8}", envelope.frame.data()[0]); |
| 74 | 74 | ||
| 75 | let latency = envelope.ts.saturating_duration_since(tx_ts); | 75 | let latency = envelope.ts.saturating_duration_since(tx_ts); |
| 76 | info!("loopback latency {} us", latency.as_micros()); | 76 | info!("loopback latency {} us", latency.as_micros()); |
