Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 110 additions & 33 deletions decoders/can/pd.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
## This file is part of the libsigrokdecode project.
##
## Copyright (C) 2012-2013 Uwe Hermann <uwe@hermann-uwe.de>
## Copyright (C) 2019 Stephan Thiele <stephan.thiele@mailbox.org>
## Copyright (C) 2019-2026 Stephan Thiele <stephan.thiele@mailbox.org>
##
## 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
Expand All @@ -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'
Expand Down Expand Up @@ -64,10 +74,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,)),
)

Expand All @@ -92,6 +103,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 = 17
else:
self.crc_len = 21
else:
self.crc_len = 15

def metadata(self, key, value):
if key == srd.SRD_CONF_SAMPLERATE:
self.samplerate = value
Expand Down Expand Up @@ -129,6 +151,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
Expand All @@ -137,6 +160,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
Expand All @@ -155,19 +180,55 @@ 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.crc_start + self.crc_len - 1:
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_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

Expand All @@ -182,37 +243,44 @@ 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
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
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]
p_gray_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"
parity = p_gray_sbc & 1
gray_sbc = p_gray_sbc >> 1

x = self.last_databit + 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,
'SBC: %d' % sbc, 'SBC']])

# 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]])
if not self.is_valid_crc(crc_bits):
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:
Expand All @@ -222,23 +290,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']])
Expand Down Expand Up @@ -295,10 +363,15 @@ 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)
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']])

Expand Down Expand Up @@ -397,10 +470,14 @@ 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)
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):
Expand Down