something worng with can clk calc?

This commit is contained in:
Robin Müller 2025-05-12 12:09:49 +02:00
parent 629ba4f8f2
commit ec954fcb16
Signed by: muellerr
GPG Key ID: A649FB78196E3849
10 changed files with 195 additions and 129 deletions

View File

@ -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 }

View File

@ -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]

1
scripts/can-clk-calc/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

View File

@ -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 }

View File

@ -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);
}
}

View File

@ -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"]

View File

@ -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<Self, InvalidBufferIndexError> {
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 {

View File

@ -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<alloc::vec::Vec<ClockConfig>, 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<alloc::vec::Vec<(ClockConfig, f32)>, 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));
}

View File

@ -352,6 +352,7 @@ impl defmt::Format for DiagnosticRegister {
}
#[derive(derive_mmio::Mmio)]
#[mmio(const_inner)]
#[repr(C)]
pub struct Can {
#[mmio(inner)]

View File

@ -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;