Skip to content

Commit 85b1cb2

Browse files
ssj71spencer
andauthored
Add FD support to slcan according to CANable 2.0 impementation (#1920)
* add FD support to slcan according to CANable 2.0 impementation * allow 0 data bitrate to allow non-FD settings * make interface more consistent with other HW * proper DLC handling for FD frames in slcan * adding tests for slcan FD support * black formatting * adding optional keyword to optional arg --------- Co-authored-by: spencer <[email protected]>
1 parent c4808b7 commit 85b1cb2

File tree

2 files changed

+328
-7
lines changed

2 files changed

+328
-7
lines changed

can/interfaces/slcan.py

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,12 @@
1616
CanOperationError,
1717
error_check,
1818
)
19-
from can.util import check_or_adjust_timing_clock, deprecated_args_alias
19+
from can.util import (
20+
CAN_FD_DLC,
21+
check_or_adjust_timing_clock,
22+
deprecated_args_alias,
23+
len2dlc,
24+
)
2025

2126
logger = logging.getLogger(__name__)
2227

@@ -48,6 +53,11 @@ class slcanBus(BusABC):
4853
1000000: "S8",
4954
83300: "S9",
5055
}
56+
_DATA_BITRATES = {
57+
0: "",
58+
2000000: "Y2",
59+
5000000: "Y5",
60+
}
5161

5262
_SLEEP_AFTER_SERIAL_OPEN = 2 # in seconds
5363

@@ -86,7 +96,8 @@ def __init__(
8696
If this argument is set then it overrides the bitrate and btr arguments. The
8797
`f_clock` value of the timing instance must be set to 8_000_000 (8MHz)
8898
for standard CAN.
89-
CAN FD and the :class:`~can.BitTimingFd` class are not supported.
99+
CAN FD and the :class:`~can.BitTimingFd` class have partial support according to the non-standard
100+
slcan protocol implementation in the CANABLE 2.0 firmware: currently only data rates of 2M and 5M.
90101
:param poll_interval:
91102
Poll interval in seconds when reading messages
92103
:param sleep_after_open:
@@ -143,9 +154,7 @@ def __init__(
143154
timing = check_or_adjust_timing_clock(timing, valid_clocks=[8_000_000])
144155
self.set_bitrate_reg(f"{timing.btr0:02X}{timing.btr1:02X}")
145156
elif isinstance(timing, BitTimingFd):
146-
raise NotImplementedError(
147-
f"CAN FD is not supported by {self.__class__.__name__}."
148-
)
157+
self.set_bitrate(timing.nom_bitrate, timing.data_bitrate)
149158
else:
150159
if bitrate is not None and btr is not None:
151160
raise ValueError("Bitrate and btr mutually exclusive.")
@@ -157,10 +166,12 @@ def __init__(
157166

158167
super().__init__(channel, **kwargs)
159168

160-
def set_bitrate(self, bitrate: int) -> None:
169+
def set_bitrate(self, bitrate: int, data_bitrate: Optional[int] = None) -> None:
161170
"""
162171
:param bitrate:
163172
Bitrate in bit/s
173+
:param data_bitrate:
174+
Data Bitrate in bit/s for FD frames
164175
165176
:raise ValueError: if ``bitrate`` is not among the possible values
166177
"""
@@ -169,9 +180,15 @@ def set_bitrate(self, bitrate: int) -> None:
169180
else:
170181
bitrates = ", ".join(str(k) for k in self._BITRATES.keys())
171182
raise ValueError(f"Invalid bitrate, choose one of {bitrates}.")
183+
if data_bitrate in self._DATA_BITRATES:
184+
dbitrate_code = self._DATA_BITRATES[data_bitrate]
185+
else:
186+
dbitrates = ", ".join(str(k) for k in self._DATA_BITRATES.keys())
187+
raise ValueError(f"Invalid data bitrate, choose one of {dbitrates}.")
172188

173189
self.close()
174190
self._write(bitrate_code)
191+
self._write(dbitrate_code)
175192
self.open()
176193

177194
def set_bitrate_reg(self, btr: str) -> None:
@@ -235,6 +252,8 @@ def _recv_internal(
235252
remote = False
236253
extended = False
237254
data = None
255+
isFd = False
256+
fdBrs = False
238257

239258
if self._queue.qsize():
240259
string: Optional[str] = self._queue.get_nowait()
@@ -268,14 +287,44 @@ def _recv_internal(
268287
dlc = int(string[9])
269288
extended = True
270289
remote = True
290+
elif string[0] == "d":
291+
# FD standard frame
292+
canId = int(string[1:4], 16)
293+
dlc = int(string[4], 16)
294+
isFd = True
295+
data = bytearray.fromhex(string[5 : 5 + CAN_FD_DLC[dlc] * 2])
296+
elif string[0] == "D":
297+
# FD extended frame
298+
canId = int(string[1:9], 16)
299+
dlc = int(string[9], 16)
300+
extended = True
301+
isFd = True
302+
data = bytearray.fromhex(string[10 : 10 + CAN_FD_DLC[dlc] * 2])
303+
elif string[0] == "b":
304+
# FD with bitrate switch
305+
canId = int(string[1:4], 16)
306+
dlc = int(string[4], 16)
307+
isFd = True
308+
fdBrs = True
309+
data = bytearray.fromhex(string[5 : 5 + CAN_FD_DLC[dlc] * 2])
310+
elif string[0] == "B":
311+
# FD extended with bitrate switch
312+
canId = int(string[1:9], 16)
313+
dlc = int(string[9], 16)
314+
extended = True
315+
isFd = True
316+
fdBrs = True
317+
data = bytearray.fromhex(string[10 : 10 + CAN_FD_DLC[dlc] * 2])
271318

272319
if canId is not None:
273320
msg = Message(
274321
arbitration_id=canId,
275322
is_extended_id=extended,
276323
timestamp=time.time(), # Better than nothing...
277324
is_remote_frame=remote,
278-
dlc=dlc,
325+
is_fd=isFd,
326+
bitrate_switch=fdBrs,
327+
dlc=CAN_FD_DLC[dlc],
279328
data=data,
280329
)
281330
return msg, False
@@ -289,6 +338,20 @@ def send(self, msg: Message, timeout: Optional[float] = None) -> None:
289338
sendStr = f"R{msg.arbitration_id:08X}{msg.dlc:d}"
290339
else:
291340
sendStr = f"r{msg.arbitration_id:03X}{msg.dlc:d}"
341+
elif msg.is_fd:
342+
fd_dlc = len2dlc(msg.dlc)
343+
if msg.bitrate_switch:
344+
if msg.is_extended_id:
345+
sendStr = f"B{msg.arbitration_id:08X}{fd_dlc:X}"
346+
else:
347+
sendStr = f"b{msg.arbitration_id:03X}{fd_dlc:X}"
348+
sendStr += msg.data.hex().upper()
349+
else:
350+
if msg.is_extended_id:
351+
sendStr = f"D{msg.arbitration_id:08X}{fd_dlc:X}"
352+
else:
353+
sendStr = f"d{msg.arbitration_id:03X}{fd_dlc:X}"
354+
sendStr += msg.data.hex().upper()
292355
else:
293356
if msg.is_extended_id:
294357
sendStr = f"T{msg.arbitration_id:08X}{msg.dlc:d}"

0 commit comments

Comments
 (0)