Skip to content

Commit 9ab5e35

Browse files
authored
Merge pull request #615 from hardbyte/bit-timing-class
Add bit timing class
2 parents 0b7af59 + 60e1491 commit 9ab5e35

File tree

6 files changed

+394
-0
lines changed

6 files changed

+394
-0
lines changed

can/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class CanError(IOError):
3737
from .interfaces import VALID_INTERFACES
3838
from . import interface
3939
from .interface import Bus, detect_available_configs
40+
from .bit_timing import BitTiming
4041

4142
from .broadcastmanager import (
4243
CyclicSendTaskABC,

can/bit_timing.py

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
from typing import Union
2+
3+
4+
class BitTiming:
5+
"""Representation of a bit timing configuration.
6+
7+
The class can be constructed in various ways, depending on the information
8+
available or the capabilities of the interfaces that need to be supported.
9+
10+
The preferred way is using bitrate, CAN clock frequency, TSEG1, TSEG2, SJW::
11+
12+
can.BitTiming(bitrate=1000000, f_clock=8000000, tseg1=5, tseg2=1, sjw=1)
13+
14+
If the clock frequency is unknown it may be omitted but some interfaces may
15+
require it.
16+
17+
Alternatively the BRP can be given instead of bitrate and clock frequency but this
18+
will limit the number of supported interfaces.
19+
20+
It is also possible specify BTR registers directly,
21+
but will not work for all interfaces::
22+
23+
can.BitTiming(btr0=0x00, btr1=0x14)
24+
"""
25+
26+
sync_seg = 1
27+
28+
def __init__(
29+
self,
30+
bitrate: int = None,
31+
f_clock: int = None,
32+
brp: int = None,
33+
tseg1: int = None,
34+
tseg2: int = None,
35+
sjw: int = None,
36+
nof_samples: int = 1,
37+
btr0: int = None,
38+
btr1: int = None,
39+
):
40+
"""
41+
:param int bitrate:
42+
Bitrate in bits/s.
43+
:param int f_clock:
44+
The CAN system clock frequency in Hz.
45+
Usually the oscillator frequency divided by 2.
46+
:param int brp:
47+
Bit Rate Prescaler. Prefer to use bitrate and f_clock instead.
48+
:param int tseg1:
49+
Time segment 1, that is, the number of quanta from (but not including)
50+
the Sync Segment to the sampling point.
51+
:param int tseg2:
52+
Time segment 2, that is, the number of quanta from the sampling
53+
point to the end of the bit.
54+
:param int sjw:
55+
The Synchronization Jump Width. Decides the maximum number of time quanta
56+
that the controller can resynchronize every bit.
57+
:param int nof_samples:
58+
Either 1 or 3. Some CAN controllers can also sample each bit three times.
59+
In this case, the bit will be sampled three quanta in a row,
60+
with the last sample being taken in the edge between TSEG1 and TSEG2.
61+
Three samples should only be used for relatively slow baudrates.
62+
:param int btr0:
63+
The BTR0 register value used by many CAN controllers.
64+
:param int btr1:
65+
The BTR1 register value used by many CAN controllers.
66+
"""
67+
self._bitrate = bitrate
68+
self._brp = brp
69+
self._sjw = sjw
70+
self._tseg1 = tseg1
71+
self._tseg2 = tseg2
72+
self._nof_samples = nof_samples
73+
self._f_clock = f_clock
74+
75+
if btr0 is not None:
76+
self._brp = (btr0 & 0x3F) + 1
77+
self._sjw = (btr0 >> 6) + 1
78+
if btr1 is not None:
79+
self._tseg1 = (btr1 & 0xF) + 1
80+
self._tseg2 = ((btr1 >> 4) & 0x7) + 1
81+
self._nof_samples = 3 if btr1 & 0x80 else 1
82+
83+
if nof_samples not in (1, 3):
84+
raise ValueError("nof_samples must be 1 or 3")
85+
86+
@property
87+
def nbt(self) -> int:
88+
"""Nominal Bit Time."""
89+
return self.sync_seg + self.tseg1 + self.tseg2
90+
91+
@property
92+
def bitrate(self) -> Union[int, float]:
93+
"""Bitrate in bits/s."""
94+
if self._bitrate:
95+
return self._bitrate
96+
if self._f_clock and self._brp:
97+
return self._f_clock / (self._brp * self.nbt)
98+
raise ValueError("bitrate must be specified")
99+
100+
@property
101+
def brp(self) -> int:
102+
"""Bit Rate Prescaler."""
103+
if self._brp:
104+
return self._brp
105+
if self._f_clock and self._bitrate:
106+
return round(self._f_clock / (self._bitrate * self.nbt))
107+
raise ValueError("Either bitrate and f_clock or brp must be specified")
108+
109+
@property
110+
def sjw(self) -> int:
111+
"""Synchronization Jump Width."""
112+
if not self._sjw:
113+
raise ValueError("sjw must be specified")
114+
return self._sjw
115+
116+
@property
117+
def tseg1(self) -> int:
118+
"""Time segment 1.
119+
120+
The number of quanta from (but not including) the Sync Segment to the sampling point.
121+
"""
122+
if not self._tseg1:
123+
raise ValueError("tseg1 must be specified")
124+
return self._tseg1
125+
126+
@property
127+
def tseg2(self) -> int:
128+
"""Time segment 2.
129+
130+
The number of quanta from the sampling point to the end of the bit.
131+
"""
132+
if not self._tseg2:
133+
raise ValueError("tseg2 must be specified")
134+
return self._tseg2
135+
136+
@property
137+
def nof_samples(self) -> int:
138+
"""Number of samples (1 or 3)."""
139+
if not self._nof_samples:
140+
raise ValueError("nof_samples must be specified")
141+
return self._nof_samples
142+
143+
@property
144+
def f_clock(self) -> int:
145+
"""The CAN system clock frequency in Hz.
146+
147+
Usually the oscillator frequency divided by 2.
148+
"""
149+
if not self._f_clock:
150+
raise ValueError("f_clock must be specified")
151+
return self._f_clock
152+
153+
@property
154+
def sample_point(self) -> float:
155+
"""Sample point in percent."""
156+
return 100.0 * (self.nbt - self.tseg2) / self.nbt
157+
158+
@property
159+
def btr0(self) -> int:
160+
sjw = self.sjw
161+
brp = self.brp
162+
163+
if brp < 1 or brp > 64:
164+
raise ValueError("brp must be 1 - 64")
165+
if sjw < 1 or sjw > 4:
166+
raise ValueError("sjw must be 1 - 4")
167+
168+
return (sjw - 1) << 6 | brp - 1
169+
170+
@property
171+
def btr1(self) -> int:
172+
sam = 1 if self.nof_samples == 3 else 0
173+
tseg1 = self.tseg1
174+
tseg2 = self.tseg2
175+
176+
if tseg1 < 1 or tseg1 > 16:
177+
raise ValueError("tseg1 must be 1 - 16")
178+
if tseg2 < 1 or tseg2 > 8:
179+
raise ValueError("tseg2 must be 1 - 8")
180+
181+
return sam << 7 | (tseg2 - 1) << 4 | tseg1 - 1
182+
183+
def __str__(self) -> str:
184+
segments = []
185+
try:
186+
segments.append(f"{self.bitrate} bits/s")
187+
except ValueError:
188+
pass
189+
try:
190+
segments.append(f"sample point: {self.sample_point:.2f}%")
191+
except ValueError:
192+
pass
193+
try:
194+
segments.append(f"BRP: {self.brp}")
195+
except ValueError:
196+
pass
197+
try:
198+
segments.append(f"TSEG1: {self.tseg1}")
199+
except ValueError:
200+
pass
201+
try:
202+
segments.append(f"TSEG2: {self.tseg2}")
203+
except ValueError:
204+
pass
205+
try:
206+
segments.append(f"SJW: {self.sjw}")
207+
except ValueError:
208+
pass
209+
try:
210+
segments.append(f"BTR: {self.btr0:02X}{self.btr1:02X}h")
211+
except ValueError:
212+
pass
213+
return ", ".join(segments)
214+
215+
def __repr__(self) -> str:
216+
kwargs = {}
217+
if self._f_clock:
218+
kwargs["f_clock"] = self._f_clock
219+
if self._bitrate:
220+
kwargs["bitrate"] = self._bitrate
221+
if self._brp:
222+
kwargs["brp"] = self._brp
223+
if self._tseg1:
224+
kwargs["tseg1"] = self._tseg1
225+
if self._tseg2:
226+
kwargs["tseg2"] = self._tseg2
227+
if self._sjw:
228+
kwargs["sjw"] = self._sjw
229+
if self._nof_samples != 1:
230+
kwargs["nof_samples"] = self._nof_samples
231+
args = ", ".join(f"{key}={value}" for key, value in kwargs.items())
232+
return f"can.BitTiming({args})"

can/util.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,25 @@ def load_config(path=None, config=None, context=None):
166166
if "bitrate" in config:
167167
config["bitrate"] = int(config["bitrate"])
168168

169+
# Create bit timing configuration if given
170+
timing_conf = {}
171+
for key in (
172+
"f_clock",
173+
"brp",
174+
"tseg1",
175+
"tseg2",
176+
"sjw",
177+
"nof_samples",
178+
"btr0",
179+
"btr1",
180+
):
181+
if key in config:
182+
timing_conf[key] = int(config[key], base=0)
183+
del config[key]
184+
if timing_conf:
185+
timing_conf["bitrate"] = config.get("bitrate")
186+
config["timing"] = can.BitTiming(**timing_conf)
187+
169188
can.log.debug("can config: {}".format(config))
170189
return config
171190

doc/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ A form of CAN interface is also required.
1717
listeners
1818
asyncio
1919
bcm
20+
bit_timing
2021
internal-api
2122

2223

doc/bit_timing.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
Bit Timing Configuration
2+
========================
3+
4+
The CAN protocol allows the bitrate, sample point and number of samples to be
5+
optimized for a given application. You can read more on Wikipedia_, Kvaser_
6+
and other sources.
7+
8+
In most cases the recommended settings for a predefined set of common
9+
bitrates will work just fine. In some cases it may however be necessary to specify
10+
custom settings. The :class:`can.BitTiming` class can be used for this purpose to
11+
specify them in a relatively interface agnostic manner.
12+
13+
It is also possible to specify the same settings for a CAN 2.0 bus
14+
using the config file:
15+
16+
17+
.. code-block:: none
18+
19+
[default]
20+
bitrate=1000000
21+
f_clock=8000000
22+
tseg1=5
23+
tseg2=2
24+
sjw=1
25+
nof_samples=1
26+
27+
28+
.. code-block:: none
29+
30+
[default]
31+
brp=1
32+
tseg1=5
33+
tseg2=2
34+
sjw=1
35+
nof_samples=1
36+
37+
38+
.. code-block:: none
39+
40+
[default]
41+
btr0=0x00
42+
btr1=0x14
43+
44+
45+
.. autoclass:: can.BitTiming
46+
47+
48+
.. _Wikipedia: https://en.wikipedia.org/wiki/CAN_bus#Bit_timing
49+
.. _Kvaser: https://www.kvaser.com/about-can/the-can-protocol/can-bit-timing/

0 commit comments

Comments
 (0)