this should be sufficient
This commit is contained in:
parent
125619e485
commit
5e9526a2d1
2
scripts/unittest.sh
Executable file
2
scripts/unittest.sh
Executable file
@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
cargo +stable test --target $(rustc -vV | grep host | cut -d ' ' -f2) -p va416xx-hal --features alloc
|
@ -15,6 +15,7 @@ cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
|
||||
critical-section = "1"
|
||||
nb = "1"
|
||||
paste = "1"
|
||||
libm = "0.2"
|
||||
embedded-hal-nb = "1"
|
||||
embedded-hal-async = "1"
|
||||
embedded-hal = "1"
|
||||
@ -40,6 +41,7 @@ defmt = { version = "1", optional = true }
|
||||
[features]
|
||||
default = ["rt", "revb"]
|
||||
rt = ["va416xx/rt"]
|
||||
alloc = []
|
||||
defmt = ["dep:defmt", "fugit/defmt", "embedded-hal/defmt-03"]
|
||||
|
||||
va41630 = ["device-selected"]
|
||||
|
@ -1,9 +1,22 @@
|
||||
use arbitrary_int::{u2, u3, u4, u7, Number};
|
||||
|
||||
use crate::{clock::Clocks, enable_peripheral_clock, time::Hertz, PeripheralSelect};
|
||||
use libm::roundf;
|
||||
|
||||
pub mod regs;
|
||||
|
||||
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,
|
||||
@ -16,6 +29,82 @@ pub struct Can {
|
||||
id: CanId,
|
||||
}
|
||||
|
||||
/// 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<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 Instance {
|
||||
const ID: CanId;
|
||||
const PERIPH_SEL: PeripheralSelect;
|
||||
@ -31,28 +120,21 @@ impl Instance for va416xx::Can1 {
|
||||
const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can1;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct ClockConfig {
|
||||
prescaler: u7,
|
||||
tseg1: u4,
|
||||
tseg2: u3,
|
||||
sjw: u2,
|
||||
prescaler: u8,
|
||||
tseg1: u8,
|
||||
tseg2: u8,
|
||||
sjw: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("sjw must be less than or equal to the smaller tseg value")]
|
||||
pub struct InvalidSjwError(u2);
|
||||
|
||||
/// Sample point between 0 and 1.0 for the given time segments.
|
||||
pub const fn sample_point(tseg1: u4, tseg2: u3) -> f32 {
|
||||
let tseg1_val = tseg1.value() as f32;
|
||||
(tseg1_val + 1.0) / (1.0 + tseg1_val + tseg2.value() as f32)
|
||||
}
|
||||
pub struct InvalidSjwError(u8);
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("invalid sample point {sample_point} for tseg1 {tseg1} and tseg2 {tseg2}.")]
|
||||
#[error("invalid sample point {sample_point}")]
|
||||
pub struct InvalidSamplePointError {
|
||||
tseg1: u4,
|
||||
tseg2: u3,
|
||||
/// Sample point, should be larger than 0.5 (50 %) but was not.
|
||||
sample_point: f32,
|
||||
}
|
||||
@ -61,27 +143,56 @@ pub struct InvalidSamplePointError {
|
||||
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 synchronization jump width MUST be smaller than the smaller of the time segment
|
||||
/// configuration values. The sample point must also be larger than 50 %.
|
||||
pub fn new(prescaler: u7, tseg1: u4, tseg2: u3, sjw: u2) -> Result<Self, ClockConfigError> {
|
||||
/// 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<Self, ClockConfigError> {
|
||||
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 {
|
||||
if sjw.value() > smaller_tseg || sjw > SJW_MAX {
|
||||
return Err(InvalidSjwError(sjw).into());
|
||||
}
|
||||
let sample_point = sample_point(tseg1, tseg2);
|
||||
if sample_point < 0.5 {
|
||||
return Err(InvalidSamplePointError {
|
||||
tseg1,
|
||||
tseg2,
|
||||
sample_point,
|
||||
}.into());
|
||||
let sample_point = calculate_sample_point(tseg1, tseg2);
|
||||
if sample_point < MIN_SAMPLE_POINT {
|
||||
return Err(InvalidSamplePointError { sample_point }.into());
|
||||
}
|
||||
Ok(Self {
|
||||
prescaler,
|
||||
@ -91,9 +202,55 @@ impl ClockConfig {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_bitrate(clocks: &Clocks, bitrate: Hertz, prescaler: u7, tseg1: u4, tseg2: u3, sjw: u2) {}
|
||||
/// 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<ClockConfig, ClockConfigError> {
|
||||
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);
|
||||
}
|
||||
|
||||
pub fn from_sample_point(clocks: &Clocks, bitrate: Hertz, sample_point: f32) {}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl Can {
|
||||
@ -110,4 +267,39 @@ impl Can {
|
||||
}
|
||||
Self { id }
|
||||
}
|
||||
|
||||
pub fn id(&self) -> CanId {
|
||||
self.id
|
||||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
//! Instances of [`DynPin`] cannot be created directly. Rather, they must be
|
||||
//! created from their type-level equivalents using [`From`]/[`Into`].
|
||||
//!
|
||||
//! ```
|
||||
//! ```ignore
|
||||
//! // Move a pin out of the Pins struct and convert to a DynPin
|
||||
//! let pa0: DynPin = pins.pa0.into();
|
||||
//! ```
|
||||
@ -22,7 +22,7 @@
|
||||
//! Conversions between pin modes use a value-level version of the type-level
|
||||
//! API.
|
||||
//!
|
||||
//! ```
|
||||
//! ```ignore
|
||||
//! // Use one of the literal function names
|
||||
//! pa0.into_floating_input();
|
||||
//! // Use a method and a DynPinMode variant
|
||||
@ -38,7 +38,7 @@
|
||||
//! guarantee the pin has the correct ID or is in the correct mode at
|
||||
//! compile-time. Use [TryFrom]/[TryInto] for this conversion.
|
||||
//!
|
||||
//! ```
|
||||
//! ```ignore
|
||||
//! // Convert to a `DynPin`
|
||||
//! let pa0: DynPin = pins.pa0.into();
|
||||
//! // Change pin mode
|
||||
|
@ -19,7 +19,7 @@
|
||||
//! Type-level [`Pin`]s are parameterized by two type-level enums, [`PinId`] and
|
||||
//! [`PinMode`].
|
||||
//!
|
||||
//! ```
|
||||
//! ```ignore
|
||||
//! pub struct Pin<I, M>
|
||||
//! where
|
||||
//! I: PinId,
|
||||
@ -49,14 +49,14 @@
|
||||
//! within the [PinsA] struct can be moved out and used individually.
|
||||
//!
|
||||
//!
|
||||
//! ```no_run
|
||||
//! ```no_run,ignore
|
||||
//! let mut peripherals = Peripherals::take().unwrap();
|
||||
//! let pinsa = PinsA::new(peripherals.porta);
|
||||
//! ```
|
||||
//!
|
||||
//! Pins can be converted between modes using several different methods.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! ```no_run,ignore
|
||||
//! // Use one of the literal function names
|
||||
//! let pa0 = pinsa.pa0.into_floating_input();
|
||||
//! // Use a generic method and one of the `PinMode` variant types
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user