Skip to content

Commit db177b3

Browse files
authored
Relax BitTiming & BitTimingFd Validation (#1618)
* add `strict` parameter to can.BitTiming * add `strict` parameter to can.BitTimingFd * use `strict` in `from_bitrate_and_segments` and `recreate_with_f_clock` * cover `strict` parameter in tests * pylint: disable=too-many-arguments
1 parent e09d35e commit db177b3

File tree

4 files changed

+181
-70
lines changed

4 files changed

+181
-70
lines changed

can/bit_timing.py

Lines changed: 105 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ def __init__(
3636
tseg2: int,
3737
sjw: int,
3838
nof_samples: int = 1,
39+
strict: bool = False,
3940
) -> None:
4041
"""
4142
:param int f_clock:
@@ -56,6 +57,10 @@ def __init__(
5657
In this case, the bit will be sampled three quanta in a row,
5758
with the last sample being taken in the edge between TSEG1 and TSEG2.
5859
Three samples should only be used for relatively slow baudrates.
60+
:param bool strict:
61+
If True, restrict bit timings to the minimum required range as defined in
62+
ISO 11898. This can be used to ensure compatibility across a wide variety
63+
of CAN hardware.
5964
:raises ValueError:
6065
if the arguments are invalid.
6166
"""
@@ -68,19 +73,13 @@ def __init__(
6873
"nof_samples": nof_samples,
6974
}
7075
self._validate()
76+
if strict:
77+
self._restrict_to_minimum_range()
7178

7279
def _validate(self) -> None:
73-
if not 8 <= self.nbt <= 25:
74-
raise ValueError(f"nominal bit time (={self.nbt}) must be in [8...25].")
75-
7680
if not 1 <= self.brp <= 64:
7781
raise ValueError(f"bitrate prescaler (={self.brp}) must be in [1...64].")
7882

79-
if not 5_000 <= self.bitrate <= 2_000_000:
80-
raise ValueError(
81-
f"bitrate (={self.bitrate}) must be in [5,000...2,000,000]."
82-
)
83-
8483
if not 1 <= self.tseg1 <= 16:
8584
raise ValueError(f"tseg1 (={self.tseg1}) must be in [1...16].")
8685

@@ -104,6 +103,18 @@ def _validate(self) -> None:
104103
if self.nof_samples not in (1, 3):
105104
raise ValueError("nof_samples must be 1 or 3")
106105

106+
def _restrict_to_minimum_range(self) -> None:
107+
if not 8 <= self.nbt <= 25:
108+
raise ValueError(f"nominal bit time (={self.nbt}) must be in [8...25].")
109+
110+
if not 1 <= self.brp <= 32:
111+
raise ValueError(f"bitrate prescaler (={self.brp}) must be in [1...32].")
112+
113+
if not 5_000 <= self.bitrate <= 1_000_000:
114+
raise ValueError(
115+
f"bitrate (={self.bitrate}) must be in [5,000...1,000,000]."
116+
)
117+
107118
@classmethod
108119
def from_bitrate_and_segments(
109120
cls,
@@ -113,6 +124,7 @@ def from_bitrate_and_segments(
113124
tseg2: int,
114125
sjw: int,
115126
nof_samples: int = 1,
127+
strict: bool = False,
116128
) -> "BitTiming":
117129
"""Create a :class:`~can.BitTiming` instance from bitrate and segment lengths.
118130
@@ -134,6 +146,10 @@ def from_bitrate_and_segments(
134146
In this case, the bit will be sampled three quanta in a row,
135147
with the last sample being taken in the edge between TSEG1 and TSEG2.
136148
Three samples should only be used for relatively slow baudrates.
149+
:param bool strict:
150+
If True, restrict bit timings to the minimum required range as defined in
151+
ISO 11898. This can be used to ensure compatibility across a wide variety
152+
of CAN hardware.
137153
:raises ValueError:
138154
if the arguments are invalid.
139155
"""
@@ -149,6 +165,7 @@ def from_bitrate_and_segments(
149165
tseg2=tseg2,
150166
sjw=sjw,
151167
nof_samples=nof_samples,
168+
strict=strict,
152169
)
153170
if abs(bt.bitrate - bitrate) > bitrate / 256:
154171
raise ValueError(
@@ -175,6 +192,11 @@ def from_registers(
175192
:raises ValueError:
176193
if the arguments are invalid.
177194
"""
195+
if not 0 <= btr0 < 2**16:
196+
raise ValueError(f"Invalid btr0 value. ({btr0})")
197+
if not 0 <= btr1 < 2**16:
198+
raise ValueError(f"Invalid btr1 value. ({btr1})")
199+
178200
brp = (btr0 & 0x3F) + 1
179201
sjw = (btr0 >> 6) + 1
180202
tseg1 = (btr1 & 0xF) + 1
@@ -239,6 +261,7 @@ def from_sample_point(
239261
tseg1=tseg1,
240262
tseg2=tseg2,
241263
sjw=sjw,
264+
strict=True,
242265
)
243266
possible_solutions.append(bt)
244267
except ValueError:
@@ -316,12 +339,12 @@ def sample_point(self) -> float:
316339

317340
@property
318341
def btr0(self) -> int:
319-
"""Bit timing register 0."""
342+
"""Bit timing register 0 for SJA1000."""
320343
return (self.sjw - 1) << 6 | self.brp - 1
321344

322345
@property
323346
def btr1(self) -> int:
324-
"""Bit timing register 1."""
347+
"""Bit timing register 1 for SJA1000."""
325348
sam = 1 if self.nof_samples == 3 else 0
326349
return sam << 7 | (self.tseg2 - 1) << 4 | self.tseg1 - 1
327350

@@ -373,6 +396,7 @@ def recreate_with_f_clock(self, f_clock: int) -> "BitTiming":
373396
tseg2=self.tseg2,
374397
sjw=self.sjw,
375398
nof_samples=self.nof_samples,
399+
strict=True,
376400
)
377401
except ValueError:
378402
pass
@@ -474,7 +498,7 @@ class BitTimingFd(Mapping):
474498
)
475499
"""
476500

477-
def __init__(
501+
def __init__( # pylint: disable=too-many-arguments
478502
self,
479503
f_clock: int,
480504
nom_brp: int,
@@ -485,6 +509,7 @@ def __init__(
485509
data_tseg1: int,
486510
data_tseg2: int,
487511
data_sjw: int,
512+
strict: bool = False,
488513
) -> None:
489514
"""
490515
Initialize a BitTimingFd instance with the specified parameters.
@@ -513,6 +538,10 @@ def __init__(
513538
:param int data_sjw:
514539
The Synchronization Jump Width for the data phase. This value determines
515540
the maximum number of time quanta that the controller can resynchronize every bit.
541+
:param bool strict:
542+
If True, restrict bit timings to the minimum required range as defined in
543+
ISO 11898. This can be used to ensure compatibility across a wide variety
544+
of CAN hardware.
516545
:raises ValueError:
517546
if the arguments are invalid.
518547
"""
@@ -528,32 +557,23 @@ def __init__(
528557
"data_sjw": data_sjw,
529558
}
530559
self._validate()
560+
if strict:
561+
self._restrict_to_minimum_range()
531562

532563
def _validate(self) -> None:
533-
if self.nbt < 8:
534-
raise ValueError(f"nominal bit time (={self.nbt}) must be at least 8.")
535-
536-
if self.dbt < 8:
537-
raise ValueError(f"data bit time (={self.dbt}) must be at least 8.")
538-
539-
if not 1 <= self.nom_brp <= 256:
540-
raise ValueError(
541-
f"nominal bitrate prescaler (={self.nom_brp}) must be in [1...256]."
542-
)
543-
544-
if not 1 <= self.data_brp <= 256:
545-
raise ValueError(
546-
f"data bitrate prescaler (={self.data_brp}) must be in [1...256]."
547-
)
564+
for param, value in self._data.items():
565+
if value < 0: # type: ignore[operator]
566+
err_msg = f"'{param}' (={value}) must not be negative."
567+
raise ValueError(err_msg)
548568

549-
if not 5_000 <= self.nom_bitrate <= 2_000_000:
569+
if self.nom_brp < 1:
550570
raise ValueError(
551-
f"nom_bitrate (={self.nom_bitrate}) must be in [5,000...2,000,000]."
571+
f"nominal bitrate prescaler (={self.nom_brp}) must be at least 1."
552572
)
553573

554-
if not 25_000 <= self.data_bitrate <= 8_000_000:
574+
if self.data_brp < 1:
555575
raise ValueError(
556-
f"data_bitrate (={self.data_bitrate}) must be in [25,000...8,000,000]."
576+
f"data bitrate prescaler (={self.data_brp}) must be at least 1."
557577
)
558578

559579
if self.data_bitrate < self.nom_bitrate:
@@ -562,30 +582,12 @@ def _validate(self) -> None:
562582
f"equal to nom_bitrate (={self.nom_bitrate})"
563583
)
564584

565-
if not 2 <= self.nom_tseg1 <= 256:
566-
raise ValueError(f"nom_tseg1 (={self.nom_tseg1}) must be in [2...256].")
567-
568-
if not 1 <= self.nom_tseg2 <= 128:
569-
raise ValueError(f"nom_tseg2 (={self.nom_tseg2}) must be in [1...128].")
570-
571-
if not 1 <= self.data_tseg1 <= 32:
572-
raise ValueError(f"data_tseg1 (={self.data_tseg1}) must be in [1...32].")
573-
574-
if not 1 <= self.data_tseg2 <= 16:
575-
raise ValueError(f"data_tseg2 (={self.data_tseg2}) must be in [1...16].")
576-
577-
if not 1 <= self.nom_sjw <= 128:
578-
raise ValueError(f"nom_sjw (={self.nom_sjw}) must be in [1...128].")
579-
580585
if self.nom_sjw > self.nom_tseg2:
581586
raise ValueError(
582587
f"nom_sjw (={self.nom_sjw}) must not be "
583588
f"greater than nom_tseg2 (={self.nom_tseg2})."
584589
)
585590

586-
if not 1 <= self.data_sjw <= 16:
587-
raise ValueError(f"data_sjw (={self.data_sjw}) must be in [1...128].")
588-
589591
if self.data_sjw > self.data_tseg2:
590592
raise ValueError(
591593
f"data_sjw (={self.data_sjw}) must not be "
@@ -604,8 +606,46 @@ def _validate(self) -> None:
604606
f"(data_sample_point={self.data_sample_point:.2f}%)."
605607
)
606608

609+
def _restrict_to_minimum_range(self) -> None:
610+
# restrict to minimum required range as defined in ISO 11898
611+
if not 8 <= self.nbt <= 80:
612+
raise ValueError(f"Nominal bit time (={self.nbt}) must be in [8...80]")
613+
614+
if not 5 <= self.dbt <= 25:
615+
raise ValueError(f"Nominal bit time (={self.dbt}) must be in [5...25]")
616+
617+
if not 1 <= self.data_tseg1 <= 16:
618+
raise ValueError(f"data_tseg1 (={self.data_tseg1}) must be in [1...16].")
619+
620+
if not 2 <= self.data_tseg2 <= 8:
621+
raise ValueError(f"data_tseg2 (={self.data_tseg2}) must be in [2...8].")
622+
623+
if not 1 <= self.data_sjw <= 8:
624+
raise ValueError(f"data_sjw (={self.data_sjw}) must be in [1...8].")
625+
626+
if self.nom_brp == self.data_brp:
627+
# shared prescaler
628+
if not 2 <= self.nom_tseg1 <= 128:
629+
raise ValueError(f"nom_tseg1 (={self.nom_tseg1}) must be in [2...128].")
630+
631+
if not 2 <= self.nom_tseg2 <= 32:
632+
raise ValueError(f"nom_tseg2 (={self.nom_tseg2}) must be in [2...32].")
633+
634+
if not 1 <= self.nom_sjw <= 32:
635+
raise ValueError(f"nom_sjw (={self.nom_sjw}) must be in [1...32].")
636+
else:
637+
# separate prescaler
638+
if not 2 <= self.nom_tseg1 <= 64:
639+
raise ValueError(f"nom_tseg1 (={self.nom_tseg1}) must be in [2...64].")
640+
641+
if not 2 <= self.nom_tseg2 <= 16:
642+
raise ValueError(f"nom_tseg2 (={self.nom_tseg2}) must be in [2...16].")
643+
644+
if not 1 <= self.nom_sjw <= 16:
645+
raise ValueError(f"nom_sjw (={self.nom_sjw}) must be in [1...16].")
646+
607647
@classmethod
608-
def from_bitrate_and_segments(
648+
def from_bitrate_and_segments( # pylint: disable=too-many-arguments
609649
cls,
610650
f_clock: int,
611651
nom_bitrate: int,
@@ -616,6 +656,7 @@ def from_bitrate_and_segments(
616656
data_tseg1: int,
617657
data_tseg2: int,
618658
data_sjw: int,
659+
strict: bool = False,
619660
) -> "BitTimingFd":
620661
"""
621662
Create a :class:`~can.BitTimingFd` instance with the bitrates and segments lengths.
@@ -644,6 +685,10 @@ def from_bitrate_and_segments(
644685
:param int data_sjw:
645686
The Synchronization Jump Width for the data phase. This value determines
646687
the maximum number of time quanta that the controller can resynchronize every bit.
688+
:param bool strict:
689+
If True, restrict bit timings to the minimum required range as defined in
690+
ISO 11898. This can be used to ensure compatibility across a wide variety
691+
of CAN hardware.
647692
:raises ValueError:
648693
if the arguments are invalid.
649694
"""
@@ -665,6 +710,7 @@ def from_bitrate_and_segments(
665710
data_tseg1=data_tseg1,
666711
data_tseg2=data_tseg2,
667712
data_sjw=data_sjw,
713+
strict=strict,
668714
)
669715

670716
if abs(bt.nom_bitrate - nom_bitrate) > nom_bitrate / 256:
@@ -724,34 +770,36 @@ def from_sample_point(
724770

725771
possible_solutions: List[BitTimingFd] = []
726772

773+
sync_seg = 1
774+
727775
for nom_brp in range(1, 257):
728776
nbt = round(int(f_clock / (nom_bitrate * nom_brp)))
729-
if nbt < 8:
777+
if nbt < 1:
730778
break
731779

732780
effective_nom_bitrate = f_clock / (nbt * nom_brp)
733781
if abs(effective_nom_bitrate - nom_bitrate) > nom_bitrate / 256:
734782
continue
735783

736784
nom_tseg1 = int(round(nom_sample_point / 100 * nbt)) - 1
737-
# limit tseg1, so tseg2 is at least 1 TQ
738-
nom_tseg1 = min(nom_tseg1, nbt - 2)
785+
# limit tseg1, so tseg2 is at least 2 TQ
786+
nom_tseg1 = min(nom_tseg1, nbt - sync_seg - 2)
739787
nom_tseg2 = nbt - nom_tseg1 - 1
740788

741789
nom_sjw = min(nom_tseg2, 128)
742790

743791
for data_brp in range(1, 257):
744792
dbt = round(int(f_clock / (data_bitrate * data_brp)))
745-
if dbt < 8:
793+
if dbt < 1:
746794
break
747795

748796
effective_data_bitrate = f_clock / (dbt * data_brp)
749797
if abs(effective_data_bitrate - data_bitrate) > data_bitrate / 256:
750798
continue
751799

752800
data_tseg1 = int(round(data_sample_point / 100 * dbt)) - 1
753-
# limit tseg1, so tseg2 is at least 1 TQ
754-
data_tseg1 = min(data_tseg1, dbt - 2)
801+
# limit tseg1, so tseg2 is at least 2 TQ
802+
data_tseg1 = min(data_tseg1, dbt - sync_seg - 2)
755803
data_tseg2 = dbt - data_tseg1 - 1
756804

757805
data_sjw = min(data_tseg2, 16)
@@ -767,6 +815,7 @@ def from_sample_point(
767815
data_tseg1=data_tseg1,
768816
data_tseg2=data_tseg2,
769817
data_sjw=data_sjw,
818+
strict=True,
770819
)
771820
possible_solutions.append(bt)
772821
except ValueError:
@@ -971,6 +1020,7 @@ def recreate_with_f_clock(self, f_clock: int) -> "BitTimingFd":
9711020
data_tseg1=self.data_tseg1,
9721021
data_tseg2=self.data_tseg2,
9731022
data_sjw=self.data_sjw,
1023+
strict=True,
9741024
)
9751025
except ValueError:
9761026
pass

can/util.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,14 +250,16 @@ 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(
257258
**{
258259
key: int(config[key])
259260
for key in typechecking.BitTimingDict.__annotations__
260-
}
261+
},
262+
strict=False,
261263
)
262264
except (ValueError, TypeError):
263265
pass

0 commit comments

Comments
 (0)