464 lines
13 KiB
Rust
464 lines
13 KiB
Rust
//! Definition for CAN Frames
|
|
use bit_field::BitField;
|
|
|
|
use crate::can::enums::FrameCreateError;
|
|
|
|
/// Calculate proper timestamp when available.
|
|
#[cfg(feature = "time")]
|
|
pub type Timestamp = embassy_time::Instant;
|
|
|
|
/// Raw register timestamp
|
|
#[cfg(not(feature = "time"))]
|
|
pub type Timestamp = u16;
|
|
|
|
/// CAN Header, without meta data
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub struct Header {
|
|
id: embedded_can::Id,
|
|
len: u8,
|
|
flags: u8,
|
|
}
|
|
|
|
#[cfg(feature = "defmt")]
|
|
impl defmt::Format for Header {
|
|
fn format(&self, fmt: defmt::Formatter<'_>) {
|
|
match self.id() {
|
|
embedded_can::Id::Standard(id) => {
|
|
defmt::write!(fmt, "Can Standard ID={:x} len={}", id.as_raw(), self.len,)
|
|
}
|
|
embedded_can::Id::Extended(id) => {
|
|
defmt::write!(fmt, "Can Extended ID={:x} len={}", id.as_raw(), self.len,)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Header {
|
|
const FLAG_RTR: usize = 0; // Remote
|
|
const FLAG_FDCAN: usize = 1; // FDCan vs Classic CAN
|
|
const FLAG_BRS: usize = 2; // Bit-rate switching, ignored for Classic CAN
|
|
|
|
/// Create new CAN Header
|
|
pub fn new(id: embedded_can::Id, len: u8, rtr: bool) -> Header {
|
|
let mut flags = 0u8;
|
|
flags.set_bit(Self::FLAG_RTR, rtr);
|
|
Header { id, len, flags }
|
|
}
|
|
|
|
/// Create new CAN FD Header
|
|
pub fn new_fd(id: embedded_can::Id, len: u8, rtr: bool, brs: bool) -> Header {
|
|
let mut flags = 0u8;
|
|
flags.set_bit(Self::FLAG_RTR, rtr);
|
|
flags.set_bit(Self::FLAG_FDCAN, true);
|
|
flags.set_bit(Self::FLAG_BRS, brs);
|
|
Header { id, len, flags }
|
|
}
|
|
|
|
/// Return ID
|
|
pub fn id(&self) -> &embedded_can::Id {
|
|
&self.id
|
|
}
|
|
|
|
/// Return length as u8
|
|
pub fn len(&self) -> u8 {
|
|
self.len
|
|
}
|
|
|
|
/// Is remote frame
|
|
pub fn rtr(&self) -> bool {
|
|
self.flags.get_bit(Self::FLAG_RTR)
|
|
}
|
|
|
|
/// Request/is FDCAN frame
|
|
pub fn fdcan(&self) -> bool {
|
|
self.flags.get_bit(Self::FLAG_FDCAN)
|
|
}
|
|
|
|
/// Request/is Flexible Data Rate
|
|
pub fn bit_rate_switching(&self) -> bool {
|
|
self.flags.get_bit(Self::FLAG_BRS)
|
|
}
|
|
|
|
/// Get priority of frame
|
|
pub(crate) fn priority(&self) -> u32 {
|
|
match self.id() {
|
|
embedded_can::Id::Standard(id) => (id.as_raw() as u32) << 18,
|
|
embedded_can::Id::Extended(id) => id.as_raw(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Trait for FDCAN frame types, providing ability to construct from a Header
|
|
/// and to retrieve the Header from a frame
|
|
pub trait CanHeader: Sized {
|
|
/// Construct frame from header and payload
|
|
fn from_header(header: Header, data: &[u8]) -> Result<Self, FrameCreateError>;
|
|
|
|
/// Get this frame's header struct
|
|
fn header(&self) -> &Header;
|
|
}
|
|
|
|
/// Payload of a classic CAN data frame.
|
|
///
|
|
/// Contains 0 to 8 Bytes of data.
|
|
#[derive(Debug, Copy, Clone)]
|
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
pub struct ClassicData {
|
|
pub(crate) bytes: [u8; Self::MAX_DATA_LEN],
|
|
}
|
|
|
|
impl ClassicData {
|
|
pub(crate) const MAX_DATA_LEN: usize = 8;
|
|
/// Creates a data payload from a raw byte slice.
|
|
///
|
|
/// Returns `None` if `data` is more than 64 bytes (which is the maximum) or
|
|
/// cannot be represented with an FDCAN DLC.
|
|
pub fn new(data: &[u8]) -> Result<Self, FrameCreateError> {
|
|
if data.len() > 8 {
|
|
return Err(FrameCreateError::InvalidDataLength);
|
|
}
|
|
|
|
let mut bytes = [0; 8];
|
|
bytes[..data.len()].copy_from_slice(data);
|
|
|
|
Ok(Self { bytes })
|
|
}
|
|
|
|
/// Raw read access to data.
|
|
pub fn raw(&self) -> &[u8] {
|
|
&self.bytes
|
|
}
|
|
|
|
/// Checks if the length can be encoded in FDCAN DLC field.
|
|
pub const fn is_valid_len(len: usize) -> bool {
|
|
match len {
|
|
0..=8 => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
/// Creates an empty data payload containing 0 bytes.
|
|
#[inline]
|
|
pub const fn empty() -> Self {
|
|
Self { bytes: [0; 8] }
|
|
}
|
|
}
|
|
|
|
/// Frame with up to 8 bytes of data payload as per Classic(non-FD) CAN
|
|
/// For CAN-FD support use FdFrame
|
|
#[derive(Debug, Copy, Clone)]
|
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
pub struct Frame {
|
|
can_header: Header,
|
|
data: ClassicData,
|
|
}
|
|
|
|
impl Frame {
|
|
/// Create a new CAN classic Frame
|
|
pub fn new(can_header: Header, raw_data: &[u8]) -> Result<Self, FrameCreateError> {
|
|
let data = ClassicData::new(raw_data)?;
|
|
Ok(Frame { can_header, data: data })
|
|
}
|
|
|
|
/// Creates a new data frame.
|
|
pub fn new_data(id: impl Into<embedded_can::Id>, data: &[u8]) -> Result<Self, FrameCreateError> {
|
|
let eid: embedded_can::Id = id.into();
|
|
let header = Header::new(eid, data.len() as u8, false);
|
|
Self::new(header, data)
|
|
}
|
|
|
|
/// Create new extended frame
|
|
pub fn new_extended(raw_id: u32, raw_data: &[u8]) -> Result<Self, FrameCreateError> {
|
|
if let Some(id) = embedded_can::ExtendedId::new(raw_id) {
|
|
Self::new(Header::new(id.into(), raw_data.len() as u8, false), raw_data)
|
|
} else {
|
|
Err(FrameCreateError::InvalidCanId)
|
|
}
|
|
}
|
|
|
|
/// Create new standard frame
|
|
pub fn new_standard(raw_id: u16, raw_data: &[u8]) -> Result<Self, FrameCreateError> {
|
|
if let Some(id) = embedded_can::StandardId::new(raw_id) {
|
|
Self::new(Header::new(id.into(), raw_data.len() as u8, false), raw_data)
|
|
} else {
|
|
Err(FrameCreateError::InvalidCanId)
|
|
}
|
|
}
|
|
|
|
/// Create new remote frame
|
|
pub fn new_remote(id: impl Into<embedded_can::Id>, len: usize) -> Result<Self, FrameCreateError> {
|
|
if len <= 8usize {
|
|
Self::new(Header::new(id.into(), len as u8, true), &[0; 8])
|
|
} else {
|
|
Err(FrameCreateError::InvalidDataLength)
|
|
}
|
|
}
|
|
|
|
/// Get reference to data
|
|
pub fn header(&self) -> &Header {
|
|
&self.can_header
|
|
}
|
|
|
|
/// Return ID
|
|
pub fn id(&self) -> &embedded_can::Id {
|
|
&self.can_header.id
|
|
}
|
|
|
|
/// Get reference to data
|
|
pub fn data(&self) -> &[u8] {
|
|
&self.data.raw()
|
|
}
|
|
|
|
/// Get priority of frame
|
|
pub fn priority(&self) -> u32 {
|
|
self.header().priority()
|
|
}
|
|
}
|
|
|
|
impl embedded_can::Frame for Frame {
|
|
fn new(id: impl Into<embedded_can::Id>, raw_data: &[u8]) -> Option<Self> {
|
|
let frameopt = Frame::new(Header::new(id.into(), raw_data.len() as u8, false), raw_data);
|
|
match frameopt {
|
|
Ok(frame) => Some(frame),
|
|
Err(_) => None,
|
|
}
|
|
}
|
|
fn new_remote(id: impl Into<embedded_can::Id>, len: usize) -> Option<Self> {
|
|
if len <= 8 {
|
|
let frameopt = Frame::new(Header::new(id.into(), len as u8, true), &[0; 8]);
|
|
match frameopt {
|
|
Ok(frame) => Some(frame),
|
|
Err(_) => None,
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
fn is_extended(&self) -> bool {
|
|
match self.can_header.id {
|
|
embedded_can::Id::Extended(_) => true,
|
|
embedded_can::Id::Standard(_) => false,
|
|
}
|
|
}
|
|
fn is_remote_frame(&self) -> bool {
|
|
self.can_header.rtr()
|
|
}
|
|
fn id(&self) -> embedded_can::Id {
|
|
self.can_header.id
|
|
}
|
|
fn dlc(&self) -> usize {
|
|
self.can_header.len as usize
|
|
}
|
|
fn data(&self) -> &[u8] {
|
|
&self.data.raw()
|
|
}
|
|
}
|
|
|
|
impl CanHeader for Frame {
|
|
fn from_header(header: Header, data: &[u8]) -> Result<Self, FrameCreateError> {
|
|
Self::new(header, data)
|
|
}
|
|
|
|
fn header(&self) -> &Header {
|
|
self.header()
|
|
}
|
|
}
|
|
|
|
/// Contains CAN frame and additional metadata.
|
|
///
|
|
/// Timestamp is available if `time` feature is enabled.
|
|
/// For CAN-FD support use FdEnvelope
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
pub struct Envelope {
|
|
/// Reception time.
|
|
pub ts: Timestamp,
|
|
/// The actual CAN frame.
|
|
pub frame: Frame,
|
|
}
|
|
|
|
impl Envelope {
|
|
/// Convert into a tuple
|
|
pub fn parts(self) -> (Frame, Timestamp) {
|
|
(self.frame, self.ts)
|
|
}
|
|
}
|
|
|
|
/// Payload of a (FD)CAN data frame.
|
|
///
|
|
/// Contains 0 to 64 Bytes of data.
|
|
#[derive(Debug, Copy, Clone)]
|
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
pub struct FdData {
|
|
pub(crate) bytes: [u8; 64],
|
|
}
|
|
|
|
impl FdData {
|
|
/// Creates a data payload from a raw byte slice.
|
|
///
|
|
/// Returns `None` if `data` is more than 64 bytes (which is the maximum) or
|
|
/// cannot be represented with an FDCAN DLC.
|
|
pub fn new(data: &[u8]) -> Result<Self, FrameCreateError> {
|
|
if !FdData::is_valid_len(data.len()) {
|
|
return Err(FrameCreateError::InvalidDataLength);
|
|
}
|
|
|
|
let mut bytes = [0; 64];
|
|
bytes[..data.len()].copy_from_slice(data);
|
|
|
|
Ok(Self { bytes })
|
|
}
|
|
|
|
/// Raw read access to data.
|
|
pub fn raw(&self) -> &[u8] {
|
|
&self.bytes
|
|
}
|
|
|
|
/// Checks if the length can be encoded in FDCAN DLC field.
|
|
pub const fn is_valid_len(len: usize) -> bool {
|
|
match len {
|
|
0..=8 => true,
|
|
12 => true,
|
|
16 => true,
|
|
20 => true,
|
|
24 => true,
|
|
32 => true,
|
|
48 => true,
|
|
64 => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
/// Creates an empty data payload containing 0 bytes.
|
|
#[inline]
|
|
pub const fn empty() -> Self {
|
|
Self { bytes: [0; 64] }
|
|
}
|
|
}
|
|
|
|
/// Frame with up to 8 bytes of data payload as per Fd CAN
|
|
#[derive(Debug, Copy, Clone)]
|
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
pub struct FdFrame {
|
|
can_header: Header,
|
|
data: FdData,
|
|
}
|
|
|
|
impl FdFrame {
|
|
/// Create a new CAN classic Frame
|
|
pub fn new(can_header: Header, raw_data: &[u8]) -> Result<Self, FrameCreateError> {
|
|
let data = FdData::new(raw_data)?;
|
|
Ok(FdFrame { can_header, data })
|
|
}
|
|
|
|
/// Create new extended frame
|
|
pub fn new_extended(raw_id: u32, raw_data: &[u8]) -> Result<Self, FrameCreateError> {
|
|
if let Some(id) = embedded_can::ExtendedId::new(raw_id) {
|
|
Self::new(Header::new(id.into(), raw_data.len() as u8, false), raw_data)
|
|
} else {
|
|
Err(FrameCreateError::InvalidCanId)
|
|
}
|
|
}
|
|
|
|
/// Create new standard frame
|
|
pub fn new_standard(raw_id: u16, raw_data: &[u8]) -> Result<Self, FrameCreateError> {
|
|
if let Some(id) = embedded_can::StandardId::new(raw_id) {
|
|
Self::new(Header::new(id.into(), raw_data.len() as u8, false), raw_data)
|
|
} else {
|
|
Err(FrameCreateError::InvalidCanId)
|
|
}
|
|
}
|
|
|
|
/// Create new remote frame
|
|
pub fn new_remote(id: impl Into<embedded_can::Id>, len: usize) -> Result<Self, FrameCreateError> {
|
|
if len <= 8 {
|
|
Self::new(Header::new(id.into(), len as u8, true), &[0; 8])
|
|
} else {
|
|
Err(FrameCreateError::InvalidDataLength)
|
|
}
|
|
}
|
|
|
|
/// Get reference to data
|
|
pub fn header(&self) -> &Header {
|
|
&self.can_header
|
|
}
|
|
|
|
/// Return ID
|
|
pub fn id(&self) -> &embedded_can::Id {
|
|
&self.can_header.id
|
|
}
|
|
|
|
/// Get reference to data
|
|
pub fn data(&self) -> &[u8] {
|
|
&self.data.raw()
|
|
}
|
|
}
|
|
|
|
impl embedded_can::Frame for FdFrame {
|
|
fn new(id: impl Into<embedded_can::Id>, raw_data: &[u8]) -> Option<Self> {
|
|
match FdFrame::new(Header::new_fd(id.into(), raw_data.len() as u8, false, true), raw_data) {
|
|
Ok(frame) => Some(frame),
|
|
Err(_) => None,
|
|
}
|
|
}
|
|
fn new_remote(id: impl Into<embedded_can::Id>, len: usize) -> Option<Self> {
|
|
if len <= 8 {
|
|
match FdFrame::new(Header::new_fd(id.into(), len as u8, true, true), &[0; 64]) {
|
|
Ok(frame) => Some(frame),
|
|
Err(_) => None,
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
fn is_extended(&self) -> bool {
|
|
match self.can_header.id {
|
|
embedded_can::Id::Extended(_) => true,
|
|
embedded_can::Id::Standard(_) => false,
|
|
}
|
|
}
|
|
fn is_remote_frame(&self) -> bool {
|
|
self.can_header.rtr()
|
|
}
|
|
fn id(&self) -> embedded_can::Id {
|
|
self.can_header.id
|
|
}
|
|
// Returns length in bytes even for CANFD packets which embedded-can does not really mention.
|
|
fn dlc(&self) -> usize {
|
|
self.can_header.len as usize
|
|
}
|
|
fn data(&self) -> &[u8] {
|
|
&self.data.raw()
|
|
}
|
|
}
|
|
|
|
impl CanHeader for FdFrame {
|
|
fn from_header(header: Header, data: &[u8]) -> Result<Self, FrameCreateError> {
|
|
Self::new(header, data)
|
|
}
|
|
|
|
fn header(&self) -> &Header {
|
|
self.header()
|
|
}
|
|
}
|
|
|
|
/// Contains CAN FD frame and additional metadata.
|
|
///
|
|
/// Timestamp is available if `time` feature is enabled.
|
|
#[derive(Debug, Clone)]
|
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
|
pub struct FdEnvelope {
|
|
/// Reception time.
|
|
pub ts: Timestamp,
|
|
|
|
/// The actual CAN frame.
|
|
pub frame: FdFrame,
|
|
}
|
|
|
|
impl FdEnvelope {
|
|
/// Convert into a tuple
|
|
pub fn parts(self) -> (FdFrame, Timestamp) {
|
|
(self.frame, self.ts)
|
|
}
|
|
}
|