diff options
-rw-r--r-- | decoders/lin/__init__.py | 28 | ||||
-rw-r--r-- | decoders/lin/pd.py | 241 |
2 files changed, 269 insertions, 0 deletions
diff --git a/decoders/lin/__init__.py b/decoders/lin/__init__.py new file mode 100644 index 0000000..4e8c579 --- /dev/null +++ b/decoders/lin/__init__.py @@ -0,0 +1,28 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 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 +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see <http://www.gnu.org/licenses/>. +## + +''' +This decoder stacks on top of the 'uart' PD and decodes the LIN +(Local Interconnect Network) protocol. + +LIN is layered on top of the UART (async serial) protocol, with 8n1 settings. +Bytes are sent LSB-first. +''' + +from .pd import Decoder diff --git a/decoders/lin/pd.py b/decoders/lin/pd.py new file mode 100644 index 0000000..d1c1481 --- /dev/null +++ b/decoders/lin/pd.py @@ -0,0 +1,241 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2018 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 +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, see <http://www.gnu.org/licenses/>. +## + +import sigrokdecode as srd + +class LinFsm: + class State: + WaitForBreak = 'WAIT FOR BREAK' + Sync = 'SYNC' + Pid = 'PID' + Data = 'DATA' + Checksum = 'CHECKSUM' + Error = 'ERROR' + + def transit(self, target_state): + if not self._transition_allowed(target_state): + return False + self.state = target_state + return True + + def _transition_allowed(self, target_state): + if target_state == LinFsm.State.Error: + return True + for s in self.allowed_state[self.state]: + if s == target_state: + return True + return False + + def reset(self): + self.state = LinFsm.State.WaitForBreak + + def __init__(self): + self.allowed_state = dict() + self.allowed_state[LinFsm.State.WaitForBreak] = (LinFsm.State.Sync,) + self.allowed_state[LinFsm.State.Sync] = (LinFsm.State.Pid,) + self.allowed_state[LinFsm.State.Pid] = (LinFsm.State.Data,) + self.allowed_state[LinFsm.State.Data] = (LinFsm.State.Data, LinFsm.State.Checksum) + self.allowed_state[LinFsm.State.Checksum] = (LinFsm.State.WaitForBreak,) + self.allowed_state[LinFsm.State.Error] = (LinFsm.State.Sync,) + + self.state = None + self.reset() + +class Decoder(srd.Decoder): + api_version = 3 + id = 'lin' + name = 'LIN' + longname = 'Local Interconnect Network' + desc = 'Local Interconnect Network (LIN) protocol.' + license = 'gplv2+' + inputs = ['uart'] + outputs = ['lin'] + options = ( + {'id': 'version', 'desc': 'Protocol version', 'default': 2, 'values': (1, 2)}, + ) + annotations = ( + ('data', 'LIN data'), + ('control', 'Protocol info'), + ('error', 'Error descriptions'), + ('inline_error', 'Protocol violations and errors'), + ) + annotation_rows = ( + ('data', 'Data', (0, 1, 3)), + ('error', 'Error', (2,)), + ) + + def __init__(self): + self.reset() + + def reset(self): + self.fsm = LinFsm() + self.lin_header = [] + self.lin_rsp = [] + self.lin_version = None + self.out_ann = None + self.ss_block = None + self.es_block = None + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + self.lin_version = self.options['version'] + + def putx(self, data): + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def wipe_break_null_byte(self, value): + # Upon a break condition a null byte is received which must be ignored. + if self.fsm.state != LinFsm.State.WaitForBreak and self.fsm.state != LinFsm.State.Error: + if len(self.lin_rsp): + value = self.lin_rsp.pop()[2] + else: + self.lin_header.pop() + + if value != 0: + self.fsm.transit(LinFsm.State.Error) + self.handle_error() + return False + + return True + + def handle_wait_for_break(self, value): + self.wipe_break_null_byte(value) + + def handle_break(self, value): + if self.fsm.state != LinFsm.State.WaitForBreak and self.fsm.state != LinFsm.State.Error: + if self.wipe_break_null_byte(value): + self.fsm.transit(LinFsm.State.Checksum) + self.handle_checksum() + + self.fsm.reset() + self.fsm.transit(LinFsm.State.Sync) + + data = [1, ['Break condition', 'Break', 'Brk', 'B']] + self.put(self.ss_block, self.es_block, self.out_ann, data) + + def handle_sync(self, value): + self.fsm.transit(LinFsm.State.Pid) + self.lin_header.append((self.ss_block, self.es_block, value)) + + def handle_pid(self, value): + self.fsm.transit(LinFsm.State.Data) + self.lin_header.append((self.ss_block, self.es_block, value)) + + def handle_data(self, value): + self.lin_rsp.append((self.ss_block, self.es_block, value)) + + def handle_checksum(self): + sync = self.lin_header.pop(0) if len(self.lin_header) else None + + self.put(sync[0], sync[1], self.out_ann, [0, ['Sync', 'S']]) + + if sync[2] != 0x55: + self.put(sync[0], sync[1], self.out_ann, + [2, ['Sync is not 0x55', 'Not 0x55', '!= 0x55']]) + + pid = self.lin_header.pop(0) if len(self.lin_header) else None + checksum = self.lin_rsp.pop() if len(self.lin_rsp) else None + + if pid: + id = pid[2] & 0x3F + parity = pid[2] >> 6 + + expected_parity = self.calc_parity(pid[2]) + parity_valid = parity == expected_parity + + if not parity_valid: + self.put(pid[0], pid[1], self.out_ann, [2, ['P != %d' % expected_parity]]) + + ann_class = 0 if parity_valid else 3 + self.put(pid[0], pid[1], self.out_ann, [ann_class, [ + 'ID: %02X Parity: %d (%s)' % (id, parity, 'ok' if parity_valid else 'bad'), + 'ID: 0x%02X' % id, 'I: %d' % id + ]]) + + if len(self.lin_rsp): + checksum_valid = self.checksum_is_valid(pid[2], self.lin_rsp, checksum[2]) + + for b in self.lin_rsp: + self.put(b[0], b[1], self.out_ann, [0, ['Data: 0x%02X' % b[2], 'D: 0x%02X' % b[2]]]) + + ann_class = 0 if checksum_valid else 3 + self.put(checksum[0], checksum[1], self.out_ann, + [ann_class, ['Checksum: 0x%02X' % checksum[2], 'Checksum', 'Chk', 'C']]) + + if not checksum_valid: + self.put(checksum[0], checksum[1], self.out_ann, [2, ['Checksum invalid']]) + else: + pass # No response. + + self.lin_header.clear() + self.lin_rsp.clear() + + def handle_error(self): + self.putx([3, ['Error', 'Err', 'E']]) + + def checksum_is_valid(self, pid, data, checksum): + if self.lin_version == 2: + id = pid & 0x3F + + if id != 60 and id != 61: + checksum += pid + + for d in data: + checksum += d[2] + + carry_bits = int(checksum / 256) + checksum += carry_bits + + return checksum & 0xFF == 0xFF + + @staticmethod + def calc_parity(pid): + id = [((pid & 0x3F) >> i) & 1 for i in range(8)] + + p0 = id[0] ^ id[1] ^ id[2] ^ id[4] + p1 = not (id[1] ^ id[3] ^ id[4] ^ id[5]) + + return (p0 << 0) | (p1 << 1) + + def decode(self, ss, es, data): + ptype, rxtx, pdata = data + + self.ss_block, self.es_block = ss, es + + # Ignore all UART packets except the actual data packets or BREAK. + if ptype == 'BREAK': + self.handle_break(pdata) + if ptype != 'DATA': + return + + # We're only interested in the byte value (not individual bits). + pdata = pdata[0] + + # Short LIN overview: + # - Message begins with a BREAK (0x00) for at least 13 bittimes. + # - Break is always followed by a SYNC byte (0x55). + # - Sync byte is followed by a PID byte (Protected Identifier). + # - PID byte is followed by 1 - 8 data bytes and a final checksum byte. + + if self.fsm.state == LinFsm.State.Error: self.handle_error() + elif self.fsm.state == LinFsm.State.WaitForBreak: self.handle_wait_for_break(pdata) + elif self.fsm.state == LinFsm.State.Sync: self.handle_sync(pdata) + elif self.fsm.state == LinFsm.State.Pid: self.handle_pid(pdata) + elif self.fsm.state == LinFsm.State.Data: self.handle_data(pdata) + elif self.fsm.state == LinFsm.State.Checksum: self.handle_checksum() |