From d877d7336b75e84900e185afedd1418a06a9fbdb Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Thu, 5 Mar 2026 19:59:14 +0100 Subject: [PATCH 01/46] decoders/can: set crc_len after dlc has been read --- decoders/can/pd.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index fcd13e6ae..987f8adbd 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -92,6 +92,17 @@ def set_nominal_bitrate(self): def set_fast_bitrate(self): self.set_bit_rate(self.options['fast_bitrate']) + def set_dlc_and_crc_len(self, dlc): + self.dlc = dlc + + if self.fd: + if dlc2len(self.dlc) < 16: + self.crc_len = 27 # 17 + SBC + stuff bits + else: + self.crc_len = 32 # 21 + SBC + stuff bits + else: + self.crc_len = 15 + def metadata(self, key, value): if key == srd.SRD_CONF_SAMPLERATE: self.samplerate = value @@ -185,13 +196,6 @@ def decode_frame_end(self, can_rx, bitnum): # Remember start of CRC sequence (see below). if bitnum == (self.last_databit + 1): self.ss_block = self.samplenum - if self.fd: - if dlc2len(self.dlc) < 16: - self.crc_len = 27 # 17 + SBC + stuff bits - else: - self.crc_len = 32 # 21 + SBC + stuff bits - else: - self.crc_len = 15 # CRC sequence (15 bits, 17 bits or 21 bits) elif bitnum == (self.last_databit + self.crc_len): @@ -295,7 +299,7 @@ def decode_standard_frame(self, can_rx, bitnum): # Bits 15-18: Data length code (DLC), in number of bytes (0-8). elif bitnum == self.dlc_start + 3: - self.dlc = bitpack_msb(self.bits[self.dlc_start:self.dlc_start + 4]) + self.set_dlc_and_crc_len(bitpack_msb(self.bits[self.dlc_start:self.dlc_start + 4])) self.putb([10, ['Data length code: %d' % self.dlc, 'DLC: %d' % self.dlc, 'DLC']]) self.last_databit = self.dlc_start + 3 + (dlc2len(self.dlc) * 8) @@ -397,7 +401,7 @@ def decode_extended_frame(self, can_rx, bitnum): # Bits 35-38: Data length code (DLC), in number of bytes (0-8). elif bitnum == self.dlc_start + 3: - self.dlc = bitpack_msb(self.bits[self.dlc_start:self.dlc_start + 4]) + self.set_dlc_and_crc_len(bitpack_msb(self.bits[self.dlc_start:self.dlc_start + 4])) self.putb([10, ['Data length code: %d' % self.dlc, 'DLC: %d' % self.dlc, 'DLC']]) self.last_databit = self.dlc_start + 3 + (dlc2len(self.dlc) * 8) From 577addb41ee87be2632709b95fdfe4f5548dcc82 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Thu, 5 Mar 2026 19:59:14 +0100 Subject: [PATCH 02/46] decoders/can: implement fixed bit stuffing for CAN-FD CRC field --- decoders/can/pd.py | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 987f8adbd..e423b15a4 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -97,9 +97,9 @@ def set_dlc_and_crc_len(self, dlc): if self.fd: if dlc2len(self.dlc) < 16: - self.crc_len = 27 # 17 + SBC + stuff bits + self.crc_len = 21 # 17 + SBC bits else: - self.crc_len = 32 # 21 + SBC + stuff bits + self.crc_len = 25 # 21 + SBC bits else: self.crc_len = 15 @@ -148,6 +148,8 @@ def reset_variables(self): self.rtr_type = None self.fd = False self.rtr = None + self.last_bit_was_stuff_bit = False + self.crc_len = 15 # Poor man's clock synchronization. Use signal edges which change to # dominant state in rather simple ways. This naive approach is neither @@ -166,17 +168,44 @@ def get_sample_point(self, bitnum): return int(samplenum) def is_stuff_bit(self): - # CAN uses NRZ encoding and bit stuffing. - # After 5 identical bits, a stuff bit of opposite value is added. - # But not in the CRC delimiter, ACK, and end of frame fields. - if len(self.bits) > self.last_databit + 17: + # CAN uses NRZ encoding (dynamic bit stuffing). + # After five consecutive bits of identical value, a stuff bit of the + # opposite polarity is inserted. + # + # In CAN-FD frames, additional fixed bit stuffing is used for the CRC field. + # This fixed stuffing begins one bit before the CRC field, regardless of + # whether five identical bits have occurred. + + # If a dynamic stuff bit and a fixed stuff bit would be inserted at the + # same position, only the fixed stuff bit is inserted. In this case, + # only a single stuff bit is inserted: + if self.last_bit_was_stuff_bit: + self.last_bit_was_stuff_bit = False return False - last_6_bits = self.rawbits[-6:] - if last_6_bits not in ([0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 0]): + + cur_bit = len(self.bits) - 1 + + # Bit stuffing is not applied to the CRC delimiter, ACK field, + # or End-of-Frame (EOF) field: + if cur_bit > self.last_databit + self.crc_len: + self.last_bit_was_stuff_bit = False return False + if self.fd and cur_bit > self.last_databit: + # Within the CAN-FD CRC field, a fixed stuff bit is inserted after every fourth bit: + if (cur_bit - self.last_databit - 1) % 4 != 0: + self.last_bit_was_stuff_bit = False + return False + else: + # NRZ dynamic bit stuffing: + last_6_bits = self.rawbits[-6:] + if last_6_bits not in ([0, 0, 0, 0, 0, 1], [1, 1, 1, 1, 1, 0]): + self.last_bit_was_stuff_bit = False + return False + # Stuff bit. Keep it in self.rawbits, but drop it from self.bits. self.bits.pop() # Drop last bit. + self.last_bit_was_stuff_bit = True return True def is_valid_crc(self, crc_bits): From 3303742537ee8034cb8fa07e5d09f4744f80237f Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Thu, 5 Mar 2026 19:59:14 +0100 Subject: [PATCH 03/46] decoders/can: introduce crc_start --- decoders/can/pd.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index e423b15a4..eb749382c 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -140,6 +140,7 @@ def reset_variables(self): self.bits = [] # Only actual CAN frame bits (no stuff bits) self.curbit = 0 # Current bit of CAN frame (bit 0 == SOF) self.last_databit = 999 # Positive value that bitnum+x will never match + self.crc_start = 999 self.ss_block = None self.ss_bit12 = None self.ss_bit32 = None @@ -332,6 +333,11 @@ def decode_standard_frame(self, can_rx, bitnum): self.putb([10, ['Data length code: %d' % self.dlc, 'DLC: %d' % self.dlc, 'DLC']]) self.last_databit = self.dlc_start + 3 + (dlc2len(self.dlc) * 8) + self.crc_start = self.last_databit + 1 + + if self.fd: + self.crc_start += 4 # Skip SBC field + if self.dlc > 8 and not self.fd: self.putb([16, ['Data length code (DLC) > 8 is not allowed']]) @@ -434,6 +440,10 @@ def decode_extended_frame(self, can_rx, bitnum): self.putb([10, ['Data length code: %d' % self.dlc, 'DLC: %d' % self.dlc, 'DLC']]) self.last_databit = self.dlc_start + 3 + (dlc2len(self.dlc) * 8) + self.crc_start = self.last_databit + 1 + + if self.fd: + self.crc_start += 4 # Skip SBC field # Remember all databyte bits, except the very last one. elif bitnum in range(self.dlc_start + 4, self.last_databit): From 1d14cc76499d906e127ba9de19360b0f7b1c67ef Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Thu, 5 Mar 2026 19:59:14 +0100 Subject: [PATCH 04/46] decoders/can: decode raw stuff bit count --- decoders/can/pd.py | 47 ++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index eb749382c..ebdd55e76 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -64,10 +64,11 @@ class Decoder(srd.Decoder): ('stuff-bit', 'Stuff bit'), ('warning', 'Warning'), ('bit', 'Bit'), + ('sbc', 'Stuff bit count') ) annotation_rows = ( ('bits', 'Bits', (15, 17)), - ('fields', 'Fields', tuple(range(15))), + ('fields', 'Fields', tuple(range(15)) + (18,)), ('warnings', 'Warnings', (16,)), ) @@ -97,9 +98,9 @@ def set_dlc_and_crc_len(self, dlc): if self.fd: if dlc2len(self.dlc) < 16: - self.crc_len = 21 # 17 + SBC bits + self.crc_len = 17 else: - self.crc_len = 25 # 21 + SBC bits + self.crc_len = 21 else: self.crc_len = 15 @@ -188,7 +189,7 @@ def is_stuff_bit(self): # Bit stuffing is not applied to the CRC delimiter, ACK field, # or End-of-Frame (EOF) field: - if cur_bit > self.last_databit + self.crc_len: + if cur_bit > self.crc_start + self.crc_len - 1: self.last_bit_was_stuff_bit = False return False @@ -223,22 +224,28 @@ def decode_overload_frame(self, bits): # Returns True if the frame ended (EOF), False otherwise. def decode_frame_end(self, can_rx, bitnum): - # Remember start of CRC sequence (see below). + # Remember start of Non-FD CRC sequence or FD-SBC field (see below). if bitnum == (self.last_databit + 1): self.ss_block = self.samplenum + elif self.fd and bitnum == (self.last_databit + 4): + # SBC field + x = self.last_databit + 1 + sbc_bits = self.bits[x:x + self.last_databit + 4] + sbc = bitpack_msb(sbc_bits) - # CRC sequence (15 bits, 17 bits or 21 bits) - elif bitnum == (self.last_databit + self.crc_len): - if self.fd: - if dlc2len(self.dlc) < 16: - crc_type = "CRC-17" - else: - crc_type = "CRC-21" - else: - crc_type = "CRC-15" + self.putb([18, ['Raw stuff bit count: %d' % sbc, + 'rSBC: %d' % sbc, 'SBC']]) - x = self.last_databit + 1 + # Remember start of FD-CRC sequence (see below). + elif self.fd and bitnum == (self.last_databit + 4 + 1): + self.ss_block = self.samplenum + + # CRC sequence (15 bits, 17 bits or 21 bits) + elif bitnum == (self.crc_start - 1 + self.crc_len): + x = self.crc_start crc_bits = self.bits[x:x + self.crc_len + 1] + + crc_type = "CRC-%d" % self.crc_len self.crc = bitpack_msb(crc_bits) self.putb([11, ['%s sequence: 0x%04x' % (crc_type, self.crc), '%s: 0x%04x' % (crc_type, self.crc), '%s' % crc_type]]) @@ -246,7 +253,7 @@ def decode_frame_end(self, can_rx, bitnum): self.putb([16, ['CRC is invalid']]) # CRC delimiter bit (recessive) - elif bitnum == (self.last_databit + self.crc_len + 1): + elif bitnum == (self.crc_start - 1 + self.crc_len + 1): self.putx([12, ['CRC delimiter: %d' % can_rx, 'CRC d: %d' % can_rx, 'CRC d']]) if can_rx != 1: @@ -256,23 +263,23 @@ def decode_frame_end(self, can_rx, bitnum): self.set_nominal_bitrate() # ACK slot bit (dominant: ACK, recessive: NACK) - elif bitnum == (self.last_databit + self.crc_len + 2): + elif bitnum == (self.crc_start - 1 + self.crc_len + 2): ack = 'ACK' if can_rx == 0 else 'NACK' self.putx([13, ['ACK slot: %s' % ack, 'ACK s: %s' % ack, 'ACK s']]) # ACK delimiter bit (recessive) - elif bitnum == (self.last_databit + self.crc_len + 3): + elif bitnum == (self.crc_start - 1 + self.crc_len + 3): self.putx([14, ['ACK delimiter: %d' % can_rx, 'ACK d: %d' % can_rx, 'ACK d']]) if can_rx != 1: self.putx([16, ['ACK delimiter must be a recessive bit']]) # Remember start of EOF (see below). - elif bitnum == (self.last_databit + self.crc_len + 4): + elif bitnum == (self.crc_start - 1 + self.crc_len + 4): self.ss_block = self.samplenum # End of frame (EOF), 7 recessive bits - elif bitnum == (self.last_databit + self.crc_len + 10): + elif bitnum == (self.crc_start - 1 + self.crc_len + 3 + 7): self.putb([2, ['End of frame', 'EOF', 'E']]) if self.rawbits[-7:] != [1, 1, 1, 1, 1, 1, 1]: self.putb([16, ['End of frame (EOF) must be 7 recessive bits']]) From f2b88cb2c7ae23b839b80953e348386770e686c9 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Thu, 5 Mar 2026 19:59:14 +0100 Subject: [PATCH 05/46] decoders/can: annotate decoded stuff bit count --- decoders/can/pd.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index ebdd55e76..cc79f7795 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -27,6 +27,16 @@ class SamplerateError(Exception): def dlc2len(dlc): return [0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 16, 20, 24, 32, 48, 64][dlc] +def gray2num(gray_code): + num = gray_code + mask = gray_code + + while mask: + mask >>= 1 + num ^= mask + + return num + class Decoder(srd.Decoder): api_version = 3 id = 'can' @@ -231,10 +241,13 @@ def decode_frame_end(self, can_rx, bitnum): # SBC field x = self.last_databit + 1 sbc_bits = self.bits[x:x + self.last_databit + 4] - sbc = bitpack_msb(sbc_bits) + p_gray_sbc = bitpack_msb(sbc_bits) + + gray_sbc = p_gray_sbc >> 1 + sbc = gray2num(gray_sbc) # Number of stuff bits modulo 8 - self.putb([18, ['Raw stuff bit count: %d' % sbc, - 'rSBC: %d' % sbc, 'SBC']]) + self.putb([18, ['Stuff bit count: %d' % sbc, + 'SBC: %d' % sbc, 'SBC']]) # Remember start of FD-CRC sequence (see below). elif self.fd and bitnum == (self.last_databit + 4 + 1): From 9609cfd1eec79756dd948d600e952f7639a5fa01 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Thu, 5 Mar 2026 19:59:14 +0100 Subject: [PATCH 06/46] decoders/can: check SBC parity and annotate warning if invalid --- decoders/can/pd.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index cc79f7795..dd054733f 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -220,6 +220,15 @@ def is_stuff_bit(self): self.last_bit_was_stuff_bit = True return True + def is_valid_parity(self, num, given_parity_bit): + calculated_parity_bit = 0 + + while num: + calculated_parity_bit ^= num & 1 + num >>= 1 + + return calculated_parity_bit == given_parity_bit + def is_valid_crc(self, crc_bits): return True # TODO @@ -243,7 +252,12 @@ def decode_frame_end(self, can_rx, bitnum): sbc_bits = self.bits[x:x + self.last_databit + 4] p_gray_sbc = bitpack_msb(sbc_bits) + parity = p_gray_sbc & 1 gray_sbc = p_gray_sbc >> 1 + + if not self.is_valid_parity(gray_sbc, parity): + self.putb([16, ['Parity is invalid']]) + sbc = gray2num(gray_sbc) # Number of stuff bits modulo 8 self.putb([18, ['Stuff bit count: %d' % sbc, From 96c4e38ea537b0ae3a2afd46845f3b6c65e15faf Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Thu, 5 Mar 2026 20:00:14 +0100 Subject: [PATCH 07/46] decoders/can: update copyright date --- decoders/can/pd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index dd054733f..f27ffd720 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -2,7 +2,7 @@ ## This file is part of the libsigrokdecode project. ## ## Copyright (C) 2012-2013 Uwe Hermann -## Copyright (C) 2019 Stephan Thiele +## Copyright (C) 2019-2026 Stephan Thiele ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by From 2ca2fbcd3688fc4560f00ec566ffb3a85ac42ae7 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Mon, 23 Mar 2026 16:39:32 +0100 Subject: [PATCH 08/46] decoders/can: rename fast_bitrate to FD bitrate --- decoders/can/pd.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index f27ffd720..a5fd419fa 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -52,7 +52,7 @@ class Decoder(srd.Decoder): ) options = ( {'id': 'nominal_bitrate', 'desc': 'Nominal bitrate (bits/s)', 'default': 1000000}, - {'id': 'fast_bitrate', 'desc': 'Fast bitrate (bits/s)', 'default': 2000000}, + {'id': 'fd_bitrate', 'desc': 'FD bitrate (bits/s)', 'default': 2000000}, {'id': 'sample_point', 'desc': 'Sample point (%)', 'default': 70.0}, ) annotations = ( @@ -100,8 +100,8 @@ def set_bit_rate(self, bitrate): def set_nominal_bitrate(self): self.set_bit_rate(self.options['nominal_bitrate']) - def set_fast_bitrate(self): - self.set_bit_rate(self.options['fast_bitrate']) + def set_fd_bitrate(self): + self.set_bit_rate(self.options['fd_bitrate']) def set_dlc_and_crc_len(self, dlc): self.dlc = dlc @@ -513,7 +513,7 @@ def handle_bit(self, can_rx): if bitnum == 16 and self.frame_type == 'standard' \ or bitnum == 35 and self.frame_type == 'extended': self.dom_edge_seen(force=True) - self.set_fast_bitrate() + self.set_fd_bitrate() # If this is a stuff bit, remove it from self.bits and ignore it. if self.is_stuff_bit(): From 84abc46fde75de59a065b8cf91d76bbfa7fc1df9 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Mon, 23 Mar 2026 16:42:12 +0100 Subject: [PATCH 09/46] decoders/can: rename sample_point to nominal_sample_point --- decoders/can/pd.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index a5fd419fa..5d4bce127 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -53,7 +53,7 @@ class Decoder(srd.Decoder): options = ( {'id': 'nominal_bitrate', 'desc': 'Nominal bitrate (bits/s)', 'default': 1000000}, {'id': 'fd_bitrate', 'desc': 'FD bitrate (bits/s)', 'default': 2000000}, - {'id': 'sample_point', 'desc': 'Sample point (%)', 'default': 70.0}, + {'id': 'nominal_sample_point', 'desc': 'Nominal sample point (%)', 'default': 70.0}, ) annotations = ( ('data', 'Payload data'), @@ -95,7 +95,7 @@ def start(self): def set_bit_rate(self, bitrate): self.bit_width = float(self.samplerate) / float(bitrate) - self.sample_point = (self.bit_width / 100.0) * self.options['sample_point'] + self.sample_point = (self.bit_width / 100.0) * self.options['nominal_sample_point'] def set_nominal_bitrate(self): self.set_bit_rate(self.options['nominal_bitrate']) @@ -118,7 +118,7 @@ def metadata(self, key, value): if key == srd.SRD_CONF_SAMPLERATE: self.samplerate = value self.bit_width = float(self.samplerate) / float(self.options['nominal_bitrate']) - self.sample_point = (self.bit_width / 100.0) * self.options['sample_point'] + self.sample_point = (self.bit_width / 100.0) * self.options['nominal_sample_point'] # Generic helper for CAN bit annotations. def putg(self, ss, es, data): From 29538b49a3500c89b3d5a2f0809671003aa83a20 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Mon, 23 Mar 2026 16:43:43 +0100 Subject: [PATCH 10/46] decoders/can: introduce FD Sample Point (%) option --- decoders/can/pd.py | 1 + 1 file changed, 1 insertion(+) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 5d4bce127..a99053693 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -54,6 +54,7 @@ class Decoder(srd.Decoder): {'id': 'nominal_bitrate', 'desc': 'Nominal bitrate (bits/s)', 'default': 1000000}, {'id': 'fd_bitrate', 'desc': 'FD bitrate (bits/s)', 'default': 2000000}, {'id': 'nominal_sample_point', 'desc': 'Nominal sample point (%)', 'default': 70.0}, + {'id': 'fd_sample_point', 'desc': 'FD sample point (%)', 'default': 70.0}, ) annotations = ( ('data', 'Payload data'), From a88c9d2ceea9079ea8082379f678b9fa0b0a6e61 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Mon, 23 Mar 2026 16:46:12 +0100 Subject: [PATCH 11/46] decoders/can: set_bit_rate: also change sample point on bitrate switch --- decoders/can/pd.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index a99053693..87039bf1a 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -94,15 +94,15 @@ def start(self): self.out_ann = self.register(srd.OUTPUT_ANN) self.out_python = self.register(srd.OUTPUT_PYTHON) - def set_bit_rate(self, bitrate): + def set_bit_rate(self, bitrate, sample_point): self.bit_width = float(self.samplerate) / float(bitrate) - self.sample_point = (self.bit_width / 100.0) * self.options['nominal_sample_point'] + self.sample_point = (self.bit_width / 100.0) * sample_point def set_nominal_bitrate(self): - self.set_bit_rate(self.options['nominal_bitrate']) + self.set_bit_rate(self.options['nominal_bitrate'], self.options['nominal_sample_point']) def set_fd_bitrate(self): - self.set_bit_rate(self.options['fd_bitrate']) + self.set_bit_rate(self.options['fd_bitrate'], self.options['fd_sample_point']) def set_dlc_and_crc_len(self, dlc): self.dlc = dlc From 2f66e1d8592d399e7f16cd8339496bb12f04ce36 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Mon, 23 Mar 2026 17:41:45 +0100 Subject: [PATCH 12/46] decoders/can: get bitnum in decode() + pass to handle_bit() --- decoders/can/pd.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 87039bf1a..23bc7539e 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -503,13 +503,10 @@ def decode_extended_frame(self, can_rx, bitnum): return False - def handle_bit(self, can_rx): + def handle_bit(self, bitnum, can_rx): self.rawbits.append(can_rx) self.bits.append(can_rx) - # Get the index of the current CAN frame bit (without stuff bits). - bitnum = len(self.bits) - 1 - if self.fd and can_rx: if bitnum == 16 and self.frame_type == 'standard' \ or bitnum == 35 and self.frame_type == 'extended': @@ -588,10 +585,12 @@ def decode(self): self.dom_edge_seen(force = True) self.state = 'GET BITS' elif self.state == 'GET BITS': + bitnum = len(self.bits) # Get the index of the current CAN frame bit (without stuff bits). + # Wait until we're in the correct bit/sampling position. pos = self.get_sample_point(self.curbit) (can_rx,) = self.wait([{'skip': pos - self.samplenum}, {0: 'f'}]) if self.matched[1]: self.dom_edge_seen() if self.matched[0]: - self.handle_bit(can_rx) + self.handle_bit(bitnum, can_rx) From 15c1eca78c9e892ff95f62ace5398fd8432eac50 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Mon, 23 Mar 2026 17:46:15 +0100 Subject: [PATCH 13/46] decoders/can: set fd_bitrate in decode() + remember if fd bitrate was switched --- decoders/can/pd.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 23bc7539e..81b302fa4 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -160,6 +160,7 @@ def reset_variables(self): self.frame_bytes = [] self.rtr_type = None self.fd = False + self.brs = False self.rtr = None self.last_bit_was_stuff_bit = False self.crc_len = 15 @@ -507,12 +508,6 @@ def handle_bit(self, bitnum, can_rx): self.rawbits.append(can_rx) self.bits.append(can_rx) - if self.fd and can_rx: - if bitnum == 16 and self.frame_type == 'standard' \ - or bitnum == 35 and self.frame_type == 'extended': - self.dom_edge_seen(force=True) - self.set_fd_bitrate() - # If this is a stuff bit, remove it from self.bits and ignore it. if self.is_stuff_bit(): self.putx([15, [str(can_rx)]]) @@ -590,7 +585,18 @@ def decode(self): # Wait until we're in the correct bit/sampling position. pos = self.get_sample_point(self.curbit) (can_rx,) = self.wait([{'skip': pos - self.samplenum}, {0: 'f'}]) + if self.matched[1]: self.dom_edge_seen() if self.matched[0]: + if self.fd: + # FD-BRS bit: + if can_rx and (bitnum == 16 and self.frame_type == 'standard' + or bitnum == 35 and self.frame_type == 'extended'): + self.brs = True if can_rx else False + + if self.brs: + self.dom_edge_seen(force=True) + self.set_fd_bitrate() + self.handle_bit(bitnum, can_rx) From fb60285a32111173e6b8af642eee592c30b69ec8 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Mon, 23 Mar 2026 17:49:55 +0100 Subject: [PATCH 14/46] decoders/can: switch back from FD to to nominal bitrate in decode() --- decoders/can/pd.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 81b302fa4..24e6f6c24 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -288,9 +288,6 @@ def decode_frame_end(self, can_rx, bitnum): if can_rx != 1: self.putx([16, ['CRC delimiter must be a recessive bit']]) - if self.fd: - self.set_nominal_bitrate() - # ACK slot bit (dominant: ACK, recessive: NACK) elif bitnum == (self.crc_start - 1 + self.crc_len + 2): ack = 'ACK' if can_rx == 0 else 'NACK' @@ -599,4 +596,8 @@ def decode(self): self.dom_edge_seen(force=True) self.set_fd_bitrate() + # FD-CRC delimiter + elif self.brs and bitnum == (self.crc_start + self.crc_len): + self.set_nominal_bitrate() + self.handle_bit(bitnum, can_rx) From 0065507bf7a32d65cc1cbccbb690b710b89bacfe Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Mon, 23 Mar 2026 17:52:14 +0100 Subject: [PATCH 15/46] decoders/can: perform bitrate switch properly --- decoders/can/pd.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 24e6f6c24..dd134386e 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -98,6 +98,11 @@ def set_bit_rate(self, bitrate, sample_point): self.bit_width = float(self.samplerate) / float(bitrate) self.sample_point = (self.bit_width / 100.0) * sample_point + if self.fd: + # Set virtual dom edge after the bit where bitrate switch happened, to adjust sample grid to new bitrate + self.dom_edge_bcount = self.curbit + 1 + self.dom_edge_snum = self.samplenum + self.bit_width - self.sample_point + def set_nominal_bitrate(self): self.set_bit_rate(self.options['nominal_bitrate'], self.options['nominal_sample_point']) @@ -593,8 +598,7 @@ def decode(self): self.brs = True if can_rx else False if self.brs: - self.dom_edge_seen(force=True) - self.set_fd_bitrate() + self.set_fd_bitrate() # FD-CRC delimiter elif self.brs and bitnum == (self.crc_start + self.crc_len): From 57ff32c947ccc4cad982c682d330137cc6f6458c Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Mon, 23 Mar 2026 17:56:46 +0100 Subject: [PATCH 16/46] decoders/can: introduce putx_brs annotation function --- decoders/can/pd.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index dd134386e..c10ccb47e 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -131,6 +131,19 @@ def putg(self, ss, es, data): left, right = int(self.sample_point), int(self.bit_width - self.sample_point) self.put(ss - left, es + right, self.out_ann, data) + # Single-CAN-bit annotation for a bitrate switch bit using the current samplenum. + def putx_brs(self, from_bitrate, from_spp, to_bitrate, to_spp, data): + from_samples_per_bit = float(self.samplerate) / float(from_bitrate) + from_spp_sample_no = (from_samples_per_bit / 100.0) * from_spp + + to_samples_per_bit = float(self.samplerate) / float(to_bitrate) + to_spp_sample_no = (to_samples_per_bit / 100.0) * to_spp + + num_samples_brs_bit = from_spp_sample_no + to_samples_per_bit - to_spp_sample_no + + left, right = int(from_spp_sample_no), int(num_samples_brs_bit - from_spp_sample_no) + self.put(self.samplenum - left, self.samplenum + right, self.out_ann, data) + # Single-CAN-bit annotation using the current samplenum. def putx(self, data): self.putg(self.samplenum, self.samplenum, data) From c9381cd15aec5498820ae60d37f87ad78178e7a4 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Mon, 23 Mar 2026 17:58:43 +0100 Subject: [PATCH 17/46] decoders/can: fix annotation of FD-BRS bit + FD-CRC delimiter bit --- decoders/can/pd.py | 60 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index c10ccb47e..8944ccd76 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -301,8 +301,19 @@ def decode_frame_end(self, can_rx, bitnum): # CRC delimiter bit (recessive) elif bitnum == (self.crc_start - 1 + self.crc_len + 1): - self.putx([12, ['CRC delimiter: %d' % can_rx, - 'CRC d: %d' % can_rx, 'CRC d']]) + data = [12, ['CRC delimiter: %d' % can_rx, + 'CRC d: %d' % can_rx, 'CRC d']] + + if self.fd and self.brs: + from_bitrate = self.options['fd_bitrate'] + from_spp = self.options['fd_sample_point'] + to_bitrate = self.options['nominal_bitrate'] + to_spp = self.options['nominal_sample_point'] + + self.putx_brs(from_bitrate, from_spp, to_bitrate, to_spp, data) + else: + self.putx(data) + if can_rx != 1: self.putx([16, ['CRC delimiter must be a recessive bit']]) @@ -369,7 +380,18 @@ def decode_standard_frame(self, can_rx, bitnum): self.putx([7, ['Reserved: %d' % can_rx, 'R0: %d' % can_rx, 'R0']]) if bitnum == 16 and self.fd: - self.putx([7, ['Bit rate switch: %d' % can_rx, 'BRS: %d' % can_rx, 'BRS']]) + data = [7, ['Bit rate switch: %d' % can_rx, + 'BRS: %d' % can_rx, 'BRS']] + + if self.brs: + from_bitrate = self.options['nominal_bitrate'] + from_spp = self.options['nominal_sample_point'] + to_bitrate = self.options['fd_bitrate'] + to_spp = self.options['fd_sample_point'] + + self.putx_brs(from_bitrate, from_spp, to_bitrate, to_spp, data) + else: + self.putx(data) if bitnum == 17 and self.fd: self.putx([7, ['Error state indicator: %d' % can_rx, 'ESI: %d' % can_rx, 'ESI']]) @@ -474,8 +496,18 @@ def decode_extended_frame(self, can_rx, bitnum): 'RB0: %d' % can_rx, 'RB0']]) elif bitnum == 35 and self.fd: - self.putx([7, ['Bit rate switch: %d' % can_rx, - 'BRS: %d' % can_rx, 'BRS']]) + data = [7, ['Bit rate switch: %d' % can_rx, + 'BRS: %d' % can_rx, 'BRS']] + + if self.brs: + from_bitrate = self.options['nominal_bitrate'] + from_spp = self.options['nominal_sample_point'] + to_bitrate = self.options['fd_bitrate'] + to_spp = self.options['fd_sample_point'] + + self.putx_brs(from_bitrate, from_spp, to_bitrate, to_spp, data) + else: + self.putx(data) elif bitnum == 36 and self.fd: self.putx([7, ['Error state indicator: %d' % can_rx, @@ -529,7 +561,23 @@ def handle_bit(self, bitnum, can_rx): self.curbit += 1 # Increase self.curbit (bitnum is not affected). return else: - self.putx([17, [str(can_rx)]]) + if self.fd and can_rx and (bitnum == 16 and self.frame_type == 'standard' + or bitnum == 35 and self.frame_type == 'extended'): + from_bitrate = self.options['nominal_bitrate'] + from_spp = self.options['nominal_sample_point'] + to_bitrate = self.options['fd_bitrate'] + to_spp = self.options['fd_sample_point'] + + self.putx_brs(from_bitrate, from_spp, to_bitrate, to_spp, [17, [str(can_rx)]]) + elif self.fd and self.brs and bitnum == (self.crc_start + self.crc_len): + from_bitrate = self.options['fd_bitrate'] + from_spp = self.options['fd_sample_point'] + to_bitrate = self.options['nominal_bitrate'] + to_spp = self.options['nominal_sample_point'] + + self.putx_brs(from_bitrate, from_spp, to_bitrate, to_spp, [17, [str(can_rx)]]) + else: + self.putx([17, [str(can_rx)]]) # Bit 0: Start of frame (SOF) bit if bitnum == 0: From da501e75cc5b1d25ff33a00dcfd6b03107bdb0aa Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Thu, 19 Mar 2026 22:48:20 +0100 Subject: [PATCH 18/46] decoders/can: decode XLF bit + remember if XLF bit is set --- decoders/can/pd.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 8944ccd76..9899da1fe 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -75,11 +75,12 @@ class Decoder(srd.Decoder): ('stuff-bit', 'Stuff bit'), ('warning', 'Warning'), ('bit', 'Bit'), - ('sbc', 'Stuff bit count') + ('sbc', 'Stuff bit count'), + ('xlf', 'Extended data length format') ) annotation_rows = ( ('bits', 'Bits', (15, 17)), - ('fields', 'Fields', tuple(range(15)) + (18,)), + ('fields', 'Fields', tuple(range(15)) + (18, 19)), ('warnings', 'Warnings', (16,)), ) @@ -178,6 +179,7 @@ def reset_variables(self): self.frame_bytes = [] self.rtr_type = None self.fd = False + self.xl = False self.brs = False self.rtr = None self.last_bit_was_stuff_bit = False @@ -377,7 +379,16 @@ def decode_standard_frame(self, can_rx, bitnum): self.dlc_start = 15 if bitnum == 15 and self.fd: - self.putx([7, ['Reserved: %d' % can_rx, 'R0: %d' % can_rx, 'R0']]) + self.xl = True if can_rx else False + + if self.xl: + self.putx([19, ['Extended data length format: %d' % can_rx, + 'XLF: %d' % can_rx, 'XLF']]) + else: + self.putx([7, ['Reserved: %d' % can_rx, 'R0: %d' % can_rx, 'R0']]) + + if self.xl: + return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. if bitnum == 16 and self.fd: data = [7, ['Bit rate switch: %d' % can_rx, From 8a8d46673cfcf79a8a04527fb7c2f89cc643b4cc Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Thu, 19 Mar 2026 22:53:41 +0100 Subject: [PATCH 19/46] decoders/can: decode resXL bit --- decoders/can/pd.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 9899da1fe..0eb041637 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -76,11 +76,12 @@ class Decoder(srd.Decoder): ('warning', 'Warning'), ('bit', 'Bit'), ('sbc', 'Stuff bit count'), - ('xlf', 'Extended data length format') + ('xlf', 'Extended data length format'), + ('resXL', 'Reserved bit extended data field length format') ) annotation_rows = ( ('bits', 'Bits', (15, 17)), - ('fields', 'Fields', tuple(range(15)) + (18, 19)), + ('fields', 'Fields', tuple(range(15)) + (18, 19, 20)), ('warnings', 'Warnings', (16,)), ) @@ -387,22 +388,26 @@ def decode_standard_frame(self, can_rx, bitnum): else: self.putx([7, ['Reserved: %d' % can_rx, 'R0: %d' % can_rx, 'R0']]) - if self.xl: - return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. - if bitnum == 16 and self.fd: - data = [7, ['Bit rate switch: %d' % can_rx, - 'BRS: %d' % can_rx, 'BRS']] + if self.xl: + self.putx([20, ['Reserved bit extended data field length format: %d' % can_rx, + 'resXL: %d' % can_rx, 'resXL']]) + else: + data = [7, ['Bit rate switch: %d' % can_rx, + 'BRS: %d' % can_rx, 'BRS']] - if self.brs: - from_bitrate = self.options['nominal_bitrate'] - from_spp = self.options['nominal_sample_point'] - to_bitrate = self.options['fd_bitrate'] - to_spp = self.options['fd_sample_point'] + if self.brs: + from_bitrate = self.options['nominal_bitrate'] + from_spp = self.options['nominal_sample_point'] + to_bitrate = self.options['fd_bitrate'] + to_spp = self.options['fd_sample_point'] - self.putx_brs(from_bitrate, from_spp, to_bitrate, to_spp, data) - else: - self.putx(data) + self.putx_brs(from_bitrate, from_spp, to_bitrate, to_spp, data) + else: + self.putx(data) + + if self.xl: + return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. if bitnum == 17 and self.fd: self.putx([7, ['Error state indicator: %d' % can_rx, 'ESI: %d' % can_rx, 'ESI']]) From 369d2290b63d571fd998700c75d793597e4fd440 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Thu, 19 Mar 2026 23:23:58 +0100 Subject: [PATCH 20/46] decoders/can: decode ADH bit --- decoders/can/pd.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 0eb041637..9d783eb18 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -77,11 +77,12 @@ class Decoder(srd.Decoder): ('bit', 'Bit'), ('sbc', 'Stuff bit count'), ('xlf', 'Extended data length format'), - ('resXL', 'Reserved bit extended data field length format') + ('resXL', 'Reserved bit extended data field length format'), + ('ADH', 'Arbitration to data high') ) annotation_rows = ( ('bits', 'Bits', (15, 17)), - ('fields', 'Fields', tuple(range(15)) + (18, 19, 20)), + ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21)), ('warnings', 'Warnings', (16,)), ) @@ -406,12 +407,15 @@ def decode_standard_frame(self, can_rx, bitnum): else: self.putx(data) + if bitnum == 17 and self.fd: + if self.xl: + self.putx([21, ['Arbitration to data high: %d' % can_rx, 'ADH: %d' % can_rx, 'ADH']]) + else: + self.putx([7, ['Error state indicator: %d' % can_rx, 'ESI: %d' % can_rx, 'ESI']]) + if self.xl: return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. - if bitnum == 17 and self.fd: - self.putx([7, ['Error state indicator: %d' % can_rx, 'ESI: %d' % can_rx, 'ESI']]) - # Remember start of DLC (see below). elif bitnum == self.dlc_start: self.ss_block = self.samplenum From 5adb5ed3611d83397d8f3e8ad9a9ca6013480a79 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 18:03:35 +0100 Subject: [PATCH 21/46] decoders/can: introduce XL bitrate option --- decoders/can/pd.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 9d783eb18..acdc04226 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -53,6 +53,7 @@ class Decoder(srd.Decoder): options = ( {'id': 'nominal_bitrate', 'desc': 'Nominal bitrate (bits/s)', 'default': 1000000}, {'id': 'fd_bitrate', 'desc': 'FD bitrate (bits/s)', 'default': 2000000}, + {'id': 'xl_bitrate', 'desc': 'XL bitrate (bits/s)', 'default': 4000000}, {'id': 'nominal_sample_point', 'desc': 'Nominal sample point (%)', 'default': 70.0}, {'id': 'fd_sample_point', 'desc': 'FD sample point (%)', 'default': 70.0}, ) @@ -112,6 +113,9 @@ def set_nominal_bitrate(self): def set_fd_bitrate(self): self.set_bit_rate(self.options['fd_bitrate'], self.options['fd_sample_point']) + def set_xl_bitrate(self): + self.set_bit_rate(self.options['xl_bitrate'], self.options['nominal_sample_point']) + def set_dlc_and_crc_len(self, dlc): self.dlc = dlc From cfcd6afad2cea68f48cee51dc85d0c79da8049cd Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 18:04:56 +0100 Subject: [PATCH 22/46] decoders/can: introduce XL sample point (%) option --- decoders/can/pd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index acdc04226..c15016679 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -56,6 +56,7 @@ class Decoder(srd.Decoder): {'id': 'xl_bitrate', 'desc': 'XL bitrate (bits/s)', 'default': 4000000}, {'id': 'nominal_sample_point', 'desc': 'Nominal sample point (%)', 'default': 70.0}, {'id': 'fd_sample_point', 'desc': 'FD sample point (%)', 'default': 70.0}, + {'id': 'xl_sample_point', 'desc': 'XL sample point (%)', 'default': 70.0}, ) annotations = ( ('data', 'Payload data'), @@ -114,7 +115,7 @@ def set_fd_bitrate(self): self.set_bit_rate(self.options['fd_bitrate'], self.options['fd_sample_point']) def set_xl_bitrate(self): - self.set_bit_rate(self.options['xl_bitrate'], self.options['nominal_sample_point']) + self.set_bit_rate(self.options['xl_bitrate'], self.options['xl_sample_point']) def set_dlc_and_crc_len(self, dlc): self.dlc = dlc From 0310742abf58fc466258aac813369557d5bef6ac Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sun, 22 Mar 2026 20:33:22 +0100 Subject: [PATCH 23/46] decoders/can: add BRS support for CAN-XL --- decoders/can/pd.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index c15016679..3afc2db21 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -100,13 +100,19 @@ def start(self): self.out_python = self.register(srd.OUTPUT_PYTHON) def set_bit_rate(self, bitrate, sample_point): + prev_bit_remaining_samples = self.bit_width - self.sample_point + self.bit_width = float(self.samplerate) / float(bitrate) self.sample_point = (self.bit_width / 100.0) * sample_point if self.fd: # Set virtual dom edge after the bit where bitrate switch happened, to adjust sample grid to new bitrate - self.dom_edge_bcount = self.curbit + 1 - self.dom_edge_snum = self.samplenum + self.bit_width - self.sample_point + if self.xl: + self.dom_edge_bcount = self.curbit + self.dom_edge_snum = self.samplenum + prev_bit_remaining_samples + else: + self.dom_edge_bcount = self.curbit + 1 + self.dom_edge_snum = self.samplenum + self.bit_width - self.sample_point def set_nominal_bitrate(self): self.set_bit_rate(self.options['nominal_bitrate'], self.options['nominal_sample_point']) @@ -691,3 +697,9 @@ def decode(self): self.set_nominal_bitrate() self.handle_bit(bitnum, can_rx) + + if self.xl: + if bitnum == 17: # After ADH bit + self.set_xl_bitrate() + elif bitnum == self.crc_start + 35: # After last FCP bit + self.set_nominal_bitrate() \ No newline at end of file From 4fa18ea5479da7145e96158cc18326448d0ad7ec Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Thu, 19 Mar 2026 23:26:28 +0100 Subject: [PATCH 24/46] decoders/can: decode DH1, DH2 and DL1 bits --- decoders/can/pd.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 3afc2db21..e2abcec99 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -80,11 +80,14 @@ class Decoder(srd.Decoder): ('sbc', 'Stuff bit count'), ('xlf', 'Extended data length format'), ('resXL', 'Reserved bit extended data field length format'), - ('ADH', 'Arbitration to data high') + ('ADH', 'Arbitration to data high'), + ('DH1', 'Data high bit 1'), + ('DH2', 'Data high bit 2'), + ('DL1', 'Data low bit 1') ) annotation_rows = ( ('bits', 'Bits', (15, 17)), - ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21)), + ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24)), ('warnings', 'Warnings', (16,)), ) @@ -424,6 +427,16 @@ def decode_standard_frame(self, can_rx, bitnum): else: self.putx([7, ['Error state indicator: %d' % can_rx, 'ESI: %d' % can_rx, 'ESI']]) + if self.xl: + if bitnum == 18: + self.putx([22, ['Data high bit 1: %d' % can_rx, 'DH1: %d' % can_rx, 'DH1']]) + + elif bitnum == 19: + self.putx([23, ['Data high bit 2: %d' % can_rx, 'DH2: %d' % can_rx, 'DH2']]) + + elif bitnum == 20: + self.putx([24, ['Data low bit 1: %d' % can_rx, 'DL1: %d' % can_rx, 'DL1']]) + if self.xl: return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. From 85612faa3d9beb373ba4c34b028e304fcc1c8a35 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Thu, 19 Mar 2026 23:28:30 +0100 Subject: [PATCH 25/46] decoders/can: decode SDT field --- decoders/can/pd.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index e2abcec99..98ecc895a 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -83,11 +83,12 @@ class Decoder(srd.Decoder): ('ADH', 'Arbitration to data high'), ('DH1', 'Data high bit 1'), ('DH2', 'Data high bit 2'), - ('DL1', 'Data low bit 1') + ('DL1', 'Data low bit 1'), + ('SDT', 'Service data unit type') ) annotation_rows = ( ('bits', 'Bits', (15, 17)), - ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24)), + ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25)), ('warnings', 'Warnings', (16,)), ) @@ -437,6 +438,16 @@ def decode_standard_frame(self, can_rx, bitnum): elif bitnum == 20: self.putx([24, ['Data low bit 1: %d' % can_rx, 'DL1: %d' % can_rx, 'DL1']]) + # Remember start of SDT (see below). + elif bitnum == 21: + self.ss_block = self.samplenum + + elif bitnum == 28: + sdt = bitpack_msb(self.bits[21:29]) + + self.putb([25, ['Service data unit type: %d' % sdt, + 'SDT: %d' % sdt, 'SDT']]) + if self.xl: return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. From 779a48dce744c2e4a884549bed8edb11bc8de0d4 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Thu, 19 Mar 2026 23:29:16 +0100 Subject: [PATCH 26/46] decoders/can: decode SEC bit --- decoders/can/pd.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 98ecc895a..95473b78d 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -84,11 +84,12 @@ class Decoder(srd.Decoder): ('DH1', 'Data high bit 1'), ('DH2', 'Data high bit 2'), ('DL1', 'Data low bit 1'), - ('SDT', 'Service data unit type') + ('SDT', 'Service data unit type'), + ('SEC', 'Simple/extended content') ) annotation_rows = ( ('bits', 'Bits', (15, 17)), - ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25)), + ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26)), ('warnings', 'Warnings', (16,)), ) @@ -448,6 +449,10 @@ def decode_standard_frame(self, can_rx, bitnum): self.putb([25, ['Service data unit type: %d' % sdt, 'SDT: %d' % sdt, 'SDT']]) + elif bitnum == 29: + self.putx([26, ['Simple/extended content: %d' % can_rx, + 'SEC: %d' % can_rx, 'SEC']]) + if self.xl: return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. From 3aa77ec9c7c4d1b3bb1eb4b52f28682ba173f2f2 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Thu, 19 Mar 2026 23:48:59 +0100 Subject: [PATCH 27/46] decoders/can: add fixed bit stuffing for XL data phase --- decoders/can/pd.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 95473b78d..385380903 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -243,7 +243,14 @@ def is_stuff_bit(self): self.last_bit_was_stuff_bit = False return False - if self.fd and cur_bit > self.last_databit: + if self.xl and cur_bit > 21: # After DL1 bit + # Within the CAN-XL Control field (starting from DL1 bit), data field and FCRC sequence, a fixed stuff bit + # is inserted after every 10th bit: + if (cur_bit - 20) % 10 != 0: + self.last_bit_was_stuff_bit = False + return False + + elif not self.xl and self.fd and cur_bit > self.last_databit: # Within the CAN-FD CRC field, a fixed stuff bit is inserted after every fourth bit: if (cur_bit - self.last_databit - 1) % 4 != 0: self.last_bit_was_stuff_bit = False From ca4eac30bfbc64af19528bba944f89023d8355f6 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 13:37:48 +0100 Subject: [PATCH 28/46] decoders/can: set dlc_start = 30 when XLF=1 --- decoders/can/pd.py | 1 + 1 file changed, 1 insertion(+) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 385380903..bfd9f9b24 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -409,6 +409,7 @@ def decode_standard_frame(self, can_rx, bitnum): if self.xl: self.putx([19, ['Extended data length format: %d' % can_rx, 'XLF: %d' % can_rx, 'XLF']]) + self.dlc_start = 30 else: self.putx([7, ['Reserved: %d' % can_rx, 'R0: %d' % can_rx, 'R0']]) From 8b0afa306b546ce84d7a658d09240bd0cbe20c29 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 13:40:47 +0100 Subject: [PATCH 29/46] decoders/can: introduce dlc_field_len --- decoders/can/pd.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index bfd9f9b24..403d3b072 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -202,6 +202,7 @@ def reset_variables(self): self.rtr = None self.last_bit_was_stuff_bit = False self.crc_len = 15 + self.dlc_field_len = 4 # Poor man's clock synchronization. Use signal edges which change to # dominant state in rather simple ways. This naive approach is neither @@ -469,11 +470,11 @@ def decode_standard_frame(self, can_rx, bitnum): self.ss_block = self.samplenum # Bits 15-18: Data length code (DLC), in number of bytes (0-8). - elif bitnum == self.dlc_start + 3: - self.set_dlc_and_crc_len(bitpack_msb(self.bits[self.dlc_start:self.dlc_start + 4])) + elif bitnum == self.dlc_start + self.dlc_field_len - 1: + self.set_dlc_and_crc_len(bitpack_msb(self.bits[self.dlc_start:self.dlc_start + self.dlc_field_len])) self.putb([10, ['Data length code: %d' % self.dlc, 'DLC: %d' % self.dlc, 'DLC']]) - self.last_databit = self.dlc_start + 3 + (dlc2len(self.dlc) * 8) + self.last_databit = self.dlc_start + self.dlc_field_len - 1 + (dlc2len(self.dlc) * 8) self.crc_start = self.last_databit + 1 if self.fd: From bed2195ee8fa1f7806d7497928885a47210d8e32 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 13:45:58 +0100 Subject: [PATCH 30/46] decoders/can: decode XL-DLC field --- decoders/can/pd.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 403d3b072..22bbba30b 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -411,6 +411,7 @@ def decode_standard_frame(self, can_rx, bitnum): self.putx([19, ['Extended data length format: %d' % can_rx, 'XLF: %d' % can_rx, 'XLF']]) self.dlc_start = 30 + self.dlc_field_len = 11 else: self.putx([7, ['Reserved: %d' % can_rx, 'R0: %d' % can_rx, 'R0']]) @@ -462,37 +463,46 @@ def decode_standard_frame(self, can_rx, bitnum): self.putx([26, ['Simple/extended content: %d' % can_rx, 'SEC: %d' % can_rx, 'SEC']]) - if self.xl: - return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. - # Remember start of DLC (see below). - elif bitnum == self.dlc_start: + if bitnum == self.dlc_start: self.ss_block = self.samplenum - # Bits 15-18: Data length code (DLC), in number of bytes (0-8). + # Data length code (DLC) + # - Classic CAN: number of bytes (0-8) + # - CAN-FD: 0=0, 1=1, 2=2, 3=3, 4=4, 5=5, 6=6, 7=7, 8=8, 9=12, 10=16, 11=20, 12=24, 13=32, 14=48, 15=64 + # - CAN-XL: Number of bytes minus one (0 - 2047) while 0 means 1 byte of data (0 bytes is not possible anymore) + elif bitnum == self.dlc_start + self.dlc_field_len - 1: self.set_dlc_and_crc_len(bitpack_msb(self.bits[self.dlc_start:self.dlc_start + self.dlc_field_len])) self.putb([10, ['Data length code: %d' % self.dlc, 'DLC: %d' % self.dlc, 'DLC']]) - self.last_databit = self.dlc_start + self.dlc_field_len - 1 + (dlc2len(self.dlc) * 8) + + if self.xl: + self.last_databit = 96 + (self.dlc + 1) * 8 + else: + self.last_databit = self.dlc_start + self.dlc_field_len - 1 + (dlc2len(self.dlc) * 8) + self.crc_start = self.last_databit + 1 - if self.fd: + if self.fd and not self.xl: self.crc_start += 4 # Skip SBC field if self.dlc > 8 and not self.fd: self.putb([16, ['Data length code (DLC) > 8 is not allowed']]) + if self.xl: + return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. + # Remember all databyte bits, except the very last one. - elif bitnum in range(self.dlc_start + 4, self.last_databit): + if bitnum in range(97 if self.xl else self.dlc_start + 4, self.last_databit): self.ss_databytebits.append(self.samplenum) - # Bits 19-X: Data field (0-8 bytes, depending on DLC) + # Data field (0-2048 bytes, depending on DLC) # The bits within a data byte are transferred MSB-first. elif bitnum == self.last_databit: self.ss_databytebits.append(self.samplenum) # Last databyte bit. - for i in range(dlc2len(self.dlc)): - x = self.dlc_start + 4 + (8 * i) + for i in range(self.dlc + 1 if self.xl else dlc2len(self.dlc)): + x = (97 if self.xl else self.dlc_start + 4) + (8 * i) b = bitpack_msb(self.bits[x:x + 8]) self.frame_bytes.append(b) ss = self.ss_databytebits[i * 8] From 194f989ffcbd465141b59cd48d822af21aba40e7 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 14:06:10 +0100 Subject: [PATCH 31/46] decoders/can: rename is_valid_parity -> is_valid_even_parity --- decoders/can/pd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 22bbba30b..2c25ee02e 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -268,7 +268,7 @@ def is_stuff_bit(self): self.last_bit_was_stuff_bit = True return True - def is_valid_parity(self, num, given_parity_bit): + def is_valid_even_parity(self, num, given_parity_bit): calculated_parity_bit = 0 while num: @@ -303,7 +303,7 @@ def decode_frame_end(self, can_rx, bitnum): parity = p_gray_sbc & 1 gray_sbc = p_gray_sbc >> 1 - if not self.is_valid_parity(gray_sbc, parity): + if not self.is_valid_even_parity(gray_sbc, parity): self.putb([16, ['Parity is invalid']]) sbc = gray2num(gray_sbc) # Number of stuff bits modulo 8 From f399ea295256ce01b5d0ff5a6891da54ca3fdeeb Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 17:18:10 +0100 Subject: [PATCH 32/46] decoders/can: introduce is_valid_odd_parity() function --- decoders/can/pd.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 2c25ee02e..46544d6cc 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -277,6 +277,9 @@ def is_valid_even_parity(self, num, given_parity_bit): return calculated_parity_bit == given_parity_bit + def is_valid_odd_parity(self, num, given_parity_bit): + return not self.is_valid_even_parity(num, given_parity_bit) + def is_valid_crc(self, crc_bits): return True # TODO From 589cf154f521a48e663cfefffadb3f78d0f862e5 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 17:20:13 +0100 Subject: [PATCH 33/46] decoders/can: decode SBC field --- decoders/can/pd.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 46544d6cc..6ef7e4bc9 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -493,6 +493,29 @@ def decode_standard_frame(self, can_rx, bitnum): if self.dlc > 8 and not self.fd: self.putb([16, ['Data length code (DLC) > 8 is not allowed']]) + if self.xl: + # Remember start of SBC (see below). + if bitnum == 41: + self.ss_block = self.samplenum + + # Stuff bit count (SBC) + elif bitnum == 43: + # SBC field + sbc_bits = self.bits[41:44] + p_gray_sbc = bitpack_msb(sbc_bits) + + parity = p_gray_sbc & 1 + gray_sbc = p_gray_sbc >> 1 + + # in contrast to FD CRC, odd parity scheme is used: + if not self.is_valid_odd_parity(gray_sbc, parity): + self.putb([16, ['Parity is invalid']]) + + sbc = gray2num(gray_sbc) # Number of stuff bits modulo 8 + + self.putb([18, ['Stuff bit count: %d' % sbc, + 'SBC: %d' % sbc, 'SBC']]) + if self.xl: return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. From 709e4d40db935466fbf7a0d90352d3faf321e6b6 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 17:21:30 +0100 Subject: [PATCH 34/46] decoders/can: decode PCRC field --- decoders/can/pd.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 6ef7e4bc9..3ee3ea156 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -85,11 +85,12 @@ class Decoder(srd.Decoder): ('DH2', 'Data high bit 2'), ('DL1', 'Data low bit 1'), ('SDT', 'Service data unit type'), - ('SEC', 'Simple/extended content') + ('SEC', 'Simple/extended content'), + ('PCRC-13', 'Preface CRC-13') ) annotation_rows = ( ('bits', 'Bits', (15, 17)), - ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26)), + ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26, 27)), ('warnings', 'Warnings', (16,)), ) @@ -516,6 +517,17 @@ def decode_standard_frame(self, can_rx, bitnum): self.putb([18, ['Stuff bit count: %d' % sbc, 'SBC: %d' % sbc, 'SBC']]) + # Remember start of PCRC (see below). + elif bitnum == 44: + self.ss_block = self.samplenum + + # Preface CRC (PCRC) + elif bitnum == 56: + pcrc = bitpack_msb(self.bits[45:58]) + + self.putb([27, ['Preface CRC-13: 0x%04x' % pcrc, + 'PCRC-13: 0x%04x' % pcrc, 'PCRC-13']]) + if self.xl: return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. From 011d9317023cc8bcd0340706b603c4a8cc42ca0f Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 17:24:45 +0100 Subject: [PATCH 35/46] decoders/can: decode VCID field --- decoders/can/pd.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 3ee3ea156..d41974341 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -86,11 +86,12 @@ class Decoder(srd.Decoder): ('DL1', 'Data low bit 1'), ('SDT', 'Service data unit type'), ('SEC', 'Simple/extended content'), - ('PCRC-13', 'Preface CRC-13') + ('PCRC-13', 'Preface CRC-13'), + ('VCID', 'Virtual CAN network channel identifier') ) annotation_rows = ( ('bits', 'Bits', (15, 17)), - ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26, 27)), + ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28)), ('warnings', 'Warnings', (16,)), ) @@ -528,6 +529,17 @@ def decode_standard_frame(self, can_rx, bitnum): self.putb([27, ['Preface CRC-13: 0x%04x' % pcrc, 'PCRC-13: 0x%04x' % pcrc, 'PCRC-13']]) + # Remember start of VCID (see below). + elif bitnum == 57: + self.ss_block = self.samplenum + + # Virtual CAN network channel identifier (VCID) + elif bitnum == 64: + vcid = bitpack_msb(self.bits[58:65]) + + self.putb([28, ['Virtual CAN network channel identifier: 0x%02X (%d)' % (vcid, vcid), + 'VCID: 0x%02X (%d)' % (vcid, vcid), 'VCID']]) + if self.xl: return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. From 6cec0a3e14b13c132d94abee0e848f329e00f5a2 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 17:28:10 +0100 Subject: [PATCH 36/46] decoders/can: decode AF field --- decoders/can/pd.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index d41974341..c08c41667 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -87,11 +87,12 @@ class Decoder(srd.Decoder): ('SDT', 'Service data unit type'), ('SEC', 'Simple/extended content'), ('PCRC-13', 'Preface CRC-13'), - ('VCID', 'Virtual CAN network channel identifier') + ('VCID', 'Virtual CAN network channel identifier'), + ('AF', 'Acceptance field') ) annotation_rows = ( ('bits', 'Bits', (15, 17)), - ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28)), + ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29)), ('warnings', 'Warnings', (16,)), ) @@ -540,6 +541,17 @@ def decode_standard_frame(self, can_rx, bitnum): self.putb([28, ['Virtual CAN network channel identifier: 0x%02X (%d)' % (vcid, vcid), 'VCID: 0x%02X (%d)' % (vcid, vcid), 'VCID']]) + # Remember start of AF (see below). + elif bitnum == 65: + self.ss_block = self.samplenum + + # Acceptance field (AF) + elif bitnum == 96: + af = bitpack_msb(self.bits[65:97]) + + self.putb([29, ['Acceptance field: 0x%02x (%d)' % (af, af), + 'AF: 0x%02x (%d)' % (af, af), 'AF']]) + if self.xl: return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. From c0d158dd0b3a1dae9fb8e68c64c37a88c7b3062e Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 17:32:47 +0100 Subject: [PATCH 37/46] decoders/can: decode FCRC field --- decoders/can/pd.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index c08c41667..f8df3ecf8 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -134,7 +134,9 @@ def set_xl_bitrate(self): def set_dlc_and_crc_len(self, dlc): self.dlc = dlc - if self.fd: + if self.xl: + self.crc_len = 32 + elif self.fd: if dlc2len(self.dlc) < 16: self.crc_len = 17 else: @@ -297,10 +299,10 @@ def decode_overload_frame(self, bits): # Returns True if the frame ended (EOF), False otherwise. def decode_frame_end(self, can_rx, bitnum): - # Remember start of Non-FD CRC sequence or FD-SBC field (see below). + # Remember start of Non-FD CRC sequence, XL FCRC sequence or FD-SBC field (see below). if bitnum == (self.last_databit + 1): self.ss_block = self.samplenum - elif self.fd and bitnum == (self.last_databit + 4): + elif self.fd and not self.xl and bitnum == (self.last_databit + 4): # SBC field x = self.last_databit + 1 sbc_bits = self.bits[x:x + self.last_databit + 4] @@ -318,21 +320,24 @@ def decode_frame_end(self, can_rx, bitnum): 'SBC: %d' % sbc, 'SBC']]) # Remember start of FD-CRC sequence (see below). - elif self.fd and bitnum == (self.last_databit + 4 + 1): + elif self.fd and not self.xl and bitnum == (self.last_databit + 4 + 1): self.ss_block = self.samplenum - # CRC sequence (15 bits, 17 bits or 21 bits) + # CRC sequence (15 bits, 17 bits, 21 bits or 32 bits) elif bitnum == (self.crc_start - 1 + self.crc_len): x = self.crc_start crc_bits = self.bits[x:x + self.crc_len + 1] - crc_type = "CRC-%d" % self.crc_len + crc_type = ("F" if self.xl else "") + "CRC-%d" % self.crc_len self.crc = bitpack_msb(crc_bits) self.putb([11, ['%s sequence: 0x%04x' % (crc_type, self.crc), '%s: 0x%04x' % (crc_type, self.crc), '%s' % crc_type]]) if not self.is_valid_crc(crc_bits): self.putb([16, ['CRC is invalid']]) + elif self.xl: + return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. + # CRC delimiter bit (recessive) elif bitnum == (self.crc_start - 1 + self.crc_len + 1): data = [12, ['CRC delimiter: %d' % can_rx, @@ -552,9 +557,6 @@ def decode_standard_frame(self, can_rx, bitnum): self.putb([29, ['Acceptance field: 0x%02x (%d)' % (af, af), 'AF: 0x%02x (%d)' % (af, af), 'AF']]) - if self.xl: - return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. - # Remember all databyte bits, except the very last one. if bitnum in range(97 if self.xl else self.dlc_start + 4, self.last_databit): self.ss_databytebits.append(self.samplenum) From c7e7875ab1cd547f7aa52f50567f54bcbcf848a3 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 17:47:49 +0100 Subject: [PATCH 38/46] decoders/can: decode FCP field --- decoders/can/pd.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index f8df3ecf8..afc6ebe9b 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -88,11 +88,12 @@ class Decoder(srd.Decoder): ('SEC', 'Simple/extended content'), ('PCRC-13', 'Preface CRC-13'), ('VCID', 'Virtual CAN network channel identifier'), - ('AF', 'Acceptance field') + ('AF', 'Acceptance field'), + ('FCP', 'Format check pattern') ) annotation_rows = ( ('bits', 'Bits', (15, 17)), - ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29)), + ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30)), ('warnings', 'Warnings', (16,)), ) @@ -335,6 +336,17 @@ def decode_frame_end(self, can_rx, bitnum): if not self.is_valid_crc(crc_bits): self.putb([16, ['CRC is invalid']]) + elif self.xl and bitnum == self.crc_start + 32: + # Remember start of FCP sequence (see below). + self.ss_block = self.samplenum + + elif self.xl and bitnum == self.crc_start + 35: + x = self.crc_start + 32 + fcp = bitpack_msb(self.bits[x:x + self.crc_start + 36]) + + self.putb([30, ['Format check pattern: 0x%02X' % fcp, + 'FCP: 0x%02X' % fcp, 'FCP']]) + elif self.xl: return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. From 84d7e3a7ec3c40b31477442e60075f410978a06e Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 17:50:00 +0100 Subject: [PATCH 39/46] decoders/can: decode DAH bit --- decoders/can/pd.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index afc6ebe9b..27786d639 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -89,11 +89,12 @@ class Decoder(srd.Decoder): ('PCRC-13', 'Preface CRC-13'), ('VCID', 'Virtual CAN network channel identifier'), ('AF', 'Acceptance field'), - ('FCP', 'Format check pattern') + ('FCP', 'Format check pattern'), + ('DAH', 'Data arbitration high') ) annotation_rows = ( ('bits', 'Bits', (15, 17)), - ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30)), + ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31)), ('warnings', 'Warnings', (16,)), ) @@ -347,11 +348,8 @@ def decode_frame_end(self, can_rx, bitnum): self.putb([30, ['Format check pattern: 0x%02X' % fcp, 'FCP: 0x%02X' % fcp, 'FCP']]) - elif self.xl: - return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. - # CRC delimiter bit (recessive) - elif bitnum == (self.crc_start - 1 + self.crc_len + 1): + elif not self.xl and bitnum == (self.crc_start + self.crc_len): data = [12, ['CRC delimiter: %d' % can_rx, 'CRC d: %d' % can_rx, 'CRC d']] @@ -368,6 +366,13 @@ def decode_frame_end(self, can_rx, bitnum): if can_rx != 1: self.putx([16, ['CRC delimiter must be a recessive bit']]) + elif self.xl and bitnum == self.crc_start + 36: + self.putx([31, ['Data arbitration high: %d' % can_rx, + 'DAH: %d' % can_rx, 'DAH']]) + + elif self.xl: + return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. + # ACK slot bit (dominant: ACK, recessive: NACK) elif bitnum == (self.crc_start - 1 + self.crc_len + 2): ack = 'ACK' if can_rx == 0 else 'NACK' From 7a22d4230d7ca65ef03f4d9d731627783cfdde7b Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 17:51:54 +0100 Subject: [PATCH 40/46] decoders/can: decode AH1 bit --- decoders/can/pd.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 27786d639..fe9891a53 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -90,11 +90,12 @@ class Decoder(srd.Decoder): ('VCID', 'Virtual CAN network channel identifier'), ('AF', 'Acceptance field'), ('FCP', 'Format check pattern'), - ('DAH', 'Data arbitration high') + ('DAH', 'Data arbitration high'), + ('AH1', 'Arbitration high 1') ) annotation_rows = ( ('bits', 'Bits', (15, 17)), - ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31)), + ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32)), ('warnings', 'Warnings', (16,)), ) @@ -370,6 +371,10 @@ def decode_frame_end(self, can_rx, bitnum): self.putx([31, ['Data arbitration high: %d' % can_rx, 'DAH: %d' % can_rx, 'DAH']]) + elif self.xl and bitnum == self.crc_start + 37: + self.putx([32, ['Arbitration high 1: %d' % can_rx, + 'AH1: %d' % can_rx, 'AH1']]) + elif self.xl: return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. From 3530717b69246386c4a009de7e78c3f0b119593f Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 17:52:23 +0100 Subject: [PATCH 41/46] decoders/can: decode AL1 bit --- decoders/can/pd.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index fe9891a53..7325ea774 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -91,11 +91,12 @@ class Decoder(srd.Decoder): ('AF', 'Acceptance field'), ('FCP', 'Format check pattern'), ('DAH', 'Data arbitration high'), - ('AH1', 'Arbitration high 1') + ('AH1', 'Arbitration high 1'), + ('AL1', 'Arbitration low 1') ) annotation_rows = ( ('bits', 'Bits', (15, 17)), - ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32)), + ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33)), ('warnings', 'Warnings', (16,)), ) @@ -375,6 +376,10 @@ def decode_frame_end(self, can_rx, bitnum): self.putx([32, ['Arbitration high 1: %d' % can_rx, 'AH1: %d' % can_rx, 'AH1']]) + elif self.xl and bitnum == self.crc_start + 38: + self.putx([33, ['Arbitration low 1: %d' % can_rx, + 'AL1: %d' % can_rx, 'AL1']]) + elif self.xl: return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. From 4ba91f8ab76451b3888833a213e0fbc7934f0931 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 17:52:55 +0100 Subject: [PATCH 42/46] decoders/can: decode AH2 bit --- decoders/can/pd.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 7325ea774..b0e508dc0 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -92,11 +92,12 @@ class Decoder(srd.Decoder): ('FCP', 'Format check pattern'), ('DAH', 'Data arbitration high'), ('AH1', 'Arbitration high 1'), - ('AL1', 'Arbitration low 1') + ('AL1', 'Arbitration low 1'), + ('AH2', 'Arbitration high 2') ) annotation_rows = ( ('bits', 'Bits', (15, 17)), - ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33)), + ('fields', 'Fields', tuple(range(15)) + (18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34)), ('warnings', 'Warnings', (16,)), ) @@ -380,8 +381,9 @@ def decode_frame_end(self, can_rx, bitnum): self.putx([33, ['Arbitration low 1: %d' % can_rx, 'AL1: %d' % can_rx, 'AL1']]) - elif self.xl: - return # Stop decoding here, as long as CAN-XL implementation is incomplete. TODO: Remove at AH2 bit. + elif self.xl and bitnum == self.crc_start + 39: + self.putx([34, ['Arbitration high 2: %d' % can_rx, + 'AH2: %d' % can_rx, 'AH2']]) # ACK slot bit (dominant: ACK, recessive: NACK) elif bitnum == (self.crc_start - 1 + self.crc_len + 2): From 56760781102478d7e9f7db684d29f444157a2b38 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 17:46:40 +0100 Subject: [PATCH 43/46] decoders/can: decode ACK delimiter bit --- decoders/can/pd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index b0e508dc0..225f488a1 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -391,7 +391,7 @@ def decode_frame_end(self, can_rx, bitnum): self.putx([13, ['ACK slot: %s' % ack, 'ACK s: %s' % ack, 'ACK s']]) # ACK delimiter bit (recessive) - elif bitnum == (self.crc_start - 1 + self.crc_len + 3): + elif self.xl and bitnum == self.crc_start + 41 or not self.xl and bitnum == (self.crc_start - 1 + self.crc_len + 3): self.putx([14, ['ACK delimiter: %d' % can_rx, 'ACK d: %d' % can_rx, 'ACK d']]) if can_rx != 1: From 29aa63cda9e5d81996b358cc7ce4333fffce784a Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 17:46:54 +0100 Subject: [PATCH 44/46] decoders/can: decode ACK slot bit --- decoders/can/pd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index 225f488a1..ddbda637c 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -386,7 +386,7 @@ def decode_frame_end(self, can_rx, bitnum): 'AH2: %d' % can_rx, 'AH2']]) # ACK slot bit (dominant: ACK, recessive: NACK) - elif bitnum == (self.crc_start - 1 + self.crc_len + 2): + elif self.xl and bitnum == self.crc_start + 40 or not self.xl and bitnum == (self.crc_start - 1 + self.crc_len + 2): ack = 'ACK' if can_rx == 0 else 'NACK' self.putx([13, ['ACK slot: %s' % ack, 'ACK s: %s' % ack, 'ACK s']]) From 3a07da8b6a843aa6a026d0417cb7af4fe829f4bb Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sat, 21 Mar 2026 17:45:57 +0100 Subject: [PATCH 45/46] decoders/can: decode EOF field --- decoders/can/pd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/decoders/can/pd.py b/decoders/can/pd.py index ddbda637c..a8c5a58e8 100644 --- a/decoders/can/pd.py +++ b/decoders/can/pd.py @@ -398,11 +398,11 @@ def decode_frame_end(self, can_rx, bitnum): self.putx([16, ['ACK delimiter must be a recessive bit']]) # Remember start of EOF (see below). - elif bitnum == (self.crc_start - 1 + self.crc_len + 4): + elif self.xl and bitnum == self.crc_start + 42 or not self.xl and bitnum == (self.crc_start - 1 + self.crc_len + 4): self.ss_block = self.samplenum # End of frame (EOF), 7 recessive bits - elif bitnum == (self.crc_start - 1 + self.crc_len + 3 + 7): + elif self.xl and bitnum == self.crc_start - 1 + 42 + 7 or not self.xl and bitnum == (self.crc_start - 1 + self.crc_len + 3 + 7): self.putb([2, ['End of frame', 'EOF', 'E']]) if self.rawbits[-7:] != [1, 1, 1, 1, 1, 1, 1]: self.putb([16, ['End of frame (EOF) must be 7 recessive bits']]) From 6d84212eb06f749215ebcddc3cfa896a984c8937 Mon Sep 17 00:00:00 2001 From: Stephan Thiele Date: Sun, 19 Apr 2026 15:14:22 +0200 Subject: [PATCH 46/46] decoders/can: mention CAN-XL support in decoder description --- decoders/can/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/decoders/can/__init__.py b/decoders/can/__init__.py index 888bb8192..b9973d09f 100644 --- a/decoders/can/__init__.py +++ b/decoders/can/__init__.py @@ -2,6 +2,7 @@ ## This file is part of the libsigrokdecode project. ## ## Copyright (C) 2012 Uwe Hermann +## Copyright (C) 2019-2026 Stephan Thiele ## ## This program is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published by @@ -25,7 +26,7 @@ the digital output side of a CAN transceiver IC such as the Microchip MCP-2515DM-BM). -It also has support for CAN-FD. +It also has support for CAN-FD and CAN-XL. ''' from .pd import Decoder