Skip to content

Commit 8865a0a

Browse files
fix(bit-timing): support extended hardware limits
1 parent b4f82ab commit 8865a0a

4 files changed

Lines changed: 123 additions & 15 deletions

File tree

can/bit_timing.py

Lines changed: 57 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,14 @@ def from_registers(
215215

216216
@classmethod
217217
def iterate_from_sample_point(
218-
cls, f_clock: int, bitrate: int, sample_point: float = 69.0
218+
cls,
219+
f_clock: int,
220+
bitrate: int,
221+
sample_point: float = 69.0,
222+
*,
223+
tseg1_max: int = 16,
224+
tseg2_max: int = 8,
225+
brp_max: int = 64,
219226
) -> Iterator["BitTiming"]:
220227
"""Create a :class:`~can.BitTiming` iterator with all the solutions for a sample point.
221228
@@ -225,14 +232,31 @@ def iterate_from_sample_point(
225232
Bitrate in bit/s.
226233
:param int sample_point:
227234
The sample point value in percent.
235+
:param int tseg1_max:
236+
Maximum time segment 1 value supported by the CAN controller.
237+
:param int tseg2_max:
238+
Maximum time segment 2 value supported by the CAN controller.
239+
:param int brp_max:
240+
Maximum bit rate prescaler supported by the CAN controller.
228241
:raises ValueError:
229242
if the arguments are invalid.
230243
"""
231244

232245
if sample_point < 50.0:
233246
raise ValueError(f"sample_point (={sample_point}) must not be below 50%.")
234247

235-
for brp in range(1, 65):
248+
for name, value in (
249+
("tseg1_max", tseg1_max),
250+
("tseg2_max", tseg2_max),
251+
("brp_max", brp_max),
252+
):
253+
if value < 1:
254+
raise ValueError(f"{name} (={value}) must be at least 1.")
255+
256+
if not 5_000 <= bitrate <= 1_000_000:
257+
return
258+
259+
for brp in range(1, brp_max + 1):
236260
nbt = int(f_clock / (bitrate * brp))
237261
if nbt < 8:
238262
break
@@ -248,22 +272,27 @@ def iterate_from_sample_point(
248272
tseg2 = nbt - tseg1 - 1
249273
sjw = min(tseg2, 4)
250274

251-
try:
252-
bt = BitTiming(
253-
f_clock=f_clock,
254-
brp=brp,
255-
tseg1=tseg1,
256-
tseg2=tseg2,
257-
sjw=sjw,
258-
strict=True,
259-
)
260-
yield bt
261-
except ValueError:
275+
if tseg1 > tseg1_max or tseg2 > tseg2_max:
262276
continue
263277

278+
yield cls(
279+
f_clock=f_clock,
280+
brp=brp,
281+
tseg1=tseg1,
282+
tseg2=tseg2,
283+
sjw=sjw,
284+
)
285+
264286
@classmethod
265287
def from_sample_point(
266-
cls, f_clock: int, bitrate: int, sample_point: float = 69.0
288+
cls,
289+
f_clock: int,
290+
bitrate: int,
291+
sample_point: float = 69.0,
292+
*,
293+
tseg1_max: int = 16,
294+
tseg2_max: int = 8,
295+
brp_max: int = 64,
267296
) -> "BitTiming":
268297
"""Create a :class:`~can.BitTiming` instance for a sample point.
269298
@@ -280,6 +309,12 @@ def from_sample_point(
280309
Bitrate in bit/s.
281310
:param int sample_point:
282311
The sample point value in percent.
312+
:param int tseg1_max:
313+
Maximum time segment 1 value supported by the CAN controller.
314+
:param int tseg2_max:
315+
Maximum time segment 2 value supported by the CAN controller.
316+
:param int brp_max:
317+
Maximum bit rate prescaler supported by the CAN controller.
283318
:raises ValueError:
284319
if the arguments are invalid.
285320
"""
@@ -288,7 +323,14 @@ def from_sample_point(
288323
raise ValueError(f"sample_point (={sample_point}) must not be below 50%.")
289324

290325
possible_solutions: list[BitTiming] = list(
291-
cls.iterate_from_sample_point(f_clock, bitrate, sample_point)
326+
cls.iterate_from_sample_point(
327+
f_clock,
328+
bitrate,
329+
sample_point,
330+
tseg1_max=tseg1_max,
331+
tseg2_max=tseg2_max,
332+
brp_max=brp_max,
333+
)
292334
)
293335

294336
if not possible_solutions:

doc/bit_timing.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,20 @@ produce an overview of possible bit timings for your desired bit rate:
8787
BR: 250_000 bit/s, SP: 87.50%, BRP: 2, TSEG1: 13, TSEG2: 2, SJW: 2, BTR: 411Ch, CLK: 8MHz
8888
BR: 250_000 bit/s, SP: 93.75%, BRP: 2, TSEG1: 14, TSEG2: 1, SJW: 1, BTR: 010Dh, CLK: 8MHz
8989

90+
Controller-specific maximum values can be supplied when the standard timing
91+
limits are too restrictive:
92+
93+
.. code-block:: python
94+
95+
timing = can.BitTiming.from_sample_point(
96+
f_clock=160_000_000,
97+
bitrate=250_000,
98+
sample_point=87.5,
99+
tseg1_max=256,
100+
tseg2_max=128,
101+
brp_max=512,
102+
)
103+
90104
91105
It is possible to specify CAN 2.0 bit timings
92106
using the config file:

doc/changelog.d/2083.fixed.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow ``BitTiming.from_sample_point`` to find valid timings for CAN controllers with bit rate prescalers above 32.

test/test_bit_timing.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,57 @@ def test_from_sample_point():
286286
)
287287

288288

289+
def test_from_sample_point_with_extended_hardware_limits():
290+
timing = can.BitTiming.from_sample_point(
291+
f_clock=160_000_000,
292+
bitrate=250_000,
293+
sample_point=87.5,
294+
)
295+
296+
assert timing.bitrate == 250_000
297+
assert timing.sample_point == 87.5
298+
assert timing.brp == 40
299+
assert timing.tseg1 == 13
300+
assert timing.tseg2 == 2
301+
302+
extended_timing = can.BitTiming.from_sample_point(
303+
f_clock=160_000_000,
304+
bitrate=250_000,
305+
sample_point=87.5,
306+
tseg1_max=256,
307+
tseg2_max=128,
308+
brp_max=512,
309+
)
310+
assert extended_timing.brp == 4
311+
assert extended_timing.tseg1 == 139
312+
assert extended_timing.tseg2 == 20
313+
314+
with pytest.raises(ValueError, match="No suitable bit timings found"):
315+
can.BitTiming.from_sample_point(
316+
f_clock=160_000_000,
317+
bitrate=250_000,
318+
sample_point=87.5,
319+
brp_max=39,
320+
)
321+
322+
with pytest.raises(ValueError, match="No suitable bit timings found"):
323+
can.BitTiming.from_sample_point(
324+
f_clock=80_000_000,
325+
bitrate=2_000_000,
326+
sample_point=75.0,
327+
)
328+
329+
for parameter in ("tseg1_max", "tseg2_max", "brp_max"):
330+
with pytest.raises(ValueError, match=rf"{parameter} \(=0\) must be at least 1"):
331+
list(
332+
can.BitTiming.iterate_from_sample_point(
333+
f_clock=16_000_000,
334+
bitrate=500_000,
335+
**{parameter: 0},
336+
)
337+
)
338+
339+
289340
def test_iterate_from_sample_point():
290341
for sp in range(50, 100):
291342
solutions = list(

0 commit comments

Comments
 (0)