Skip to content

Commit 63a1028

Browse files
fix(bit-timing): preserve controller constraints
1 parent 8865a0a commit 63a1028

2 files changed

Lines changed: 98 additions & 17 deletions

File tree

‎can/bit_timing.py‎

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -74,19 +74,30 @@ def __init__(
7474
"sjw": sjw,
7575
"nof_samples": nof_samples,
7676
}
77+
self._tseg1_max = max(16, tseg1)
78+
self._tseg2_max = max(8, tseg2)
79+
self._brp_max = max(64, brp)
7780
if strict:
7881
self._validate()
7982
self._restrict_to_minimum_range()
8083

81-
def _validate(self) -> None:
82-
if not 1 <= self.brp <= 64:
83-
raise ValueError(f"bitrate prescaler (={self.brp}) must be in [1...64].")
84+
def _validate(
85+
self,
86+
*,
87+
tseg1_max: int = 16,
88+
tseg2_max: int = 8,
89+
brp_max: int = 64,
90+
) -> None:
91+
if not 1 <= self.brp <= brp_max:
92+
raise ValueError(
93+
f"bitrate prescaler (={self.brp}) must be in [1...{brp_max}]."
94+
)
8495

85-
if not 1 <= self.tseg1 <= 16:
86-
raise ValueError(f"tseg1 (={self.tseg1}) must be in [1...16].")
96+
if not 1 <= self.tseg1 <= tseg1_max:
97+
raise ValueError(f"tseg1 (={self.tseg1}) must be in [1...{tseg1_max}].")
8798

88-
if not 1 <= self.tseg2 <= 8:
89-
raise ValueError(f"tseg2 (={self.tseg2}) must be in [1...8].")
99+
if not 1 <= self.tseg2 <= tseg2_max:
100+
raise ValueError(f"tseg2 (={self.tseg2}) must be in [1...{tseg2_max}].")
90101

91102
if not 1 <= self.sjw <= 4:
92103
raise ValueError(f"sjw (={self.sjw}) must be in [1...4].")
@@ -264,24 +275,31 @@ def iterate_from_sample_point(
264275
effective_bitrate = f_clock / (nbt * brp)
265276
if abs(effective_bitrate - bitrate) > bitrate / 256:
266277
continue
278+
if not 5_000 <= round(effective_bitrate) <= 1_000_000:
279+
continue
280+
281+
tseg1_min = max(1, nbt - tseg2_max - 1)
282+
tseg1_max_feasible = min(tseg1_max, nbt - 2)
283+
if tseg1_min > tseg1_max_feasible:
284+
continue
267285

268286
tseg1 = round(sample_point / 100 * nbt) - 1
269-
# limit tseg1, so tseg2 is at least 1 TQ
270-
tseg1 = min(tseg1, nbt - 2)
287+
tseg1 = max(tseg1_min, min(tseg1, tseg1_max_feasible))
271288

272289
tseg2 = nbt - tseg1 - 1
273290
sjw = min(tseg2, 4)
274291

275-
if tseg1 > tseg1_max or tseg2 > tseg2_max:
276-
continue
277-
278-
yield cls(
292+
timing = cls(
279293
f_clock=f_clock,
280294
brp=brp,
281295
tseg1=tseg1,
282296
tseg2=tseg2,
283297
sjw=sjw,
284298
)
299+
timing._tseg1_max = tseg1_max
300+
timing._tseg2_max = tseg2_max
301+
timing._brp_max = brp_max
302+
yield timing
285303

286304
@classmethod
287305
def from_sample_point(
@@ -455,7 +473,7 @@ def recreate_with_f_clock(self, f_clock: int) -> "BitTiming":
455473
"""
456474
# try the most simple solution first: another bitrate prescaler
457475
try:
458-
return BitTiming.from_bitrate_and_segments(
476+
bt = BitTiming.from_bitrate_and_segments(
459477
f_clock=f_clock,
460478
bitrate=self.bitrate,
461479
tseg1=self.tseg1,
@@ -464,12 +482,21 @@ def recreate_with_f_clock(self, f_clock: int) -> "BitTiming":
464482
nof_samples=self.nof_samples,
465483
strict=True,
466484
)
485+
bt._tseg1_max = self._tseg1_max
486+
bt._tseg2_max = self._tseg2_max
487+
bt._brp_max = self._brp_max
488+
return bt
467489
except ValueError:
468490
pass
469491

470492
# create a new timing instance with the same sample point
471493
bt = BitTiming.from_sample_point(
472-
f_clock=f_clock, bitrate=self.bitrate, sample_point=self.sample_point
494+
f_clock=f_clock,
495+
bitrate=self.bitrate,
496+
sample_point=self.sample_point,
497+
tseg1_max=self._tseg1_max,
498+
tseg2_max=self._tseg2_max,
499+
brp_max=self._brp_max,
473500
)
474501
if abs(bt.sample_point - self.sample_point) > 1.0:
475502
raise ValueError(
@@ -480,7 +507,11 @@ def recreate_with_f_clock(self, f_clock: int) -> "BitTiming":
480507
sjw = max(1, min(4, bt.tseg2, sjw))
481508
bt._data["sjw"] = sjw # pylint: disable=protected-access
482509
bt._data["nof_samples"] = self.nof_samples # pylint: disable=protected-access
483-
bt._validate() # pylint: disable=protected-access
510+
bt._validate( # pylint: disable=protected-access
511+
tseg1_max=self._tseg1_max,
512+
tseg2_max=self._tseg2_max,
513+
brp_max=self._brp_max,
514+
)
484515
return bt
485516

486517
def __str__(self) -> str:

‎test/test_bit_timing.py‎

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ def test_from_sample_point_with_extended_hardware_limits():
316316
f_clock=160_000_000,
317317
bitrate=250_000,
318318
sample_point=87.5,
319-
brp_max=39,
319+
brp_max=28,
320320
)
321321

322322
with pytest.raises(ValueError, match="No suitable bit timings found"):
@@ -337,6 +337,37 @@ def test_from_sample_point_with_extended_hardware_limits():
337337
)
338338

339339

340+
def test_from_sample_point_rejects_effective_bitrate_outside_supported_range():
341+
with pytest.raises(ValueError, match="No suitable bit timings found"):
342+
can.BitTiming.from_sample_point(
343+
f_clock=8_024_000,
344+
bitrate=1_000_000,
345+
sample_point=75.0,
346+
)
347+
348+
349+
@pytest.mark.parametrize(
350+
("limits", "expected_tseg1", "expected_tseg2"),
351+
[
352+
({"tseg1_max": 4}, 4, 3),
353+
({"tseg2_max": 1}, 6, 1),
354+
],
355+
)
356+
def test_from_sample_point_clamps_to_controller_segment_limits(
357+
limits, expected_tseg1, expected_tseg2
358+
):
359+
timing = can.BitTiming.from_sample_point(
360+
f_clock=8_000_000,
361+
bitrate=1_000_000,
362+
sample_point=75.0,
363+
**limits,
364+
)
365+
366+
assert timing.bitrate == 1_000_000
367+
assert timing.tseg1 == expected_tseg1
368+
assert timing.tseg2 == expected_tseg2
369+
370+
340371
def test_iterate_from_sample_point():
341372
for sp in range(50, 100):
342373
solutions = list(
@@ -543,6 +574,25 @@ def test_recreate_with_f_clock():
543574
)
544575
assert timing_8mhz.nof_samples == timing_16mhz.nof_samples
545576

577+
extended_timing_160mhz = can.BitTiming.from_sample_point(
578+
f_clock=160_000_000,
579+
bitrate=5_000,
580+
sample_point=75.0,
581+
tseg1_max=256,
582+
tseg2_max=128,
583+
brp_max=512,
584+
)
585+
extended_timing_200mhz = extended_timing_160mhz.recreate_with_f_clock(
586+
f_clock=200_000_000
587+
)
588+
assert (
589+
abs(extended_timing_200mhz.bitrate - extended_timing_160mhz.bitrate)
590+
<= extended_timing_160mhz.bitrate / 256
591+
)
592+
assert extended_timing_200mhz.sample_point == pytest.approx(
593+
extended_timing_160mhz.sample_point, abs=1.0
594+
)
595+
546596
timing_16mhz = can.BitTiming(
547597
f_clock=16000000, brp=2, tseg1=12, tseg2=3, sjw=3, nof_samples=1
548598
)

0 commit comments

Comments
 (0)