embassy_stm32/can/util.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
//! Utility functions shared between CAN controller types.
use core::num::{NonZeroU16, NonZeroU8};
/// Shared struct to represent bit timings used by calc_can_timings.
#[derive(Clone, Copy, Debug)]
pub struct NominalBitTiming {
/// Value by which the oscillator frequency is divided for generating the bit time quanta. The bit
/// time is built up from a multiple of this quanta. Valid values are 1 to 512.
pub prescaler: NonZeroU16,
/// Valid values are 1 to 128.
pub seg1: NonZeroU8,
/// Valid values are 1 to 255.
pub seg2: NonZeroU8,
/// Valid values are 1 to 128.
pub sync_jump_width: NonZeroU8,
}
/// Calculate nominal CAN bit timing based on CAN bitrate and periphial clock frequency
pub fn calc_can_timings(periph_clock: crate::time::Hertz, can_bitrate: u32) -> Option<NominalBitTiming> {
const BS1_MAX: u8 = 16;
const BS2_MAX: u8 = 8;
const MAX_SAMPLE_POINT_PERMILL: u16 = 900;
let periph_clock = periph_clock.0;
if can_bitrate < 1000 {
return None;
}
// Ref. "Automatic Baudrate Detection in CANopen Networks", U. Koppe, MicroControl GmbH & Co. KG
// CAN in Automation, 2003
//
// According to the source, optimal quanta per bit are:
// Bitrate Optimal Maximum
// 1000 kbps 8 10
// 500 kbps 16 17
// 250 kbps 16 17
// 125 kbps 16 17
let max_quanta_per_bit: u8 = if can_bitrate >= 1_000_000 { 10 } else { 17 };
// Computing (prescaler * BS):
// BITRATE = 1 / (PRESCALER * (1 / PCLK) * (1 + BS1 + BS2)) -- See the Reference Manual
// BITRATE = PCLK / (PRESCALER * (1 + BS1 + BS2)) -- Simplified
// let:
// BS = 1 + BS1 + BS2 -- Number of time quanta per bit
// PRESCALER_BS = PRESCALER * BS
// ==>
// PRESCALER_BS = PCLK / BITRATE
let prescaler_bs = periph_clock / can_bitrate;
// Searching for such prescaler value so that the number of quanta per bit is highest.
let mut bs1_bs2_sum = max_quanta_per_bit - 1;
while (prescaler_bs % (1 + bs1_bs2_sum) as u32) != 0 {
if bs1_bs2_sum <= 2 {
return None; // No solution
}
bs1_bs2_sum -= 1;
}
let prescaler = prescaler_bs / (1 + bs1_bs2_sum) as u32;
if (prescaler < 1) || (prescaler > 1024) {
return None; // No solution
}
// Now we have a constraint: (BS1 + BS2) == bs1_bs2_sum.
// We need to find such values so that the sample point is as close as possible to the optimal value,
// which is 87.5%, which is 7/8.
//
// Solve[(1 + bs1)/(1 + bs1 + bs2) == 7/8, bs2] (* Where 7/8 is 0.875, the recommended sample point location *)
// {{bs2 -> (1 + bs1)/7}}
//
// Hence:
// bs2 = (1 + bs1) / 7
// bs1 = (7 * bs1_bs2_sum - 1) / 8
//
// Sample point location can be computed as follows:
// Sample point location = (1 + bs1) / (1 + bs1 + bs2)
//
// Since the optimal solution is so close to the maximum, we prepare two solutions, and then pick the best one:
// - With rounding to nearest
// - With rounding to zero
let mut bs1 = ((7 * bs1_bs2_sum - 1) + 4) / 8; // Trying rounding to nearest first
let mut bs2 = bs1_bs2_sum - bs1;
core::assert!(bs1_bs2_sum > bs1);
let sample_point_permill = 1000 * ((1 + bs1) / (1 + bs1 + bs2)) as u16;
if sample_point_permill > MAX_SAMPLE_POINT_PERMILL {
// Nope, too far; now rounding to zero
bs1 = (7 * bs1_bs2_sum - 1) / 8;
bs2 = bs1_bs2_sum - bs1;
}
// Check is BS1 and BS2 are in range
if (bs1 < 1) || (bs1 > BS1_MAX) || (bs2 < 1) || (bs2 > BS2_MAX) {
return None;
}
// Check if final bitrate matches the requested
if can_bitrate != (periph_clock / (prescaler * (1 + bs1 + bs2) as u32)) {
return None;
}
// One is recommended by DS-015, CANOpen, and DeviceNet
let sync_jump_width = core::num::NonZeroU8::new(1)?;
let seg1 = core::num::NonZeroU8::new(bs1)?;
let seg2 = core::num::NonZeroU8::new(bs2)?;
let nz_prescaler = core::num::NonZeroU16::new(prescaler as u16)?;
Some(NominalBitTiming {
sync_jump_width,
prescaler: nz_prescaler,
seg1,
seg2,
})
}