Skip to content

Commit 3e1a3a5

Browse files
committed
add strict parameter to can.BitTimingFd
1 parent 4a8eee5 commit 3e1a3a5

File tree

2 files changed

+65
-45
lines changed

2 files changed

+65
-45
lines changed

can/bit_timing.py

Lines changed: 63 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ class BitTimingFd(Mapping):
491491
)
492492
"""
493493

494-
def __init__(
494+
def __init__( # pylint: disable=too-many-arguments
495495
self,
496496
f_clock: int,
497497
nom_brp: int,
@@ -502,6 +502,7 @@ def __init__(
502502
data_tseg1: int,
503503
data_tseg2: int,
504504
data_sjw: int,
505+
strict: bool = False,
505506
) -> None:
506507
"""
507508
Initialize a BitTimingFd instance with the specified parameters.
@@ -530,6 +531,10 @@ def __init__(
530531
:param int data_sjw:
531532
The Synchronization Jump Width for the data phase. This value determines
532533
the maximum number of time quanta that the controller can resynchronize every bit.
534+
:param strict:
535+
If True, restrict bit timings to the minimum required range as defined in
536+
ISO 11898. This can be used to ensure compatibility across a wide variety
537+
of CAN hardware.
533538
:raises ValueError:
534539
if the arguments are invalid.
535540
"""
@@ -545,32 +550,23 @@ def __init__(
545550
"data_sjw": data_sjw,
546551
}
547552
self._validate()
553+
if strict:
554+
self._restrict_to_minimum_range()
548555

549556
def _validate(self) -> None:
550-
if self.nbt < 8:
551-
raise ValueError(f"nominal bit time (={self.nbt}) must be at least 8.")
552-
553-
if self.dbt < 8:
554-
raise ValueError(f"data bit time (={self.dbt}) must be at least 8.")
557+
for param, value in self._data.items():
558+
if value < 0: # type: ignore[operator]
559+
err_msg = f"'{param}' (={value}) must not be negative."
560+
raise ValueError(err_msg)
555561

556-
if not 1 <= self.nom_brp <= 256:
562+
if self.nom_brp < 1:
557563
raise ValueError(
558-
f"nominal bitrate prescaler (={self.nom_brp}) must be in [1...256]."
564+
f"nominal bitrate prescaler (={self.nom_brp}) must be at least 1."
559565
)
560566

561-
if not 1 <= self.data_brp <= 256:
567+
if self.data_brp < 1:
562568
raise ValueError(
563-
f"data bitrate prescaler (={self.data_brp}) must be in [1...256]."
564-
)
565-
566-
if not 5_000 <= self.nom_bitrate <= 2_000_000:
567-
raise ValueError(
568-
f"nom_bitrate (={self.nom_bitrate}) must be in [5,000...2,000,000]."
569-
)
570-
571-
if not 25_000 <= self.data_bitrate <= 8_000_000:
572-
raise ValueError(
573-
f"data_bitrate (={self.data_bitrate}) must be in [25,000...8,000,000]."
569+
f"data bitrate prescaler (={self.data_brp}) must be at least 1."
574570
)
575571

576572
if self.data_bitrate < self.nom_bitrate:
@@ -579,30 +575,12 @@ def _validate(self) -> None:
579575
f"equal to nom_bitrate (={self.nom_bitrate})"
580576
)
581577

582-
if not 2 <= self.nom_tseg1 <= 256:
583-
raise ValueError(f"nom_tseg1 (={self.nom_tseg1}) must be in [2...256].")
584-
585-
if not 1 <= self.nom_tseg2 <= 128:
586-
raise ValueError(f"nom_tseg2 (={self.nom_tseg2}) must be in [1...128].")
587-
588-
if not 1 <= self.data_tseg1 <= 32:
589-
raise ValueError(f"data_tseg1 (={self.data_tseg1}) must be in [1...32].")
590-
591-
if not 1 <= self.data_tseg2 <= 16:
592-
raise ValueError(f"data_tseg2 (={self.data_tseg2}) must be in [1...16].")
593-
594-
if not 1 <= self.nom_sjw <= 128:
595-
raise ValueError(f"nom_sjw (={self.nom_sjw}) must be in [1...128].")
596-
597578
if self.nom_sjw > self.nom_tseg2:
598579
raise ValueError(
599580
f"nom_sjw (={self.nom_sjw}) must not be "
600581
f"greater than nom_tseg2 (={self.nom_tseg2})."
601582
)
602583

603-
if not 1 <= self.data_sjw <= 16:
604-
raise ValueError(f"data_sjw (={self.data_sjw}) must be in [1...128].")
605-
606584
if self.data_sjw > self.data_tseg2:
607585
raise ValueError(
608586
f"data_sjw (={self.data_sjw}) must not be "
@@ -621,6 +599,44 @@ def _validate(self) -> None:
621599
f"(data_sample_point={self.data_sample_point:.2f}%)."
622600
)
623601

602+
def _restrict_to_minimum_range(self) -> None:
603+
# restrict to minimum required range as defined in ISO 11898
604+
if not 8 <= self.nbt <= 80:
605+
raise ValueError(f"Nominal bit time (={self.nbt}) must be in [8...80]")
606+
607+
if not 5 <= self.dbt <= 25:
608+
raise ValueError(f"Nominal bit time (={self.dbt}) must be in [5...25]")
609+
610+
if not 1 <= self.data_tseg1 <= 16:
611+
raise ValueError(f"data_tseg1 (={self.data_tseg1}) must be in [1...16].")
612+
613+
if not 2 <= self.data_tseg2 <= 8:
614+
raise ValueError(f"data_tseg2 (={self.data_tseg2}) must be in [2...8].")
615+
616+
if not 1 <= self.data_sjw <= 8:
617+
raise ValueError(f"data_sjw (={self.data_sjw}) must be in [1...8].")
618+
619+
if self.nom_brp == self.data_brp:
620+
# shared prescaler
621+
if not 2 <= self.nom_tseg1 <= 128:
622+
raise ValueError(f"nom_tseg1 (={self.nom_tseg1}) must be in [2...128].")
623+
624+
if not 2 <= self.nom_tseg2 <= 32:
625+
raise ValueError(f"nom_tseg2 (={self.nom_tseg2}) must be in [2...32].")
626+
627+
if not 1 <= self.nom_sjw <= 32:
628+
raise ValueError(f"nom_sjw (={self.nom_sjw}) must be in [1...32].")
629+
else:
630+
# separate prescaler
631+
if not 2 <= self.nom_tseg1 <= 64:
632+
raise ValueError(f"nom_tseg1 (={self.nom_tseg1}) must be in [2...64].")
633+
634+
if not 2 <= self.nom_tseg2 <= 16:
635+
raise ValueError(f"nom_tseg2 (={self.nom_tseg2}) must be in [2...16].")
636+
637+
if not 1 <= self.nom_sjw <= 16:
638+
raise ValueError(f"nom_sjw (={self.nom_sjw}) must be in [1...16].")
639+
624640
@classmethod
625641
def from_bitrate_and_segments(
626642
cls,
@@ -741,34 +757,36 @@ def from_sample_point(
741757

742758
possible_solutions: List[BitTimingFd] = []
743759

760+
sync_seg = 1
761+
744762
for nom_brp in range(1, 257):
745763
nbt = round(int(f_clock / (nom_bitrate * nom_brp)))
746-
if nbt < 8:
764+
if nbt < 1:
747765
break
748766

749767
effective_nom_bitrate = f_clock / (nbt * nom_brp)
750768
if abs(effective_nom_bitrate - nom_bitrate) > nom_bitrate / 256:
751769
continue
752770

753771
nom_tseg1 = int(round(nom_sample_point / 100 * nbt)) - 1
754-
# limit tseg1, so tseg2 is at least 1 TQ
755-
nom_tseg1 = min(nom_tseg1, nbt - 2)
772+
# limit tseg1, so tseg2 is at least 2 TQ
773+
nom_tseg1 = min(nom_tseg1, nbt - sync_seg - 2)
756774
nom_tseg2 = nbt - nom_tseg1 - 1
757775

758776
nom_sjw = min(nom_tseg2, 128)
759777

760778
for data_brp in range(1, 257):
761779
dbt = round(int(f_clock / (data_bitrate * data_brp)))
762-
if dbt < 8:
780+
if dbt < 1:
763781
break
764782

765783
effective_data_bitrate = f_clock / (dbt * data_brp)
766784
if abs(effective_data_bitrate - data_bitrate) > data_bitrate / 256:
767785
continue
768786

769787
data_tseg1 = int(round(data_sample_point / 100 * dbt)) - 1
770-
# limit tseg1, so tseg2 is at least 1 TQ
771-
data_tseg1 = min(data_tseg1, dbt - 2)
788+
# limit tseg1, so tseg2 is at least 2 TQ
789+
data_tseg1 = min(data_tseg1, dbt - sync_seg - 2)
772790
data_tseg2 = dbt - data_tseg1 - 1
773791

774792
data_sjw = min(data_tseg2, 16)
@@ -784,6 +802,7 @@ def from_sample_point(
784802
data_tseg1=data_tseg1,
785803
data_tseg2=data_tseg2,
786804
data_sjw=data_sjw,
805+
strict=True,
787806
)
788807
possible_solutions.append(bt)
789808
except ValueError:

can/util.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,8 @@ def _create_bus_config(config: Dict[str, Any]) -> typechecking.BusConfig:
250250
**{
251251
key: int(config[key])
252252
for key in typechecking.BitTimingFdDict.__annotations__
253-
}
253+
},
254+
strict=False,
254255
)
255256
elif set(typechecking.BitTimingDict.__annotations__).issubset(config):
256257
config["timing"] = can.BitTiming(

0 commit comments

Comments
 (0)