2019-09-09 00:07:19 -07:00
|
|
|
##
|
|
|
|
## This file is part of the libsigrokdecode project.
|
|
|
|
##
|
|
|
|
## Copyright (C) 2018 Jorge Solla Rubiales <jorgesolla@gmail.com>
|
|
|
|
## Copyright (C) 2019 DreamSourceLab <support@dreamsourcelab.com>
|
|
|
|
##
|
|
|
|
## 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
|
|
|
|
from .protocoldata import *
|
|
|
|
|
|
|
|
# Pulse types
|
|
|
|
class Pulse:
|
|
|
|
INVALID, START, ZERO, ONE = range(4)
|
|
|
|
|
|
|
|
# Protocol stats
|
|
|
|
class Stat:
|
|
|
|
WAIT_START, GET_BITS, WAIT_EOM, WAIT_ACK = range(4)
|
|
|
|
|
|
|
|
# Pulse times in milliseconds
|
|
|
|
timing = {
|
|
|
|
Pulse.START: {
|
|
|
|
'low': { 'min': 3.5, 'max': 3.9 },
|
|
|
|
'total': { 'min': 4.3, 'max': 4.7 }
|
|
|
|
},
|
|
|
|
Pulse.ZERO: {
|
|
|
|
'low': { 'min': 1.3, 'max': 1.7 },
|
|
|
|
'total': { 'min': 2.05, 'max': 2.75 }
|
|
|
|
},
|
|
|
|
Pulse.ONE: {
|
|
|
|
'low': { 'min': 0.4, 'max': 0.8 },
|
|
|
|
'total': { 'min': 2.05, 'max': 2.75 }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ChannelError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
class Decoder(srd.Decoder):
|
|
|
|
api_version = 3
|
|
|
|
id = 'cec'
|
|
|
|
name = 'CEC'
|
|
|
|
longname = 'HDMI-CEC'
|
|
|
|
desc = 'HDMI Consumer Electronics Control (CEC) protocol.'
|
|
|
|
license = 'gplv2+'
|
|
|
|
inputs = ['logic']
|
|
|
|
outputs = []
|
|
|
|
tags = ['Display', 'PC']
|
|
|
|
channels = (
|
|
|
|
{'id': 'cec', 'name': 'CEC', 'desc': 'CEC bus data'},
|
|
|
|
)
|
|
|
|
annotations = (
|
|
|
|
('st', 'Start'),
|
|
|
|
('eom-0', 'End of message'),
|
|
|
|
('eom-1', 'Message continued'),
|
|
|
|
('nack', 'ACK not set'),
|
|
|
|
('ack', 'ACK set'),
|
|
|
|
('bits', 'Bits'),
|
|
|
|
('bytes', 'Bytes'),
|
|
|
|
('frames', 'Frames'),
|
|
|
|
('sections', 'Sections'),
|
|
|
|
('warnings', 'Warnings')
|
|
|
|
)
|
|
|
|
annotation_rows = (
|
|
|
|
('bits', 'Bits', (0, 1, 2, 3, 4, 5)),
|
|
|
|
('bytes', 'Bytes', (6,)),
|
|
|
|
('frames', 'Frames', (7,)),
|
|
|
|
('sections', 'Sections', (8,)),
|
|
|
|
('warnings', 'Warnings', (9,))
|
|
|
|
)
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.reset()
|
|
|
|
|
|
|
|
def precalculate(self):
|
|
|
|
# Restrict max length of ACK/NACK labels to 2 BIT pulses.
|
|
|
|
bit_time = timing[Pulse.ZERO]['total']['min'] * 2
|
|
|
|
self.max_ack_len_samples = round((bit_time / 1000) * self.samplerate)
|
|
|
|
|
|
|
|
def reset(self):
|
|
|
|
self.stat = Stat.WAIT_START
|
|
|
|
self.samplerate = None
|
|
|
|
self.fall_start = None
|
|
|
|
self.fall_end = None
|
|
|
|
self.rise = None
|
|
|
|
self.reset_frame_vars()
|
|
|
|
|
|
|
|
def reset_frame_vars(self):
|
|
|
|
self.eom = None
|
|
|
|
self.bit_count = 0
|
|
|
|
self.byte_count = 0
|
|
|
|
self.byte = 0
|
|
|
|
self.byte_start = None
|
|
|
|
self.frame_start = None
|
|
|
|
self.frame_end = None
|
|
|
|
self.is_nack = 0
|
|
|
|
self.cmd_bytes = []
|
|
|
|
|
|
|
|
def metadata(self, key, value):
|
|
|
|
if key == srd.SRD_CONF_SAMPLERATE:
|
|
|
|
self.samplerate = value
|
|
|
|
self.precalculate()
|
|
|
|
|
|
|
|
def handle_frame(self, is_nack):
|
|
|
|
if self.fall_start is None or self.fall_end is None:
|
|
|
|
return
|
|
|
|
|
|
|
|
i = 0
|
|
|
|
string = ''
|
|
|
|
while i < len(self.cmd_bytes):
|
|
|
|
string += '{:02x}'.format(self.cmd_bytes[i]['val'])
|
|
|
|
if i != (len(self.cmd_bytes) - 1):
|
|
|
|
string += ':'
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
self.put(self.frame_start, self.frame_end, self.out_ann, [7, [string]])
|
|
|
|
|
|
|
|
i = 0
|
|
|
|
operands = 0
|
|
|
|
string = ''
|
|
|
|
while i < len(self.cmd_bytes):
|
|
|
|
if i == 0: # Parse header
|
|
|
|
(src, dst) = decode_header(self.cmd_bytes[i]['val'])
|
|
|
|
string = 'HDR: ' + src + ', ' + dst
|
|
|
|
elif i == 1: # Parse opcode
|
|
|
|
string += ' | OPC: ' + opcodes.get(self.cmd_bytes[i]['val'], 'Invalid')
|
|
|
|
else: # Parse operands
|
|
|
|
if operands == 0:
|
|
|
|
string += ' | OPS: '
|
|
|
|
operands += 1
|
|
|
|
string += '0x{:02x}'.format(self.cmd_bytes[i]['val'])
|
|
|
|
if i != len(self.cmd_bytes) - 1:
|
|
|
|
string += ', '
|
|
|
|
i += 1
|
|
|
|
|
|
|
|
# Header only commands are PINGS
|
|
|
|
if i == 1:
|
|
|
|
string += ' | OPC: PING' if self.eom else ' | OPC: NONE. Aborted cmd'
|
|
|
|
|
|
|
|
# Add extra information (ack of the command from the destination)
|
|
|
|
string += ' | R: NACK' if is_nack else ' | R: ACK'
|
|
|
|
|
|
|
|
self.put(self.frame_start, self.frame_end, self.out_ann, [8, [string]])
|
|
|
|
|
|
|
|
def process(self):
|
|
|
|
zero_time = ((self.rise - self.fall_start) / self.samplerate) * 1000.0
|
|
|
|
total_time = ((self.fall_end - self.fall_start) / self.samplerate) * 1000.0
|
|
|
|
pulse = Pulse.INVALID
|
|
|
|
|
|
|
|
# VALIDATION: Identify pulse based on length of the low period
|
|
|
|
for key in timing:
|
|
|
|
if zero_time >= timing[key]['low']['min'] and zero_time <= timing[key]['low']['max']:
|
|
|
|
pulse = key
|
|
|
|
break
|
|
|
|
|
|
|
|
# VALIDATION: Invalid pulse
|
|
|
|
if pulse == Pulse.INVALID:
|
|
|
|
self.stat = Stat.WAIT_START
|
|
|
|
self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Invalid pulse: Wrong timing']])
|
|
|
|
return
|
|
|
|
|
|
|
|
# VALIDATION: If waiting for start, discard everything else
|
|
|
|
if self.stat == Stat.WAIT_START and pulse != Pulse.START:
|
|
|
|
self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Expected START: BIT found']])
|
|
|
|
return
|
|
|
|
|
|
|
|
# VALIDATION: If waiting for ACK or EOM, only BIT pulses (0/1) are expected
|
|
|
|
if (self.stat == Stat.WAIT_ACK or self.stat == Stat.WAIT_EOM) and pulse == Pulse.START:
|
|
|
|
self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Expected BIT: START received)']])
|
|
|
|
self.stat = Stat.WAIT_START
|
|
|
|
|
|
|
|
# VALIDATION: ACK bit pulse remains high till the next frame (if any): Validate only min time of the low period
|
|
|
|
if self.stat == Stat.WAIT_ACK and pulse != Pulse.START:
|
|
|
|
if total_time < timing[pulse]['total']['min']:
|
|
|
|
pulse = Pulse.INVALID
|
|
|
|
self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['ACK pulse below minimun time']])
|
|
|
|
self.stat = Stat.WAIT_START
|
|
|
|
return
|
|
|
|
|
|
|
|
# VALIDATION / PING FRAME DETECTION: Initiator doesn't sets the EOM = 1 but stops sending when ack doesn't arrive
|
|
|
|
if self.stat == Stat.GET_BITS and pulse == Pulse.START:
|
|
|
|
# Make sure we received a complete byte to consider it a valid ping
|
|
|
|
if self.bit_count == 0:
|
|
|
|
self.handle_frame(self.is_nack)
|
|
|
|
else:
|
|
|
|
self.put(self.frame_start, self.samplenum, self.out_ann, [9, ['ERROR: Incomplete byte received']])
|
|
|
|
|
|
|
|
# Set wait start so we receive next frame
|
|
|
|
self.stat = Stat.WAIT_START
|
|
|
|
|
|
|
|
# VALIDATION: Check timing of the BIT (0/1) pulse in any other case (not waiting for ACK)
|
|
|
|
if self.stat != Stat.WAIT_ACK and pulse != Pulse.START:
|
|
|
|
if total_time < timing[pulse]['total']['min'] or total_time > timing[pulse]['total']['max']:
|
|
|
|
self.put(self.fall_start, self.fall_end, self.out_ann, [9, ['Bit pulse exceeds total pulse timespan']])
|
|
|
|
pulse = Pulse.INVALID
|
|
|
|
self.stat = Stat.WAIT_START
|
|
|
|
return
|
|
|
|
|
|
|
|
if pulse == Pulse.ZERO:
|
|
|
|
bit = 0
|
|
|
|
elif pulse == Pulse.ONE:
|
|
|
|
bit = 1
|
|
|
|
|
|
|
|
# STATE: WAIT START
|
|
|
|
if self.stat == Stat.WAIT_START:
|
|
|
|
self.stat = Stat.GET_BITS
|
|
|
|
self.reset_frame_vars()
|
|
|
|
self.put(self.fall_start, self.fall_end, self.out_ann, [0, ['ST']])
|
|
|
|
|
|
|
|
# STATE: GET BITS
|
|
|
|
elif self.stat == Stat.GET_BITS:
|
|
|
|
# Reset stats on first bit
|
|
|
|
if self.bit_count == 0:
|
|
|
|
self.byte_start = self.fall_start
|
|
|
|
self.byte = 0
|
|
|
|
|
|
|
|
# If 1st byte of the datagram save its sample num
|
|
|
|
if len(self.cmd_bytes) == 0:
|
|
|
|
self.frame_start = self.fall_start
|
|
|
|
|
|
|
|
self.byte += (bit << (7 - self.bit_count))
|
|
|
|
self.bit_count += 1
|
|
|
|
self.put(self.fall_start, self.fall_end, self.out_ann, [5, [str(bit)]])
|
|
|
|
|
|
|
|
if self.bit_count == 8:
|
|
|
|
self.bit_count = 0
|
|
|
|
self.byte_count += 1
|
|
|
|
self.stat = Stat.WAIT_EOM
|
|
|
|
self.put(self.byte_start, self.samplenum, self.out_ann, [6, ['0x{:02x}'.format(self.byte)]])
|
|
|
|
self.cmd_bytes.append({'st': self.byte_start, 'ed': self.samplenum, 'val': self.byte})
|
|
|
|
|
|
|
|
# STATE: WAIT EOM
|
|
|
|
elif self.stat == Stat.WAIT_EOM:
|
|
|
|
self.eom = bit
|
|
|
|
self.frame_end = self.fall_end
|
|
|
|
|
|
|
|
a = [2, ['EOM=Y']] if self.eom else [1, ['EOM=N']]
|
|
|
|
self.put(self.fall_start, self.fall_end, self.out_ann, a)
|
|
|
|
|
|
|
|
self.stat = Stat.WAIT_ACK
|
|
|
|
|
|
|
|
# STATE: WAIT ACK
|
|
|
|
elif self.stat == Stat.WAIT_ACK:
|
|
|
|
# If a frame with broadcast destination is being sent, the ACK is
|
|
|
|
# inverted: a 0 is considered a NACK, therefore we invert the value
|
|
|
|
# of the bit here, so we match the real meaning of it.
|
|
|
|
if (self.cmd_bytes[0]['val'] & 0x0F) == 0x0F:
|
|
|
|
bit = ~bit & 0x01
|
|
|
|
|
|
|
|
if (self.fall_end - self.fall_start) > self.max_ack_len_samples:
|
|
|
|
ann_end = self.fall_start + self.max_ack_len_samples
|
|
|
|
else:
|
|
|
|
ann_end = self.fall_end
|
|
|
|
|
|
|
|
if bit:
|
|
|
|
# Any NACK detected in the frame is enough to consider the
|
|
|
|
# whole frame NACK'd.
|
|
|
|
self.is_nack = 1
|
|
|
|
self.put(self.fall_start, ann_end, self.out_ann, [3, ['NACK']])
|
|
|
|
else:
|
|
|
|
self.put(self.fall_start, ann_end, self.out_ann, [4, ['ACK']])
|
|
|
|
|
|
|
|
# After ACK bit, wait for new datagram or continue reading current
|
|
|
|
# one based on EOM value.
|
|
|
|
if self.eom or self.is_nack:
|
|
|
|
self.stat = Stat.WAIT_START
|
|
|
|
self.handle_frame(self.is_nack)
|
|
|
|
else:
|
|
|
|
self.stat = Stat.GET_BITS
|
|
|
|
|
|
|
|
def start(self):
|
|
|
|
self.out_ann = self.register(srd.OUTPUT_ANN)
|
|
|
|
|
|
|
|
def decode(self):
|
|
|
|
if not self.samplerate:
|
|
|
|
raise SamplerateError('Cannot decode without samplerate.')
|
|
|
|
|
|
|
|
# Wait for first falling edge.
|
|
|
|
self.wait({0: 'f'})
|
|
|
|
self.fall_end = self.samplenum
|
|
|
|
|
|
|
|
while True:
|
|
|
|
self.wait({0: 'r'})
|
|
|
|
self.rise = self.samplenum
|
|
|
|
|
|
|
|
if self.stat == Stat.WAIT_ACK:
|
|
|
|
self.wait([{0: 'f'}, {'skip': self.max_ack_len_samples}])
|
|
|
|
else:
|
|
|
|
self.wait([{0: 'f'}])
|
|
|
|
|
|
|
|
self.fall_start = self.fall_end
|
|
|
|
self.fall_end = self.samplenum
|
|
|
|
self.process()
|
|
|
|
|
|
|
|
# If there was a timeout while waiting for ACK: RESYNC.
|
|
|
|
# Note: This is an expected situation as no new falling edge will
|
|
|
|
# happen until next frame is transmitted.
|
2019-11-01 18:33:47 -07:00
|
|
|
if self.matched == 0b10:
|
2019-09-09 00:07:19 -07:00
|
|
|
self.wait({0: 'f'})
|
|
|
|
self.fall_end = self.samplenum
|