]> sigrok.org Git - libsigrokdecode.git/blame - decoders/cec/pd.py
Add PD tags handling and some tags
[libsigrokdecode.git] / decoders / cec / pd.py
CommitLineData
fb248c04
JS
1##
2## This file is part of the libsigrokdecode project.
3##
4## Copyright (C) 2018 Jorge Solla Rubiales <jorgesolla@gmail.com>
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
21from .protocoldata import *
22
23# Pulse types
24class Pulse:
25 INVALID, START, ZERO, ONE = range(4)
26
27# Protocol stats
28class Stat:
29 WAIT_START, GET_BITS, WAIT_EOM, WAIT_ACK = range(4)
30
31# Pulse times in milliseconds
32timing = {
33 Pulse.START: {
34 'low': { 'min': 3.5, 'max': 3.9 },
35 'total': { 'min': 4.3, 'max': 4.7 }
36 },
37 Pulse.ZERO: {
38 'low': { 'min': 1.3, 'max': 1.7 },
39 'total': { 'min': 2.05, 'max': 2.75 }
40 },
41 Pulse.ONE: {
42 'low': { 'min': 0.4, 'max': 0.8 },
43 'total': { 'min': 2.05, 'max': 2.75 }
44 }
45}
46
47class ChannelError(Exception):
48 pass
49
50class Decoder(srd.Decoder):
51 api_version = 3
52 id = 'cec'
53 name = 'CEC'
54 longname = 'HDMI-CEC'
55 desc = 'HDMI Consumer Electronics Control (CEC) protocol.'
56 license = 'gplv2+'
57 inputs = ['logic']
58 outputs = ['cec']
4c180223 59 tags = ['Logic', 'Bus', 'Video']
fb248c04
JS
60 channels = (
61 {'id': 'cec', 'name': 'CEC', 'desc': 'CEC bus data'},
62 )
63 annotations = (
64 ('st', 'Start'),
65 ('eom-0', 'End of message'),
66 ('eom-1', 'Message continued'),
67 ('nack', 'ACK not set'),
68 ('ack', 'ACK set'),
69 ('bits', 'Bits'),
70 ('bytes', 'Bytes'),
71 ('frames', 'Frames'),
72 ('sections', 'Sections'),
73 ('warnings', 'Warnings')
74 )
75 annotation_rows = (
76 ('bits', 'Bits', (0, 1, 2, 3, 4, 5)),
77 ('bytes', 'Bytes', (6,)),
78 ('frames', 'Frames', (7,)),
79 ('sections', 'Sections', (8,)),
80 ('warnings', 'Warnings', (9,))
81 )
82
83 def __init__(self):
84 self.reset()
85
86 def precalculate(self):
87 # Restrict max length of ACK/NACK labels to 2 BIT pulses.
8a8b516a 88 bit_time = timing[Pulse.ZERO]['total']['min'] * 2
fb248c04
JS
89 self.max_ack_len_samples = round((bit_time / 1000) * self.samplerate)
90
91 def reset(self):
92 self.stat = Stat.WAIT_START
93 self.samplerate = None
94 self.fall_start = None
95 self.fall_end = None
96 self.rise = None
97 self.reset_frame_vars()
98
99 def reset_frame_vars(self):
100 self.eom = None
101 self.bit_count = 0
102 self.byte_count = 0
103 self.byte = 0
104 self.byte_start = None
105 self.frame_start = None
106 self.frame_end = None
107 self.is_nack = 0
108 self.cmd_bytes = []
109
110 def metadata(self, key, value):
111 if key == srd.SRD_CONF_SAMPLERATE:
112 self.samplerate = value
113 self.precalculate()
114
fb248c04
JS
115 def handle_frame(self, is_nack):
116 if self.fall_start is None or self.fall_end is None:
117 return
118
119 i = 0
16d2031b 120 string = ''
fb248c04 121 while i < len(self.cmd_bytes):
16d2031b 122 string += '{:02x}'.format(self.cmd_bytes[i]['val'])
fb248c04 123 if i != (len(self.cmd_bytes) - 1):
16d2031b 124 string += ':'
fb248c04
JS
125 i += 1
126
16d2031b 127 self.put(self.frame_start, self.frame_end, self.out_ann, [7, [string]])
fb248c04
JS
128
129 i = 0
130 operands = 0
16d2031b 131 string = ''
fb248c04
JS
132 while i < len(self.cmd_bytes):
133 if i == 0: # Parse header
134 (src, dst) = decode_header(self.cmd_bytes[i]['val'])
16d2031b 135 string = 'HDR: ' + src + ', ' + dst
fb248c04 136 elif i == 1: # Parse opcode
16d2031b 137 string += ' | OPC: ' + opcodes.get(self.cmd_bytes[i]['val'], 'Invalid')
fb248c04
JS
138 else: # Parse operands
139 if operands == 0:
16d2031b 140 string += ' | OPS: '
fb248c04 141 operands += 1
16d2031b 142 string += '0x{:02x}'.format(self.cmd_bytes[i]['val'])
fb248c04 143 if i != len(self.cmd_bytes) - 1:
16d2031b 144 string += ', '
fb248c04
JS
145 i += 1
146
147 # Header only commands are PINGS
148 if i == 1:
16d2031b 149 string += ' | OPC: PING' if self.eom else ' | OPC: NONE. Aborted cmd'
fb248c04
JS
150
151 # Add extra information (ack of the command from the destination)
16d2031b 152 string += ' | R: NACK' if is_nack else ' | R: ACK'
fb248c04 153
16d2031b 154 self.put(self.frame_start, self.frame_end, self.out_ann, [8, [string]])
fb248c04
JS
155
156 def process(self):
157 zero_time = ((self.rise - self.fall_start) / self.samplerate) * 1000.0
158 total_time = ((self.fall_end - self.fall_start) / self.samplerate) * 1000.0
159 pulse = Pulse.INVALID
160
161 # VALIDATION: Identify pulse based on length of the low period
162 for key in timing:
163 if zero_time >= timing[key]['low']['min'] and zero_time <= timing[key]['low']['max']:
164 pulse = key
165 break
166
167 # VALIDATION: Invalid pulse
168 if pulse == Pulse.INVALID:
e8c1209d 169 self.stat = Stat.WAIT_START
fb248c04
JS
170 self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Invalid pulse: Wrong timing']])
171 return
172
173 # VALIDATION: If waiting for start, discard everything else
174 if self.stat == Stat.WAIT_START and pulse != Pulse.START:
175 self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Expected START: BIT found']])
176 return
177
178 # VALIDATION: If waiting for ACK or EOM, only BIT pulses (0/1) are expected
179 if (self.stat == Stat.WAIT_ACK or self.stat == Stat.WAIT_EOM) and pulse == Pulse.START:
180 self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Expected BIT: START received)']])
e8c1209d 181 self.stat = Stat.WAIT_START
fb248c04
JS
182
183 # VALIDATION: ACK bit pulse remains high till the next frame (if any): Validate only min time of the low period
184 if self.stat == Stat.WAIT_ACK and pulse != Pulse.START:
185 if total_time < timing[pulse]['total']['min']:
186 pulse = Pulse.INVALID
187 self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['ACK pulse below minimun time']])
e8c1209d 188 self.stat = Stat.WAIT_START
fb248c04
JS
189 return
190
191 # VALIDATION / PING FRAME DETECTION: Initiator doesn't sets the EOM = 1 but stops sending when ack doesn't arrive
192 if self.stat == Stat.GET_BITS and pulse == Pulse.START:
193 # Make sure we received a complete byte to consider it a valid ping
194 if self.bit_count == 0:
195 self.handle_frame(self.is_nack)
196 else:
197 self.put(self.frame_start, self.samplenum, self.out_ann, [9, ['ERROR: Incomplete byte received']])
198
199 # Set wait start so we receive next frame
e8c1209d 200 self.stat = Stat.WAIT_START
fb248c04
JS
201
202 # VALIDATION: Check timing of the BIT (0/1) pulse in any other case (not waiting for ACK)
203 if self.stat != Stat.WAIT_ACK and pulse != Pulse.START:
204 if total_time < timing[pulse]['total']['min'] or total_time > timing[pulse]['total']['max']:
205 self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Bit pulse exceeds total pulse timespan']])
206 pulse = Pulse.INVALID
e8c1209d 207 self.stat = Stat.WAIT_START
fb248c04
JS
208 return
209
210 if pulse == Pulse.ZERO:
211 bit = 0
212 elif pulse == Pulse.ONE:
213 bit = 1
214
215 # STATE: WAIT START
216 if self.stat == Stat.WAIT_START:
e8c1209d 217 self.stat = Stat.GET_BITS
fb248c04
JS
218 self.reset_frame_vars()
219 self.put(self.fall_start, self.fall_end, self.out_ann, [0, ['ST']])
220
221 # STATE: GET BITS
222 elif self.stat == Stat.GET_BITS:
223 # Reset stats on first bit
224 if self.bit_count == 0:
225 self.byte_start = self.fall_start
226 self.byte = 0
227
228 # If 1st byte of the datagram save its sample num
229 if len(self.cmd_bytes) == 0:
230 self.frame_start = self.fall_start
231
232 self.byte += (bit << (7 - self.bit_count))
233 self.bit_count += 1
234 self.put(self.fall_start, self.fall_end, self.out_ann, [5, [str(bit)]])
235
236 if self.bit_count == 8:
237 self.bit_count = 0
238 self.byte_count += 1
e8c1209d 239 self.stat = Stat.WAIT_EOM
fb248c04
JS
240 self.put(self.byte_start, self.samplenum, self.out_ann, [6, ['0x{:02x}'.format(self.byte)]])
241 self.cmd_bytes.append({'st': self.byte_start, 'ed': self.samplenum, 'val': self.byte})
242
243 # STATE: WAIT EOM
244 elif self.stat == Stat.WAIT_EOM:
245 self.eom = bit
246 self.frame_end = self.fall_end
247
8a8b516a
UH
248 a = [2, ['EOM=Y']] if self.eom else [1, ['EOM=N']]
249 self.put(self.fall_start, self.fall_end, self.out_ann, a)
fb248c04 250
e8c1209d 251 self.stat = Stat.WAIT_ACK
fb248c04
JS
252
253 # STATE: WAIT ACK
254 elif self.stat == Stat.WAIT_ACK:
255 # If a frame with broadcast destination is being sent, the ACK is
256 # inverted: a 0 is considered a NACK, therefore we invert the value
257 # of the bit here, so we match the real meaning of it.
258 if (self.cmd_bytes[0]['val'] & 0x0F) == 0x0F:
259 bit = ~bit & 0x01
260
261 if (self.fall_end - self.fall_start) > self.max_ack_len_samples:
262 ann_end = self.fall_start + self.max_ack_len_samples
263 else:
264 ann_end = self.fall_end
265
266 if bit:
267 # Any NACK detected in the frame is enough to consider the
268 # whole frame NACK'd.
269 self.is_nack = 1
270 self.put(self.fall_start, ann_end, self.out_ann, [3, ['NACK']])
271 else:
272 self.put(self.fall_start, ann_end, self.out_ann, [4, ['ACK']])
273
274 # After ACK bit, wait for new datagram or continue reading current
275 # one based on EOM value.
276 if self.eom or self.is_nack:
e8c1209d 277 self.stat = Stat.WAIT_START
fb248c04
JS
278 self.handle_frame(self.is_nack)
279 else:
e8c1209d 280 self.stat = Stat.GET_BITS
fb248c04
JS
281
282 def start(self):
283 self.out_ann = self.register(srd.OUTPUT_ANN)
284
285 def decode(self):
286 if not self.samplerate:
287 raise SamplerateError('Cannot decode without samplerate.')
288
289 # Wait for first falling edge.
290 self.wait({0: 'f'})
291 self.fall_end = self.samplenum
292
293 while True:
294 self.wait({0: 'r'})
295 self.rise = self.samplenum
296
297 if self.stat == Stat.WAIT_ACK:
298 self.wait([{0: 'f'}, {'skip': self.max_ack_len_samples}])
299 else:
300 self.wait([{0: 'f'}])
301
302 self.fall_start = self.fall_end
303 self.fall_end = self.samplenum
304 self.process()
305
306 # If there was a timeout while waiting for ACK: RESYNC.
307 # Note: This is an expected situation as no new falling edge will
308 # happen until next frame is transmitted.
309 if self.matched == (False, True):
310 self.wait({0: 'f'})
311 self.fall_end = self.samplenum