]> sigrok.org Git - libsigrokdecode.git/blame - decoders/lin/pd.py
lin: calculate checksum on two consecutive UART idle frames
[libsigrokdecode.git] / decoders / lin / pd.py
CommitLineData
fe9160d5 1##
2## This file is part of the libsigrokdecode project.
3##
4## Copyright (C) 2018 Stephan Thiele <stephan.thiele@mailbox.org>
5##
6## This program is free software; you can redistribute it and/or modify
7## it under the terms of the GNU General Public License as published by
8## the Free Software Foundation; either version 2 of the License, or
9## (at your option) any later version.
10##
11## This program is distributed in the hope that it will be useful,
12## but WITHOUT ANY WARRANTY; without even the implied warranty of
13## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14## GNU General Public License for more details.
15##
16## You should have received a copy of the GNU General Public License
17## along with this program; if not, see <http://www.gnu.org/licenses/>.
18##
19
20import sigrokdecode as srd
21
22class LinFsm:
23 class State:
546733a2 24 WaitForBreak = 'WAIT_FOR_BREAK'
fe9160d5 25 Sync = 'SYNC'
26 Pid = 'PID'
27 Data = 'DATA'
28 Checksum = 'CHECKSUM'
29 Error = 'ERROR'
30
31 def transit(self, target_state):
32 if not self._transition_allowed(target_state):
33 return False
34 self.state = target_state
35 return True
36
37 def _transition_allowed(self, target_state):
38 if target_state == LinFsm.State.Error:
39 return True
b1e3616d 40 return target_state in self.allowed_state[self.state]
fe9160d5 41
42 def reset(self):
43 self.state = LinFsm.State.WaitForBreak
d871cc2d 44 self.uart_idle_count = 0
fe9160d5 45
46 def __init__(self):
546733a2
UH
47 a = dict()
48 a[LinFsm.State.WaitForBreak] = (LinFsm.State.Sync,)
49 a[LinFsm.State.Sync] = (LinFsm.State.Pid,)
50 a[LinFsm.State.Pid] = (LinFsm.State.Data,)
51 a[LinFsm.State.Data] = (LinFsm.State.Data, LinFsm.State.Checksum)
52 a[LinFsm.State.Checksum] = (LinFsm.State.WaitForBreak,)
53 a[LinFsm.State.Error] = (LinFsm.State.Sync,)
54 self.allowed_state = a
fe9160d5 55
56 self.state = None
d871cc2d 57 self.uart_idle_count = 0
fe9160d5 58 self.reset()
59
60class Decoder(srd.Decoder):
61 api_version = 3
62 id = 'lin'
63 name = 'LIN'
64 longname = 'Local Interconnect Network'
65 desc = 'Local Interconnect Network (LIN) protocol.'
66 license = 'gplv2+'
67 inputs = ['uart']
6cbba91f 68 outputs = []
d6d8a8a4 69 tags = ['Automotive']
fe9160d5 70 options = (
71 {'id': 'version', 'desc': 'Protocol version', 'default': 2, 'values': (1, 2)},
72 )
73 annotations = (
74 ('data', 'LIN data'),
75 ('control', 'Protocol info'),
76 ('error', 'Error descriptions'),
77 ('inline_error', 'Protocol violations and errors'),
78 )
79 annotation_rows = (
80 ('data', 'Data', (0, 1, 3)),
81 ('error', 'Error', (2,)),
82 )
83
84 def __init__(self):
85 self.reset()
86
87 def reset(self):
88 self.fsm = LinFsm()
89 self.lin_header = []
90 self.lin_rsp = []
91 self.lin_version = None
92 self.out_ann = None
93 self.ss_block = None
94 self.es_block = None
95
96 def start(self):
97 self.out_ann = self.register(srd.OUTPUT_ANN)
98 self.lin_version = self.options['version']
99
100 def putx(self, data):
101 self.put(self.ss_block, self.es_block, self.out_ann, data)
102
103 def wipe_break_null_byte(self, value):
104 # Upon a break condition a null byte is received which must be ignored.
546733a2 105 if self.fsm.state not in (LinFsm.State.WaitForBreak, LinFsm.State.Error):
fe9160d5 106 if len(self.lin_rsp):
107 value = self.lin_rsp.pop()[2]
108 else:
109 self.lin_header.pop()
110
111 if value != 0:
112 self.fsm.transit(LinFsm.State.Error)
546733a2 113 self.handle_error(None)
fe9160d5 114 return False
115
116 return True
117
d871cc2d
ST
118 def handle_uart_idle(self):
119 if self.fsm.state not in (LinFsm.State.WaitForBreak, LinFsm.State.Error):
120 self.fsm.uart_idle_count += 1
121
122 if self.fsm.uart_idle_count == 2:
123 self.fsm.transit(LinFsm.State.Checksum)
124 self.handle_checksum()
125 self.fsm.reset()
126
fe9160d5 127 def handle_wait_for_break(self, value):
128 self.wipe_break_null_byte(value)
129
130 def handle_break(self, value):
546733a2 131 if self.fsm.state not in (LinFsm.State.WaitForBreak, LinFsm.State.Error):
fe9160d5 132 if self.wipe_break_null_byte(value):
133 self.fsm.transit(LinFsm.State.Checksum)
134 self.handle_checksum()
135
136 self.fsm.reset()
137 self.fsm.transit(LinFsm.State.Sync)
138
740a49d4 139 self.putx([1, ['Break condition', 'Break', 'Brk', 'B']])
fe9160d5 140
141 def handle_sync(self, value):
142 self.fsm.transit(LinFsm.State.Pid)
143 self.lin_header.append((self.ss_block, self.es_block, value))
144
145 def handle_pid(self, value):
146 self.fsm.transit(LinFsm.State.Data)
147 self.lin_header.append((self.ss_block, self.es_block, value))
148
149 def handle_data(self, value):
150 self.lin_rsp.append((self.ss_block, self.es_block, value))
151
152 def handle_checksum(self):
153 sync = self.lin_header.pop(0) if len(self.lin_header) else None
154
155 self.put(sync[0], sync[1], self.out_ann, [0, ['Sync', 'S']])
156
157 if sync[2] != 0x55:
158 self.put(sync[0], sync[1], self.out_ann,
159 [2, ['Sync is not 0x55', 'Not 0x55', '!= 0x55']])
160
161 pid = self.lin_header.pop(0) if len(self.lin_header) else None
162 checksum = self.lin_rsp.pop() if len(self.lin_rsp) else None
163
164 if pid:
16d2031b 165 id_ = pid[2] & 0x3F
fe9160d5 166 parity = pid[2] >> 6
167
168 expected_parity = self.calc_parity(pid[2])
169 parity_valid = parity == expected_parity
170
171 if not parity_valid:
172 self.put(pid[0], pid[1], self.out_ann, [2, ['P != %d' % expected_parity]])
173
174 ann_class = 0 if parity_valid else 3
175 self.put(pid[0], pid[1], self.out_ann, [ann_class, [
16d2031b
RJ
176 'ID: %02X Parity: %d (%s)' % (id_, parity, 'ok' if parity_valid else 'bad'),
177 'ID: 0x%02X' % id_, 'I: %d' % id_
fe9160d5 178 ]])
179
180 if len(self.lin_rsp):
181 checksum_valid = self.checksum_is_valid(pid[2], self.lin_rsp, checksum[2])
182
183 for b in self.lin_rsp:
184 self.put(b[0], b[1], self.out_ann, [0, ['Data: 0x%02X' % b[2], 'D: 0x%02X' % b[2]]])
185
186 ann_class = 0 if checksum_valid else 3
187 self.put(checksum[0], checksum[1], self.out_ann,
188 [ann_class, ['Checksum: 0x%02X' % checksum[2], 'Checksum', 'Chk', 'C']])
189
190 if not checksum_valid:
191 self.put(checksum[0], checksum[1], self.out_ann, [2, ['Checksum invalid']])
192 else:
193 pass # No response.
194
195 self.lin_header.clear()
196 self.lin_rsp.clear()
197
546733a2 198 def handle_error(self, dummy):
fe9160d5 199 self.putx([3, ['Error', 'Err', 'E']])
200
201 def checksum_is_valid(self, pid, data, checksum):
202 if self.lin_version == 2:
16d2031b 203 id_ = pid & 0x3F
fe9160d5 204
16d2031b 205 if id_ != 60 and id_ != 61:
fe9160d5 206 checksum += pid
207
208 for d in data:
209 checksum += d[2]
210
211 carry_bits = int(checksum / 256)
212 checksum += carry_bits
213
214 return checksum & 0xFF == 0xFF
215
216 @staticmethod
217 def calc_parity(pid):
16d2031b 218 id_ = [((pid & 0x3F) >> i) & 1 for i in range(8)]
fe9160d5 219
16d2031b
RJ
220 p0 = id_[0] ^ id_[1] ^ id_[2] ^ id_[4]
221 p1 = not (id_[1] ^ id_[3] ^ id_[4] ^ id_[5])
fe9160d5 222
223 return (p0 << 0) | (p1 << 1)
224
225 def decode(self, ss, es, data):
226 ptype, rxtx, pdata = data
227
228 self.ss_block, self.es_block = ss, es
229
230 # Ignore all UART packets except the actual data packets or BREAK.
d871cc2d
ST
231 if ptype == 'IDLE':
232 self.handle_uart_idle()
fe9160d5 233 if ptype == 'BREAK':
234 self.handle_break(pdata)
235 if ptype != 'DATA':
236 return
237
238 # We're only interested in the byte value (not individual bits).
239 pdata = pdata[0]
240
241 # Short LIN overview:
242 # - Message begins with a BREAK (0x00) for at least 13 bittimes.
243 # - Break is always followed by a SYNC byte (0x55).
244 # - Sync byte is followed by a PID byte (Protected Identifier).
245 # - PID byte is followed by 1 - 8 data bytes and a final checksum byte.
246
546733a2
UH
247 handler = getattr(self, 'handle_%s' % self.fsm.state.lower())
248 handler(pdata)