diff --git a/va416xx-hal/Cargo.toml b/va416xx-hal/Cargo.toml index 9334b9c..d9687ab 100644 --- a/va416xx-hal/Cargo.toml +++ b/va416xx-hal/Cargo.toml @@ -13,8 +13,10 @@ categories = ["embedded", "no-std", "hardware-support"] [dependencies] cortex-m = { version = "0.7", features = ["critical-section-single-core"] } va416xx = { version = "0.4", features = ["critical-section"], default-features = false } +derive-mmio = "0.4" vorago-shared-periphs = { git = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs.git", features = ["vor4x"] } +libm = "0.2" nb = "1" embedded-hal = "1" num_enum = { version = "0.7", default-features = false } @@ -22,6 +24,7 @@ bitflags = "2" bitbybit = "1.3" arbitrary-int = "1.3" fugit = "0.3" +embedded-can = "0.4" thiserror = { version = "2", default-features = false } defmt = { version = "0.3", optional = true } diff --git a/va416xx-hal/src/can/frame.rs b/va416xx-hal/src/can/frame.rs new file mode 100644 index 0000000..033f9fc --- /dev/null +++ b/va416xx-hal/src/can/frame.rs @@ -0,0 +1,117 @@ +#[derive(Debug, thiserror::Error)] +#[error("invalid data size error {0}")] +pub struct InvalidDataSizeError(usize); + +pub struct CanFrameNormal { + id: embedded_can::Id, + size: usize, + data: [u8; 8], +} + +impl CanFrameNormal { + pub fn new(id: embedded_can::Id, data: &[u8]) -> Self { + let size = data.len(); + let mut data_array = [0; 8]; + data_array[..size].copy_from_slice(data); + Self { + id, + size, + data: data_array, + } + } + + pub fn id(&self) -> embedded_can::Id { + self.id + } + + pub fn data(&self) -> &[u8] { + &self.data[..self.size] + } + + pub fn dlc(&self) -> usize { + self.size + } +} + +pub struct CanFrameRtr { + id: embedded_can::Id, + dlc: usize, +} + +impl CanFrameRtr { + pub fn new(id: embedded_can::Id, dlc: usize) -> Self { + Self { id, dlc } + } + + pub fn id(&self) -> embedded_can::Id { + self.id + } + + pub fn dlc(&self) -> usize { + self.dlc + } +} + +pub enum CanFrame { + Normal(CanFrameNormal), + Rtr(CanFrameRtr), +} + +impl From for CanFrame { + fn from(value: CanFrameNormal) -> Self { + Self::Normal(value) + } +} + +impl From for CanFrame { + fn from(value: CanFrameRtr) -> Self { + Self::Rtr(value) + } +} + +impl embedded_can::Frame for CanFrame { + fn new(id: impl Into, data: &[u8]) -> Option { + let id: embedded_can::Id = id.into(); + Some(Self::Normal(CanFrameNormal::new(id, data))) + } + + fn new_remote(id: impl Into, dlc: usize) -> Option { + let id: embedded_can::Id = id.into(); + Some(Self::Rtr(CanFrameRtr::new(id, dlc))) + } + + fn is_extended(&self) -> bool { + match self.id() { + embedded_can::Id::Extended(_) => true, + embedded_can::Id::Standard(_) => false, + } + } + + fn is_remote_frame(&self) -> bool { + match self { + CanFrame::Normal(_) => false, + CanFrame::Rtr(_) => true, + } + } + + fn id(&self) -> embedded_can::Id { + match self { + CanFrame::Normal(can_frame_normal) => can_frame_normal.id(), + CanFrame::Rtr(can_frame_rtr) => can_frame_rtr.id(), + } + } + + fn dlc(&self) -> usize { + match self { + CanFrame::Normal(can_frame_normal) => can_frame_normal.dlc(), + CanFrame::Rtr(can_frame_rtr) => can_frame_rtr.dlc(), + } + } + + fn data(&self) -> &[u8] { + match self { + CanFrame::Normal(can_frame_normal) => can_frame_normal.data(), + CanFrame::Rtr(_) => &[], + } + } +} diff --git a/va416xx-hal/src/can/mod.rs b/va416xx-hal/src/can/mod.rs new file mode 100644 index 0000000..c321e0d --- /dev/null +++ b/va416xx-hal/src/can/mod.rs @@ -0,0 +1,606 @@ +//! CAN driver. +//! +//! The VA416xx CAN module is based on the CP3UB26 module. +use arbitrary_int::{u11, u15, u2, u3, u4, u7, Number}; +use embedded_can::Frame; +use regs::{ + BaseId, BufStatusAndControl, Control, DataDirection, ExtendedId, MmioCan, TimingConfig, +}; + +use crate::{clock::Clocks, enable_peripheral_clock, time::Hertz, PeripheralSelect}; +use libm::roundf; + +pub mod frame; +pub mod regs; +pub use frame::*; + +pub const PRESCALER_MIN: u8 = 2; +pub const PRESCALER_MAX: u8 = 128; +/// 1 is the minimum value, but not recommended by Vorago. +pub const TSEG1_MIN: u8 = 1; +pub const TSEG1_MAX: u8 = 16; +pub const TSEG2_MAX: u8 = 8; +/// In addition, SJW may not be larger than TSEG2. +pub const SJW_MAX: u8 = 4; + +pub const MIN_SAMPLE_POINT: f32 = 0.5; +pub const MAX_BITRATE_DEVIATION: f32 = 0.005; + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum CanId { + Can0 = 0, + Can1 = 1, +} + +impl CanId { + /// Steal the register block for the CAN ID. + /// + /// # Safety + /// + /// See safety of the [regs::Can::new_mmio_fixed_0]. + pub unsafe fn steal_regs(&self) -> regs::MmioCan<'static> { + match self { + CanId::Can0 => unsafe { regs::Can::new_mmio_fixed_0() }, + CanId::Can1 => unsafe { regs::Can::new_mmio_fixed_1() }, + } + } +} + +/// Sample point between 0 and 1.0 for the given time segments. +pub const fn calculate_sample_point(tseg1: u8, tseg2: u8) -> f32 { + let tseg1_val = tseg1 as f32; + (tseg1_val + 1.0) / (1.0 + tseg1_val + tseg2 as f32) +} + +/// Calculate all viable clock configurations for the given input clock, the target bitrate and +/// for a sample point between 0.5 and 1.0. +/// +/// There are various recommendations for the sample point when using the CAN bus. The value +/// depends on different parameters like the bus length and propagation time, as well as +/// the information processing time of the nodes. It should always be at least 50 %. +/// In doubt, select a value like 0.75. +/// +/// - The [Python CAN library](https://python-can.readthedocs.io/en/stable/bit_timing.html) +/// assumes a default value of 69 % as the sample point if none is specified. +/// - CiA-301 recommends 87.5 % +/// - For simpler setups like laboratory setups, smaller values should work as well. +/// +/// A clock configuration is consideres viable when +/// +/// - The sample point deviation is less than 5 %. +/// - The bitrate error is less than +-0.5 %. +/// +/// SJW will be set to either TSEG2 or 4, whichever is smaller. +#[cfg(feature = "alloc")] +pub fn calculate_all_viable_clock_configs( + apb1_clock: Hertz, + bitrate: Hertz, + sample_point: f32, +) -> Result, InvalidSamplePointError> { + if sample_point < 0.5 || sample_point > 1.0 { + return Err(InvalidSamplePointError { sample_point }); + } + let mut configs = alloc::vec::Vec::new(); + for prescaler in PRESCALER_MIN..PRESCALER_MAX { + let nom_bit_time = apb1_clock / (bitrate * prescaler as u32); + // This is taken from the Python CAN library. NBT should not be too small. + if nom_bit_time < 8 { + break; + } + let actual_bitrate = apb1_clock / (prescaler as u32 * nom_bit_time); + let bitrate_deviation = ((actual_bitrate.raw() as i32 - bitrate.raw() as i32).abs() as f32) + / bitrate.raw() as f32; + if bitrate_deviation > 0.05 { + continue; + } + let tseg1 = roundf(sample_point * nom_bit_time as f32) as u32 - 1; + if tseg1 > TSEG1_MAX as u32 || tseg1 < TSEG1_MIN as u32 { + continue; + } + // limit tseg1, so tseg2 is at least 1 TQ + let tseg1 = core::cmp::min(tseg1, nom_bit_time - 2) as u8; + let tseg2 = nom_bit_time - tseg1 as u32 - 1; + if tseg2 > TSEG2_MAX as u32 { + continue; + } + let tseg2 = tseg2 as u8; + let sjw = core::cmp::min(tseg2, 4) as u8; + // Use percent to have a higher resolution for the sample point deviation. + let sample_point_actual = roundf(calculate_sample_point(tseg1, tseg2) * 100.0) as u32; + let sample_point = roundf(sample_point * 100.0) as u32; + let deviation = (sample_point_actual as i32 - sample_point as i32).abs(); + if deviation > 5 { + continue; + } + configs.push(ClockConfig { + prescaler, + tseg1, + tseg2, + sjw, + }); + } + Ok(configs) +} + +pub trait Instance { + const ID: CanId; + const PERIPH_SEL: PeripheralSelect; +} + +impl Instance for va416xx::Can0 { + const ID: CanId = CanId::Can0; + const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can0; +} + +impl Instance for va416xx::Can1 { + const ID: CanId = CanId::Can1; + const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can1; +} + +#[derive(Debug, Clone, Copy)] +pub struct ClockConfig { + prescaler: u8, + tseg1: u8, + tseg2: u8, + sjw: u8, +} + +#[derive(Debug, thiserror::Error)] +#[error("invalid buffer index {0}")] +pub struct InvalidBufferIndexError(usize); + +#[derive(Debug, thiserror::Error)] +#[error("sjw must be less than or equal to the smaller tseg value")] +pub struct InvalidSjwError(u8); + +#[derive(Debug, thiserror::Error)] +#[error("invalid sample point {sample_point}")] +pub struct InvalidSamplePointError { + /// Sample point, should be larger than 0.5 (50 %) but was not. + sample_point: f32, +} + +#[derive(Debug, thiserror::Error)] +pub enum ClockConfigError { + #[error("invalid sjw: {0}")] + InvalidSjw(#[from] InvalidSjwError), + #[error("TSEG is zero which is not allowed")] + TsegIsZero, + #[error("TSEG1 is larger than 16")] + InvalidTseg1, + #[error("TSEG1 is larger than 8")] + InvalidTseg2, + #[error("invalid sample point: {0}")] + InvalidSamplePoint(#[from] InvalidSamplePointError), + #[error("bitrate is zero")] + BitrateIsZero, + #[error("bitrate error larger than +-0.5 %")] + BitrateErrorTooLarge, + #[error("maximum or minimum allowed prescaler is not sufficient for target bitrate clock")] + CanNotFindPrescaler, +} + +impl ClockConfig { + /// New clock configuration from the raw configuration values. + /// + /// The values specified here are not the register values, but the actual numerical values + /// relevant for calculations. + /// + /// The values have the following requirements: + /// + /// - Prescaler must be between 2 and 128. + /// - TSEG1 must be smaller than 16 and should be larger than 1. + /// - TSEG2 must be smaller than 8 and small enough so that the calculated sample point + /// is larger than 0.5 (50 %). + /// - SJW (Synchronization Jump Width) must be smaller than the smaller of the time segment + /// configuration values and smaller than 4. + pub fn new(prescaler: u8, tseg1: u8, tseg2: u8, sjw: u8) -> Result { + if !(PRESCALER_MIN..=PRESCALER_MAX).contains(&prescaler.value()) { + return Err(ClockConfigError::CanNotFindPrescaler); + } + if tseg1 == 0 || tseg2 == 0 { + return Err(ClockConfigError::TsegIsZero); + } + if tseg1 > TSEG1_MAX { + return Err(ClockConfigError::InvalidTseg1); + } + if tseg2 > TSEG2_MAX { + return Err(ClockConfigError::InvalidTseg2); + } + let smaller_tseg = core::cmp::min(tseg1.value(), tseg2.value()); + if sjw.value() > smaller_tseg || sjw > SJW_MAX { + return Err(InvalidSjwError(sjw).into()); + } + let sample_point = calculate_sample_point(tseg1, tseg2); + if sample_point < MIN_SAMPLE_POINT { + return Err(InvalidSamplePointError { sample_point }.into()); + } + Ok(Self { + prescaler, + tseg1, + tseg2, + sjw, + }) + } + + /// Calculate the clock configuration for the given input clock, the target bitrate and for a + /// set of timing parameters. + /// + /// This function basically calculates the necessary prescaler to achieve the given timing + /// parameters. It also performs sanity and validity checks for the calculated prescaler: + /// The bitrate error for the given prescaler needs to be smaller than 0.5 %. + pub fn from_bitrate_and_segments( + clocks: &Clocks, + bitrate: Hertz, + tseg1: u8, + tseg2: u8, + sjw: u8, + ) -> Result { + if bitrate.raw() == 0 { + return Err(ClockConfigError::BitrateIsZero); + } + let prescaler = roundf( + clocks.apb1().raw() as f32 + / (bitrate.raw() as f32 * (1.0 + tseg1.as_u32() as f32 + tseg2.as_u32() as f32)), + ) as u32; + if !(PRESCALER_MIN as u32..=PRESCALER_MAX as u32).contains(&prescaler) { + return Err(ClockConfigError::CanNotFindPrescaler); + } + + let actual_bitrate = clocks.apb1() / (prescaler * (1 + tseg1.as_u32() + tseg2.as_u32())); + let bitrate_deviation = ((actual_bitrate.raw() as i32 - bitrate.raw() as i32).abs() as f32) + / bitrate.raw() as f32; + if bitrate_deviation > MAX_BITRATE_DEVIATION { + return Err(ClockConfigError::BitrateErrorTooLarge); + } + // The subtractions are fine because we made checks to avoid underflows. + Self::new(prescaler as u8, tseg1, tseg2, sjw) + } + + pub fn sjw_reg_value(&self) -> u2 { + u2::new(self.sjw.value() - 1) + } + + pub fn tseg1_reg_value(&self) -> u4 { + u4::new(self.tseg1.value() - 1) + } + + pub fn tseg2_reg_value(&self) -> u3 { + u3::new(self.tseg2.value() - 1) + } + + pub fn prescaler_reg_value(&self) -> u7 { + u7::new(self.prescaler.value() - 2) + } +} + +pub struct Can { + regs: regs::MmioCan<'static>, + id: CanId, +} + +impl Can { + pub fn new(_can: CanI, clk_config: ClockConfig) -> Self { + enable_peripheral_clock(CanI::PERIPH_SEL); + let id = CanI::ID; + let mut regs = if id == CanId::Can0 { + unsafe { regs::Can::new_mmio_fixed_0() } + } else { + unsafe { regs::Can::new_mmio_fixed_1() } + }; + // Disable the CAN bus before configuring it. + regs.write_control(Control::new_with_raw_value(0)); + for i in 0..15 { + regs.msg_buf_block_mut(i).reset(); + } + regs.write_timing( + TimingConfig::builder() + .with_tseg2(clk_config.tseg2_reg_value()) + .with_tseg1(clk_config.tseg1_reg_value()) + .with_sync_jump_width(clk_config.sjw_reg_value()) + .with_prescaler(clk_config.prescaler_reg_value()) + .build(), + ); + Self { regs, id } + } + + /// This configures the global mask so that acceptance is only determined by an exact match + /// with the ID in the receive message buffers. This is the default reset configuration for + /// the global mask as well. + pub fn set_global_mask_for_exact_id_match(&mut self) { + self.regs.write_gmskx(ExtendedId::new_with_raw_value(0)); + self.regs.write_gmskb(BaseId::new_with_raw_value(0)); + } + + /// Similar to [Self::set_global_mask_for_exact_id_match] but masks the XRTR and RTR/SRR bits. + /// + /// This is useful for when transmitting remote frames with the RTR bit set. The hardware + /// will automatically go into the [regs::BufferState::RxReady] state after the transmission, + /// but the XRTR and RTR/SRR bits need to be masked for the response frame to be accepted + /// on that buffer. + pub fn set_global_mask_for_exact_id_match_with_rtr_masked(&mut self) { + self.regs.write_gmskx( + ExtendedId::builder() + .with_mask_14_0(u15::new(0)) + .with_xrtr(true) + .build(), + ); + self.regs.write_gmskb( + BaseId::builder() + .with_mask_28_18(u11::new(0)) + .with_rtr_or_srr(true) + .with_ide(false) + .with_mask_17_15(u3::new(0)) + .build(), + ); + } + + /// This configures the base mask for buffer 14 so that acceptance is only determined by an + /// exact match with the ID in the receive message buffers. This is the default reset + /// configuration for the global mask as well. + pub fn set_base_mask_for_exact_id_match(&mut self) { + self.regs.write_bmskx(ExtendedId::new_with_raw_value(0)); + self.regs.write_bmskb(BaseId::new_with_raw_value(0)); + } + + /// This configures the base mask so that all CAN frames which are not handled by any other + /// buffers are accepted by the base buffer 14. + pub fn set_base_mask_for_all_match(&mut self) { + self.regs + .write_bmskx(ExtendedId::new_with_raw_value(0xffff)); + self.regs.write_bmskb(BaseId::new_with_raw_value(0xffff)); + } + + #[inline] + pub fn regs(&mut self) -> &mut MmioCan<'static> { + &mut self.regs + } + + #[inline] + pub fn id(&self) -> CanId { + self.id + } + + #[inline] + pub fn write_ctrl_reg(&mut self, ctrl: Control) { + self.regs.write_control(ctrl); + } + + #[inline] + pub fn enable_bufflock(&mut self) { + self.regs.modify_control(|mut ctrl| { + ctrl.set_bufflock(true); + ctrl + }); + } + + #[inline] + pub fn enable(&mut self) { + self.regs.modify_control(|mut ctrl| { + ctrl.set_enable(true); + ctrl + }); + } +} + +#[derive(Debug)] +pub enum ChannelState { + Idle, + Receiving, + Transmitting, + AwaitingRtrReply, +} + +pub struct CanChannel { + can_id: CanId, + idx: usize, + regs: regs::MmioCanMsgBuf<'static>, + mode: ChannelState, +} + +impl core::fmt::Debug for CanChannel { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("CanChannel") + .field("can_id", &self.can_id) + .field("idx", &self.idx) + .field("mode", &self.mode) + .finish() + } +} + +impl CanChannel { + pub fn configure_for_reception_with_standard_id( + &mut self, + standard_id: embedded_can::StandardId, + set_rtr: bool, + ) -> Result<(), InvalidBufferIndexError> { + let mut id1_reg = standard_id.as_raw() << 5; + if set_rtr { + id1_reg |= 1 << 4; + } + self.regs + .write_id1(BaseId::new_with_raw_value(id1_reg as u32)); + + self.regs.write_stat_ctrl( + BufStatusAndControl::builder() + .with_dlc(u4::new(0)) + .with_priority(u4::new(0)) + .with_status(regs::BufferState::RxReady) + .build(), + ); + Ok(()) + } + + pub fn configure_for_reception_with_extended_id( + &mut self, + extended_id: embedded_can::ExtendedId, + set_rtr: bool, + ) -> Result<(), InvalidBufferIndexError> { + let mut regs = unsafe { self.can_id.steal_regs() }; + let mut cmb_block = regs.msg_buf_block_mut(self.idx); + let id_raw = extended_id.as_raw(); + let id1_reg = (((id_raw >> 18) & 0x7FF) << 4) as u16 | ((id_raw >> 15) & 0b111) as u16; + cmb_block.write_id1(BaseId::new_with_raw_value(id1_reg as u32)); + let id0_reg = ((id_raw & 0x7FFF) << 1) as u16 | set_rtr as u16; + cmb_block.write_id0(ExtendedId::new_with_raw_value(id0_reg as u32)); + cmb_block.write_stat_ctrl( + BufStatusAndControl::builder() + .with_dlc(u4::new(0)) + .with_priority(u4::new(0)) + .with_status(regs::BufferState::RxReady) + .build(), + ); + self.mode = ChannelState::Receiving; + Ok(()) + } + + pub fn configure_for_transmission( + &mut self, + tx_priority: u4, + ) -> Result<(), InvalidBufferIndexError> { + let mut regs = unsafe { self.can_id.steal_regs() }; + let mut cmb_block = regs.msg_buf_block_mut(self.idx); + cmb_block.write_stat_ctrl( + BufStatusAndControl::builder() + .with_dlc(u4::new(0)) + .with_priority(tx_priority) + .with_status(regs::BufferState::TxNotActive) + .build(), + ); + self.mode = ChannelState::Receiving; + Ok(()) + } + + /// Reads a received CAN frame from the message buffer. + /// + /// This function does not check whether the pre-requisites for reading a CAN frame were + /// met and assumes this was already checked by the user. + pub fn read_frame_unchecked(&self) -> CanFrame { + let id0 = self.regs.read_id0(); + let id1 = self.regs.read_id1(); + let data0 = self.regs.read_data0(); + let data1 = self.regs.read_data1(); + let data2 = self.regs.read_data2(); + let data3 = self.regs.read_data3(); + let mut data: [u8; 8] = [0; 8]; + let mut read_data = |dlc: u4| { + (0..dlc.as_usize()).for_each(|i| match i { + 0 => data[i] = data3.data_upper_byte().as_u8(), + 1 => data[i] = data3.data_lower_byte().as_u8(), + 2 => data[i] = data2.data_upper_byte().as_u8(), + 3 => data[i] = data2.data_lower_byte().as_u8(), + 4 => data[i] = data1.data_upper_byte().as_u8(), + 5 => data[i] = data1.data_lower_byte().as_u8(), + 6 => data[i] = data0.data_upper_byte().as_u8(), + 7 => data[i] = data0.data_lower_byte().as_u8(), + _ => unreachable!(), + }); + }; + let (id, rtr) = if !id1.ide() { + let id = embedded_can::Id::Standard( + embedded_can::StandardId::new(id1.mask_28_18().as_u16()).unwrap(), + ); + if id1.rtr_or_srr() { + (id, true) + } else { + (id, false) + } + } else { + let id_raw = (id1.mask_28_18().as_u32() << 18) + | (id1.mask_17_15().as_u32() << 15) + | id0.mask_14_0().as_u32(); + let id = embedded_can::Id::Extended(embedded_can::ExtendedId::new(id_raw).unwrap()); + if id0.xrtr() { + (id, true) + } else { + (id, false) + } + }; + if rtr { + CanFrameRtr::new(id, self.regs.read_stat_ctrl().dlc().as_usize()).into() + } else { + let dlc = self.regs.read_stat_ctrl().dlc(); + read_data(dlc); + CanFrameNormal::new(id, &data[0..dlc.as_usize()]).into() + } + } + + pub fn transmit_frame_unchecked(&mut self, frame: CanFrame) { + let is_remote = frame.is_remote_frame(); + self.write_id(frame.id(), is_remote); + let dlc = frame.dlc(); + self.regs.modify_stat_ctrl(|mut ctrl| { + ctrl.set_status(regs::BufferState::TxOnce); + ctrl + }); + } + + fn write_id(&mut self, id: embedded_can::Id, is_remote: bool) { + match id { + embedded_can::Id::Standard(standard_id) => { + self.regs.write_id1( + BaseId::builder() + .with_mask_28_18(u11::new(standard_id.as_raw())) + .with_rtr_or_srr(is_remote) + .with_ide(false) + .with_mask_17_15(u3::new(0)) + .build(), + ); + self.regs.write_id0(ExtendedId::new_with_raw_value(0)); + } + embedded_can::Id::Extended(extended_id) => { + let id_raw = extended_id.as_raw(); + self.regs.write_id1( + BaseId::builder() + .with_mask_28_18(u11::new(((id_raw >> 18) & 0x7FF) as u16)) + .with_rtr_or_srr(true) + .with_ide(true) + .with_mask_17_15(u3::new(((id_raw >> 15) & 0b111) as u8)) + .build(), + ); + self.regs.write_id0( + ExtendedId::builder() + .with_mask_14_0(u15::new((id_raw & 0x7FFF) as u16)) + .with_xrtr(is_remote) + .build(), + ); + } + } + } +} + +pub struct CanWorker { + can: Can, + channels: [CanChannel; 15], +} + +#[cfg(test)] +mod tests { + #[cfg(feature = "alloc")] + use std::println; + + #[cfg(feature = "alloc")] + #[test] + pub fn test_clock_calculator_example_1() { + let configs = super::calculate_all_viable_clock_configs( + crate::time::Hertz::from_raw(50_000_000), + crate::time::Hertz::from_raw(25_000), + 0.75, + ) + .expect("clock calculation failed"); + // Bitrate: 25278.05 Hz. Sample point: 0.7391 + assert_eq!(configs[0].prescaler, 84); + assert_eq!(configs[0].tseg1, 16); + assert_eq!(configs[0].tseg2, 6); + assert_eq!(configs[0].sjw, 4); + // Vorago sample value. + let sample_cfg = configs + .iter() + .find(|c| c.prescaler == 100) + .expect("clock config not found"); + // Slightly different distribution because we use a different sample point, but + // the sum of TSEG1 and TSEG2 is the same as the Vorago example 1. + assert_eq!(sample_cfg.tseg1, 14); + assert_eq!(sample_cfg.tseg2, 5); + } +} diff --git a/va416xx-hal/src/can/regs.rs b/va416xx-hal/src/can/regs.rs new file mode 100644 index 0000000..8aad508 --- /dev/null +++ b/va416xx-hal/src/can/regs.rs @@ -0,0 +1,349 @@ +//! Custom register definitions for the CAN register block to circumvent PAC API / SVD +//! shortcomings. + +use arbitrary_int::{u11, u15, u2, u3, u4, u7}; + +pub const CAN_0_BASE: usize = 0x4001_4000; +pub const CAN_1_BASE: usize = 0x4001_4400; + +#[derive(Debug)] +#[bitbybit::bitenum(u4)] +pub enum BufferState { + /// Passive channel. + RxNotActive = 0b0000, + /// This condition indicated that SW wrote RxNotActive to a buffer when a data copy + /// process is still active. + RxBusy = 0b0001, + RxReady = 0b0010, + /// Indicated that data is being copied for the first time (RxRead -> RxBusy0). + RxBusy0 = 0b0011, + RxFull = 0b0100, + /// Indicated that data is being copied for the second time (RxFull -> RxBusy2). + RxBusy1 = 0b0101, + RxOverrun = 0b0110, + RxBusy2 = 0b0111, + TxNotActive = 0b1000, + /// Automatical response to a remote frame. + TxRtr = 0b1010, + /// Transmit one frame. + TxOnce = 0b1100, + TxBusy0 = 0b1101, + /// Transmit one frame, and changes to TxRtr after that. This can either be written by + /// software, or it will be written by the hardware after an auto response of the + /// [BufferState::TxRtr] state. + TxOnceRtr = 0b1110, + TxBusy2 = 0b1111, +} + +/// Status control register for individual message buffers. +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct BufStatusAndControl { + /// Data length code. + #[bits(12..=15, rw)] + dlc: u4, + #[bits(4..=7, rw)] + priority: u4, + #[bits(0..=3, rw)] + status: Option, +} + +#[derive(Debug)] +pub struct Timestamp(arbitrary_int::UInt); + +impl Timestamp { + pub fn new(value: u16) -> Self { + Self(value.into()) + } + pub fn value(&self) -> u16 { + self.0.value() as u16 + } + + pub fn write(&mut self, value: u16) { + self.0 = value.into(); + } +} + +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct TwoBytesData { + #[bits(8..=15, rw)] + data_upper_byte: u8, + #[bits(8..=15, rw)] + data_lower_byte: u8, +} + +#[derive(derive_mmio::Mmio)] +#[repr(C)] +pub struct CanMsgBuf { + stat_ctrl: BufStatusAndControl, + timestamp: Timestamp, + data3: TwoBytesData, + data2: TwoBytesData, + data1: TwoBytesData, + data0: TwoBytesData, + id0: ExtendedId, + id1: BaseId, +} + +impl MmioCanMsgBuf<'_> { + pub fn reset(&mut self) { + self.write_stat_ctrl(BufStatusAndControl::new_with_raw_value(0)); + self.write_timestamp(Timestamp::new(0)); + self.write_data3(TwoBytesData::new_with_raw_value(0)); + self.write_data2(TwoBytesData::new_with_raw_value(0)); + self.write_data1(TwoBytesData::new_with_raw_value(0)); + self.write_data0(TwoBytesData::new_with_raw_value(0)); + self.write_id1(BaseId::new_with_raw_value(0)); + self.write_id0(ExtendedId::new_with_raw_value(0)); + } +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug)] +pub enum PinLogicLevel { + DominantIsZero = 0b0, + DominantIsOne = 0b1, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug)] +pub enum ErrorInterruptType { + /// EIPND bit is set on every error. + EveryError = 0b0, + /// EIPND bit is only set if error state changes as a result of a receive or transmit + /// error counter increment. + ErrorOnRxTxCounterChange = 0b1, +} + +#[bitbybit::bitenum(u1, exhaustive = true)] +#[derive(Debug)] +pub enum DataDirection { + FirstByteAtHighestAddr = 0b0, + LastByteAtHighestAddr = 0b1, +} + +#[bitbybit::bitfield(u32)] +pub struct Control { + #[bit(11, rw)] + error_interrupt_type: ErrorInterruptType, + /// Enables special diagnostics features of the CAN like LO, IGNACK, LOOPBACK, INTERNAL. + #[bit(10, rw)] + diag_enable: bool, + /// CANTX and CANRX pins are internally connected to each other. + #[bit(9, rw)] + internal: bool, + /// All messages sent by the CAN controller can also be received by a CAN buffer with a + /// matching buffer ID. + #[bit(8, rw)] + loopback: bool, + /// IGNACK feature. The CAN does not expect to receive an ACK bit. + #[bit(7, rw)] + ignore_ack: bool, + /// LO feature. The CAN is only configured as a receiver. + #[bit(6, rw)] + listen_only: bool, + #[bit(5, rw)] + data_dir: DataDirection, + #[bit(4, rw)] + timestamp_enable: bool, + #[bit(3, rw)] + bufflock: bool, + #[bit(2, rw)] + tx_logic_level: PinLogicLevel, + #[bit(1, rw)] + rx_logic_level: PinLogicLevel, + #[bit(0, rw)] + enable: bool, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct TimingConfig { + #[bits(0..=2, rw)] + tseg2: u3, + #[bits(3..=6, rw)] + tseg1: u4, + #[bits(7..=8, rw)] + sync_jump_width: u2, + #[bits(9..=15, rw)] + prescaler: u7, +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct InterruptEnable { + #[bit(15, rw)] + error: bool, + #[bit(0, rw)] + buffer: [bool; 15], +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct InterruptClear { + #[bit(15, w)] + error: bool, + #[bit(0, w)] + buffer: [bool; 15], +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct InterruptPending { + #[bit(15, r)] + error: bool, + #[bit(0, r)] + buffer: [bool; 15], +} + +#[bitbybit::bitfield(u32)] +#[derive(Debug)] +pub struct ErrorCounter { + #[bits(0..=7, r)] + transmit: u8, + #[bits(8..=15, r)] + receive: u8, +} + +/// This register is unused for standard frames. +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct ExtendedId { + /// Mask for ID bits \[14:0\] of extended frames. + #[bits(1..=15, rw)] + mask_14_0: u15, + /// CAN XRTR bit. + #[bit(0, rw)] + xrtr: bool, +} + +#[bitbybit::bitfield(u32, default = 0x0)] +#[derive(Debug)] +pub struct BaseId { + /// This will contain ID\[10:0\] for standard frames and bits [28:18] for extended frames. + #[bits(5..=15, rw)] + mask_28_18: u11, + /// This is the RTR bit for standard frames, and the SRR bit for extended frames. + #[bit(4, rw)] + rtr_or_srr: bool, + /// Identifier extension bit. + #[bit(3, rw)] + ide: bool, + /// Mask for ID bits \[17:15\] of extended frames. + #[bits(0..=2, rw)] + mask_17_15: u3, +} + +#[derive(derive_mmio::Mmio)] +#[repr(C)] +pub struct Can { + #[mmio(inner)] + cmb0: CanMsgBuf, + #[mmio(inner)] + cmb1: CanMsgBuf, + #[mmio(inner)] + cmb2: CanMsgBuf, + #[mmio(inner)] + cmb3: CanMsgBuf, + #[mmio(inner)] + cmb4: CanMsgBuf, + #[mmio(inner)] + cmb5: CanMsgBuf, + #[mmio(inner)] + cmb6: CanMsgBuf, + #[mmio(inner)] + cmb7: CanMsgBuf, + #[mmio(inner)] + cmb8: CanMsgBuf, + #[mmio(inner)] + cmb9: CanMsgBuf, + #[mmio(inner)] + cmb10: CanMsgBuf, + #[mmio(inner)] + cmb11: CanMsgBuf, + #[mmio(inner)] + cmb12: CanMsgBuf, + #[mmio(inner)] + cmb13: CanMsgBuf, + // This CAN message buffer has different mask registers. + #[mmio(inner)] + cmb14: CanMsgBuf, + /// Hidden CAN message buffer. Only allowed to be used internally by the peripheral. + #[mmio(inner)] + _hcmb: CanMsgBuf, + control: Control, + timing: TimingConfig, + /// Global mask extension used for buffers 0 to 13. + gmskx: ExtendedId, + /// Global mask base used for buffers 0 to 13. + gmskb: BaseId, + /// Basic mask extension used for buffer 14. + bmskx: ExtendedId, + /// Basic mask base used for buffer 14. + bmskb: BaseId, + ien: InterruptEnable, + #[mmio(PureRead)] + ipnd: InterruptPending, + #[mmio(Write)] + iclr: InterruptClear, + /// Interrupt Code Enable Register. + icen: InterruptEnable, + #[mmio(PureRead)] + status_pending: u32, + #[mmio(PureRead)] + error_counter: ErrorCounter, + #[mmio(PureRead)] + diag: u32, + #[mmio(PureRead)] + timer: u32, +} + +impl Can { + /// Create a new CAN MMIO instance for peripheral 0. + /// + /// # Safety + /// + /// This API can be used to potentially create a driver to the same peripheral structure + /// from multiple threads. The user must ensure that concurrent accesses are safe and do not + /// interfere with each other. + pub const unsafe fn new_mmio_fixed_0() -> MmioCan<'static> { + Self::new_mmio_at(CAN_0_BASE) + } + + /// Create a new CAN MMIO instance for peripheral 1. + /// + /// # Safety + /// + /// This API can be used to potentially create a driver to the same peripheral structure + /// from multiple threads. The user must ensure that concurrent accesses are safe and do not + /// interfere with each other. + pub const unsafe fn new_mmio_fixed_1() -> MmioCan<'static> { + Self::new_mmio_at(CAN_1_BASE) + } +} + +impl MmioCan<'_> { + // TODO: It would be nice if derive-mmio could generate this for us.. + pub fn msg_buf_block_mut(&mut self, idx: usize) -> MmioCanMsgBuf<'static> { + assert!(idx < 15, "invalid index for CAN message buffer"); + match idx { + 0 => unsafe { self.steal_cmb0() }, + 1 => unsafe { self.steal_cmb1() }, + 2 => unsafe { self.steal_cmb2() }, + 3 => unsafe { self.steal_cmb3() }, + 4 => unsafe { self.steal_cmb4() }, + 5 => unsafe { self.steal_cmb5() }, + 6 => unsafe { self.steal_cmb6() }, + 7 => unsafe { self.steal_cmb7() }, + 8 => unsafe { self.steal_cmb8() }, + 9 => unsafe { self.steal_cmb9() }, + 10 => unsafe { self.steal_cmb10() }, + 11 => unsafe { self.steal_cmb11() }, + 12 => unsafe { self.steal_cmb12() }, + 13 => unsafe { self.steal_cmb13() }, + 14 => unsafe { self.steal_cmb14() }, + _ => unreachable!(), + } + } +} diff --git a/va416xx-hal/src/lib.rs b/va416xx-hal/src/lib.rs index 84b058d..1d01fdb 100644 --- a/va416xx-hal/src/lib.rs +++ b/va416xx-hal/src/lib.rs @@ -34,7 +34,6 @@ pub use va416xx as device; pub use va416xx as pac; pub mod prelude; - pub mod clock; pub mod dma; pub mod edac; @@ -47,6 +46,7 @@ pub mod spi; pub mod time; pub mod timer; pub mod uart; +pub mod can; pub mod wdt; #[cfg(feature = "va41630")]