Skip to content

Commit a047dc8

Browse files
committed
turn oscillator_tolerance into function, improve from_sample_point
1 parent 68f695b commit a047dc8

File tree

2 files changed

+185
-55
lines changed

2 files changed

+185
-55
lines changed

can/bit_timing.py

Lines changed: 150 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import math
12
from typing import List, Mapping, Iterator, cast
23

34
from can.typechecking import BitTimingFdDict, BitTimingDict
@@ -139,6 +140,13 @@ def from_sample_point(
139140
) -> "BitTiming":
140141
"""Create a BitTiming instance for a sample point.
141142
143+
This function tries to find bit timings, which are close to the requested
144+
sample point. It does not take physical bus properties into account, so the
145+
calculated bus timings might not work properly for you.
146+
147+
The :func:`oscillator_tolerance` function might be helpful to evaluate the
148+
bus timings.
149+
142150
:param int f_clock:
143151
The CAN system clock frequency in Hz.
144152
:param int bitrate:
@@ -161,8 +169,10 @@ def from_sample_point(
161169
continue
162170

163171
tseg1 = int(round(sample_point / 100 * nbt)) - 1
164-
tseg2 = nbt - tseg1 - 1
172+
# limit tseg1, so tseg2 is at least 1 TQ
173+
tseg1 = min(tseg1, nbt - 2)
165174

175+
tseg2 = nbt - tseg1 - 1
166176
sjw = min(tseg2, 4)
167177

168178
try:
@@ -173,17 +183,23 @@ def from_sample_point(
173183
tseg2=tseg2,
174184
sjw=sjw,
175185
)
176-
if abs(bt.sample_point - sample_point) < 1:
177-
possible_solutions.append(bt)
186+
possible_solutions.append(bt)
178187
except ValueError:
179188
continue
180189

181190
if not possible_solutions:
182191
raise ValueError("No suitable bit timings found.")
183192

184-
return sorted(
185-
possible_solutions, key=lambda x: x.oscillator_tolerance, reverse=True
186-
)[0]
193+
# sort solutions
194+
for key, reverse in (
195+
# prefer low prescaler
196+
(lambda x: x.brp, False),
197+
# prefer low sample point deviation from requested values
198+
(lambda x: abs(x.sample_point - sample_point), False),
199+
):
200+
possible_solutions.sort(key=key, reverse=reverse)
201+
202+
return possible_solutions[0]
187203

188204
@property
189205
def nbt(self) -> int:
@@ -200,6 +216,11 @@ def brp(self) -> int:
200216
"""Bit Rate Prescaler."""
201217
return int(round(self.f_clock / (self.bitrate * self.nbt)))
202218

219+
@property
220+
def tq(self) -> int:
221+
"""Time quantum in nanoseconds"""
222+
return int(round(self.brp / self.f_clock * 1e9))
223+
203224
@property
204225
def sjw(self) -> int:
205226
"""Synchronization Jump Width."""
@@ -236,14 +257,35 @@ def sample_point(self) -> float:
236257
"""Sample point in percent."""
237258
return 100.0 * (1 + self.tseg1) / (1 + self.tseg1 + self.tseg2)
238259

239-
@property
240-
def oscillator_tolerance(self) -> float:
241-
"""Oscillator tolerance in percent."""
260+
def oscillator_tolerance(
261+
self,
262+
node_loop_delay_ns: float = 250.0,
263+
bus_length_m: float = 10.0,
264+
) -> float:
265+
"""Oscillator tolerance in percent according to ISO 11898-1.
266+
267+
:param node_loop_delay_ns:
268+
Transceiver loop delay in nanoseconds.
269+
:param bus_length_m:
270+
Bus length in meters.
271+
"""
272+
delay_per_meter = 5
273+
bidirectional_propagation_delay_ns = 2 * (
274+
node_loop_delay_ns + delay_per_meter * bus_length_m
275+
)
276+
277+
prop_seg = math.ceil(bidirectional_propagation_delay_ns / self.tq)
278+
nom_phase_seg1 = self.tseg1 - prop_seg
279+
nom_phase_seg2 = self.tseg2
242280
df_clock_list = [
243281
_oscillator_tolerance_condition_1(nom_sjw=self.sjw, nbt=self.nbt),
244-
_oscillator_tolerance_condition_2(nbt=self.nbt, nom_tseg2=self.tseg2),
282+
_oscillator_tolerance_condition_2(
283+
nbt=self.nbt,
284+
nom_phase_seg1=nom_phase_seg1,
285+
nom_phase_seg2=nom_phase_seg2,
286+
),
245287
]
246-
return min(df_clock_list) * 100
288+
return max(0.0, min(df_clock_list) * 100)
247289

248290
@property
249291
def btr0(self) -> int:
@@ -290,10 +332,6 @@ def __str__(self) -> str:
290332
segments.append(f"f_clock: {self.f_clock / 1e6:.0f}MHz")
291333
except ValueError:
292334
pass
293-
try:
294-
segments.append(f"df_clock: {self.oscillator_tolerance:.2f}%")
295-
except ValueError:
296-
pass
297335
return ", ".join(segments)
298336

299337
def __repr__(self) -> str:
@@ -509,6 +547,13 @@ def from_sample_point(
509547
) -> "BitTimingFd":
510548
"""Create a BitTimingFd instance for a given nominal/data sample point pair.
511549
550+
This function tries to find bit timings, which are close to the requested
551+
sample points. It does not take physical bus properties into account, so the
552+
calculated bus timings might not work properly for you.
553+
554+
The :func:`oscillator_tolerance` function might be helpful to evaluate the
555+
bus timings.
556+
512557
:param int f_clock:
513558
The CAN system clock frequency in Hz.
514559
:param int nom_bitrate:
@@ -542,6 +587,8 @@ def from_sample_point(
542587
continue
543588

544589
nom_tseg1 = int(round(nom_sample_point / 100 * nbt)) - 1
590+
# limit tseg1, so tseg2 is at least 1 TQ
591+
nom_tseg1 = min(nom_tseg1, nbt - 2)
545592
nom_tseg2 = nbt - nom_tseg1 - 1
546593

547594
nom_sjw = min(nom_tseg2, 128)
@@ -556,28 +603,25 @@ def from_sample_point(
556603
continue
557604

558605
data_tseg1 = int(round(data_sample_point / 100 * dbt)) - 1
606+
# limit tseg1, so tseg2 is at least 1 TQ
607+
data_tseg1 = min(data_tseg1, dbt - 2)
559608
data_tseg2 = dbt - data_tseg1 - 1
560609

561610
data_sjw = min(data_tseg2, 16)
562611

563-
bit_timings = {
564-
"f_clock": f_clock,
565-
"nom_bitrate": nom_bitrate,
566-
"nom_tseg1": nom_tseg1,
567-
"nom_tseg2": nom_tseg2,
568-
"nom_sjw": nom_sjw,
569-
"data_bitrate": data_bitrate,
570-
"data_tseg1": data_tseg1,
571-
"data_tseg2": data_tseg2,
572-
"data_sjw": data_sjw,
573-
}
574612
try:
575-
bt = BitTimingFd(**bit_timings)
576-
if (
577-
abs(bt.nom_sample_point - nom_sample_point) < 1
578-
and abs(bt.data_sample_point - bt.data_sample_point) < 1
579-
):
580-
possible_solutions.append(bt)
613+
bt = BitTimingFd(
614+
f_clock=f_clock,
615+
nom_bitrate=nom_bitrate,
616+
nom_tseg1=nom_tseg1,
617+
nom_tseg2=nom_tseg2,
618+
nom_sjw=nom_sjw,
619+
data_bitrate=data_bitrate,
620+
data_tseg1=data_tseg1,
621+
data_tseg2=data_tseg2,
622+
data_sjw=data_sjw,
623+
)
624+
possible_solutions.append(bt)
581625
except ValueError:
582626
continue
583627

@@ -591,11 +635,20 @@ def from_sample_point(
591635
if same_prescaler:
592636
possible_solutions = same_prescaler
593637

594-
# sort solutions: prefer high tolerance, low prescaler and high sjw
638+
# sort solutions
595639
for key, reverse in (
596-
(lambda x: x.data_brp, False),
597-
(lambda x: x.nom_brp, False),
598-
(lambda x: x.oscillator_tolerance, True),
640+
# prefer low prescaler
641+
(lambda x: x.nom_brp + x.data_brp, False),
642+
# prefer same prescaler for arbitration and data
643+
(lambda x: abs(x.nom_brp - x.data_brp), False),
644+
# prefer low sample point deviation from requested values
645+
(
646+
lambda x: (
647+
abs(x.nom_sample_point - nom_sample_point)
648+
+ abs(x.data_sample_point - data_sample_point)
649+
),
650+
False,
651+
),
599652
):
600653
possible_solutions.sort(key=key, reverse=reverse)
601654

@@ -611,6 +664,11 @@ def nom_brp(self) -> int:
611664
"""Prescaler value for the arbitration phase."""
612665
return int(round(self.f_clock / (self.nom_bitrate * self.nbt)))
613666

667+
@property
668+
def nom_tq(self) -> int:
669+
"""Nominal time quantum in nanoseconds"""
670+
return int(round(self.nom_brp / self.f_clock * 1e9))
671+
614672
@property
615673
def nbt(self) -> int:
616674
"""Number of time quanta in a bit of the arbitration phase."""
@@ -652,6 +710,11 @@ def data_brp(self) -> int:
652710
"""Prescaler value for the data phase."""
653711
return int(round(self.f_clock / (self.data_bitrate * self.dbt)))
654712

713+
@property
714+
def data_tq(self) -> int:
715+
"""Data time quantum in nanoseconds"""
716+
return int(round(self.data_brp / self.f_clock * 1e9))
717+
655718
@property
656719
def dbt(self) -> int:
657720
"""Number of time quanta in a bit of the data phase."""
@@ -688,25 +751,52 @@ def f_clock(self) -> int:
688751
"""The CAN system clock frequency in Hz."""
689752
return self["f_clock"]
690753

691-
@property
692-
def oscillator_tolerance(self) -> float:
693-
"""Oscillator tolerance in percent."""
754+
def oscillator_tolerance(
755+
self,
756+
node_loop_delay_ns: float = 250.0,
757+
bus_length_m: float = 10.0,
758+
) -> float:
759+
"""Oscillator tolerance in percent according to ISO 11898-1.
760+
761+
:param node_loop_delay_ns:
762+
Transceiver loop delay in nanoseconds.
763+
:param bus_length_m:
764+
Bus length in meters.
765+
"""
766+
delay_per_meter = 5
767+
bidirectional_propagation_delay_ns = 2 * (
768+
node_loop_delay_ns + delay_per_meter * bus_length_m
769+
)
770+
771+
prop_seg = math.ceil(bidirectional_propagation_delay_ns / self.nom_tq)
772+
nom_phase_seg1 = self.nom_tseg1 - prop_seg
773+
nom_phase_seg2 = self.nom_tseg2
774+
775+
data_phase_seg2 = self.data_tseg2
776+
694777
df_clock_list = [
695778
_oscillator_tolerance_condition_1(nom_sjw=self.nom_sjw, nbt=self.nbt),
696-
_oscillator_tolerance_condition_2(nbt=self.nbt, nom_tseg2=self.nom_tseg2),
779+
_oscillator_tolerance_condition_2(
780+
nbt=self.nbt,
781+
nom_phase_seg1=nom_phase_seg1,
782+
nom_phase_seg2=nom_phase_seg2,
783+
),
697784
_oscillator_tolerance_condition_3(data_sjw=self.data_sjw, dbt=self.dbt),
698785
_oscillator_tolerance_condition_4(
699-
data_tseg2=self.data_tseg2,
786+
nom_phase_seg1=nom_phase_seg1,
787+
nom_phase_seg2=nom_phase_seg2,
788+
data_phase_seg2=data_phase_seg2,
700789
nbt=self.nbt,
790+
dbt=self.dbt,
701791
data_brp=self.data_brp,
702792
nom_brp=self.nom_brp,
703793
),
704794
_oscillator_tolerance_condition_5(
705795
data_sjw=self.data_sjw,
706796
data_brp=self.data_brp,
707797
nom_brp=self.nom_brp,
708-
data_tseg2=self.data_tseg2,
709-
nom_tseg2=self.nom_tseg2,
798+
data_phase_seg2=data_phase_seg2,
799+
nom_phase_seg2=nom_phase_seg2,
710800
nbt=self.nbt,
711801
dbt=self.dbt,
712802
),
@@ -767,10 +857,6 @@ def __str__(self) -> str:
767857
segments.append(f"f_clock: {self.f_clock / 1e6:.0f}MHz")
768858
except ValueError:
769859
pass
770-
try:
771-
segments.append(f"df_clock: {self.oscillator_tolerance:.2f}%")
772-
except ValueError:
773-
pass
774860
return ", ".join(segments)
775861

776862
def __repr__(self) -> str:
@@ -798,9 +884,11 @@ def _oscillator_tolerance_condition_1(nom_sjw: int, nbt: int) -> float:
798884
return nom_sjw / (2 * 10 * nbt)
799885

800886

801-
def _oscillator_tolerance_condition_2(nbt: int, nom_tseg2: int) -> float:
887+
def _oscillator_tolerance_condition_2(
888+
nbt: int, nom_phase_seg1: int, nom_phase_seg2: int
889+
) -> float:
802890
"""Arbitration phase - sampling of bit after error flag"""
803-
return nom_tseg2 / (2 * (13 * nbt - nom_tseg2))
891+
return min(nom_phase_seg1, nom_phase_seg2) / (2 * (13 * nbt - nom_phase_seg2))
804892

805893

806894
def _oscillator_tolerance_condition_3(data_sjw: int, dbt: int) -> float:
@@ -809,24 +897,32 @@ def _oscillator_tolerance_condition_3(data_sjw: int, dbt: int) -> float:
809897

810898

811899
def _oscillator_tolerance_condition_4(
812-
data_tseg2: int, nbt: int, data_brp: int, nom_brp: int
900+
nom_phase_seg1: int,
901+
nom_phase_seg2: int,
902+
data_phase_seg2: int,
903+
nbt: int,
904+
dbt: int,
905+
data_brp: int,
906+
nom_brp: int,
813907
) -> float:
814908
"""Data phase - sampling of bit after error flag"""
815-
return data_tseg2 / (2 * ((6 * nbt - data_tseg2) * data_brp / nom_brp + 7 * nbt))
909+
return min(nom_phase_seg1, nom_phase_seg2) / (
910+
2 * ((6 * dbt - data_phase_seg2) * data_brp / nom_brp + 7 * nbt)
911+
)
816912

817913

818914
def _oscillator_tolerance_condition_5(
819915
data_sjw: int,
820916
data_brp: int,
821917
nom_brp: int,
822-
nom_tseg2: int,
823-
data_tseg2: int,
918+
nom_phase_seg2: int,
919+
data_phase_seg2: int,
824920
nbt: int,
825921
dbt: int,
826922
) -> float:
827923
"""Data phase - bit rate switch"""
828924
max_correctable_phase_shift = data_sjw - max(0.0, nom_brp / data_brp - 1)
829925
time_between_resync = 2 * (
830-
(2 * nbt - nom_tseg2) * nom_brp / data_brp + data_tseg2 + 4 * dbt
926+
(2 * nbt - nom_phase_seg2) * nom_brp / data_brp + data_phase_seg2 + 4 * dbt
831927
)
832928
return max_correctable_phase_shift / time_between_resync

0 commit comments

Comments
 (0)