1use core::sync::atomic::AtomicBool;
21
22use arbitrary_int::{prelude::*, u2, u3, u4, u7, u11, u15};
23use embedded_can::Frame;
24use ll::CanChannelLowLevel;
25use regs::{BaseId, BufferState, Control, MmioCan, TimingConfig};
26use vorago_shared_hal::enable_nvic_interrupt;
27
28use crate::{PeripheralSelect, clock::Clocks, enable_peripheral_clock, time::Hertz};
29use libm::roundf;
30
31pub mod frame;
32pub use frame::*;
33
34pub mod asynch;
35pub mod ll;
36pub mod regs;
37
38pub const PRESCALER_MIN: u8 = 2;
39pub const PRESCALER_MAX: u8 = 128;
40pub const TSEG1_MIN: u8 = 1;
42pub const TSEG1_MAX: u8 = 16;
43pub const TSEG2_MAX: u8 = 8;
44pub const SJW_MAX: u8 = 4;
46
47pub const MIN_SAMPLE_POINT: f32 = 0.5;
48pub const MAX_BITRATE_DEVIATION: f32 = 0.005;
49
50static CHANNELS_TAKEN: [AtomicBool; 2] = [AtomicBool::new(false), AtomicBool::new(false)];
51
52#[derive(Debug, PartialEq, Eq, Clone, Copy)]
53#[cfg_attr(feature = "defmt", derive(defmt::Format))]
54pub enum CanId {
55 Can0 = 0,
56 Can1 = 1,
57}
58
59impl CanId {
60 #[inline]
66 pub const unsafe fn steal_regs(&self) -> regs::MmioCan<'static> {
67 match self {
68 CanId::Can0 => unsafe { regs::Can::new_mmio_fixed_0() },
69 CanId::Can1 => unsafe { regs::Can::new_mmio_fixed_1() },
70 }
71 }
72
73 #[inline]
74 pub const fn irq_id(&self) -> va416xx::Interrupt {
75 match self {
76 CanId::Can0 => va416xx::Interrupt::CAN0,
77 CanId::Can1 => va416xx::Interrupt::CAN1,
78 }
79 }
80}
81
82pub const fn calculate_sample_point(tseg1: u8, tseg2: u8) -> f32 {
84 let tseg1_val = tseg1 as f32;
85 (tseg1_val + 1.0) / (1.0 + tseg1_val + tseg2 as f32)
86}
87
88#[derive(Debug, Clone, Copy)]
89#[cfg_attr(feature = "defmt", derive(defmt::Format))]
90pub struct ClockConfig {
91 prescaler: u8,
92 tseg1: u8,
93 tseg2: u8,
94 sjw: u8,
95}
96
97impl ClockConfig {
98 pub fn new(prescaler: u8, tseg1: u8, tseg2: u8, sjw: u8) -> Result<Self, ClockConfigError> {
112 if !(PRESCALER_MIN..=PRESCALER_MAX).contains(&prescaler.value()) {
113 return Err(ClockConfigError::CanNotFindPrescaler);
114 }
115 if tseg1 == 0 || tseg2 == 0 {
116 return Err(ClockConfigError::TsegIsZero);
117 }
118 if tseg1 > TSEG1_MAX {
119 return Err(ClockConfigError::InvalidTseg1);
120 }
121 if tseg2 > TSEG2_MAX {
122 return Err(ClockConfigError::InvalidTseg2);
123 }
124 let smaller_tseg = core::cmp::min(tseg1.value(), tseg2.value());
125 if sjw.value() > smaller_tseg || sjw > SJW_MAX {
126 return Err(InvalidSjwError(sjw).into());
127 }
128 let sample_point = calculate_sample_point(tseg1, tseg2);
129 if sample_point < MIN_SAMPLE_POINT {
130 return Err(InvalidSamplePointError { sample_point }.into());
131 }
132 Ok(Self {
133 prescaler,
134 tseg1,
135 tseg2,
136 sjw,
137 })
138 }
139
140 pub fn from_bitrate_and_segments(
147 clocks: &Clocks,
148 bitrate: Hertz,
149 tseg1: u8,
150 tseg2: u8,
151 sjw: u8,
152 ) -> Result<ClockConfig, ClockConfigError> {
153 if bitrate.to_raw() == 0 {
154 return Err(ClockConfigError::BitrateIsZero);
155 }
156 let nominal_bit_time = 1 + tseg1 as u32 + tseg2 as u32;
157 let prescaler = roundf(
158 clocks.apb1().to_raw() as f32 / (bitrate.to_raw() as f32 * nominal_bit_time as f32),
159 ) as u32;
160 if !(PRESCALER_MIN as u32..=PRESCALER_MAX as u32).contains(&prescaler) {
161 return Err(ClockConfigError::CanNotFindPrescaler);
162 }
163
164 let actual_bitrate =
165 (clocks.apb1().to_raw() as f32) / (prescaler * nominal_bit_time) as f32;
166 let bitrate_deviation = calculate_bitrate_deviation(actual_bitrate, bitrate);
167 if bitrate_deviation > MAX_BITRATE_DEVIATION {
168 return Err(ClockConfigError::BitrateErrorTooLarge);
169 }
170 Self::new(prescaler as u8, tseg1, tseg2, sjw)
172 }
173
174 #[inline]
175 pub fn sjw_reg_value(&self) -> u2 {
176 u2::new(self.sjw.value() - 1)
177 }
178
179 #[inline]
180 pub fn tseg1_reg_value(&self) -> u4 {
181 u4::new(self.tseg1.value() - 1)
182 }
183
184 #[inline]
185 pub fn tseg2_reg_value(&self) -> u3 {
186 u3::new(self.tseg2.value() - 1)
187 }
188
189 #[inline]
190 pub fn prescaler_reg_value(&self) -> u7 {
191 u7::new(self.prescaler.value() - 2)
192 }
193}
194
195#[cfg(feature = "alloc")]
215pub fn calculate_all_viable_clock_configs(
216 apb1_clock: Hertz,
217 bitrate: Hertz,
218 sample_point: f32,
219) -> Result<alloc::vec::Vec<(ClockConfig, f32)>, InvalidSamplePointError> {
220 if sample_point < 0.5 || sample_point > 1.0 {
221 return Err(InvalidSamplePointError { sample_point });
222 }
223 let mut configs = alloc::vec::Vec::new();
224 for prescaler in PRESCALER_MIN..PRESCALER_MAX {
225 let nom_bit_time = calculate_nominal_bit_time(apb1_clock, bitrate, prescaler);
226 if nom_bit_time < 8 {
228 break;
229 }
230 let actual_bitrate = calculate_actual_bitrate(apb1_clock, prescaler, nom_bit_time);
231 let bitrate_deviation = calculate_bitrate_deviation(actual_bitrate, bitrate);
232 if bitrate_deviation > 0.05 {
233 continue;
234 }
235 let tseg1 = roundf(sample_point * nom_bit_time as f32) as u32 - 1;
236 if tseg1 > TSEG1_MAX as u32 || tseg1 < TSEG1_MIN as u32 {
237 continue;
238 }
239 let tseg1 = core::cmp::min(tseg1, nom_bit_time - 2) as u8;
241 let tseg2 = nom_bit_time - tseg1 as u32 - 1;
242 if tseg2 > TSEG2_MAX as u32 {
243 continue;
244 }
245 let tseg2 = tseg2 as u8;
246 let sjw = core::cmp::min(tseg2, 4) as u8;
247 let sample_point_actual = roundf(calculate_sample_point(tseg1, tseg2) * 100.0) as u32;
249 let sample_point = roundf(sample_point * 100.0) as u32;
250 let deviation = (sample_point_actual as i32 - sample_point as i32).abs();
251 if deviation > 5 {
252 continue;
253 }
254 configs.push((
255 ClockConfig {
256 prescaler,
257 tseg1,
258 tseg2,
259 sjw,
260 },
261 bitrate_deviation,
262 ));
263 }
264 Ok(configs)
265}
266
267#[inline]
268pub const fn calculate_nominal_bit_time(
269 apb1_clock: Hertz,
270 target_bitrate: Hertz,
271 prescaler: u8,
272) -> u32 {
273 apb1_clock.to_raw() / (target_bitrate.to_raw() * prescaler as u32)
274}
275
276#[inline]
277pub const fn calculate_actual_bitrate(apb1_clock: Hertz, prescaler: u8, nom_bit_time: u32) -> f32 {
278 apb1_clock.to_raw() as f32 / (prescaler as u32 * nom_bit_time) as f32
279}
280
281#[inline]
282pub const fn calculate_bitrate_deviation(actual_bitrate: f32, target_bitrate: Hertz) -> f32 {
283 (actual_bitrate - target_bitrate.to_raw() as f32).abs() / target_bitrate.to_raw() as f32
284}
285
286pub trait CanInstance {
287 const ID: CanId;
288 const IRQ: va416xx::Interrupt;
289 const PERIPH_SEL: PeripheralSelect;
290}
291
292impl CanInstance for va416xx::Can0 {
293 const ID: CanId = CanId::Can0;
294 const IRQ: va416xx::Interrupt = va416xx::Interrupt::CAN0;
295 const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can0;
296}
297
298impl CanInstance for va416xx::Can1 {
299 const ID: CanId = CanId::Can1;
300 const IRQ: va416xx::Interrupt = va416xx::Interrupt::CAN1;
301 const PERIPH_SEL: PeripheralSelect = PeripheralSelect::Can1;
302}
303
304#[derive(Debug, thiserror::Error)]
305#[cfg_attr(feature = "defmt", derive(defmt::Format))]
306#[error("invalid buffer index {0}")]
307pub struct InvalidBufferIndexError(usize);
308
309#[derive(Debug, thiserror::Error)]
310#[cfg_attr(feature = "defmt", derive(defmt::Format))]
311#[error("sjw must be less than or equal to the smaller tseg value")]
312pub struct InvalidSjwError(u8);
313
314#[derive(Debug, thiserror::Error)]
315#[error("invalid sample point {sample_point}")]
316#[cfg_attr(feature = "defmt", derive(defmt::Format))]
317pub struct InvalidSamplePointError {
318 sample_point: f32,
320}
321
322#[derive(Debug, thiserror::Error)]
323#[cfg_attr(feature = "defmt", derive(defmt::Format))]
324pub enum ClockConfigError {
325 #[error("invalid sjw: {0}")]
326 InvalidSjw(#[from] InvalidSjwError),
327 #[error("TSEG is zero which is not allowed")]
328 TsegIsZero,
329 #[error("TSEG1 is larger than 16")]
330 InvalidTseg1,
331 #[error("TSEG1 is larger than 8")]
332 InvalidTseg2,
333 #[error("invalid sample point: {0}")]
334 InvalidSamplePoint(#[from] InvalidSamplePointError),
335 #[error("bitrate is zero")]
336 BitrateIsZero,
337 #[error("bitrate error larger than +-0.5 %")]
338 BitrateErrorTooLarge,
339 #[error("maximum or minimum allowed prescaler is not sufficient for target bitrate clock")]
340 CanNotFindPrescaler,
341}
342
343pub struct Can {
345 regs: regs::MmioCan<'static>,
346 id: CanId,
347}
348
349impl Can {
350 pub fn new<CanI: CanInstance>(_can: CanI, clk_config: ClockConfig) -> Self {
351 enable_peripheral_clock(CanI::PERIPH_SEL);
352 let id = CanI::ID;
353 let mut regs = if id == CanId::Can0 {
354 unsafe { regs::Can::new_mmio_fixed_0() }
355 } else {
356 unsafe { regs::Can::new_mmio_fixed_1() }
357 };
358 regs.write_control(Control::new_with_raw_value(0));
360 for i in 0..15 {
361 regs.cmbs(i).unwrap().reset();
362 }
363 regs.write_timing(
364 TimingConfig::builder()
365 .with_tseg2(clk_config.tseg2_reg_value())
366 .with_tseg1(clk_config.tseg1_reg_value())
367 .with_sync_jump_width(clk_config.sjw_reg_value())
368 .with_prescaler(clk_config.prescaler_reg_value())
369 .build(),
370 );
371 Self { regs, id }
372 }
373
374 pub fn set_global_mask_for_exact_id_match(&mut self) {
378 self.regs
379 .write_gmskx(regs::ExtendedId::new_with_raw_value(0));
380 self.regs.write_gmskb(BaseId::new_with_raw_value(0));
381 }
382
383 pub fn take_channels(&self) -> Option<CanChannels> {
385 if CHANNELS_TAKEN[self.id() as usize].swap(true, core::sync::atomic::Ordering::SeqCst) {
386 return None;
387 }
388 Some(CanChannels::new(self.id))
389 }
390
391 pub fn set_global_mask_for_exact_id_match_with_rtr_masked(&mut self) {
398 self.regs.write_gmskx(
399 regs::ExtendedId::builder()
400 .with_mask_14_0(u15::new(0))
401 .with_xrtr(true)
402 .build(),
403 );
404 self.regs.write_gmskb(
405 BaseId::builder()
406 .with_mask_28_18(u11::new(0))
407 .with_rtr_or_srr(true)
408 .with_ide(false)
409 .with_mask_17_15(u3::new(0))
410 .build(),
411 );
412 }
413
414 #[inline]
418 pub fn set_base_mask_for_exact_id_match(&mut self) {
419 self.regs
420 .write_bmskx(regs::ExtendedId::new_with_raw_value(0));
421 self.regs.write_bmskb(BaseId::new_with_raw_value(0));
422 }
423
424 #[inline]
427 pub fn set_base_mask_for_all_match(&mut self) {
428 self.regs
429 .write_bmskx(regs::ExtendedId::new_with_raw_value(0xffff));
430 self.regs.write_bmskb(BaseId::new_with_raw_value(0xffff));
431 }
432
433 #[inline]
434 pub fn regs(&mut self) -> &mut MmioCan<'static> {
435 &mut self.regs
436 }
437
438 #[inline]
440 pub fn clear_interrupts(&mut self) {
441 self.regs
442 .write_iclr(regs::InterruptClear::new_with_raw_value(0xFFFF_FFFF));
443 }
444
445 #[inline]
450 pub fn enable_nvic_interrupt(&mut self) {
451 unsafe {
452 enable_nvic_interrupt(self.id().irq_id());
453 }
454 }
455
456 #[inline]
457 pub fn read_error_counters(&self) -> regs::ErrorCounter {
458 self.regs.read_error_counter()
459 }
460
461 #[inline]
462 pub fn read_error_diagnostics(&self) -> regs::DiagnosticRegister {
463 self.regs.read_diag()
464 }
465
466 #[inline]
467 pub fn id(&self) -> CanId {
468 self.id
469 }
470
471 #[inline]
472 pub fn write_ctrl_reg(&mut self, ctrl: Control) {
473 self.regs.write_control(ctrl);
474 }
475
476 #[inline]
477 pub fn modify_control<F>(&mut self, f: F)
478 where
479 F: FnOnce(Control) -> Control,
480 {
481 self.regs.modify_control(f);
482 }
483
484 #[inline]
485 pub fn set_bufflock(&mut self, enable: bool) {
486 self.regs.modify_control(|mut ctrl| {
487 ctrl.set_bufflock(enable);
488 ctrl
489 });
490 }
491
492 #[inline]
493 pub fn enable(&mut self) {
494 self.regs.modify_control(|mut ctrl| {
495 ctrl.set_enable(true);
496 ctrl
497 });
498 }
499}
500
501#[derive(Debug, PartialEq, Eq, Clone, Copy)]
502#[cfg_attr(feature = "defmt", derive(defmt::Format))]
503pub enum TxState {
504 Idle,
505 TransmittingDataFrame,
506 TransmittingRemoteFrame,
507 AwaitingRemoteFrameReply,
508}
509
510#[derive(Debug)]
511#[cfg_attr(feature = "defmt", derive(defmt::Format))]
512pub enum InvalidTxState {
513 State(TxState),
514 BufferState(BufferState),
515}
516
517impl From<TxState> for InvalidTxState {
518 fn from(state: TxState) -> Self {
519 InvalidTxState::State(state)
520 }
521}
522
523impl From<BufferState> for InvalidTxState {
524 fn from(state: BufferState) -> Self {
525 InvalidTxState::BufferState(state)
526 }
527}
528
529#[derive(Debug, thiserror::Error)]
530#[error("invalid tx state {0:?}")]
531#[cfg_attr(feature = "defmt", derive(defmt::Format))]
532pub struct InvalidTxStateError(pub InvalidTxState);
533
534#[derive(Debug, PartialEq, Eq, Clone, Copy)]
535#[cfg_attr(feature = "defmt", derive(defmt::Format))]
536pub enum RxState {
537 Idle,
538 Receiving,
539}
540
541#[derive(Debug, thiserror::Error)]
542#[error("invalid rx state {0:?}")]
543#[cfg_attr(feature = "defmt", derive(defmt::Format))]
544pub struct InvalidRxStateError(pub RxState);
545
546#[derive(Debug)]
548pub struct CanTx {
549 ll: CanChannelLowLevel,
550 mode: TxState,
551}
552
553impl CanTx {
554 pub fn new(mut ll: CanChannelLowLevel, tx_priority: Option<u4>) -> Self {
555 ll.reset();
556 ll.configure_for_transmission(tx_priority);
557 Self {
558 ll,
559 mode: TxState::Idle,
560 }
561 }
562
563 #[inline]
564 pub fn into_rx_channel(self) -> CanRx {
565 CanRx::new(self.ll)
566 }
567
568 pub fn transmit_frame(&mut self, frame: CanFrame) -> Result<(), InvalidTxStateError> {
576 if self.mode == TxState::AwaitingRemoteFrameReply {
577 self.ll.configure_for_transmission(None);
578 self.mode = TxState::Idle;
579 }
580 if self.mode != TxState::Idle {
581 return Err(InvalidTxStateError(self.mode.into()));
582 }
583 if !frame.is_remote_frame() {
584 self.mode = TxState::TransmittingDataFrame;
585 } else {
586 self.mode = TxState::TransmittingRemoteFrame;
587 }
588 if let Ok(state) = self.ll.read_state()
589 && state != BufferState::TxNotActive
590 {
591 return Err(InvalidTxStateError(state.into()));
592 }
593 self.ll.transmit_frame_unchecked(frame);
594 Ok(())
595 }
596
597 pub fn transfer_done(&mut self) -> nb::Result<(), InvalidTxStateError> {
601 if self.mode != TxState::TransmittingDataFrame {
602 return Err(nb::Error::Other(InvalidTxStateError(self.mode.into())));
603 }
604 let status = self.ll.read_state();
605 if status.is_err() {
606 return Err(nb::Error::WouldBlock);
607 }
608 let status = status.unwrap();
609 if status == BufferState::TxNotActive {
610 self.mode = TxState::Idle;
611 return Ok(());
612 }
613 Err(nb::Error::WouldBlock)
614 }
615
616 pub fn remote_transfer_done(&mut self) -> nb::Result<CanRx, InvalidTxStateError> {
628 if self.mode != TxState::TransmittingRemoteFrame {
629 return Err(nb::Error::Other(InvalidTxStateError(self.mode.into())));
630 }
631 let status = self.ll.read_state();
632 if status.is_err() {
633 return Err(nb::Error::WouldBlock);
634 }
635 let status = status.unwrap();
636 if status == BufferState::RxReady {
637 self.mode = TxState::AwaitingRemoteFrameReply;
638 return Ok(CanRx {
639 ll: unsafe { self.ll.clone() },
640 mode: RxState::Receiving,
641 });
642 }
643 Err(nb::Error::WouldBlock)
644 }
645
646 pub fn remote_transfer_done_with_tx_reconfig(&mut self) -> nb::Result<(), InvalidTxStateError> {
653 if self.mode != TxState::TransmittingRemoteFrame {
654 return Err(nb::Error::Other(InvalidTxStateError(self.mode.into())));
655 }
656 let status = self.ll.read_state();
657 if status.is_err() {
658 return Err(nb::Error::WouldBlock);
659 }
660 let status = status.unwrap();
661 if status == BufferState::RxReady {
662 self.ll.write_state(BufferState::TxNotActive);
663 self.mode = TxState::Idle;
664 return Ok(());
665 }
666 Err(nb::Error::WouldBlock)
667 }
668
669 pub fn reset(&mut self) {
670 self.ll.reset();
671 self.mode = TxState::Idle;
672 }
673}
674
675pub struct CanRx {
677 ll: CanChannelLowLevel,
678 mode: RxState,
679}
680
681impl CanRx {
682 pub fn new(mut ll: CanChannelLowLevel) -> Self {
683 ll.reset();
684 Self {
685 ll,
686 mode: RxState::Idle,
687 }
688 }
689
690 #[inline]
691 pub fn into_tx_channel(self, tx_priority: Option<u4>) -> CanTx {
692 CanTx::new(self.ll, tx_priority)
693 }
694
695 #[inline]
696 pub fn enable_interrupt(&mut self, enable_translation: bool) {
697 self.ll.enable_interrupt(enable_translation);
698 }
699
700 pub fn configure_for_reception_with_standard_id(
701 &mut self,
702 standard_id: embedded_can::StandardId,
703 set_rtr: bool,
704 ) {
705 self.ll.set_standard_id(standard_id, set_rtr);
706 self.configure_for_reception();
707 }
708
709 pub fn configure_for_reception_with_extended_id(
710 &mut self,
711 extended_id: embedded_can::ExtendedId,
712 set_rtr: bool,
713 ) {
714 self.ll.set_extended_id(extended_id, set_rtr);
715 self.configure_for_reception();
716 }
717
718 pub fn configure_for_reception(&mut self) {
719 self.ll.configure_for_reception();
720 self.mode = RxState::Receiving;
721 }
722
723 #[inline]
724 pub fn frame_available(&self) -> bool {
725 self.ll
726 .read_state()
727 .is_ok_and(|state| state == BufferState::RxFull || state == BufferState::RxOverrun)
728 }
729
730 pub fn receive(
732 &mut self,
733 reconfigure_for_reception: bool,
734 ) -> nb::Result<CanFrame, InvalidRxStateError> {
735 if self.mode != RxState::Receiving {
736 return Err(nb::Error::Other(InvalidRxStateError(self.mode)));
737 }
738 let status = self.ll.read_state();
739 if status.is_err() {
740 return Err(nb::Error::WouldBlock);
741 }
742 let status = status.unwrap();
743 if status == BufferState::RxFull || status == BufferState::RxOverrun {
744 self.mode = RxState::Idle;
745 if reconfigure_for_reception {
746 self.ll.write_state(BufferState::RxReady);
747 }
748 return Ok(self.ll.read_frame_unchecked());
749 }
750 Err(nb::Error::WouldBlock)
751 }
752}
753
754pub struct CanChannels {
755 id: CanId,
756 channels: [Option<CanChannelLowLevel>; 15],
757}
758
759impl CanChannels {
760 const fn new(id: CanId) -> Self {
761 unsafe {
763 Self {
764 id,
765 channels: [
766 Some(CanChannelLowLevel::steal_unchecked(id, 0)),
767 Some(CanChannelLowLevel::steal_unchecked(id, 1)),
768 Some(CanChannelLowLevel::steal_unchecked(id, 2)),
769 Some(CanChannelLowLevel::steal_unchecked(id, 3)),
770 Some(CanChannelLowLevel::steal_unchecked(id, 4)),
771 Some(CanChannelLowLevel::steal_unchecked(id, 5)),
772 Some(CanChannelLowLevel::steal_unchecked(id, 6)),
773 Some(CanChannelLowLevel::steal_unchecked(id, 7)),
774 Some(CanChannelLowLevel::steal_unchecked(id, 8)),
775 Some(CanChannelLowLevel::steal_unchecked(id, 9)),
776 Some(CanChannelLowLevel::steal_unchecked(id, 10)),
777 Some(CanChannelLowLevel::steal_unchecked(id, 11)),
778 Some(CanChannelLowLevel::steal_unchecked(id, 12)),
779 Some(CanChannelLowLevel::steal_unchecked(id, 13)),
780 Some(CanChannelLowLevel::steal_unchecked(id, 14)),
781 ],
782 }
783 }
784 }
785
786 pub const fn can_id(&self) -> CanId {
787 self.id
788 }
789
790 pub fn take(&mut self, idx: usize) -> Option<CanChannelLowLevel> {
792 if idx > 14 {
793 return None;
794 }
795 self.channels[idx].take()
796 }
797
798 pub fn give(&mut self, idx: usize, channel: CanChannelLowLevel) {
799 if idx > 14 {
800 panic!("invalid buffer index for CAN channel");
801 }
802 self.channels[idx] = Some(channel);
803 }
804}
805
806#[cfg(test)]
807mod tests {
808 #[cfg(feature = "alloc")]
809 use std::println;
810
811 #[cfg(feature = "alloc")]
812 #[test]
813 pub fn test_clock_calculator_example_1() {
814 let configs = super::calculate_all_viable_clock_configs(
815 crate::time::Hertz::from_raw(50_000_000),
816 crate::time::Hertz::from_raw(25_000),
817 0.75,
818 )
819 .expect("clock calculation failed");
820 assert_eq!(configs[0].prescaler, 84);
822 assert_eq!(configs[0].tseg1, 16);
823 assert_eq!(configs[0].tseg2, 6);
824 assert_eq!(configs[0].sjw, 4);
825 let sample_cfg = configs
827 .iter()
828 .find(|c| c.prescaler == 100)
829 .expect("clock config not found");
830 assert_eq!(sample_cfg.tseg1, 14);
833 assert_eq!(sample_cfg.tseg2, 5);
834 }
835}