diff --git a/examples/embassy/Cargo.toml b/examples/embassy/Cargo.toml index 5daa165..2ac9a67 100644 --- a/examples/embassy/Cargo.toml +++ b/examples/embassy/Cargo.toml @@ -13,7 +13,7 @@ embedded-io-async = "0.6" heapless = "0.8" defmt-rtt = "0.4" defmt = "1" -panic-probe = { version = "1", features = ["defmt"] } +panic-probe = { version = "1", features = ["print-defmt"] } static_cell = "2" critical-section = "1" ringbuf = { version = "0.4", default-features = false } diff --git a/examples/embassy/src/bin/can.rs b/examples/embassy/src/bin/can.rs index 17fb23d..4f0c757 100644 --- a/examples/embassy/src/bin/can.rs +++ b/examples/embassy/src/bin/can.rs @@ -29,7 +29,10 @@ async fn main(_spawner: Spawner) { .unwrap(); // Safety: Only called once here. va416xx_embassy::init(dp.tim15, dp.tim14, &clocks); - let clk_config = ClockConfig::from_bitrate_and_segments(&clocks, 250.kHz(), 16, 4, 4).unwrap(); + defmt::info!("creating CAN peripheral driver"); + defmt::info!("clocks: {}", clocks); + let clk_config = ClockConfig::from_bitrate_and_segments(&clocks, 250.kHz(), 16, 4, 4) + .expect("CAN clock config error"); let mut can = Can::new(dp.can0, clk_config); can.set_loopback(true); can.set_bufflock(true); @@ -48,6 +51,7 @@ async fn main(_spawner: Spawner) { tx.transmit_frame(send_frame).unwrap(); let _frame = nb::block!(rx.receive(true)).expect("invalid CAN rx state"); defmt::info!("received CAN frame with data"); + loop {} } #[interrupt] diff --git a/scripts/can-clk-calc/.gitignore b/scripts/can-clk-calc/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/scripts/can-clk-calc/.gitignore @@ -0,0 +1 @@ +/target diff --git a/scripts/can-clk-calc/Cargo.toml b/scripts/can-clk-calc/Cargo.toml new file mode 100644 index 0000000..ad25349 --- /dev/null +++ b/scripts/can-clk-calc/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] + +[package] +name = "can-clk-calc" +version = "0.1.0" +edition = "2024" + +[dependencies] +va416xx-hal = { path = "../../va416xx-hal", features = ["alloc", "revb"], default-features = false } diff --git a/scripts/can-clk-calc/src/main.rs b/scripts/can-clk-calc/src/main.rs new file mode 100644 index 0000000..0031a05 --- /dev/null +++ b/scripts/can-clk-calc/src/main.rs @@ -0,0 +1,14 @@ +use va416xx_hal::can::calculate_all_viable_clock_configs; +use va416xx_hal::time::Hertz; + +fn main() { + let cfgs = calculate_all_viable_clock_configs( + Hertz::from_raw(20_000_000), + Hertz::from_raw(250_000), + 0.75, + ) + .unwrap(); + for cfg in &cfgs { + println!("Config: {:#?}", cfg); + } +} diff --git a/va416xx-hal/Cargo.toml b/va416xx-hal/Cargo.toml index 42730a4..7f081bf 100644 --- a/va416xx-hal/Cargo.toml +++ b/va416xx-hal/Cargo.toml @@ -13,7 +13,7 @@ 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 = { version = "0.4", git = "https://github.com/us-irs/derive-mmio.git", branch = "more-constness" } +derive-mmio = { version = "0.4", git = "https://github.com/knurling-rs/derive-mmio.git" } vorago-shared-periphs = { git = "https://egit.irs.uni-stuttgart.de/rust/vorago-shared-periphs.git", features = ["vor4x"] } libm = "0.2" @@ -33,6 +33,7 @@ defmt = { version = "0.3", optional = true } [features] default = ["rt", "revb"] rt = ["va416xx/rt"] +alloc = [] defmt = ["dep:defmt", "fugit/defmt", "vorago-shared-periphs/defmt"] va41630 = ["device-selected"] diff --git a/va416xx-hal/src/can/ll.rs b/va416xx-hal/src/can/ll.rs index 277798b..41b35e0 100644 --- a/va416xx-hal/src/can/ll.rs +++ b/va416xx-hal/src/can/ll.rs @@ -23,6 +23,11 @@ impl core::fmt::Debug for CanChannelLowLevel { } impl CanChannelLowLevel { + /// Steal a low level instance of a CAN channel. + /// + /// # Safety + /// + /// Circumvents ownership and safety guarantees of the HAL. #[inline] pub unsafe fn steal(can: CanId, idx: usize) -> Result { if idx > 14 { @@ -36,6 +41,12 @@ impl CanChannelLowLevel { }) } + /// Steal a low level instance of a CAN channel without and index checks. + /// + /// # Safety + /// + /// Does not perform any bound checks. Passing an invalid index of 15 or higher leads to + /// undefined behaviour. #[inline] pub const unsafe fn steal_unchecked(can: CanId, idx: usize) -> Self { if idx > 14 { diff --git a/va416xx-hal/src/can/mod.rs b/va416xx-hal/src/can/mod.rs index b78da1e..8395914 100644 --- a/va416xx-hal/src/can/mod.rs +++ b/va416xx-hal/src/can/mod.rs @@ -58,91 +58,6 @@ pub const fn calculate_sample_point(tseg1: u8, tseg2: u8) -> 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 CanMarker { - const ID: CanId; - const PERIPH_SEL: PeripheralSelect; -} - -impl CanMarker for va416xx::Can0 { - const ID: CanId = CanId::Can0; - const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can0; -} - -impl CanMarker for va416xx::Can1 { - const ID: CanId = CanId::Can1; - const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can1; -} - #[derive(Debug, Clone, Copy)] pub struct ClockConfig { prescaler: u8, @@ -151,41 +66,6 @@ pub struct ClockConfig { 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. /// @@ -245,17 +125,20 @@ impl ClockConfig { if bitrate.raw() == 0 { return Err(ClockConfigError::BitrateIsZero); } - let prescaler = roundf( + let mut 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; + // defmt::info!("calc prescaler: {}", prescaler); 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; + let actual_bitrate = (clocks.apb1().raw() as f32) + / (prescaler * (1 + tseg1.as_u32() + tseg2.as_u32())) as f32; + let bitrate_deviation = + ((actual_bitrate as i32 - bitrate.raw() as i32).abs() as f32) / bitrate.raw() as f32; + //defmt::info!("actual bitrate: {}, target {}", actual_bitrate, bitrate); if bitrate_deviation > MAX_BITRATE_DEVIATION { return Err(ClockConfigError::BitrateErrorTooLarge); } @@ -280,6 +163,144 @@ impl ClockConfig { } } +/// 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 = calculate_nominal_bit_time(apb1_clock, bitrate, prescaler); + // This is taken from the Python CAN library. NBT should not be too small. + if nom_bit_time < 8 { + break; + } + let actual_bitrate = calculate_actual_bitrate(apb1_clock, prescaler, nom_bit_time); + let bitrate_deviation = calculate_bitrate_deviation(actual_bitrate, bitrate); + 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, + }, + bitrate_deviation, + )); + } + Ok(configs) +} + +pub const fn calculate_nominal_bit_time( + apb1_clock: Hertz, + target_bitrate: Hertz, + prescaler: u8, +) -> u32 { + apb1_clock.raw() / (target_bitrate.raw() * prescaler as u32) +} + +pub const fn calculate_actual_bitrate(apb1_clock: Hertz, prescaler: u8, nom_bit_time: u32) -> f32 { + apb1_clock.raw() as f32 / (prescaler as u32 * nom_bit_time) as f32 +} + +pub const fn calculate_bitrate_deviation(actual_bitrate: f32, target_bitrate: Hertz) -> f32 { + (actual_bitrate - target_bitrate.raw() as f32).abs() / target_bitrate.raw() as f32 +} + +pub trait CanMarker { + const ID: CanId; + const PERIPH_SEL: PeripheralSelect; +} + +impl CanMarker for va416xx::Can0 { + const ID: CanId = CanId::Can0; + const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can0; +} + +impl CanMarker for va416xx::Can1 { + const ID: CanId = CanId::Can1; + const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can1; +} + +#[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, +} + pub struct Can { regs: regs::MmioCan<'static>, id: CanId, @@ -314,7 +335,8 @@ impl Can { /// 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(regs::ExtendedId::new_with_raw_value(0)); + self.regs + .write_gmskx(regs::ExtendedId::new_with_raw_value(0)); self.regs.write_gmskb(BaseId::new_with_raw_value(0)); } @@ -352,7 +374,8 @@ impl Can { /// 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(regs::ExtendedId::new_with_raw_value(0)); + self.regs + .write_bmskx(regs::ExtendedId::new_with_raw_value(0)); self.regs.write_bmskb(BaseId::new_with_raw_value(0)); } diff --git a/va416xx-hal/src/can/regs.rs b/va416xx-hal/src/can/regs.rs index 3879f7c..bf7a35b 100644 --- a/va416xx-hal/src/can/regs.rs +++ b/va416xx-hal/src/can/regs.rs @@ -352,6 +352,7 @@ impl defmt::Format for DiagnosticRegister { } #[derive(derive_mmio::Mmio)] +#[mmio(const_inner)] #[repr(C)] pub struct Can { #[mmio(inner)] diff --git a/va416xx-hal/src/lib.rs b/va416xx-hal/src/lib.rs index de5faa5..e4c9861 100644 --- a/va416xx-hal/src/lib.rs +++ b/va416xx-hal/src/lib.rs @@ -26,6 +26,8 @@ //! faulty register reset values which might lead to weird bugs and glitches. #![no_std] #![cfg_attr(docsrs, feature(doc_auto_cfg))] +#[cfg(feature = "alloc")] +extern crate alloc; #[cfg(test)] extern crate std;