2 ## This file is part of the libsigrokdecode project.
4 ## Copyright (C) 2018 Jorge Solla Rubiales <jorgesolla@gmail.com>
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.
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.
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/>.
20 import sigrokdecode as srd
21 from .protocoldata import *
25 INVALID, START, ZERO, ONE = range(4)
29 WAIT_START, GET_BITS, WAIT_EOM, WAIT_ACK = range(4)
31 # Pulse times in milliseconds
34 'low': { 'min': 3.5, 'max': 3.9 },
35 'total': { 'min': 4.3, 'max': 4.7 }
38 'low': { 'min': 1.3, 'max': 1.7 },
39 'total': { 'min': 2.05, 'max': 2.75 }
42 'low': { 'min': 0.4, 'max': 0.8 },
43 'total': { 'min': 2.05, 'max': 2.75 }
47 class ChannelError(Exception):
50 class Decoder(srd.Decoder):
55 desc = 'HDMI Consumer Electronics Control (CEC) protocol.'
60 {'id': 'cec', 'name': 'CEC', 'desc': 'CEC bus data'},
64 ('eom-0', 'End of message'),
65 ('eom-1', 'Message continued'),
66 ('nack', 'ACK not set'),
71 ('sections', 'Sections'),
72 ('warnings', 'Warnings')
75 ('bits', 'Bits', (0, 1, 2, 3, 4, 5)),
76 ('bytes', 'Bytes', (6,)),
77 ('frames', 'Frames', (7,)),
78 ('sections', 'Sections', (8,)),
79 ('warnings', 'Warnings', (9,))
85 def precalculate(self):
86 # Restrict max length of ACK/NACK labels to 2 BIT pulses.
87 bit_time = timing[Pulse.ZERO]['total']['min'] * 2
88 self.max_ack_len_samples = round((bit_time / 1000) * self.samplerate)
91 self.stat = Stat.WAIT_START
92 self.samplerate = None
93 self.fall_start = None
96 self.reset_frame_vars()
98 def reset_frame_vars(self):
103 self.byte_start = None
104 self.frame_start = None
105 self.frame_end = None
109 def metadata(self, key, value):
110 if key == srd.SRD_CONF_SAMPLERATE:
111 self.samplerate = value
114 def handle_frame(self, is_nack):
115 if self.fall_start is None or self.fall_end is None:
120 while i < len(self.cmd_bytes):
121 string += '{:02x}'.format(self.cmd_bytes[i]['val'])
122 if i != (len(self.cmd_bytes) - 1):
126 self.put(self.frame_start, self.frame_end, self.out_ann, [7, [string]])
131 while i < len(self.cmd_bytes):
132 if i == 0: # Parse header
133 (src, dst) = decode_header(self.cmd_bytes[i]['val'])
134 string = 'HDR: ' + src + ', ' + dst
135 elif i == 1: # Parse opcode
136 string += ' | OPC: ' + opcodes.get(self.cmd_bytes[i]['val'], 'Invalid')
137 else: # Parse operands
141 string += '0x{:02x}'.format(self.cmd_bytes[i]['val'])
142 if i != len(self.cmd_bytes) - 1:
146 # Header only commands are PINGS
148 string += ' | OPC: PING' if self.eom else ' | OPC: NONE. Aborted cmd'
150 # Add extra information (ack of the command from the destination)
151 string += ' | R: NACK' if is_nack else ' | R: ACK'
153 self.put(self.frame_start, self.frame_end, self.out_ann, [8, [string]])
156 zero_time = ((self.rise - self.fall_start) / self.samplerate) * 1000.0
157 total_time = ((self.fall_end - self.fall_start) / self.samplerate) * 1000.0
158 pulse = Pulse.INVALID
160 # VALIDATION: Identify pulse based on length of the low period
162 if zero_time >= timing[key]['low']['min'] and zero_time <= timing[key]['low']['max']:
166 # VALIDATION: Invalid pulse
167 if pulse == Pulse.INVALID:
168 self.stat = Stat.WAIT_START
169 self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Invalid pulse: Wrong timing']])
172 # VALIDATION: If waiting for start, discard everything else
173 if self.stat == Stat.WAIT_START and pulse != Pulse.START:
174 self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Expected START: BIT found']])
177 # VALIDATION: If waiting for ACK or EOM, only BIT pulses (0/1) are expected
178 if (self.stat == Stat.WAIT_ACK or self.stat == Stat.WAIT_EOM) and pulse == Pulse.START:
179 self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Expected BIT: START received)']])
180 self.stat = Stat.WAIT_START
182 # VALIDATION: ACK bit pulse remains high till the next frame (if any): Validate only min time of the low period
183 if self.stat == Stat.WAIT_ACK and pulse != Pulse.START:
184 if total_time < timing[pulse]['total']['min']:
185 pulse = Pulse.INVALID
186 self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['ACK pulse below minimun time']])
187 self.stat = Stat.WAIT_START
190 # VALIDATION / PING FRAME DETECTION: Initiator doesn't sets the EOM = 1 but stops sending when ack doesn't arrive
191 if self.stat == Stat.GET_BITS and pulse == Pulse.START:
192 # Make sure we received a complete byte to consider it a valid ping
193 if self.bit_count == 0:
194 self.handle_frame(self.is_nack)
196 self.put(self.frame_start, self.samplenum, self.out_ann, [9, ['ERROR: Incomplete byte received']])
198 # Set wait start so we receive next frame
199 self.stat = Stat.WAIT_START
201 # VALIDATION: Check timing of the BIT (0/1) pulse in any other case (not waiting for ACK)
202 if self.stat != Stat.WAIT_ACK and pulse != Pulse.START:
203 if total_time < timing[pulse]['total']['min'] or total_time > timing[pulse]['total']['max']:
204 self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Bit pulse exceeds total pulse timespan']])
205 pulse = Pulse.INVALID
206 self.stat = Stat.WAIT_START
209 if pulse == Pulse.ZERO:
211 elif pulse == Pulse.ONE:
215 if self.stat == Stat.WAIT_START:
216 self.stat = Stat.GET_BITS
217 self.reset_frame_vars()
218 self.put(self.fall_start, self.fall_end, self.out_ann, [0, ['ST']])
221 elif self.stat == Stat.GET_BITS:
222 # Reset stats on first bit
223 if self.bit_count == 0:
224 self.byte_start = self.fall_start
227 # If 1st byte of the datagram save its sample num
228 if len(self.cmd_bytes) == 0:
229 self.frame_start = self.fall_start
231 self.byte += (bit << (7 - self.bit_count))
233 self.put(self.fall_start, self.fall_end, self.out_ann, [5, [str(bit)]])
235 if self.bit_count == 8:
238 self.stat = Stat.WAIT_EOM
239 self.put(self.byte_start, self.samplenum, self.out_ann, [6, ['0x{:02x}'.format(self.byte)]])
240 self.cmd_bytes.append({'st': self.byte_start, 'ed': self.samplenum, 'val': self.byte})
243 elif self.stat == Stat.WAIT_EOM:
245 self.frame_end = self.fall_end
247 a = [2, ['EOM=Y']] if self.eom else [1, ['EOM=N']]
248 self.put(self.fall_start, self.fall_end, self.out_ann, a)
250 self.stat = Stat.WAIT_ACK
253 elif self.stat == Stat.WAIT_ACK:
254 # If a frame with broadcast destination is being sent, the ACK is
255 # inverted: a 0 is considered a NACK, therefore we invert the value
256 # of the bit here, so we match the real meaning of it.
257 if (self.cmd_bytes[0]['val'] & 0x0F) == 0x0F:
260 if (self.fall_end - self.fall_start) > self.max_ack_len_samples:
261 ann_end = self.fall_start + self.max_ack_len_samples
263 ann_end = self.fall_end
266 # Any NACK detected in the frame is enough to consider the
267 # whole frame NACK'd.
269 self.put(self.fall_start, ann_end, self.out_ann, [3, ['NACK']])
271 self.put(self.fall_start, ann_end, self.out_ann, [4, ['ACK']])
273 # After ACK bit, wait for new datagram or continue reading current
274 # one based on EOM value.
275 if self.eom or self.is_nack:
276 self.stat = Stat.WAIT_START
277 self.handle_frame(self.is_nack)
279 self.stat = Stat.GET_BITS
282 self.out_ann = self.register(srd.OUTPUT_ANN)
285 if not self.samplerate:
286 raise SamplerateError('Cannot decode without samplerate.')
288 # Wait for first falling edge.
290 self.fall_end = self.samplenum
294 self.rise = self.samplenum
296 if self.stat == Stat.WAIT_ACK:
297 self.wait([{0: 'f'}, {'skip': self.max_ack_len_samples}])
299 self.wait([{0: 'f'}])
301 self.fall_start = self.fall_end
302 self.fall_end = self.samplenum
305 # If there was a timeout while waiting for ACK: RESYNC.
306 # Note: This is an expected situation as no new falling edge will
307 # happen until next frame is transmitted.
308 if self.matched == (False, True):
310 self.fall_end = self.samplenum